Re-using server side logic for client side validation

I have posted a while back the possibility of re-using your server side validation for client side validation. Few days ago, when it wasn’t so busy at the office, I was able to put this concept to test. In this post, I will explain on how to do this.

First of all, if you are required to create a form with validation rules – ALWAYS create the server side validation first. Client side is a nice thing to have on top of the server side validation – but you should never have client side validation alone. The reason is obvious, javascript breaks easily and it is also possible for your users to turn off javascript on their browser completely. Also sometimes if you are limited in time, I think it’s safe to sacrifice client side validation.

Having said that, if there’s a way to simplify client side validation, then perhaps you don’t have to sacrifice it at all.

The sample form consists of 2 ColdFusion templates: the form itself and the server side validation template.

Below is the form template:

[coldfusion] <cfsetting showdebugoutput="false"> <cfsilent> <cfparam name="FORM.IsPosted" default=false>

&lt;cfset lAllFields = &quot;first_name,last_name,email_address,login_name,password,password2&quot;&gt;

&lt;cfset stValidationError = StructNew()&gt;
&lt;cfset arrSystemError = ArrayNew( 1 )&gt;

&lt;cfloop list=&quot;#lAllFields#&quot; index=&quot;thisField&quot;&gt;
	&lt;cfparam name=&quot;FORM.#thisField#&quot; default=&quot;&quot;&gt;
&lt;/cfloop&gt;

&lt;cfif FORM.IsPosted&gt;
	&lt;!---
		if FORM.isPosted exists - do server side validation
		if we ever get to this part, this means client side validation is not triggered
	---&gt;
	&lt;cfinclude template=&quot;actSimpleRegister.cfm&quot;&gt;
&lt;/cfif&gt;

</cfsilent>

<cfoutput> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Example</title>

<script src="http://www.google.com/jsapi"></script> <script> // Load jQuery google.load("jquery", "1.3.2"); google.load("jqueryui", "1.7.1"); </script> </head> <body>

<script type="text/javascript" language="javascript"> <!— In this example, I created two ColdFusion templates, one for the form display (simpleRegistration.cfm) and the other one for the form action (actSimpleRegistration.cfm). This is an example of a plain vanilla registration form, all of fields are required. There are 2 types of validation required here: the required fields validation and password - confirm password fields matching validation.

The aim of this exercise is to prove that we can use the server side validation code in actSimpleRegistration.cfm by utilizing AJAX on form submit.

Below is the simpleRegistration.cfm code.

The form submits to itself. On top of the template there is an include to actSimpleRegistration.cfm, if

—> function submitForm(){ /* Build the parameters - I sort of cheated by using ColdFusion list to built it */ var parameters = ‘fromAjax=1’; <cfloop list="#lAllFields#" index="field"> parameters = parameters + ‘&#field#=’ + $( ‘[ name = #field#]’ ).val(); </cfloop> $.ajax({ url : ‘actSimpleRegister.cfm’ , type : ‘POST’ , data : parameters , error : function(){ alert( "Error loading Ajax Response" ); } , dataType : ‘json’ , success : function( json ) { var popUpErrorMessage = ‘’; $.each( json , function( i, item ){ //if validation is successful we will receive JSON Structure of SUCCESS:success if ( i == ‘SUCCESS’ ){ return true; //NOTE: this is how we BREAK from jQuery each loop } errorMessage = item.replace( ‘<br />’, ‘\n’ ); popUpErrorMessage = popUpErrorMessage + i + ‘:’ + errorMessage + ‘\n’; } ); if ( popUpErrorMessage != ’’ ){ alert( popUpErrorMessage ); return false; }else{ alert( ‘Registration successful’ ); //Normally we want to redirect to a successful page return true; } } }); return false; } </script>

<cfif NOT FORM.isPosted OR NOT StructIsEmpty( stValidationError ) OR ArrayLen( arrSystemError ) gt 0 >

&lt;cfif ArrayLen( arrSystemError ) gt 0&gt;
	&lt;div style=&quot;border:solid 1px red&quot;&gt;
		&lt;cfloop from=&quot;1&quot; to=&quot;#ArrayLen( arrSystemError )#&quot; index=&quot;index&quot;&gt;
		#Array[ index ]# &lt;br /&gt;
		&lt;/cfloop&gt;
	&lt;/div&gt;
&lt;/cfif&gt;

&lt;cfif NOT StructIsEmpty( stValidationError )&gt;
	&lt;div style=&quot;border:solid 1px red; padding:10px; font-size:12px; font-weight:bold; font-style:italic; color:red;width:300px&quot;&gt;
		&lt;h3&gt;Error(s) found&lt;/h3&gt;
		&lt;ul&gt;
		&lt;cfloop collection=&quot;#stValidationError#&quot; item=&quot;key&quot;&gt;
		&lt;li&gt;#key# #stValidationError[ key ]# &lt;/li&gt;
		&lt;/cfloop&gt;
		&lt;/ul&gt;

	&lt;/div&gt;
&lt;/cfif&gt;

&lt;h1&gt;Registration&lt;/h1&gt;

&lt;form action=&quot;#CGI.SCRIPT_NAME#&quot; method=&quot;post&quot; name=&quot;accountFORM&quot; onsubmit=&quot;return submitForm()&quot;&gt;
	&lt;input name=&quot;IsPosted&quot; value=&quot;true&quot; type=&quot;hidden&quot;&gt;
	&lt;table border=&quot;0&quot; width=&quot;100%&quot;&gt;
	&lt;tr&gt;
		&lt;td&gt;
			&lt;strong&gt;* First Name:&lt;/strong&gt;
		&lt;/td&gt;
		&lt;td&gt;
			&lt;input name=&quot;first_name&quot; id=&quot;first_name&quot; value=&quot;#FORM.first_name#&quot; maxlength=&quot;25&quot; type=&quot;text&quot;&gt;
		&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;
			&lt;strong&gt;* Last Name:&lt;/strong&gt;
		&lt;/td&gt;
		&lt;td&gt;
			&lt;input name=&quot;last_name&quot; id=&quot;last_name&quot; value=&quot;#FORM.last_name#&quot; maxlength=&quot;25&quot; type=&quot;text&quot;&gt;
		&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;
			&lt;strong&gt;* E-mail:&lt;/strong&gt;
		&lt;/td&gt;
		&lt;td&gt;
			&lt;input name=&quot;email_address&quot; id=&quot;email_address&quot; value=&quot;#FORM.email_address#&quot; maxlength=&quot;50&quot; type=&quot;text&quot;&gt;
		&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;
			&lt;strong&gt;* Login name:&lt;/strong&gt;
		&lt;/td&gt;
		&lt;td&gt;
			&lt;input name=&quot;login_name&quot; id=&quot;login_name&quot; value=&quot;#FORM.login_name#&quot; maxlength=&quot;50&quot; type=&quot;text&quot;&gt;
		&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;
			&lt;strong&gt;* Password:&lt;/strong&gt;&lt;br&gt;
		&lt;/td&gt;
		&lt;td&gt;
			&lt;input name=&quot;password&quot; id=&quot;password&quot; value=&quot;#FORM.password#&quot; type=&quot;password&quot;&gt;
		&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;
		&lt;strong&gt;* Confirm Password:&lt;/strong&gt;
		&lt;/td&gt;
		&lt;td&gt;&lt;input name=&quot;password2&quot; id=&quot;password2&quot; value=&quot;#FORM.password2#&quot; type=&quot;password&quot;&gt;&lt;/td&gt;
	&lt;/tr&gt;

	&lt;tr&gt;
		&lt;td colspan=&quot;2&quot;&gt;
			&lt;input type=&quot;submit&quot; value=&quot;submit&quot;&gt;
		&lt;/td&gt;
	&lt;/tr&gt;
	&lt;/table&gt;
&lt;/form&gt;

<cfelse> The registration has been forwarded to our administrator. You will be notified about the outcome shortly. </cfif> </cfoutput>

[/coldfusion]

Below is the server side validation template:

[coldfusion] <cfsetting showdebugoutput="no">

<cfset lRequiredFields = "first_name,last_name,email_address,login_name,password,password2">

<cfif StructKeyExists( FORM, "fromAjax" )> <cfset stValidationError = StructNew()> <cfset arrSystemError = ArrayNew( 1 )> </cfif>

<cfset stValidationError = StructNew()>

<cffunction name="structAppendValue"> <cfargument name="st" required="yes" type="struct"> <cfargument name="key" required="yes"> <cfargument name="value" required="yes"> <cfif NOT StructKeyExists( st, key )> <cfset StructInsert( st, key, value )> <cfelse> <cfset st[ key ] = st[ key ] & value> </cfif> <cfreturn st> </cffunction>

<cfloop list="#lRequiredFields#" index="requiredField"> <!— we force every call to this include is by form post not get —> <cfif NOT StructKeyExists( FORM, requiredField ) OR FORM[ requiredField ] eq ""> <cfset stValidationError = structAppendValue( stValidationError, requiredField, "this field cannot be empty <br />" )> </cfif> </cfloop>

<!— Custom validation —> <cfif FORM.password neq FORM.password2> <cfset stValidationError = structAppendValue( stValidationError, ‘password’, "password and confirm password don’t match <br /> " )> <cfset stValidationError = structAppendValue( stValidationError, ‘password2’, "password and confirm password don’t match <br /> " )> </cfif>

<!— END | VALIDATION SERVER SIDE —>

<cfif NOT StructIsEmpty( stValidationError )> <cfif StructKeyExists( FORM, "fromAjax" )> <!— if called from ajax, output the error struct in JSON format—> <cfoutput>#SerializeJSON( stValidationError )#</cfoutput> <cfelse> <!— do nothing, on the parent page, we will just output the struct —> </cfif> <cfelse><!— form is validated! —> <!— this is where your saving logic will be —> <cfif StructKeyExists( FORM, "fromAjax" )> <cfset stSuccess = { success = "success" }> <cfoutput>#SerializeJSON( stSuccess )#</cfoutput> </cfif> </cfif> [/coldfusion]

Now if you see the form template above, you see on there is an include to the validation template if the form was posted (the form is posting to itself). If you leave at this and not having the javascript (jQuery) code, that’s fine - you have a functioning form and server side validation. Now if you want to have a client side validation on top of what you have, you would only need to call your server side validation template using Ajax.

On the validation template, I use a struct with field name, error message pair as key and value, if the template is called from Ajax call, I will output the struct in JSON format (using serializeJSON function). If the validation template is included from a parent template (form template), I will simply pass along the struct back to the parent template and let the parent template handles it.

That’s it! Pretty easy huh? The biggest hurdle for me was to work out how to parse JSON in jQuery.