For one of my projects based on Zend Framework I do a lot of forms creation and validation but I found out is that there is no way to perform all validation on the client side, which is a huge time-saver.
So I figured that since all form generaing code is already in place and all elements are rendered so nicely – it should be possible to generate validation code with as simple step as just adding one line of decorator. Such a decorator would have to do the following steps:
- For each element defined in the form
- get defined validators
- for each validator generate validation javascript code
- get all error messages TRANSLATED if necessarry and apply them when validating (eg. using alert window)
- add onsubmit handler for running validation code
One major requirement is to have ONLY standard JavaScript code NO EXTERNAL libraries. Why? Because it just makes things easier. This doesn’t mean that it’s not possible – anyone interested can just extend JsValidation class and perform his own validation.
The idea isn’t new, there already exists an approach that performs javascript validation but it lacks translated error messages. Additionally it contains very bad line:
//Replace Form decorator with our own
$form->removeDecorator('Form');
$form->addDecorator(new CU_Form_Decorator_Form());
Why is it bad, you might ask – along JS decorator one is forced to use SPECIAL form decorator.
Currently my library supports 5 9 – in my opinion* – most widely used decorators :
- Zend_Validate_StringLength
- Zend_Validate_Alnum
- Zend_Validate_Regex
- Zend_Validate_Digits
- Zend_Validate_NotEmpty
- Zend_Validate_LessThan
- Zend_Validate_GreaterThan
- Zend_Validate_Float
- Zend_Validate_Between
There’s probably one thing that nees some more explanation – namely the strategy for keeping consistent order of running validation rules. So you do want to have check for non-empty before validating that value is float. In order to achieve it every validator is connected with corresponding STRENGTH integer value which defines the order of execution. So non-empty check is before float but less-than and greater-than might be executed in any order.
In the code it’s realized inside getValidator method whose second returned value is the execution order. Afterwards which value is used by „inner” class ElementValidators inside of its add method which uses it as an array key.
So this is it – the rest should be pretty straighforward – in case it isn’t feel free to leave some comments/questions below. One more remark – currently validation errors are displayed using alert function but yes I’m planning to display them inline the same way ZF’s validation erros are displayed. Erros are displayed the same way regular form validation happens so from user perspective there should be difference – additionally you can provide/override your own Zend.Form.ErrorReporter. Additionally validator rules are defines as functions in Zend.Validator.Rules namespace – each rules has the same name as its PHP counter-part, eg.
Zend.Form.Validator.Rules.Zend_Validate_StringLength = function(value, opts, msgs) {
}
Where:
- value is the value to be validated
- opts are options passed to the validator eg. minLength
- msgs are internationalized messages displayed when validation fails
Source code available on github.
You can also checkout gists for this project
Usage:
<?php error_reporting(E_ALL|E_STRICT); set_include_path(get_include_path() . PATH_SEPARATOR . './library'); require_once 'Zend/Loader.php'; Zend_Loader::registerAutoload(); //May need to have this set, the JavaScript file paths use baseUrl at the moment //Zend_Controller_Front::getInstance()->setBaseUrl('public');
// display validation messages in French$translator = new Zend_Translate( array( 'adapter' => 'array', 'content' => '/resources/languages', 'locale' => 'fr', 'scan' => Zend_Translate::LOCALE_DIRECTORY ));Zend_Validate_Abstract::setDefaultTranslator($translator);
$form = new Zend_Form(); $form->setView(new Zend_View()); $form->addDecorator(new Zend_Form_Decorator_JsValidation()); $name = $form->createElement('text', 'name', array( 'label' => 'Name' )); $name->addValidator('NotEmpty') ->setRequired(true); $submit = $form->createElement('submit', 'ok', array( 'ignore' => true, 'label' => 'OK' )); $form->setElements(array( $name, $submit ));
echo $form->getView()->headScript(); echo $form->getView()->inlineScript(); echo $form->render();
custom validator (php part):
<?phpclass My_Form_Decorator_JsValidation extends Zend_Form_Decorator_JsValidation{ const ORDER_MY_CUSTOM = 100;
protected function handleCustomValidators($name, $msgs, $validator) { switch($name) { case 'MyCustomValidator': return array( self::ORDER_MY_CUSTOM, array( 'param' => $validator->getParam() ), array( 'notValid' => $msgs[MyCustomValidator::NOT_VALID] ) ); default: return parent::handleCustomValidators($name, $msgs, $validator); }
return null; }}custom validator (javascript part):
(function() {Zend = this.Zend || {};Zend.Form = Zend.Form || {};Zend.Form.Validator = Zend.Form.Validator || {};Zend.Form.Validator.Rules = Zend.Form.Validator.Rules || {};
Zend.Form.Validator.Rules.MyCustomValidator = function(value, opts, msgs) { if (value < opts.param) { return msgs.notValid; }}
})();* After one project with ZF