Start by including a couple of stock functions and the custom tests that pertain only to a specific form designated by a "Customs" variable that will be passed in a ColdFusion variable by the page that includes this script. (If no field requires a custom test, then Customs should be undefined or empty to avoid having this routine try to include custom code.) Have this page add the file extension to the Customs string so that the same basic name can be used for server-side custom code later on.
// Include externally defined functions <cfinclude template="Chosen.js"> <cfinclude template="Entered.js"> // Include custom tests <cfparam name="Customs" default=""> <cfif len(trim(Customs))> <cfinclude template="#Customs#.js"> </cfif>
As mentioned in the solution above, this script will apply tests named by hidden fields to their corresponding real fields. Thus, in addition to the usual counter and a temporary variable for interim results, we'll need to store the real name, the contents of the hidden field, the list of tests (parsed from the hidden field's contents), the label (also parsed from the hidden field's contents), the error message, and the portion of the error message to be displayed (since it may not all fit on one screen for a VERY large form).
// Apply tests named in hidden fields to their "real" counterparts
function formCheck(TheForm) {
// Initialize
var i=0; // counter
var Realname=''; // name of field to be tested
var Teststring=''; // value of hidden test field:
// sequence~tests~label
var Tests=''; // tests for a specific field
var TheLabel=''; // label for a specific field
var Msg=''; // error message
var Temp=''; // temporary variable
var Show=''; // portion of error message to be displayed
Just as you may have looped through a form structure on the server side through ColdFusion, this code loops through the form structure (up to its "length", the number of elements it contains) on the client side using javascript. Look at each element's name, and if it contains _t, get to work. Temporarily store the length of the field's name. (Note that javascript doesn't care that Temp was initialized as a string; you can store anything in it.) Let Realname be the name of the hidden field with the "_t" removed from the end. Use javascript's eval function to get the real counterpart to this hidden field.
The hidden field's value is formatted like this: sequence~test1 test2 testN~label. Store it in Teststring, which the code will parse by splitting on tildes (~). This code doesn't use the sequence value, but a server-side counterpart to this routine would; so, let it exist in the form but ignore it here. Save the list of tests to Tests. Save the label to Label.
// Work
for (i=0;i<TheForm.elements.length;i++) {
if (TheForm.elements[i].name.search(/_t/) > -1) {
// (a test field)
Temp=TheForm.elements[i].name.length;
Realname=TheForm.elements[i].name.substr(0,Temp-2);
// (the NAME of its real counterpart)
TheField = eval('TheForm.'+Realname);
// (the real field)
// Parse the value of the hidden test field: Teststring
Teststring=TheForm.elements[i].value;
// (Sequence=Teststring.split("~")[0];) (for server checks)
Tests=Teststring.split("~")[1];
TheLabel=Teststring.split("~")[2];
To proceed, the code needs to know if it should check text or check choices; the two require fundamentally different approaches. Thus, if the field is not a text field, not a password field and not a textarea field, the code should check choices.
Have the chosen function check for useful choices. Then invoke the custom function if appropriate. Recall that Msg (the message) starts off empty. As errors are found, the code adds them to Msg. This standard code can supply standard error messages that look like {label} {problem}. Taking this approach permits the true/false value returned by standard tests you may have already written to be grafted into this code. If the problem is found, formcheck provides the message. In this case, if the word "required" is in the test list but no useful choice was chosen, then formcheck adds an appropriate error message to Msg.
However, custom situations require custom messages. Thus it is that the custom function supplies its own error message. If the word "custom" is in the test list, the custom function is invoked. If the custom function finds problems, it returns their description, which gets added to Msg.
// Determine how to check field values
if ((TheField.type!='text')&&
(TheField.type!='password')&&
(TheField.type!='textarea')) {
// FIELD IS SELECT/CHECKBOX/RADIO; CHECK CHOICES
if (!(chosen(TheField, TheLabel))) {
// No useful choices made
if (Tests.indexOf('required') > -1) {
Msg=Msg+'\n'+TheLabel+' must be selected';
}
}
// PERFORM CUSTOM TESTS ON SELECT/CHECKBOX/RADIO
if (Tests.indexOf('custom') > -1) {
Msg=Msg+custom(Realname, TheForm, TheLabel);
}
}
If text fields (and password fields and textarea fields) are empty, not many further checks are appropriate. So, have the code see if they're empty first.
else {
// FIELD IS TEXT/PASSWORD/TEXTAREA; SEE IF EMPTY
if (!(entered(TheField))) {
// No useful text
if (Tests.indexOf('required') > -1) {
Msg=Msg+'\n'+TheLabel+' must not be empty';
}
}
If there is useful text, apply standard checks. Representative tests are defined here inline for brevity: hasBadChars, number, gezero (greater than or equal to zero), and limited (a limited character set provided as an example). Each test is only made if its name appears in the test list for this field.
To add one of your own external tests to this toolbox, have ColdFusion include its script in the page before formcheck itself, have your copy of formcheck look for the name of your test in the test list, and if the name of your test is there, invoke it the same way as the "entered" test above: pass it TheField, and based on the true/false or numeric value your function returns, provide an error message that includes TheLabel.
Due to their length, the heart of each test can't be indented normally below. So, blank lines set them off instead.
else {
// PERFORM STANDARD CONTENT CHECKS ON TEXT/PASSWORD/TEXTAREA
// If the "any" override is NOT present,
// complain if bad chars
if (!(Tests.indexOf('any') > -1)) {
if(TheField.value.search(/[\*%#~<<""\>]/) > -1) {
Msg=Msg+"\n"+TheLabel+' cannot contain ';
Msg=Msg+'%, #, >, *, ", <, or ~.';
}
}
// Complain if it should be a number but isn't
if (Tests.indexOf('number') > -1) {
if (!(TheField.value.search(/^[-+]?(([0-9]+\.?[0-9]*)|(\.[0-9]+))$/) > -1)) {
Msg=Msg+'\n'+TheLabel+' must be a number';
}
}
// Complain if it should be greater than or equal to zero but isn't
if (Tests.indexOf('gezero') > -1) {
if (TheField.value < 0 ) {
Msg=Msg+"\n"+TheLabel+' must be greater than or equal to zero';
}
}
// Complain if contents exceed limited character set
if (Tests.indexOf('limited') > -1) {
if (TheField.value.search(/[^a-zA-Z\-]/) > -1) {
Msg=Msg+"\n"+TheLabel+' must contain only letters ';
Msg=Msg+"and hyphens";
}
}
// PERFORM CUSTOM TESTS ON TEXT/PASSWORD/TEXTAREA
if (Tests.indexOf('custom') > -1) {
Msg=Msg+custom(Realname, TheForm, TheLabel);
}
} // checked contents
} // determined how to test
} // applied tests to a field
} // did it for all elements
Finally, if the error message isn't empty, display it. As mentioned in the solution above, a vary large form containing MANY fields might produce an error message too big to fit on one screen. So, stuff Show with 25 lines, display Show, and repeat until done. Close the function.
// If any errors, display them 25 lines at a time; return false
if (Msg.length > 0) {
Temp=0;
Show='';
for (i=0;i<=Msg.split('\n').length-1;i++) {
Temp=Temp+1;
Show=Show+'\n'+Msg.split('\n')[i];
if ((Temp >= 25)||(i>=Msg.split('\n').length-1)) {
alert(Show);
Temp=0;
Show='';
}
}
return false;
}
else {
return true;
}
}
function chosen(InField) {
var Templength=InField.length;
if (Templength < 1) {
return false;
}
else if ( (InField[0].type == 'radio') ||
(InField[0].type == 'checkbox') ) {
for (j=0;j<Templength;j++) {
if (InField[j].checked) {
return true;
}
}
return false;
}
else {
// select box; test for pick beyond the first (default)
for (j=1;j<Templength;j++) {
if (InField.options[j].selected) {
return true;
}
}
return false;
}
}
Put this code in Entered.js. If the value is null or zero, return false. Nothing was entered. If the value contains only spaces (does not have character types other than a space), then return false; else, return true.
function entered(InField) {
// text or password or textarea
if (InField.value == null || InField.value.length == 0) {
// Empty
return false;
}
else if (!(InField.value.search(/[^ ]/) > -1)) {
// Consists of nothing but spaces
return false;
}
else {
return true;
}
}
Initialize TempMsg to hold error messages to be returned to formCheck. For specific field names, perform hard-coded tests and return hard-coded error messages that might or might not include a field label. Note that these tests can make use of external functions included at the beginning of Formcheck.js.
For this demonstration, if the user selects the third option ("No") when asked if the user wants to play, and the user has also entered an amount of money to risk, complain. If the user enters the word "nothing" as part of the response when asked what might be behind the selected door, complain.
function custom(InName, InForm, InLabel) {
var TempMsg='';
// Check total on invoice screen
if (InName == 'Play') {
if (InForm.Play.options[2].selected) { // won't play
if (entered(InForm.Money, "dummy")) {
TempMsg=TempMsg+'\n'+"Reconsider your decision not to play. ";
TempMsg=TempMsg+"Don't specify an amount to risk ";
TempMsg=TempMsg+"if you won't play.";
}
}
}
else if (InName == 'Guess') {
if (entered(InForm.Guess)) {
if (InForm.Guess.value.indexOf('nothing') > -1) {
TempMsg=TempMsg+'\n'+"We wouldn't provide an empty showcase. ";
TempMsg=TempMsg+" Make a better guess.";
}
}
}
return TempMsg;
}
<!--- Tell standard code the partial name of the page that holds custom code ---> <cfset Customs="Form42"> <!--- Include client-side checks invoked on submission of the form ---> <script language="javascript"> <cfinclude template="FormCheck.js"> </script> <!--- Act when the form is submitted ---> <cfparam name="form.Go" default=""> <cfif len(trim(form.Go))> <!--- server-side checks here would stop processing if problems were found, else... ---> <cfinclude template="MyAct.cfm"> <cfabort> </cfif>
For this demonstration, the form is worded as an invitation to play a TV game show. Open the form. Copy the Customs variable to a hidden field to provide future flexibility. If this is not done and you choose later to have the form target be a page other than the form itself, the Customs variable needs to be received by the server-side checks in this manner. (If you'll ALWAYS submit your forms to themselves as shown in this example, you won't need to use this field. It's safest to code it now for the future.) Name the game.
<form name="MyForm" method="post" onSubmit="return formCheck(this)"> <!--- Pass "Customs" to the server in case the form doesn't submit to itself; support potential server-side checks ---> <input type="hidden" name="Customs" value=<cfoutput>"#Customs#"</cfoutput>> <!--- Time to play... ---> Let's break a deal [grin] <p>
Provide a select box that asks if the user wants to play. Note that the first option consists of instructions to the user, not a choice. Follow it with a corresponding hidden form that provides a sequence, a list of tests (required and custom) and a label to become part of associated error messages. Note that the entire message isn't needed here, just the label. Note that a single hidden field names as many tests as you want for the corresponding "real" field.
Do you want to play?<br> <select name="Play"> <option value="" selected>Decide <option value="Yes">Yes <option value="No">No </select> <input type="hidden" name="Play_t" value="01~required custom~Play decision"> <p>
Now add a text field with its hidden field and a set of radio buttons with its hidden field.
How much money will you risk?<br> <input type="text" name="Money" value=""> <input type="hidden" name="Money_t" value="02~required number gezero~Money to risk"> <p> Which do you want to open:<br> <input type="radio" name="Door" value="1"> door number 1,<br> <input type="radio" name="Door" value="2"> door number 2,<br> <input type="radio" name="Door" value="3"> door number 3,<br> <input type="radio" name="Door" value="stop"> or stop and keep the money?<br> <input type="hidden" name="Door_t" value="03~required~Door to open"> <p>
Toss in a textarea with its hidden field, a set of checkboxes with its hidden field, and a multiple select box with its hidden field. Be sure to use the word "multiple" in the select tag to let the user make multiple choices.
What do you think is behind it?<br> <textarea name="Guess" rows="3" cols="45"></textarea> <input type="hidden" name="Guess_t" value="04~required custom~Your guess"> <p> Pick ways that you'll show you're having fun:<br> <input type="checkbox" name="Fun" value="sing">sing,<br> <input type="checkbox" name="Fun" value="grin">grin<br> <input type="hidden" name="Fun_t" value="15~required~Ways to show you're having fun"> <p> You'll need to change clothes during competition. What colors will you wear?<br> <select name="Colors" multiple> <option value="" selected>Decide <option value="Red">Red <option value="White">White <option value="Blue">Blue </select> <input type="hidden" name="Colors_t" value="16~required~Clothing colors"> <p>
To provide an excuse to practice limiting the character set, ask for the user's last name. Provide a submit button and close the form.
What's your last name? <input type="input" name="LastName"> <input type="hidden" name="LastName_t" value="20~limited~Last name"> <input type="submit" name="Go" value="Respond"> </form>
Put this one line in MyAct.cfm.
SUCCESS!
Now consider how little it will take to add server-side checks for forms like this. The server receives form fields in an unpredictable order, but this form overcomes that difficulty by specifying the sequence in which related errors should appear. Everything needed by the server is known: the field sequence, the list of tests to perform, a label to include in error messages, and the partial name of the page that should contain custom code (passed in a hidden field in case the form doesn't submit to itself.
All that remains is for you to create server-side code that loops the form structure and works in a manner similar to this project.
Therefore, every form you will write in this manner requires no changes at all to let it be universally tested on the client side and the server. =Marty=