Why would you want to do something the hard way? Well of course you do something the hard way when you can't do something the easy way (unless you are Oren in which case you do everything your way :). Hammett has several excellent posts on how to do form validation the easy way using the Castle Validator component (screencast and additional thoughts). The component makes validation dirt simple especially if you are using ActiveRecord. However the default implementation ties validators to attributes on the model which works 80% of the time.
For the 20% of the time when you want to get validators from somewhere else one would need to implement a custom IValidatorRegistry. IValidatorRegistry is responsible for providing all validators for given type and/or property back to the validation framework. Are you feeling the love? All you need to do is customize where the validators come from, and you still benefit from the runtime and client side validation generation. w00t! So, in theory your DatabaseValidatorRegistry could query a...database...to retrieve all validators for a given type and property. Allowing you to craft an interface where by the end user could modify validation rules at will. I'm getting a little dizzy just thinking about the possibilities. I haven't gone there yet but when I do you will be the first to know.
That last paragraph literally popped into my head as I was starting this post. I actually was going in a different direction for this particular discussion but I just had to put it down on paper. What I really want to talk about is doing simple client side validation using the client side Validator scripts. I have a simple login prompt with username and password. I want to do client side validation to ensure they enter a username and password before submitting the form. What do you do? Go.
First things first, you need to install the client side scripts. It is recommended to do this in your layout so that the scripts are included on all of your pages:
<head>
<title>MonoRail Validation</title>
${Ajax.InstallScripts()}
${Form.InstallScripts()}
</head>
What exactly does this do? Among other things it emits script tags for the Prototype libraries and the Dexagogo 'really easy field validation' libraries. The next step is to create a form in your view with the necessary fields:
${Form.FormTag({'action':'login'})}
<table>
<tr>
<td>${Form.LabelFor('username', 'Username')}</td>
<td>${Form.TextField('username', {'class':'required'})}</td>
<td><div id="advice-username" class="advice" style="display: none;">Please enter a Username</div></td>
</tr>
<tr>
<td>${Form.LabelFor('password', 'Password')}</td>
<td>${Form.PasswordField('password', {'class':'required'})}</td>
<td />
</tr>
<tr>
<td />
<td>${Form.Submit('Login')}</td>
<td />
</tr>
</table>
${Form.EndFormTag()}
How hard is it to make a field required? Excruciating! First the Form.FormTag automatically hooks up the required logic to the validation libraries. See the 'class':'required' in the TextField and PasswordField elements? The Dexagogo code uses CSS selectors (similar to Behaviour) to execute validation when the user clicks the Login button. THAT'S IT! Now, when you click Login without entering a username, the form will not be submitted and you will be presented with a lovely error message. The Dexagogo site documents the twelve built in validations you can perform (dates, numbers, email, etc). If you need more you can specify your own validation callback that will get executed when the form is submitted. Dexagogo has a number of standard validation messages, but you can easily substitute your own by adding an element to your page with an id of "advice-<fieldName>". If validation fails, this element will be shown to the user. I applied a class of "advice" to my element so I could easily style error messages. One oddity was that I had to inline specify the display: none. Putting this in my external stylesheet caused the error message to not be shown properly. The use of CSS selectors also makes it very easy for you to style your required fields (with a yellow background maybe?) and style your numeric only fields and provide a standard legend to your users.
You can also specify some additional options in the FormTag (Hammett touched on these briefly in his screen cast). If you want "advice" shown to the user immediately when the leave a field and you want to turn off the default of focusing on invalid fields, you would specify your FormTag like so.
${Form.FormTag({'action':'login','immediate':'true','focusOnError':'false'})}
Finally I'll reiterate the best practice you've heard before, always validate your data server side in addition to any client side validation you may perform. For your reference here is the fully generated code for the view above:
<!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>
<title>MonoRail Validation</title>
<script type="text/javascript" src="/MonoRail/Files/AjaxScripts.rails?RC3_0006"></script>
<script type="text/javascript" src="/MonoRail/Files/FormHelperScript.rails?RC3_0006"></script>
</head>
<body>
<form action='/login/login.rails' method='post' id='form1' >
<table>
<tr>
<td><label for="username">Username</label></td>
<td><input type="text" id="username" name="username" value="" class="required" /></td>
<td><div id="advice-username" class="advice" style="display: none;">Please enter a Username</div></td>
</tr>
<tr>
<td><label for="password">Password</label></td>
<td><input type="password" id="password" name="password" value="" class="required" /></td>
<td />
</tr>
<tr>
<td />
<td><input type="submit" value="Login" /></td>
<td />
</tr>
</table>
<script type="text/javascript">
if (!window.prototypeValidators) prototypeValidators = $A([]);
var validator = new Validation('form1', {onSubmit:true, focusOnError:true, stopOnFirst:false, immediate:true, useTitles:true});
prototypeValidators['form1'] = validator;
</script>
</form>
</body>
</html>
posted @ Saturday, June 23, 2007 7:47 AM