Blog

Custom Js Validation Magento2

4 minutes reading

A fairy tale inside Magento 2 checkout world

Spoiler alert, it won’t be 100 but it definitely felt like it was.

Spanish version here (guest post on Damian Culotta blog).

One day the queen of a flourishing reign that sells all her lands good products realised that they were bought by some undeserving people and so she asked to her faithful developer to find the way to take care of this.

Our developer started her journey by reading a very huge file made with a very ancient technique called xml in the following location:

vendor/magento/module-checkout/view/frontend/layout/checkout_index_index.xml

And inside found something similar that she thought to be the way to add a custom js validation to the vat_id field:

<item name="company" xsi:type="array">
    <item name="validation" xsi:type="array">
        <item name="min_text_length" xsi:type="number">0</item>
    </item>
</item>

So she added the following code (here how to modify the checkout):

<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="checkout" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="checkout.root">
            <arguments>
                <argument name="jsLayout" xsi:type="array">
                    <item name="components" xsi:type="array">
                        <item name="checkout" xsi:type="array">
                            <item name="children" xsi:type="array">
                                <item name="steps" xsi:type="array">
                                    <item name="children" xsi:type="array">
                                        <item name="shipping-step" xsi:type="array">
                                            <item name="children" xsi:type="array">
                                                <item name="shippingAddress" xsi:type="array">
                                                    <item name="children" xsi:type="array">
                                                        <item name="shipping-address-fieldset" xsi:type="array">
                                                            <item name="children" xsi:type="array">
                                                                <item name="vat_id" xsi:type="array">
                                                                    <item name="validation" xsi:type="array">
                                                                        <item name="custom_validation" xsi:type="boolean">true</item>
                                                                    </item>
                                                                </item>
                                                            </item>
                                                        </item>
                                                    </item>
                                                </item>
                                            </item>
                                        </item>
                                    </item>
                                </item>
                            </item>
                        </item>
                    </item>
                </argument>
            </arguments>
        </referenceBlock>
    </body>
</page>

Second step was to add the custom js validation rule to the already existing ones and used a famous tool that many developers before already used to find how:

https://magento.stackexchange.com/questions/190125/magento-2-add-custom-validation-rule

She followed all the steps but when it was time to test, it didn’t work, the undeserving people could still buy the products.

It was unclear how the validation would be triggered but thinking of the way Magento 1 worked she thought it didn’t work because the css class was missing from the field (since required class was added for the required fields) and apparently the js class in charge of this had the following method:

_setClasses: function () {
    var additional = this.additionalClasses,
                classes;

     if (_.isString(additional) && additional.trim().length) {
             additional = this.additionalClasses.trim().split(' ');
           classes = this.additionalClasses = {};

           additional.forEach(function (name) {
               classes[name] = true;
                }, this);
            }

The way she could set this value was taken from other xml files:

<item name="shipping-address-fieldset" xsi:type="array">
    <item name="children" xsi:type="array">
        <item name="vat_id" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="additionalClasses" xsi:type="string">custom-validation</item>
            </item>
        </item>
    </item>
</item>

And again it didn’t work because there was no passing of the additionalClasses from the xml file to the template and so to the js component which apparently is done only for the components of the admin area like in this example vendor/magento/module-ui/view/base/web/templates/form/field.html

At this point our developer decides to try and change directly the templates in order to add the css class needed for the validation. There are 2 templates that compose the vat_id ui component:

<item name="shipping-address-fieldset" xsi:type="array">
    <item name="children" xsi:type="array">
        <item name="vat_id" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="elementTmpl" xsi:type="string">Vendor_Module/form/element/custom</item>
                <item name="template" xsi:type="string">Vendor_Module/form/field</item>
            </item>
        </item>
    </item>
</item>

While elementTmpl can be overwritten from the xml, our developer discovers that isn’t the same for the template. In fact it is hardcoded in the following location:

vendor/magento/module-checkout/Block/Checkout/AttributeMerger.php:173 forcing her to use a Plugin to be able to change the template.

In the meanwhile, the Queen asks how come is taking so long for such a small task and the developer is considering a change in carrier.

At this point, she realises that the problem is mostly on js part and in fact the validation trigger is handled by the following js file:

vendor/magento/module-ui/view/base/web/js/lib/validation/validator.js

Inside has a specific method to add new validation rule:

/**
  * Adds new validation rule.
  *
  * @param {String} id - Rule identifier.
  * @param {Function} handler - Validation function.
  * @param {String} message - Error message.
  */
  validator.addRule = function (id, handler, message) {
    ulesList[id] = {
      handler: handler,
      message: message
    };

She is getting closer finally, at least it’s what she thinks, using this example:

vendor/magento/module-catalog/view/adminhtml/web/component/image-size-field.js

If you made it so far, you’ll realise that also in this case it didn’t work, leaving our developer with no other choice than go for the overwriting the core, specifically vendor/magento/module-ui/view/base/web/js/lib/validation/rules.js by adding the custom validation rule directly.

var config = {
    map: {
        '*': {
            "Magento_Ui/js/lib/validation/rules": "Vendor_Module/js/lib/validation/rules"
        }
    }
};

Her final step was to do what was her first:

<item name="shipping-address-fieldset" xsi:type="array">
     <item name="children" xsi:type="array">
         <item name="vat_id" xsi:type="array">
             <item name="validation" xsi:type="array">
                 <item name="custom_validation" xsi:type="boolean">true</item>
             </item>
         </item>
    </item>
</item>

And, as you may have guessed, ladies and gentlemen, she finally succeed in accomplishing her mission.

THE END

Andra Lungu