Client Side Form Validation with JavaScript
Set Up Validation Rules for Form Fields
Client-side validation supports the same validation annotation that is used for the server-side - Symfony validation. Once validation.yml is created, all rules get translated to the frontend format and put into fields’ data-validation attribute. For example:
Bundle\UserBundle\Entity\User:
properties:
username:
- NotBlank: ~
- Length:
min: 3
max: 255
will be translated to
<input name="user_form[username]"
data-validation="{"NotBlank":null,"Length":{"min":3,"max":255}}">
This data-validation is supported by client-side validation, which is an extended version of the jQuery Validation Plugin.
The main entry point of this mechanism from the backend point of view is the \Oro\Bundle\FormBundle\Form\Extension\JsValidationExtension
Symfony Form Type Extension, which in its turn is backed by constraints provider and constraint converters.
Constraints Provider and Constraints Converter
Constraint provider (\Oro\Bundle\FormBundle\Form\Extension\JsValidation\ConstraintsProvider
) is responsible for the extraction of the Symfony validation constraints from a Symfony Form for using in JsValidationExtension.
Constraint converter is responsible for converting a Symfony validation constraint object (\Symfony\Component\Validator\Constraint
) into the validation constraint suitable for converting to frontend format, e.g. add extra data or totally change a constraint. Constraint converter is represented by \Oro\Bundle\FormBundle\Form\Extension\JsValidation\ConstraintConverter
, but actually it delegates calls to the inner converters. Inner converters implement same \Oro\Bundle\FormBundle\Form\Extension\JsValidation\ConstraintConverterInterface
interface and are collected by the service container tag oro_form.extension.js_validation.constraint_converter, so you can easily hook into the process if needed.
The basic constraint converter is \Oro\Bundle\FormBundle\Form\Extension\JsValidation\GenericConstraintConverter
that supports all constraints. Also it adds an important feature - allows total override of a constraint via jsValidation payload option, for example:
Oro\Bundle\UserBundle\Entity\User:
# ...
properties:
# ...
birthday:
- Type:
type: DateTimeInterface
payload:
jsValidation:
type: Date
options:
# ...
# ...
Validation Rules
The client-side validation method is the JS module, which should export an array with three values:
Methods name
Validation function
Error message or function that defines a message and returns it
The trivial validation rule module would look like this:
import _ from 'underscore';
import __ from 'orotranslation/js/translator';
const DEFAULT_PARAM = {
message: 'Invalid input value'
};
export default [
'ValidationMethodRule',
/**
* @param {string|undefined} value
* @param {Element} element
* @param {?Object} param
* @this {jQuery.validator}
* @returns {boolean|string}
*/
(value, element, param) => true
/**
* @param {Object} param
* @param {Element} element
* @this {jQuery.validator}
* @returns {string}
*/
function(param, element) {
param = {...DEFAULT_PARAM, ...param};
return __(param.message);
}
];
Loading Custom Validation Rules
To load a custom validator, call $.validator.loadMethod with the name of the JS module, which exports the validation method:
$.validator.loadMethod('my/validation/method')
Next, the form fields with this constraint are processed by this validation method.
Validation for Optional Group
When you have one form that saves several different entities at once (e.g., contact entity + address sub-entity), mark the container of sub-entity field elements with the attribute data-validation-optional-group.
<form>
|
+--<fieldset>
| +--<input>
| +--<input>
| +--<input>
|
+--<fieldset data-validation-optional-group>
+--<input>
+--<input>
+--<input>
Next, validation for sub-entity will work only if some of the fields are not blank. Otherwise, it will ignore all validation rules for the fields element of the sub-entity.
Override of Optional Validation Logic
To customize “optional validation group” behavior, override a handler responsible for handling field changes in a specific optional validation group. In this case, you need to:
add a custom handler to jsmodules.yml
dynamic-imports: commons: - example/js/custom-handler
Custom optional validation handler should have two methods: initialize and handle. Method “Initialise” is responsible for updating the validation state for “optional validation group” after being loaded to the page. Method “Handle” is responsible for update “optional validation group” validation state after the descendant field will be changed.
You can have any level of “optional validation group” inheritance in your page. In case if your field has more than one “optional validation group” ancestor, all the “optional validation group” handlers will be called from closest ancestor to root by default. This behavior is configurable, you can return true or false in your custom “Handle” method.
add a data attribute to the validation group
+--<fieldset data-validation-optional-group data-validation-optional-group-handler="example/js/custom-handler"> +--<input> +--<input> +--<input>
Ignore Validation Section
To suppress validation for a field or a group of fields, use the data-validation-ignore attribute of the container element. It works the same way as with the data-validation-optional-group attribute, except that the validator omits these fields even if they have a value.
+<form>
|
+--<fieldset>
| +--<input>
| +--<input>
| +--<input>
|
+--<fieldset data-validation-ignore>
+--<input>
+--<input>
+--<input>
This attribute is checked in each validation cycle, so you can add/remove it in the runtime to get required behavior.
Conformity Server Side Validations to Client Once
Server side |
Symfony |
Oro |
Client side |
Coment. |
All |
√ |
|||
Blank |
√ |
|||
Callback |
√ |
|||
Choice |
√ |
|||
Collection |
√ |
|||
Count |
√ |
oroform/js/validator/count |
||
Country |
√ |
|||
DateTime |
√ |
√ |
oroform/js/validator/datetime |
|
Date |
√ |
√ |
oroform/js/validator/date |
|
√ |
oroform/js/validator/email |
|||
False |
√ |
|||
File |
√ |
|||
Image |
√ |
|||
Ip |
√ |
|||
Language |
√ |
|||
Length |
√ |
oroform/js/validator/length |
||
Locale |
√ |
|||
MaxLength |
√ |
|||
Max |
√ |
√ |
||
MinLength |
√ |
|||
Min |
√ |
√ |
||
NotBlank |
√ |
oroform/js/validator/notblank |
||
NotNull |
√ |
√ |
oroform/js/validator/notnull |
|
Null |
√ |
|||
Range |
√ |
√ |
oroform/js/validator/range |
|
NumericRange |
√ |
√ |
oroform/js/validator/numeric-range |
|
Regex |
√ |
oroform/js/validator/regex |
||
Repeated |
√ |
oroform/js/validator/repeated |
||
SizeLength |
√ |
|||
Size |
√ |
√ |
||
Time |
√ |
|||
True |
√ |
|||
Type |
√ |
oroform/js/validator/type |
||
UniqueEntity |
√ |
|||
Url |
√ |
oroform/js/validator/url |
supports the only group of checkboxes with the same name (like
user[role][]
)cannot be supported on client-side
alias for required validator (standard jQuery.validate)
supports only integer type