Forms (functions)
The Form module provides functions for manipulating forms such as the bulk retreival or setting of form field values. In particular, the Forms.InsertResponseValues function is a convenience function that allows you to pass a form's ID, a key name that is expected to be within the results, and the JSON encoded responseText returned from a HTTP request; and the function will fill in the values of that form, as appropriate.
var
Forms = {}
Forms.AddToMulti = AddToMulti
Forms.Changed = purejavascript_Forms_Changed
Forms.GetValues = GetFormValues
Forms.InsertResponseValues = InsertResponseValues
Forms.InsertValues = InsertFormValues
Forms.Save = Save
Forms.SelectAll = SelectAll
Forms.Submission = Submission
Forms.Submit = Submit
Forms.SubmitTableValues = SubmitTableValues
Forms.Validate = Validate
Forms.ValidateForm = ValidateForm
Forms.Validate.Submit = Validate.Submit
AddToMulti
Forms.AddToMulti( event )
This function triggers the addition of a span containing text and a 'data-value' attribute into the div identified with 'data-target-id'. It will also add the value to hidden text attribute with a name matching the value of 'data-target-id'.
<select placeholder='Select option' onchange='Forms.AddToMulti( event );' data-target-id='div-multi'></select>
<div id='div-mutli' data-hidden-input-id='input-hidden'> <input name='' type='hidden' id='input-hidden'> ... </div>
<input name='' type='hidden' id='input-hidden' value='[5=support | group=5]>
function AddToMulti( event )
{
var select = event.target;
var index = select.selectedIndex;
var text = select.options[index].text;
var value = select.options[index].value;
var target_id = event.target.getAttribute( "data-target-id" );
var target = document.getElementById( target_id );
if ( target && index )
{
var hidden_input_id = target.getAttribute( "data-hidden-input-id" );
var hidden = target.querySelector( "INPUT[type='hidden']" + "#" + hidden_input_id );
if ( !hidden )
{
alert( "Error: Could not find hidden input for multi!" );
}
else
{
var val = "[" + value + "=" + text + "]";
if ( !hidden.value.includes( val ) )
{
hidden.value += val;
}
var span = target.querySelector( "SPAN[data-value='" + value + "']" );
if ( !span )
{
var span = document.createElement( "SPAN" );
span.innerText = text;
span.onclick = AddToMulti.Remove;
span.setAttribute( "data-value", value );
target.appendChild( span );
}
}
}
}
AddToMulti.Remove
=
function( event )
{
var span = event.target;
var value = span.getAttribute( "data-value" );
var container = span.parentNode;
var hidden_input_id = container.getAttribute( "data-hidden-input-id" );
var hidden = container.querySelector( "INPUT[type='hidden']" + "#" + hidden_input_id );
var value_string = hidden.value.substring( 1, hidden.value.length - 2 );
var list = value_string.split( "][" );
var n = list.length;
for ( var i=n-1; i >= 0; i-- )
{
var bits = list[i].split( "=" );
if ( bits[0] == value )
{
list.splice( i, 1 );
}
}
hidden.value = (0 == list.length) ? "" : "[" + list.join( "][" ) + "]";
container.removeChild( span );
Forms.Changed( { "target": hidden } );
}
Changed
Forms.Changed( event )
This function is called by input fields when they change in order to enable the submit button, if needed.
<input type='text' name='given_name' onchange='Forms.Changed( event );return false'>
function purejavascript_Forms_Changed( event )
{
var input = event.target;
if ( "BUTTON" != input.type )
{
var form = input.form;
var buttons = form.querySelectorAll( "BUTTON" );
var n = buttons.length;
for ( var i=0; i < n; i++ )
{
var submit = buttons[i];
if ( submit && ("button" != submit.type) )
{
submit.disabled = false;
}
}
}
}
Disable All
Forms.DisableAll( form )
Disables all form elements.
Forms.DisableAll
=
function( form )
{
var len = form.elements.length;
for ( var i=0; i < len; i++ )
{
form.elements[i].disabled = true;
}
}
Get Values
Forms.GetValues( form )
function GetFormValues( form )
{
var object = new Object;
if ( form && form.elements )
{
var n = form.elements.length;
for ( var i=0; i < n; i++ )
{
var e = form.elements[i];
var key = e.name;
var value = e.value;
if ( e.hasAttribute( "data-date-format" ) )
{
value = GetFormValues.ConvertDateToMySQLDateFormatFrom( value, e.getAttribute( "data-date-format" ) );
}
switch ( e.type )
{
case "checkbox":
if ( ! e.disabled && e.checked )
{
value = e.checked ? value : "";
}
else
{
value = "";
}
break;
case "radio":
var v = value;
if ( ! e.checked )
{
key = null;
value = null;
object[v] = 0;
}
else
{
object[v] = 1;
}
break;
case "hidden":
if ( "" != e.hasAttribute( "data-table" ) )
{
value = GetFormValues.ConvertTableToJSON( e.getAttribute( "data-table" ) );
}
break;
}
switch ( e.tagName )
{
case "BUTTON":
value = e.innerHTML.trim();
break;
}
if ( key && value )
{
if ( GetFormValues.IsTimeComponent( key ) )
{
key = GetFormValues.GetTimePrefix( key );
value = GetFormValues.ExtractTime( form, key );
}
if ( object[key] )
{
object[key] += ("," + value);
}
else
{
object[key] = value;
}
}
}
}
else
{
console.log( "GetFormValues: null form passed!" );
}
return object;
}
Insert Response Values
Forms.InsertResponseValues( formID, keyName, responseText )
function InsertResponseValues( formID, keyName, responseText )
{
var status = false;
if ( ! responseText )
{
console.log( "Error, no content returned for: " + formID );
}
else
{
var parameters = GetSearchValues();
var form = document.getElementById( formID );
if ( form && ((null == keyName) || ("" != parameters[keyName])) )
{
form.autocomplete = "off";
var json = JSON.parse( responseText );
if ( json && form && ("OK" == json.status))
{
if ( 1 == json.results.length )
{
var tuple = json.results[0];
form.disabled = ("1" === tuple["form_disabled"]);
InsertFormValues( form, tuple );
}
status = true;
var submit = form.querySelector( "BUTTON[type='submit']" );
if ( submit )
{
submit.disabled = true;
}
var n = form.elements.length;
for ( var i=0; i < n; i++ )
{
var input = form.elements[i];
if ( input.addEventListener )
{
switch ( input.tagName )
{
case "SELECT":
input.addEventListener( "change", Forms.Changed );
break;
case "INPUT":
switch ( input.type )
{
case "checkbox":
case "radio":
case "file":
input.addEventListener( "change", Forms.Changed );
break;
default:
input.addEventListener( "change", Forms.Changed );
input.addEventListener( "keyup", Forms.Changed );
}
if( "checkbox" == input.type )
{
if ( input.setup )
{
input.setup( { "target": input } );
}
}
break;
case "TEXTAREA":
input.addEventListener( "keyup", Forms.Changed );
break;
default:
break;
}
}
if ( ! input.disabled ) input.disabled = form.disabled;
if ( input.className )
{
if ( -1 !== input.className.indexOf( "do_not_disable" ) )
{
input.disabled = false;
}
}
}
var buttons = form.querySelectorAll( "BUTTON.validate" );
var n = buttons.length;
if ( 0 < n )
{
if ( Forms.ValidateForm( form ) )
{
for ( var i=0; i < n; i++ )
{
buttons[i].disabled = false;
}
}
else
{
for ( var i=0; i < n; i++ )
{
buttons[i].disabled = true;
}
}
}
}
}
}
return status;
}
Insert Values
Forms.InsertValues( form, dictionary )
function InsertFormValues( form, object )
{
for ( var member in object )
{
if ( form[member] )
{
var input = form[member]; // May return one item or node list.
var value = DecodeHTMLEntities( object[member] );
if ( ("SELECT" != input.tagName) && input.length )
{
var values = value.split( "," );
var n = input.length;
for ( var i=0; i < n; i++ )
{
if ( "radio" == input[i].type )
{
if ( value == input[i].value )
{
input[i].checked = true;
}
}
else
if ( "checkbox" == input[i].type )
{
if ( values.includes( input[i].value ) )
{
input[i].checked = true;
}
}
}
}
else
if ( input && value )
{
if ( "INPUT" == input.tagName )
{
var ph = input.placeholder;
if ( "checkbox" == input.type )
{
input.checked = (("0" == value) || ("" == value)) ? false : true;
}
else
if ( "radio" == input.type )
{
if ( value == input.value )
{
input.checked = true;
}
}
else
{
input.value = value;
if ( input.hasAttribute( "data-cascade" ) )
{
Selects.DoCascade( input );
}
}
input.placeholder = "";
input.placeholder = ph;
}
else
if ( "SELECT" == input.tagName )
{
if ( input.setValue )
{
input.setValue( value );
}
else
{
input.value = value;
}
input.setAttribute( "data-value", value );
}
else
if ( "TEXTAREA" == input.tagName )
{
value = value.replace( /<br>/g, "\n" );
input.innerHTML = value;
if ( input.onchange )
{
var evt = new Object();
evt.target = input;
input.onchange( evt );
}
}
}
}
}
for ( var index in form.elements )
{
var input = form.elements[index];
if ( input.getAttribute )
{
if ( "true" == input.getAttribute( "data-required" ) )
{
switch( input.type )
{
case "radio":
if ( input.checked )
{
input.className += " desired";
}
break;
default:
if ( "" == input.value.trim() )
{
input.className += " desired";
}
}
}
if ( "true" == input.getAttribute( "data-confirmation" ) )
{
if ( "No" == input.value.trim() )
{
var target_id = input.getAttribute( "data-target" );
if ( target_id )
{
var target = document.getElementById( target_id );
if ( target )
{
target.className += " desired";
}
}
}
}
}
}
var randoms = form.querySelectorAll( "INPUT[data-generate-random]" );
var n = randoms.length;
for ( var i=0; i < n; i++ )
{
randoms[i].value = Strings.GenerateSalt();
}
}
Save (form values)
Forms.Save( event, handler )
function Save( event, handler )
{
var element = event.target;
var form = event.target.form;
var parameters = GetFormValues( form );
var url = form.getAttribute( "data-change-url" );
if ( ! parameters.hasOwnProperty( "USER" ) )
{
// parameters.USER = Session.USER;
}
switch ( element.type )
{
case 'checkbox':
parameters.name = element.name;
parameters.value = element.checked ? "1" : "0";
break;
case 'select-one':
case 'text':
default:
parameters.name = element.name;
parameters.value = element.value;
}
if ( element.hasAttribute( "id" ) )
{
parameters.target_id = element.getAttribute( "id" );
}
Call( Resolve() + url, parameters, handler ? handler : Save.Handler );
}
Save.Handler
=
function( responseText )
{
console.log( responseText );
}
SelectAll (form id)
Forms.SelectAll( form_id )
function SelectAll( event, form_id )
{
var input = event.target;
var form = document.getElementById( form_id );
if ( form )
{
var inputs = form.querySelectorAll( "INPUT[type=checkbox]" );
var n = inputs.length;
for ( var i=0; i < n; i++ )
{
inputs[i].checked = input.checked;
}
}
}
Submission
Forms.Submission( event )
Used to automatically remove the name atribute from any sibling buttons and set the 'name' attribute of the target to 'submit'.
function Submission( event )
{
var button = event.target;
var parent = button.parentNode;
var buttons = parent.querySelectorAll( "BUTTON" );
var n = buttons.length;
for ( var i=0; i < n; i++ )
{
if ( "submit" == buttons[i].name )
{
buttons[i].name = "";
}
}
button.name = "submit";
}
Submit
Forms.Submit( event, custom_handler )
function Submit( event, custom_handler )
{
var form = event.target;
var buttons = form.querySelectorAll( "BUTTON" );
var n = buttons.length;
//
// Too many unintended consequences.
//
for ( var i=0; i < n; i++ )
{
var button = buttons[i];
if ( "button" != button.type )
{
button.disabled = true;
}
}
var handler = custom_handler ? custom_handler : Submit.SubmitDefaultHandler;
var parameters = GetFormValues( form );
var submit = form.elements['submit'];
if ( submit && submit.value && ("delete" == submit.value.toLowerCase()) )
{
if ( form && form.hasAttribute( "data-delete-url" ) )
{
var url = form.getAttribute( "data-delete-url" );
var handler = form.handler ? form.handler : handler;
Call( url, parameters, handler );
}
}
else
if ( form && form.hasAttribute( "data-url" ) )
{
var url = form.getAttribute( "data-url" );
var handler = form.handler ? form.handler : handler;
Call( url, parameters, handler );
}
else
if ( form.hasAttribute( "data-submit-url" ) )
{
var url = form.getAttribute( "data-submit-url" );
var handler = form.handler ? form.handler : handler;
Call( url, parameters, handler );
}
return false;
}
Submit.SubmitDefaultHandler
=
function( responseText, target )
{
var response = JSON.parse( responseText );
if ( "OK" == response.status )
{
if ( 1 == response.results.length )
{
Setup.CreateFormHandlerFn.UpdateSearchVariables( response.results[0] );
}
Locations.Reload();
}
}
Submit.SubmitReloadHandler
=
function( responseText )
{
var response = JSON.parse( responseText );
if ( "OK" == response.status )
{
location.reload();
}
else
{
alert( "Error: " + response.error )
}
}
Submit Table Values
SubmitTableValues( event, verify )
function SubmitTableValues( event, verify, method )
{
SubmitTableValues.Method = !method ? "POST" : method;
var form = event.target;
var target_id = form.getAttribute( "data-target" );
var table = document.getElementById( target_id );
var endpoint = table.getAttribute( "data-url" );
var parameters = Forms.GetValues( form );
var submit = form.querySelectorAll( "BUTTON" );
var n = submit.length;
for ( var i=0; i < n; i++ )
{
submit[i].disabled = true;
}
if ( ! verify )
{
verify
=
function( tr )
{
return true;
}
}
if ( table && table.rows && (1 < table.rows.length) )
{
var i = SubmitTableValues.NextVerifiedRow( table, verify, 0 );
if ( i )
{
SubmitTableValues.DoCall( endpoint, parameters, table, i, verify );
}
else
{
alert( "Finished submitting table values." );
}
}
else
{
Call( "/auth/session/", new Object(), SubmitTableValues.Finish );
}
return false;
}
SubmitTableValues.NextVerifiedRow
=
function( table, verify, i )
{
var j = false;
var progress_id = table.getAttribute( "data-progress" );
var progress = progress_id ? document.getElementById( progress_id ) : null;
var loop = true;
while ( table.rows[++i] )
{
if ( progress )
{
progress.style.width = (i / table.rows.length) * 100 + "%";
}
if ( verify( table.rows[i] ) )
{
j = i;
break;
}
}
if ( progress && ! table.rows[i] )
{
progress.style.width = "100%";
}
if ( progress && (i == table.rows.length) )
{
progress.style.background = "green";
}
return j;
}
SubmitTableValues.Handler
=
function( responseText, parameters, table, i, verify )
{
var endpoint = table.getAttribute( "data-url" );
var json = "";
try
{
json = JSON.parse( responseText );
}
catch ( err )
{
console.log( responseText );
}
SubmitTableValues.MarkupRow( json, table, i );
var i = SubmitTableValues.NextVerifiedRow( table, verify, i );
if ( false !== i )
{
SubmitTableValues.DoCall( endpoint, parameters, table, i, verify );
}
else
{
var progress_id = table.getAttribute( "data-progress" );
if ( progress_id )
{
var progress = document.getElementById( progress_id );
progress.style.width = "100%";
}
Call( "/auth/session/", new Object(), SubmitTableValues.Finish );
}
}
SubmitTableValues.DoCall
=
function( endpoint, parameters, table, i, verify )
{
var combined_parameters = SubmitTableValues.ConvertTRToParameters( parameters, table.rows[i] );
Call
(
endpoint,
combined_parameters,
function ( responseText )
{
var table_copy = table;
var i_copy = i;
var v_copy = verify;
SubmitTableValues.Handler( responseText, parameters, table_copy, i_copy, v_copy );
},
SubmitTableValues.Method
);
}
SubmitTableValues.MarkupRow
=
function( json, table, i )
{
if ( "OK" == json.status )
{
table.rows[i].classList.add( "import_ok" );
table.rows[i].style.background = "green";
table.rows[i].style.color = "white";
}
else
if ( "EXISTS" == json.error )
{
table.rows[i].classList.add( "import_exists" );
table.rows[i].style.background = "#888";
table.rows[i].style.color = "#ddd";
}
else
{
table.rows[i].classList.add( "import_error" );
table.rows[i].style.background = "red";
table.rows[i].style.color = "white";
}
if ( "ERROR" == json.status )
{
table.rows[i].title = json.error;
}
if ( table.customRowHandler )
{
table.customRowHandler( table.rows[i], json );
}
}
SubmitTableValues.ConvertTRToParameters
=
function( parameters, tr )
{
var ret = SubmitTableValues.ConvertTRToParameters.Clone( parameters );
var n = tr.cells.length;
for ( var i=0; i < n; i++ )
{
if ( "TD" == tr.cells[i].tagName )
{
var key = tr.cells[i].getAttribute( "data-name" );
var val = tr.cells[i].innerHTML;
if ( key && val )
{
ret[key] = val;
}
}
}
return ret;
}
SubmitTableValues.ConvertTRToParameters.Clone
=
function( obj )
{
var ret = {};
for ( var name in obj )
{
var value = obj[name];
ret[name] = obj[name];
}
return ret;
}
SubmitTableValues.Finish
=
function( responseText )
{
alert( "Finished submitting table values." );
}
Validate
Forms.Validate( event, handler )
function Validate( event, handler )
{
var valid = true;
var form = event.target;
var n = form.elements.length;
var del = (form.elements['submit'] && ('delete' == form.elements['submit'].value));
if ( ! del )
{
valid = Forms.ValidateForm( form );
/*
form.checkValidity();
for ( var i=0; i < n; i++ )
{
var element = form.elements[i];
if ( element.hasAttribute( "required" ) )
{
var type = element.type;
var name = element.name;
var value = element.value;
var validated = element.validity.valid;
Validate.AddClass( element, "checked" );
if ( (false === validated) || (('hidden' == type) && (0 == value)) )
{
valid = false;
}
else
{
}
}
}
*/
}
if ( valid && handler )
{
handler( event );
}
else
{
alert( "Please complete the form before submitting." );
}
return false;
}
Validate Form
Forms.ValidateForm( form )
function ValidateForm( form )
{
var valid = true;
var n = form.elements.length;
form.checkValidity();
for ( var i=0; i < n; i++ )
{
var element = form.elements[i];
if ( element.hasAttribute( "required" ) )
{
var type = element.type;
var name = element.name;
var value = element.value.trim();
var validated = element.validity.valid;
Validate.AddClass( element, "checked" );
if ( "" == value )
{
valid = false;
}
else
if ( (false === validated) || (('hidden' == type) && (0 == value)) )
{
valid = false;
}
}
}
return valid;
}
Validate Form
Forms.Validate.Submit( event )
Convenience function to allow validation and submission to be perforemd by adding an event listener via javascript. Example usage:
document.getElementById( <id> ).addEventListener( 'subit', Forms.Validate.Submit );
Validate.Submit
=
function( event )
{
event.preventDefault();
Forms.Validate( event, Forms.Submit );
}
Word Limit
Forms.WordLimit( elements )
For each text area within 'elements', if there is an attribute 'data-limit', it will truncate the contained text to that number of words.
function WordLimit( elements )
{
var n = elements.length;
for ( var i=0; i < n; i++ )
{
var e = elements[i];
if ( ("TEXTAREA" == e.tagName) && e.hasAttribute( "data-limit" ) )
{
e.oninput = WordLimit.OnInput;
}
}
}
WordLimit.OnInput
=
function( event )
{
var textarea = event.target;
var limit = textarea.getAttribute( "data-limit" );
var target_id = textarea.getAttribute( "data-target" );
var target = target_id ? document.getElementById( target_id ) : null;
var last_char = WordLimit.LastChar ( textarea.value );
var words = WordLimit.CountWords( textarea.value );
var truncated = false;
if ( limit < words )
{
textarea.value = WordLimit.TruncateTextToWords( textarea.value, limit );
words = WordLimit.CountWords( textarea.value );
truncated = true;
}
if ( truncated )
{
switch ( last_char )
{
case " ":
case "\n":
alert( "Warning, your have reached the word limit!" );
}
}
if ( target ) target.innerHTML = (words) + " words";
}
WordLimit.CountWords
=
function( value )
{
return value.split( " " ).length;
}
WordLimit.TruncateTextToWords
=
function( value, limit )
{
var words = 0;
var i = -1;
while ( -1 != (i = WordLimit.NextWhitespace( value, i + 1 )) )
{
words++;
if ( limit < words ) break;
}
if ( -1 == i ) i = value.length;
return value.substring( 0, i );
}
WordLimit.LastChar
=
function( value )
{
return value.length ? value.substring( value.length - 1, value.length ) : null;
}
WordLimit.NextWhitespace
=
function( value, i )
{
var s = value.indexOf( " ", i );
var n = value.indexOf( "\n", i );
var r = -1;
if ( (-1 != s) && (-1 != n) )
{
r = Math.min( s, n );
}
else
if ( -1 != s )
{
r = s;
}
else
if ( -1 != n )
{
r = n;
}
return r;
}
Helper functions
GetFormValues.ConvertTableToJSON
=
function( table_id )
{
var tuples = new Array();
var table = document.getElementById( table_id );
if ( table )
{
var rows = table.getElementsByTagName( "TR" );
var n = rows.length;
for ( var i=0; i < n; i++ )
{
var tuple = new Object();
var row = rows[i];
var fields = row.getElementsByTagName( "TD" );
var m = fields.length;
for ( var j=0; j < m; j++ )
{
var field = fields[j];
if ( field.hasAttribute( "data-name" ) )
{
var key = field.getAttribute( "data-name" );
var value = field.innerHTML.trim();
tuple[key] = value;
}
}
tuples.push( tuple );
}
}
return JSON.stringify( tuples );
}
GetFormValues.IsTimeComponent
=
function( name )
{
return (-1 !== name.indexOf( "_hour" ));
}
GetFormValues.GetTimePrefix
=
function( name )
{
var index = name.indexOf( "_hour" );
return name.substring( 0, index );
}
GetFormValues.ExtractTime
=
function( form, key )
{
var ret = "";
var key_hour = key + "_hour";
var key_minutes = key + "_minutes";
var key_seconds = key + "_seconds";
ret += form.elements[key_hour] ? form.elements[key_hour ].value : "00";
ret += ":";
ret += form.elements[key_minutes] ? form.elements[key_minutes].value : "00";
ret += ":";
ret += form.elements[key_seconds] ? form.elements[key_seconds].value : "00";
return ret;
}
GetFormValues.ConvertDateToMySQLDateFormatFrom
=
function( date_value, date_format )
{
var converted = "0000-00-00";
var delimiter = (-1 != date_value.indexOf( "/" )) ? "/" : "-";
var bits = date_value.split( delimiter );
if ( 3 == bits.length )
{
var yy = "";
var mm = "";
var dd = "";
switch ( date_format )
{
case "DD-MM-YY":
case "DD/MM/YY":
case "DD-MM-YYYY":
case "DD/MM/YYYY":
dd = bits[0];
mm = bits[1];
yy = bits[2];
break;
case "MM-DD-YY":
case "MM/DD/YY":
case "MM-DD-YYYY":
case "MM/DD/YYYY":
mm = bits[0];
dd = bits[1];
yy = bits[2];
break;
case "YY-MM-DD":
case "YYYY-MM-DD":
default:
yy = bits[0];
mm = bits[1];
dd = bits[2];
}
var year = parseInt( yy )
if ( !isNaN( year ) && (year < 100) )
{
yy = (year < 50) ? 2000 + year : 1900 + year;
}
converted = "" + yy + "-" + GetFormValues.ZeroPad( mm ) + "-" + GetFormValues.ZeroPad( dd );
}
return converted;
}
GetFormValues.ZeroPad
=
function( value )
{
var val = parseInt( value );
if ( isNaN( val ) )
{
return "00";
}
else
{
return (val <= 9) ? "0" + val : "" + val;
}
}
Validate.HasClass
=
function ( element, cls )
{
var classes = element.className;
return (-1 != classes.indexOf( cls ));
}
Validate.AddClass
=
function ( element, cls )
{
if ( element && cls )
{
var classes = element.className;
if ( -1 == classes.indexOf( cls ) )
{
element.className += (" " + cls);
}
}
}
Validate.RemoveClass
=
function ( element, cls )
{
var classes = element.className;
var f = 0;
var n = cls.length;
if ( (-1 != classes.indexOf( " " + cls )) || (-1 != classes.indexOf( cls + " " )) || (-1 != classes.indexOf( cls )) )
{
var f = classes.indexOf( cls );
if ( (0 < f) && (' ' == classes[f - 1]) ) f--;
element.className = classes.substring( 0, f ) + classes.substring( f + n + 1 );
}
}