ZenValidator aims to make silverstripe form validation as painless as possible, by allowing configuration of serverside and clientside validation through one simple API. Parsley.js is used for the clientside validation in the frontend.
Out of the box constraints include:
- required
- value (min, max, range)
- length (min, max, range)
- check (min, max, range)
- type (email, url, number, integer, digits, alphanumeric)
- equalto (equal to the value of another field)
- regex
- remote (validate remotely via ajax)
public function Form(){
$fields = FieldList::create(array(
TestField::create('Name'),
TextField::create('Username'),
TextField::create('Email'),
TextField::create('FavoriteColor'),
TextField::create('Age'),
TextField::create('Website')
));
$actions = FieldList::create(FormAction::create('submit', 'submit'));
$validator = ZenValidator::create();
return Form::create($this, 'Form', $fields, $actions, $validator);
}
The following examples demonstrate various ways to add constraints to the above form example.
The addRequiredFields() is available for quick and clean adding of required fields.
$validator->addRequiredFields(array(
'Name',
'Email'
));
$validator->addRequiredFields(array(
'Name' => 'Please enter your name',
'Email' => 'Please enter your email'
));
All other constraints are added via the setConstraint() method. This method takes 2 parameters: $fieldName and $constraint. See examples below.
Test for number of min, max or between range value
Min
$validator->setConstraint('Age', Constraint_value::create('min', 18));
Max
$validator->setConstraint('Age', Constraint_value::create('max', 25));
Range
$validator->setConstraint('Age', Constraint_value::create('range', 18, 25));
Test for a min, max or between range length of string
Min
$validator->setConstraint('Username', Constraint_length::create('min', 3));
Max
$validator->setConstraint('Username', Constraint_length::create('max', 5));
Range
$validator->setConstraint('Username', Constraint_length::create('range', 3, 5));
Test for a min, max or range of elements checked
Min
$validator->setConstraint('Options', Constraint_check::create('min', 3));
Max
$validator->setConstraint('Options', Constraint_check::create('max', 5));
Range
$validator->setConstraint('Options', Constraint_check::create('range', 3, 5));
The Constraint_type constraint can be used to validate inputs of type email, url, number, integer, digits or alphanum. Pass one of said options as the first parameter into the constructor.
$validator->setConstraint('Email', Constraint_type::create('email'));
URL
$validator->setConstraint('Website', Constraint_type::create('url'));
Number
$validator->setConstraint('Age', Constraint_type::create('number'));
Integer
$validator->setConstraint('Age', Constraint_type::create('integer'));
Digits
$validator->setConstraint('Age', Constraint_type::create('digits'));
Alphanum
$validator->setConstraint('Username', Constraint_type::create('alphanum'));
Check for a value equal to that of another field
$validator->setConstraint('Username', Constraint_equalto::create('Name'));
Check for a valid hex color, for example…
$validator->setConstraint('FavoriteColor', Constraint_regex::create("/^#(?:[0-9a-fA-F]{3}){1,2}$/"));
Validate based on the response from a remote url. The following are valid responses from the remote url, with a 200 response code: 1, true, { "success": "..." } and assume false otherwise. You can show a specific specific error message by returning { "error": "your custom message" } or { "message": "your custom message" }
$validator->setConstraint('Username', Constraint_remote::create($this->Link('checkusername')));
The above example will send an ajax request to my checkusername method on the same controller as the Form. A request var with a key the same as the field name will contain the value to test. So my checkusername method might look something like this:
public function checkusername($request){
$username = $request->requestVar('Username');
// check for existing user with same username
if(Member::get()->filter('Username', $username)->count()){
return $this->httpError(400, 'Sorry, that username is already taken.');
));
}else{
return $this->getResponse()->setBody('OK');
}
}
All arguments/settings for the Constraint_remote constructor:
- $url - The URL to send the validation request to (do not include a query string, use $params)
- $params - An array of request vars to be sent with the request
- $options - An array of key => value options for the validator (eg: ['type' : 'POST', 'dataType' : 'jsonp'])
- $validator - Use a specific remote validator. Default validators are 'default' and 'reverse'
For serverside validation: if a relative url is given the response will be obtained internally using Director::test, otherwise curl will be used to get the response from the remote url.
Any of the above examples can be configured to display a custom error message. For example:
$validator->setConstraint(
'FavoriteColor',
Constraint_regex::create("/^#(?:[0-9a-fA-F]{3}){1,2}$/")->setMessage('Please enter a valid HEX color code, starting with a #')
);
$validator->setConstraints(array(
'Name' => 'Age', Constraint_value::create('min', 18),
'Username' => array(
Constraint_required::create(),
Constraint_type::create('alphanum'),
)
));
The setConstraint() method is also chainable so you can:
$validator
->setConstraint('Website', Constraint_type::create('url'))
->setConstraint('Content', Constraint_required::create());
$validator->removeConstraint(string $fieldName, string $constraintClassname);
OR
$validator->removeConstraints(string $fieldName);
It is likely that you may want to have your validation messages displaying in a custom element, with custom classes or any other custom frontend validation behaviour that is configurable with Parsley. In this case, you can set the third parameter ($defaultJS) of Zenvalidator's __construct to false.
$validator = ZenValidator::create(null, true, false);
Or set globally via yml
ZenValidator:
default_js: false
This will tell ZenValidator not to initialise with the default settings, and also to add the class "custom-parsley" to the form. You'll then need to add some custom javascript to the page with your own settings, for example:
$('form.custom-parsley').parsley({
errorsWrapper: '<div></div>',
errorTemplate: '<small class="error"></small>',
errorClass: 'error',
errorsContainer: function ( elem, isRadioOrCheckbox ) {
return $(elem).parents('.field:first');
},
excluded: 'input[type=button], input[type=submit], input[type=reset], input[type=hidden], :hidden, .ignore-validation'
});
Warning: $(elem).parsley() can return an array (in which can, attaching subscribe will fail) in case you match multiple forms. Use the following syntax instead if you need to match multiple elements:
$.each($('form.parsley').parsley(),function(i,parsleyForm) {
parsleyForm.subscribe('parsley:field:validate', function(field) { ... });
});
See Parsley.js for the full list of configuration settings
To use ZenValidator in the CMS, simply implement a getCMSValidator() method on your custom Page type or DataObject class
public function getCMSValidator(){
return ZenValidator::create()->setConstraint('Content', Constraint_required::create()
->setMessage('Please enter some content'));
// currently parsley validation doesn't work so well in the cms, so disable.
$validator->disableParsley();
}
This feature allows you to specify under what conditions a field should or should not have it's validation constraints applied, based on the value(s) of other fields on the form. The concept borrows heavily from and compliments Uncle Cheese's Display Logic module.
You can use the following conditional:
- isEqualTo
- isEmpty
- isGreaterThan
- isLessThan
- contains
- isChecked
$country->validateIf('EmailAddress')->isEqualTo('s')->orIf('EmailAddress')->isEqualTo('v');
$field->validateIf('IsChecked')->isEmpty();
You can also use extra validators crafted by the community. The module is shipped with the default validators:
- Comparison
- Words
- Date Iso
Javascript and language files are loaded only if you use these validators.
In some scenarios, you don't want to trigger validation when submitting the form (i.e.: "previous" button in a multi step form). Although this is easy to achieve by yourself, the module provides a standard implementation for doing this.
Instead of using the standard FormAction class, you can use its subclass "FormActionNoValidation". It will prevent the client and server side validation from happening.
You can create your own validation constraints by subclassing the abstract ZenValidatorConstraint class. For frontend implementation of your custom validator, see Parsley.extend.js. Unfortunately there is no real documentation other than the code itself at this stage so good luck!
For everything else in the frontend (triggers, error classes, error placement, etc) See the Parsley.js documentation
- SilverStripe 3.*
##TODO
- Parsley validation in CMS (currently only serverside) (ajax)
- Finish conditional validation ie. only validate constraint if field x value is y, document