Custom ValidateThis Result
The talented Bob Silverberg released version 0.95 of his validation framework; ValidateThis. I wanted to blog about one of the cool new features that Bob added, which is the ability to add your own custom result object.
To demonstrate, let's build a simple contact form (yes, you can use ValidateThis with simple CFCs!).
First off we need download ValidateThis and extract it into the web root (you can create a mapping if you prefer). Next we need an Enquiry object:
<cfcomponent accessors="true" output="false" hint="I model a simple contact form">
<!--- ColdFusion 9 will create a getXXX and setXXX for each property --->
<cfproperty name="name">
<cfproperty name="email">
<cfproperty name="phone">
<cfproperty name="message">
</cfcomponent>
Now we need a form. To keep things simple, I've made the form self-submitting and also create the ValidateThis framework on each page request. Normally ValidateThis would be used as a singleton stored in the application scope. So here is the form script.
<!---
validatethis would normally be saved in the application scope
such as <cfset application.validatethis = new validatethis.ValidateThis()>
I've put it here for demo purposes.
--->
<cfset validatethis = new validatethis.ValidateThis()>
<cfif StructCount( form ) gt 0>
<!--- form submitted --->
<cfset Enquiry = new Enquiry()>
<cfset Enquiry.setname( form.name )>
<cfset Enquiry.setemail( form.email )>
<cfset Enquiry.setphone( form.phone )>
<cfset Enquiry.setmessage( form.message )>
<!--- validate the enquiry --->
<cfset validationresult = validatethis.validate( Enquiry )>
<cfif !validationresult.hasErrors()>
<!--- send email here and redirect --->
<cfoutput>Enquiry is valid!</cfoutput>
<cfabort>
</cfif>
<cfelse>
<!--- new Enquiry --->
<cfset Enquiry = new Enquiry()>
<cfset validationresult = validatethis.newresult()>
</cfif>
<cfoutput>
<!--- display any validation errors --->
<cfif validationresult.hasErrors()>
<div style="border:2px solid red">
<p>Please review the following:</p>
<ul>
<cfloop array="#validationresult.getFailureMessages()#" index="message">
<li>#message#</li>
</cfloop>
</ul>
</div>
</cfif>
<form action="myform.cfm" method="post">
<dl>
<dt>name</dt>
<dd><input type="text" id="name" name="name" value="#HtmlEditFormat( Enquiry.getname() )#" /></dd>
<dt>email</dt>
<dd><input type="text" id="email" name="email" value="#HtmlEditFormat( Enquiry.getemail() )#" /></dd>
<dt>phone</dt>
<dd><input type="text" id="phone" name="phone" value="#HtmlEditFormat( Enquiry.getphone() )#" /></dd>
<dt>message</dt>
<dd><textarea id="message" name="message">#HtmlEditFormat( Enquiry.getmessage() )#</textarea></dd>
<dd><input type="submit" id="submit" name="submit" value="Send Enquiry" /></dd>
</dl>
</form>
</cfoutput>
Finally we need to create the validation rules. ValidateThis uses a lot of conventions (which you can override in the config), so all we need to do is create an XML file called Enquiry.xml.cfm in the same folder as the Enquiry.cfc file and ValidateThis will find it for you. Here's what Enquiry.xml.cfm looks like:
<?xml version="1.0" encoding="UTF-8"?>
<validateThis xsi:noNamespaceSchemaLocation="validateThis.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<objectProperties>
<property name="Name">
<rule type="required" />
</property>
<property name="Email" desc="Email Address">
<rule type="required" />
<rule type="email" />
</property>
<property name="Phone" desc="Daytime Telephone Number">
<rule type="required" />
</property>
<property name="Message">
<rule type="required" />
</property>
</objectProperties>
</validateThis>
All this is saying is that name, email, phone and message are required. We also want to check that the email property contains an email address. Again we can customise the messages that ValidateThis sends back, or check that the messge is at least 100 chars, but I'm keeping things simple.
If you run this script you'll have fully working validation with minimum effort.
So, what about the result object I mentioned earlier? At the moment we are adding code in the view to render the error messages as a list. If we had lots of forms this would mean copying and pasting this code. As developers we should aim to not duplicate code (a principle known as DRY). So wouldn't it be cool if the result object had a renderit() method which generated the HTML for us? Well, we can!
Result.cfc
<cfcomponent extends="validatethis.util.Result" output="false">
<cffunction name="renderit" returntype="string" output="false" hint="I render validation error messages (if any)">
<cfset var html="">
<cfset var message="">
<cfif hasErrors()>
<cfsavecontent variable="html">
<cfoutput>
<div style="border:2px solid red">
<p>Please review the following:</p>
<ul>
<cfloop array="#getFailureMessages()#" index="message">
<li>#message#</li>
</cfloop>
</ul>
</div>
</cfoutput>
</cfsavecontent>
</cfif>
<cfreturn html>
</cffunction>
</cfcomponent>
We need to modify our form script to tell ValidateThis about our custom result object. This can be done easily like this:
<cfset validateThisConfig = { ResultPath='Result' }>
<cfset validatethis = new validatethis.ValidateThis( validateThisConfig )>
Note: ResultPath is the dotted path to your CFC. As I'm sticking the Result.cfc in the same folder as my form script I haven't needed to put the path in.
Finally we need to replace all that HTML code to render the validation messages with a call to the result object:
<!--- display any validation errors using our custom result --->
#validationresult.renderit()#
Here is the complete updated form script:
<!---
validatethis would normally be saved in the application scope
such as <cfset application.validatethis = new validatethis.ValidateThis()>
I've put it here for demo purposes.
Because I want to use my own result object I need to pass it as an argument to
ValidateThis using the ResultPath setting.
--->
<cfset validateThisConfig = { ResultPath='Result' }>
<cfset validatethis = new validatethis.ValidateThis( validateThisConfig )>
<cfif StructCount( form ) gt 0>
<!--- form submitted --->
<cfset Enquiry = new Enquiry()>
<cfset Enquiry.setname( form.name )>
<cfset Enquiry.setemail( form.email )>
<cfset Enquiry.setphone( form.phone )>
<cfset Enquiry.setmessage( form.message )>
<!--- validate the enquiry --->
<cfset validationresult = validatethis.validate( Enquiry )>
<cfif !validationresult.hasErrors()>
<!--- send email here and redirect --->
<cfoutput>Enquiry is valid!</cfoutput>
<cfabort>
</cfif>
<cfelse>
<!--- new Enquiry --->
<cfset Enquiry = new Enquiry()>
<cfset validationresult = validatethis.newresult()>
</cfif>
<cfoutput>
<!--- display any validation errors using our custom result --->
#validationresult.renderit()#
<form action="myform.cfm" method="post">
<dl>
<dt>name</dt>
<dd><input type="text" id="name" name="name" value="#HtmlEditFormat( Enquiry.getname() )#" /></dd>
<dt>email</dt>
<dd><input type="text" id="email" name="email" value="#HtmlEditFormat( Enquiry.getemail() )#" /></dd>
<dt>phone</dt>
<dd><input type="text" id="phone" name="phone" value="#HtmlEditFormat( Enquiry.getphone() )#" /></dd>
<dt>message</dt>
<dd><textarea id="message" name="message">#HtmlEditFormat( Enquiry.getmessage() )#</textarea></dd>
<dd><input type="submit" id="submit" name="submit" value="Send Enquiry" /></dd>
</dl>
</form>
</cfoutput>
- Posted in:
- ColdFusion
- OOP


Comment by Bob Silverberg – June 28, 2010