There are multiple ways and techniques to program almost any task. Because of this, there is one thing I can guarantee: If you analyze how three different applications validate form data, you will find three different ways of doing it (assuming they validate at all). Some techniques are better than others. Here I'll describe one that works for me.
A little about me first so you understand where I am coming from. I have been a professional application designer and programmer for over 20 years; the last ten years I have had a heavy focus on web based applications using various technologies. I'll be the first to admit that my views of user applications is myopic -- I tend to see everything with a "how usable is the application to the user" and "how is the user going to break the application."
Now two of my pet peeves: 1) Programs that don't gracefully display entry errors to the users and 2) Programs that don't validate EVERYTHING that the user enters. To me, customer impression wise, there is nothing worse that a generic "Invalid data, press the back button and try again." System security wise, there is nothing worse that assuming that FORM data is valid and blindly acting on it. Also, client side validation (JavaScript, etc.) is good for "customer impression" but cannot be used as a substitute for server side validation.
Anyway, now that bored stiff with my high and mighty ideals, I'll present you with a simple technique I use for validating data.
The biggest difference between this technique and most "common" tutorials that I have seen on CF is that the FORM page and the ACTION page are one and the same and this has three key benefits that can be problematical using separate FORM and ACTION pages:
First, you have full control on what information is "defaulted" to the user in form fields and you can preserve any previously keyed information in the event of any entry errors. Second, with some very minor tweaks, you can allow the passing of "default" form field values via URL parameters. Third, using the <cflocation> tag to direct the user to the next page allows for refresh to work properly without the nagging "do you want to repost" browser message and all the associated code to handle double-posts.
MySampleForm.cfm:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<cfparam name="FORM.FirstName" default="">
<cfparam name="FORM.LastName" default="">
<cfparam name="FORM.CardNumber" default="">
<cfparam name="FORM.ExpirationMonth" default="">
<cfparam name="FORM.ExpirationYear" default="">
<cfparam name="FORM.Action" default="">
<cfset VARIABLES.y1=DatePart("yyyy",Now())>
<cfset VARIABLES.y2=VARIABLES.y1+10>
<cfparam name="URL.Msg" default="">
<cfset VARIABLES.Msg="#URL.Msg#">
<cfif VARIABLES.Msg is "" and FORM.Action is "Submit">
<!-- validate data -->
<cfif Trim(FORM.FirstName) is "">
<cfset VARIABLES.Msg=VARIABLES.Msg & "<li>You must enter your first name</li>">
</cfif>
<cfif Trim(FORM.LastName) is "">
<cfset VARIABLES.Msg=VARIABLES.Msg & "<li>You must enter your last name</li>">
</cfif>
<cfif Trim(FORM.CardNumber) is "">
<!--
This is a simple "card number entered" check for the
purpose of this example; I would do a more thorough
check to determine number was valid!
-->
<cfset VARIABLES.Msg=VARIABLES.Msg & "<li>You must enter your last name</li>">
</cfif>
<cfif Val(FORM.ExpirationMonth) LT 1 or Val(FORM.ExpirationMonth) GT 12>
<cfset VARIABLES.Msg=VARIABLES.Msg & "<li>You must select a valid expiration month</li>">
</cfif>
<cfif Val(FORM.ExpirationYear) LT VARIABLES.y1 or Val(FORM.ExpirationYear) GT VARIABLES.y2>
<cfset VARIABLES.Msg=VARIABLES.Msg & "<li>You must select a valid expiration year</li>">
</cfif>
<cfif VARIABLES.Msg is "">
<!-- validation passed, process data -->
<!?
ADD YOUR CODE HERE TO PROCESS THE DATA, IF ANYTHING
FAILS THEN SET VARIABLES.Msg
-->
<cfif VARIABLES.Msg is "">
<!-- process completed successfully, send client to next page -->
<!?
MODIFY THE FOLLOWING CFLOACTION TAG TO SEND THE
USER TO THE NEXT PAGE IN THE PROCESS LIKE A THANK
YOU PAGE, YOU'VE BEEN VALIDATED, ETC.
-->
<cflocation url="MySampleForm.cfm?Msg=#URLEncodedFormat('Information validated & posted - Thank you!')#" addtoken="No">
</cfif>
<cfelse>
<!-- validation failed, make the message pretty for the user -->
<cfset VARIABLES.Msg="<h1>ENTRY ERROR</h1>The following error(s) were detected:<ul>#VARIABLES.Msg#</ul>">
</cfif>
</cfif>
<html>
<head>
<title>My Test Form and Validation</title>
</head>
<body>
<cfoutput>
<cfif VARIABLES.Msg is not "">
<p>#VARIABLES.Msg#</p>
</cfif>
<cfform action="MySampleForm.cfm" method="POST" enablecab="No">
<table cellspacing="2" cellpadding="2" border="0">
<tr>
<th align="right">First Name</th>
<td><cfinput type="Text" name="FirstName" value="#FORM.FirstName#" message="You must enter your first name." required="Yes" size="30"></td>
</tr>
<tr>
<th align="right">Last Name</th>
<td><cfinput type="Text" name="LastName" value="#FORM.LastName#" message="You must enter your last name." required="Yes" size="30"></td>
</tr>
<tr>
<th align="right">Credit Card</th>
<td><cfinput type="Text" name="CardNumber" value="#FORM.CardNumber#" message="You must enter your credit card number." validate="creditcard" required="Yes" size="20"></td>
</tr>
<tr>
<th>Expiration</th>
<td>
<select name="ExpirationMonth">
<cfloop index="i" from="1" to="12">
<option value="#i#"<cfif FORM.ExpirationMonth EQ i> selected</cfif>>#NumberFormat(i,"00")#</option>
</cfloop>
</select>
/
<select name="ExpirationYear">
<cfloop index="i" from="#VARIABLES.y1#" to="#VARIABLES.y2#">
<option value="#i#"<cfif FORM.ExpirationYear EQ i> selected</cfif>>#NumberFormat(i,"0000")#</option>
</cfloop>
</select>
</td>
</tr>
<tr>
<td> </td>
<td>
<input type="submit" name="Action" id="Submit" value="Submit">
<input type="reset" value="Reset">
</td>
</tr>
</table>
</cfform>
</cfoutput>
</body>
</html>
To summarize the technique:
- FORM page and ACTION page are one and the same (MySampleForm.cfm in this example)
- Define all your form variables using the <cfparam> tag -- this example default all the values to "" but you can default any value the application requires
- Make sure your submit button has a name and value and use this value to determine whether you are acting on POSTed data -- make sure the defaulted value for this field is "" (<cfparam name="FORM.Action" default="">)
- If acting on the data (FORM.Action is "Submit"), validate all the data -- I use VARIABLES.Msg to hold any entry or action error for displaying to the user
- If all the data validates, act on the data and if successful, use the <cflocation> tag to send the user to the next page in the process (this example sends us back to the same page with a message -- this is unlikely in a real application)
- If you fall through to the actual page building code, display any errors or warnings to the user, if applicable, and default the FORM fields to their prior or defaulted values
Security wise, step #4 is the most important step -- validate everything!
There are other advantages to this single page technique, especially if you have to deal sites that have secure and unsecure portions and you are attempting to secure as few pages as possible because of CPU and bandwidth limitations.
One last note, I mentioned an parameter passing tweak to allow passing "default" values as URL parameters ? here's the tweak:
<!-- original code from above -->
<cfparam name="FORM.FirstName" default="">
<cfparam name="FORM.LastName" default="">
<!-- the tweak -->
<cfparam name="URL.FirstName" default="">
<cfparam name="FORM.FirstName" default="#URL.FirstName#">
<cfparam name="URL.LastName" default="">
<cfparam name="FORM.LastName" default="#URL.LastName#">
Like I originally said, there are multiple ways program almost any task -- this is one way. Is it "the best?" -- I doubt it, but it's the best that I have found and it works for me. Hope this helps someone?
*** Seven Year Update - 10/06/2010 ***
It's been seven years since I originally posted this tutorial and my CF coding techniques have matured. While I still use the technique described in this tutorial, I have incorporated some of my Fusebox experience into applying this technique. I'll briefly describe the differences here but hopefully, if I get some time, I'll rewrite a 2010 version of the tutorial.
URL and FORM Scope
To make scope handling easier and consistent, I copy both the URL and FORM scopes into an ATTRIBUTES scope. Somewhere near the very tope of the template, I include the following:
<cfif NOT IsDefined("attributes")>
<cfset attributes = StructNew() />
</cfif>
<cfif IsDefined("FORM")>
<cfset tmp = StructAppend(attributes,FORM,false) />
</cfif>
<cfif IsDefined("URL")>
<cfset tmp = StructAppend(attributes,URL,false) />
</cfif>
Now you only need a single cfparam for each variable you require as opposed to the cfparam of the URL variable and using it as the default for the cfparam of the FORM variable. Another advantage is that you can doctor the variable in the ATTRIBUTES scope all you want but if you need it, the original FORM or URL variable still exists with its original un-doctored value.
Fuses
Fusebox is build around fuses, or in non-fusebox terms, (usually) small modules which are easier to develop and maintain that single huge all encompassing mega modules. Even though the technique here does not require Fusebox, I still like the fuse concept.
In the above example, there is a somewhat large validation block:
<cfif VARIABLES.Msg is "" and FORM.Action is "Submit">
<!-- validate data -->
<cfif Trim(FORM.FirstName) is "">
<cfset VARIABLES.Msg=VARIABLES.Msg & "<li>You must enter your first name</li>">
</cfif>
...
</cfif>
Now a days, I take the contents of that entire block and put it in a separate module that gets cfincluded. This shortens this module (significantly in many cases) and makes it easier to maintain.
<cfif VARIABLES.Msg is "" and FORM.Action is "Submit">
<cfinclude template="act_formValidate.cfm" />
</cfif>
Solid Coding
I forgot about this tutorial but when someone commented on it, I had to reread it. I saw one part that made me cringe and I'm embarrassed that I included such a poor example in a tutorial designed for early ColdFusion programmers. As a challenge, go back and look at my coding example and see if you can find this glaring example of poor coding...
(LONG PAUSE FOR DRAMATIC EFFECT).....
If you did not find it yet, or you are unsure which atrocious code I am referring to, here is a hint: cross site scripting.
(slightly shorter pause)...
Got it? For programmers just starting out with ColdFusion, never -- I MEAN NEVER do something like this:
<cflocation url="MySampleForm.cfm?Msg=#URLEncodedFormat('Information validated & posted - Thank you!')#" />
NEVER pass messages as-is in URL or FORM variables. This can lead to a bored degenerate crafting code to insult your customers with a message like "Thank you, now go jump in a lake!" to hackers doing whatever they want to you or your customers. Instead, something like this would have made a MUCH better example:
<cflocation url="MySampleForm.cfm?Msg=thanks" />
and change the initial Msg handling to this (using the attributes scope change):
<cfset variables.msg = "" />
<cfif attributes.msg EQ "thanks">
<cfset variables.msg = "Information validated & posted - Thank you!" />
</cfif>
Hope this update helps someone.