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="{&quot;NotBlank&quot;:null,&quot;Length&quot;:{&quot;min&quot;:3,&quot;max&quot;: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:

config/validation.yml 
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:

  1. Methods name

  2. Validation function

  3. 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:

  1. 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.

  1. 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

Email

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

  1. supports the only group of checkboxes with the same name (like user[role][])

  2. cannot be supported on client-side

  3. alias for required validator (standard jQuery.validate)

  4. supports only integer type