Reactor Validation Part III
In the final installment of our mini series we are getting down and dirty with custom validation. We learned in part II how basic validation is done. If you were to launch our application, go to add contact, and submit the form without entering any data you should see the following errors.
- The fname field is required but was not provided
- The lname field is required but was not provided
- The emailaddress field is required but was not provided
So why did it choose these fields as required? If you look into our database schema you will noticed that I marked fname, lname, and emailaddress as not null. Reactor knows this and said at the very least I need these fields filled in (and within the data type and length requirements) to submit this form. At this point things are good because we have not written any validation code yet our form submission is being checked for us. First thing you probably noticed is that the error messages are reading the form field names. We want to make these error messages a little more descriptive. To accomplish this we will make some minor changes to our Dictionary file. The file contactdictionary.xml is located in your rvdata folder. Let us take a look at the 3 fields we are trying to update.
<label>fname</label>
<comment/>
<maxlength>50</maxlength>
<notProvided>The fname field is required but was not provided.</notProvided>
<invalidType>The fname field does not contain valid data. This field must be a string value.</invalidType>
<invalidLength>The fname field is too long. This field must be no more than 50 bytes long.</invalidLength>
</fname>
<lname>
<label>lname</label>
<comment/>
<maxlength>50</maxlength>
<notProvided>The lname field is required but was not provided.</notProvided>
<invalidType>The lname field does not contain valid data. This field must be a string value.</invalidType>
<invalidLength>The lname field is too long. This field must be no more than 50 bytes long.</invalidLength>
</lname>
<emailaddress>
<label>emailaddress</label>
<comment/>
<maxlength>50</maxlength>
<notProvided>The emailaddress field is required but was not provided.</notProvided>
<invalidType>The emailaddress field does not contain valid data. This field must be a string value.</invalidType>
<invalidLength>The emailaddress field is too long. This field must be no more than 50 bytes long.</invalidLength>
</emailaddress>
To update our error messages with a little bit more of a description we can simply update our xml file to look like this
<label>fname</label>
<comment/>
<maxlength>50</maxlength>
<notProvided>The first name field is required but was not provided.</notProvided>
<invalidType>The first name field does not contain valid data. This field must be a string value.</invalidType>
<invalidLength>The first name field is too long. This field must be no more than 50 bytes long.</invalidLength>
</fname>
Ok, so now our error messages look a lot better and we are validating that the user entered something in all three of the fields, but were not done yet. What if wanted to validate that what the user was entering in the email field was a properly formatted email address. If you look in your rvdata/Validator folder you have a file called contactValidator.cfc. If you open the file you will notice there is no code, what gives? This component is extending the base contactValidator.cfc located In reactor/project/ReactorValidation/Validator/contactValidator/. The extends keyword tell us what file to extend
extends="reactor.project.ReactorValidation.Validator.contactValidator">
<!--- Place custom code here, it will not be overwritten --->
</cfcomponent>
The first thing we are doing here is overriding our validateEmailAddress method so we can add some custom code. We still want the original validation to be run, that will check to make sure something was entered and that it fits within the data type and length constraints. To accomplish this we will use the super keyword which calls the parent method. Now that basic validation has been done lets call our validate email format method. Next we instantiate our contactGateway and call our validation method, which sticking with best practices is business logic and located in our gateway. Finally if an error is found we add a new error to the errorCollection array.
extends="reactor.project.ReactorValidation.Validator.contactValidator">
<!--- Place custom code here, it will not be overwritten --->
<cffunction name="validateEmailAddress" access="public" hint="I validate the emailaddress field" output="false" returntype="reactor.util.ErrorCollection">
<cfargument name="ContactRecord" required="false" type="reactor.project.ReactorValidaion.Record.ContactRecord"/>
<cfargument name="ErrorCollection" hint="I am the error collection to populate. If not provided a new collection is created." required="no" type="reactor.util.ErrorCollection" default="#createErrorCollection(arguments.UserRecord._getDictionary())#" />
<cfset super.validateEmailAddress(arguments.ContactRecord,arguments.ErrorCollection)>
<!--- validate that the email adddress is formated correctly --->
<cfset isValidEmail(arguments.ContactRecord,arguments.ErrorCollection)>
<cfreturn arguments.ErrorCollection />
</cffunction>
<!--- validateEmailFormat --->
<cffunction name="validateEmailFormat" access="public" hint="I validate that the users email address is correctly formated." output="false" returntype="reactor.util.ErrorCollection">
<cfargument name="ContactRecord" required="false" type="reactor.project.ReactorValidaion.Record.ContactRecord"/>
<cfargument name="ErrorCollection" hint="I am the error collection to populate. If not provided a new collection is created." required="no" type="reactor.util.ErrorCollection" default="#createErrorCollection(arguments.UserRecord._getDictionary())#" />
<cfset var ContactGateway = _getReactorFactory().createGateway("contact") />
<!--- returns true if the email address is formated properly --->
<cfif NOT ContactGateway.validateEmailAddress(arguments.ContactRecord.getEmailAddress())>
<cfset arguments.ErrorCollection.addError("contct.emailaddress.format") />
</cfif>
<cfreturn arguments.ErrorCollection />
</cffunction>
</cfcomponent>
The contact.emailaddress.format tells us what type of error. We can then go back to our dictorary file and add a custom message for that.
<label>emailaddress</label>
<comment/>
<maxlength>50</maxlength>
<notProvided>The emailaddress field is required but was not provided.</notProvided>
<invalidType>The emailaddress field does not contain valid data. This field must be a string value.</invalidType>
<invalidLength>The emailaddress field is too long. This field must be no more than 50 bytes long.</invalidLength>
<format>The email address field is not properly formatted.</format>
</emailaddress>
Last but not least we will extend our contactGateway and add our method that checks to make sure the address is properly formatted.
extends="reactor.project.ReactorValidation.Gateway.contactGateway" >
<!--- Place custom code here, it will not be overwritten --->
<cffunction name="validateEmailAddress" access="public" output="false">
<cfargument name="email" type="string" required="true"/>
<cfset var str = arguments.email>
<cfscript>
/**
* Tests passed value to see if it is a valid e-mail address (supports subdomain nesting and new top-level domains).
* Update by David Kearns to support '
* SBrown@xacting.com pointing out regex still wasn't accepting ' correctly.
* More TLDs
* Version 4 by P Farrel, supports limits on u/h
*
* @param str The string to check. (Required)
* @return Returns a boolean.
* @author Jeff Guillaume (jeff@kazoomis.com)
* @version 4, December 30, 2005
*/
return (REFindNoCase("^['_a-z0-9-]+(\.['_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*\.(([a-z]{2,3})|(aero|coop|info|museum|name|jobs|travel))$",str) AND len(listGetAt(str, 1, "@")) LTE 64 AND len(listGetAt(str, 2, "@")) LTE 255) IS 1;
</cfscript>
</cffunction>
</cfcomponent>
If you try and submit the form now you will see another error and it should say the email address field is not properly formatted. So there you have, you can now do basic validation as well as adding some custom validation. I hope these short tutorials have helped you out, please drop me a line if they did or what you would like to see next. If I made any mistakes please drop me a line as well, I am tired and flying through the end of this. This may seem like a lot at first but don't worry, it is easy once you get the hang of it.
