One of the capabilities of ColdFusion is automatic server-side validation. This is triggered by having extra form fields specify the validation that the "real" fields should have. For example, if you have a field named Username and a hidden field whose name is Username_required, ColdFusion will require the Username field to have something in it.
However, there are three problems with this approach as it's implemented today. You must add a hidden field for each requirement to be met. You generally want to customize only the nice field label but must instead specify the entire message or take a default message that includes the raw field name. And, the built-in approach cannot be extended to provide additional kinds of custom validation or provide existing kinds more smoothly.
This tip proposes a better way to add server-side validation. Not only is it faster to apply and easier to extend than the native server-side method, but it's also faster to apply and easier to extend than the native client-side method.
Suppose you could specify multiple tests with a single hidden field and only had to specify the data label instead of detailed instructions to the user. That's what this alternative will demonstrate with just one hidden field per data field, no matter how many requirements you want it to meet.
You'll need to specify three pieces of information in that hidden field. Because you can't guarantee the sequence in which the form fields reach the server, you'll need a sequence. You'll need a list of requirements to meet. And, you'll need the data label to become part of any error messages. Let's separate them with a tilde (~) character.
So the program will know which data to apply this requirement to, give the hidden field the same name as the data field, only add a suffix of "_t" to the name of the hidden field. Notice that the suffix is the same no matter how many requirements or which requirements the data field must satisfy.
You'll specify the field sequence with numbers less than one. This makes it easy to insert fields later on without renumbering.
For the proof of concept, assume you have written five canned tests: z (zero length), p (plain text), l (letters only), d (digits only), and s (spaces). You'll use any combination of these that makes sense to you.
For the third piece of information, you'll specify the data label, not instructions to the user.
Put this sample form in MyForm.cfm.
<form action="MyAction.cfm" method="post"> Enter the following to see automatic server-side validation and formatting...<br> Enter a Nickname without problem characters: <input type="text" name="MyName"><br> <input type="hidden" name="MyName_t" value=".1~pz~Nickname"> You may enter a Your Digit String: <input type="text" name="MyDigits"><br> <input type="hidden" name="MyDigits_t" value=".18~d~Your Digit String"> Enter Your Letter String: <input type="text" name="MyLetters"><br> <input type="hidden" name="MyLetters_t" value=".25~lz~Your Letter String"> Enter Running Text without spaces: <input type="text" name="Mouth"><br> <input type="hidden" name="Mouth_t" value=".3~sz~Running Text"> <input type="submit" name="doit" value="Go"> </form>
Put this code in MyAction.cfm. Notice that you don't have to list the fields over again in any way. The program flow won't make it past Autoval.cfm if fields are wrong.
<cfinclude template="ServerVal.cfm"> Congratulations on successful completion of your entry!
Put the rest of the code in ServerVal.cfm. It will 1) pull the test information into a list, 2) give the sequence numbers a common format, 3) sort the list by sequence number, 4) Run requested tests to build the error message line by line, and 5) if an error was found, display the error message and stop the process flow.
To pull the test information into a list, define a line separator (|), an element separator (~), use structKeyList() to walk through the form field names, and add those names to a list of test requirements. You could use different characters other than a pipe and a tilde. Just be consistent. If you change the tilde here, change it in your forms as well.
<!--- Pull the test information into a list: sequence~requirement~label~fieldname ---> <cfset TestList=""> <cfset LS="|"> <cfset ES="~"> <cfloop list="#structKeyList(form)#" index="Key"> <cfif Key contains "_t"> <cfset TestList=TestList&LS&form[Key]&ES&Key> </cfif> </cfloop>
Because the sequence number will receive a text sort, it's not enough to use it in its raw form. To force the sequence numbers to a common format, you need to walk through the list line by line and then replace the first item on each line with a formatted version of itself.
<!--- Give the sequence numbers a common format ---> <cfset Nr=1> <cfloop list="#TestList#" index="Line" delimiters="#LS#"> <cfset Line=listSetAt(Line,1,numberFormat(listGetAt(Line,1,"#ES#"),".999999"),"#ES#")> <cfset TestList=listSetAt(TestList,Nr,Line,"#LS#")> <cfset Nr=Nr+1> </cfloop>
Sorting lists is easy. Just remember to name the delimiter, your line separator in this case.
<!--- Sort the test list by sequence number; sequence numbers must be less than 1 ---> <cfset TestList=listSort(TestList,"text","asc","#LS#")> <cfloop list="#TestList#" index="Line" delimiters="#LS#"> <!--- <cfoutput>#Line#</cfoutput><br> ---> </cfloop>
Set up to run the tests. Empty an error message. Start to loop through the test field names. For each name, get the requirement, label, and test key (test field fieldname). Drop the _t from the test key to seek the data field name in the form structure. If you find it, record the data value for later use. (Otherwise, use an empty string for the data value.)
<!--- Run requested tests to build the error message ---> <cfset ErrorMsg=""> <cfloop list="#TestList#" index="Line" delimiters="#LS#"> <cfset Requirement=listGetAt(Line,2,"#ES#")> <cfset Label=listGetAt(Line,3,"#ES#")> <cfset TestKey=listGetAt(Line,4,"#ES#")> <!--- Get the data value to be checked ---> <cfset DataValue=""> <cfset DataKey=left(TestKey,len(TestKey)-2)> <cfif structKeyExists(form,DataKey)> <cfset DataValue=structFind(form,DataKey)> </cfif>
It's convenient to be able to skip a span of code when desired. One trick for doing this is to set up a one-pass loop (from 1 to 1, for example). A cfbreak at any point skips you to the end of the loop.
If the data value is empty, skip all tests but the zero test. If the requirement contains "z", complain. Whether the test was a requirement or not, skip remaining tests; because, they can't be performed on empty fields.
<!--- This one-pass loop lets you skip code when desired --->
<cfloop from=1 to=1 index="Pass">
<!--- If empty, skip all tests but the zero test --->
<cfif not len(trim(DataValue))>
<!--- Zero test --->
<cfif Requirement contains "z">
<cfset ErrorMsg=ErrorMsg&LS&Label&
" must not be empty">
</cfif>
<cfbreak>
</cfif>
Regular expressions are your friend when it comes to specifying formats. Double pound signs and double your double quotes. Put a backslash in front of other special characters. The plus sign after the square brackets means "one or more times". If one or more of these characters is found, the code complains.
<!--- Plain test --->
<cfif Requirement contains "p">
<cfif reFind("[%##\>\*\""\<~]+",DataValue)>
<cfset ErrorMsg=
ErrorMsg&LS&Label&' cannot contain %, ##, >, *, ", <, or ~.'>
</cfif>
</cfif>
After the plain test, the spaces test is easy.
<!--- Spaces test --->
<cfif Requirement contains "s">
<cfif reFind("[ ]+",DataValue)>
<cfset ErrorMsg=
ErrorMsg&LS&Label&" cannot contain spaces">
</cfif>
</cfif>
The digits test introduces a new wrinkle. To find items that are NOT in the square brackets, make an up caret (^) the first character inside them. Regular expressions understand ranges. So, the digit test says that if you see one or more characters that is not in the range of 0-9, complain.
<!--- Digits test --->
<cfif Requirement contains "d">
<cfif reFind("[^0-9]+",DataValue)>
<cfset ErrorMsg=
ErrorMsg&LS&Label&" can contain only digits">
</cfif>
</cfif>
The letters test makes the same complaint for characters that aren't from a-z. However, by using the reFindNoCase, it will treat A-Z the same way that it treats a-z.
<!--- Letters test --->
<cfif Requirement contains "l">
<cfif reFindNoCase("[^a-z]+",DataValue)>
<cfset ErrorMsg=
ErrorMsg&LS&Label&" can contain only letters">
</cfif>
</cfif>
Wind down. End the one-pass loop. Continue the main loop until all requirement fields have been checked. If the error message isn't empty, loop through it, tell the user how to get back to the previous page, and use the cfabort tag to stop processing. If the error message is empty, processing will continue with the rest of whatever tag included this one.
<!--- End one-pass loop ---> </cfloop> <!--- Test the next field ---> </cfloop> <cfif len(ErrorMsg)> <cfloop list="#ErrorMsg#" index="Line" delimiters="#LS#"> <cfoutput>#Line#</cfoutput><br> </cfloop> Press your back button, correct any problems, and re-submit. <cfabort> </cfif>
You could place a special "required" ending on the data field names themselves instead of using a separate hidden field. If the user tampered with the data field name, the data would not reach its destination.
You could encode business rules into the database. Users who try to hack the system don't need nice-looking error messages as long as an error is thrown.
However, for your routine needs, the technique demonstrated here is simple to use and easy to extend. =Marty=