Form Validation and Flow -- One Technique
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. Third, 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:
1. FORM page and ACTION page are one and the same (MySampleForm.cfm in this
example)
2. 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
3. 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="">)
4. 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
5. If all the data validates, act on the data and if successful, us 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)
6. 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?