Client-side validation with Zend_Form

Tags:

Zend_Form is Zend Framework’s brand new tool for simplifying form creation, parsing, validation and filtering.

There’s one big thing I want from any kind of form generation tool: The ability to let me combine my server-side validators with JavaScript validation on the client, or other such features.

And here’s the thing: Zend_Form is easily extendable to automatically generate JavaScript for validating fields on the client!

Decorators, go

The key to this is the ability to give the form decorators. They can be easily used to perform tasks based on the fields in the form, such as seeing what validators they have and generating some JS.

We can easily do this with two decorators:

  • A modified Form decorator to add an onsubmit event
  • Another decorator for looping through each form field and generating the JS code needed. Let’s call this JsValidation decorator.

We will also need to write a bit of JavaScript that will be used for processing the code generated by the second decorator.

The form decorator

The form decorator is very simple. It only needs to set the onsubmit event and add our JavaScript file which will contain the helper code needed.

Download this from CodeUtopia’s SVN repo

<?php
class CU_Form_Decorator_Form extends Zend_Form_Decorator_Form 
{
    public function getOptions()
    {
        $this->setOption('onsubmit','return App.validate(this)');
 
        $baseUrl = Zend_Controller_Front::getInstance()->getBaseUrl();
        $this->getElement()->getView()->headScript()->appendFile($baseUrl . '/js/Validator.js');
 
        return parent::getOptions();
    }
}

So that’s it. I will show how to use it later in the post.

The JsValidation decorator

The JsValidation decorator can simply generate a JavaScript object which contains arrays for each of the fields. The arrays will then contain objects which contain the validator’s name and parameters.

//For example, our form has a field called "firstname" with validators NotEmpty and Alnum:
var MyForm = {
    firstname: [{
        name: 'NotEmpty',
        parameters: { }
    }, {
        name: 'Alnum',
        parameters: { allowWhiteSpace: true }
    }]
};

So how do we go about generating such code?

Download this from CodeUtopia’s SVN repo

<?php
/**
 * Generates JS validation rules for form fields
 */
class CU_Form_Decorator_JsValidation extends Zend_Form_Decorator_Abstract 
{
	/**
	 * The name of the form
	 * @var string
	 */
	protected $_formName;
 
	public function render($content)
	{
		$form = $this->getElement();
		$view = $form->getView();
		$this->_formName = $form->getName();
 
		if(!$this->_formName)
			$this->_formName = 'form';
 
		$script = "var Forms = Forms || { };\r\n"
				. "Forms." . $this->_formName . " = { };\r\n";
 
		foreach($form as $element)
		{
			$validators = $element->getValidators();
 
			if(count($validators) > 0)
				$script .= $this->_buildValidationRules($element);	
		}
 
		$view->inlineScript()->captureStart();
		echo $script;
		$view->inlineScript()->captureEnd();
 
		return $content;
	}
 
	/**
	 * Generate the JavaScript code for the validation rules
	 * @param Zend_Form_Element $element
	 * @return string
	 */
	protected function _buildValidationRules(Zend_Form_Element $element)
	{
		$name = $element->getName();
		$formName = $this->_formName;
		$validators = $element->getValidators();
 
 
		$rules = array();
		foreach($validators as $validator)
		{
			$class = get_class($validator);
			$params = $this->_buildValidatorParameters($class, $validator);
			$rules[] = "{ name: '$class', parameters: $params }";
		}
 
		if(count($rules) > 0)
			$script = "Forms." . $this->_formName . ".$name = [ " . implode(', ', $rules) . " ];\r\n";
 
		return $script;
	}
 
	/**
	 * Generate parameters for a validator rule
	 * @param string $class The name of the validator class
	 * @param Zend_Validate_Interface $validator the validator
	 * @return string
	 */
	protected function _buildValidatorParameters($class, Zend_Validate_Interface $validator)
	{
		$params = '{}';
		switch($class)
		{
			case 'Zend_Validate_Alnum':
			case 'Zend_Validate_Alpha':
				$params = '{ allowWhitespace: ' . (($validator->allowWhiteSpace) ? 'true' : 'false') . ' } ';
				break;
 
			case 'Zend_Validate_Between':
				$params = '{ min: ' . $validator->getMin() . ', max: ' . $validator->getMax() . ' } ';
				break;
		}
 
		return $params;
	}
}

Let’s look at each method. Starting from the top, the render method is called when the form is rendered, so we just use this to do our thing. First we grab some variables and define the JS Forms variable that will act as a namespace for our form rules.

In case we have multiple forms, we use the Forms || { } to create it based on the old one if it exists. We also use the form’s name to store the actual rules, so be sure to give your form a name if you have more than one per page – otherwise their rules may collide etc.

Then it’s simply looping over each form field, generating code if necessary.

the _buildValidationRules method generates JS code for each validator in an element. It pretty much just loops over each validator and returns the code.

Finally, the _buildValidatorParameters method is used to generate the parameters for each validator. Since Zend_Validate does not specify a way to get all the parameters a validator may have, we’re just using a switch and looking at each validator class type on its own. This switch can be easily extended by adding more validators. You can also easily create a custom class based on this one and then override this method to do the same.

If you look at the switch, you’ll notice both Alnum and Alpha have the same code. This is because they both share the same parameters. You can actually do this with some validators, and many validators don’t even have any special parameters, so you won’t need a case for them.

The JavaScript code

Of course, we will also need that Validator.js file mentioned in the Form helper. Without that, our rule definitions would just sit in our page doing nothing.

Download this from CodeUtopia’s SVN repo

var App = App || { };
 
App.validate = function(form)
{
	var formName = (form.name)
	             ? form.name
	             : 'form';
 
	var rules = Forms[formName];
 
	var formValidates = true;
 
	for(var key in rules)
	{
		var element = form[key];
		var ruleset = rules[key];
 
		var value = (element.nodeName == 'INPUT') 
		          ? element.value
		          : element.innerHTML;
 
		for(var i in ruleset)
		{
			var validatorName = ruleset[i].name;
			var elementValidates = App.Validator[validatorName](value, ruleset[i].parameters);
 
			if(elementValidates)
			{
				//You can replace this with whatever you want to happen to invalid fields.
				YAHOO.util.Dom.removeClass(element, 'invalid');
			}
			else 
			{
				//Again, replace if you aren't using YUI.
				YAHOO.util.Dom.addClass(element, 'invalid');
 
				//but not this
				formValidates = false;
				break;
			}
		}
	}
 
	return formValidates;
}
 
 
App.Validator = {
	Zend_Validate_NotEmpty: function(value, parameters)
	{
		if(value != '')
			return true;
 
		return false;
	},
 
	Zend_Validate_Alnum: function(value, parameters)
	{
		if(parameters.allowWhiteSpace)
			return value.match(/^[a-z0-9\s]*$/i);
		else
			return value.match(/^[a-z0-9]*$/i);
	}
};

Again, let’s go through the code step by step.

First, we’re defining the App namespace. Similar to how the Forms variable was defined, we’re making sure that if it exists, we’ll just use the existing one. It’s good practice to namespace your JS code so that it does not interfere with others.

Next, we define the validate function. It looks at the validation rules created by the JsValidation decorator and loops through them. It takes each field and their value and calls each validator defined for the element. I have left some Yahoo UI library specific code there, which you can replace with your own if you aren’t using YUI. It’s nothing major, simply adding a CSS class “invalid” to fields which fail validation.

In the end the function returns if the form validates or not. This is so that if the validation fails, the form will not get submitted.

Next, in the App.Validator namespace we define our JS versions of Zend_Validate classes. I have added two examples, Zend_Validate_NotEmpty and Zend_Validate_Alnum. NotEmpty simply looks if the value was empty and Alnum uses regular expressions to see if the value only contains alphanumeric characters, and if the allowWhiteSpace parameter is set, it also allows that.

Based on those two you should be able to implement your own validators, but as always, feel free to ask if anything is unclear.

Using the decorators

Finally we have all the code we need to actually create an example!

I’m not going to deal with creating forms with Zend_Form here. If you need help with that, there’s some good information in the Zend_Form Webinar slides you can find at Zend Framework Webinars site, and there’s also a good introductionary article to Zend_Form at Rob Allen’s blog.

To use the new decorators, you will need to replace the old Form decorator and add the JsValidation decorator, like this:

//Replace Form decorator with our own
$form->removeDecorator('Form');
$form->addDecorator(new CU_Form_Decorator_Form());
 
//Add JsValidation
$form->addDecorator(new CU_Form_Decorator_JsValidation());

And that’s it! You now have client-side validation in your form. Do note that you will need to have both HeadScript and InlineScript view helpers’ contents echoed in your view. Otherwise the JavaScript code will not be shown.

You will probably have to define some more validators in JS and to the switch in the PHP code, but other than that, this is a working solution.

I have tested this code myself and it worked, but if you encounter any issues, please post a comment. Also, if you create any JS code for validators, I encourage you to share your code in the comments so that everyone can benefit.

More on Zend_Form:
Complex custom elements in Zend_Form
Another idea for using models with forms