Cien formas de agregar validaciones Javascript personalizadas que NO funcionan y una que si

(un cuento de hadas dentro del mundo del checkout de Magento2)

Cuento de hadas

Alerta de spoiler: no serán 100 pero definitivamente se sintió como si lo fuera.

Un día, la Reina de un pujante reinado que vendía todos sus buenos productos, se dio cuenta que los compraban algunas personas que no lo merecían, por lo que le pidió a su fiel desarrolladora que encontrara la forma de encargarse de esto.

Nuestro desarrollador comenzó su viaje leyendo un enorme archivo hecho con una técnica muy muy antigua, llamada xml, en la siguiente ubicación: vendor/magento/module-checkout/view/frontend/layout/checkout_index_index.xml

Y dentro encontró algo similar a lo que ella pensó que era la forma de agregar una validación js personalizada al campo vat_id:

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

Entonces, ella agregó:

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2013-2017 Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<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>

El segundo paso fue agregar la regla de validación javascript personalizada a las ya existentes y utilizar una famosa herramienta que muchos desarrolladores ya usaban en el pasado para encontrar cómo hacerlo: https://magento.stackexchange.com/questions/190125/magento-2-add-custom-validation-rule.

Ella siguió todos los pasos, pero cuando llegó el momento de probar, no funcionó, las personas que no lo merecían todavía podían comprar los productos.

No estaba claro cómo se activaría la validación, pero al pensar en la forma en que Magento 1 funcionó, pensó que debía ser porque faltaba la clase css en el campo (ya se había agregado la clase required para los campos obligatorios) y aparentemente la clase javascript a cargo en esto tenía el siguiente método:

_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);
    }
 
    ………

La forma en que pensó que podía establecer este valor era de otros ejemplos de archivos xml:

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2013-2017 Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<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="config" xsi:type="array">
                                                                        <item name="additionalClasses" xsi:type="string">custom-validation</item>
                                                                    </item>
                                                                </item>
                                                            </item>
                                                        </item>
                                                    </item>
                                                </item>
                                            </item>
                                        </item>
                                    </item>
                                </item>
                            </item>
                        </item>
                    </item>
                </argument>
            </arguments>
        </referenceBlock>
    </body>
</page>

Nuevamente no funcionó porque no se pasaron las additionalClasses del xml a la plantilla y, por lo tanto, al componente js, que aparentemente solo se hace para los componentes del área de administración, como en este ejemplo: vendor/magento/module-ui/view/base/web/templates/form/field.html.

En este punto, nuestra desarrolladora decide intentar cambiar directamente las plantillas para agregar la clase css necesaria para la validación. Hay 2 plantillas que componen el componente vat_id ui:

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2013-2017 Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<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="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>
                                                    </item>
                                                </item>
                                            </item>
                                        </item>
                                    </item>
                                </item>
                            </item>
                        </item>
                    </item>
                </argument>
            </arguments>
        </referenceBlock>
    </body>
</page>

Mientras que elementTmpl se puede sobrescribir desde el xml, nuestro desarrolladora descubre que no es lo mismo para la plantilla. De hecho, está codificado en la siguiente ubicación: vendor/magento/module-checkout/Block/Checkout/AttributeMerger.php:173 obligándola a usar un Plugin para poder cambiar la plantilla.

Mientras tanto la Reina pregunta por qué toma tanto tiempo para una tarea tan pequeña y la desarrolladora está considerando un cambio de profesión.

En este punto, ella se da cuenta que el problema está principalmente en la parte javascript y, de hecho, el trigger de validación se maneja con el siguiente archivo js: vendor/magento/module-ui/view/base/web/js/lib/validation/validator.js.

Ese archivo contiene un método específico para agregar una nueva regla de validación:

        /**
 	* 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) {
    	rulesList[id] = {
        	handler: handler,
        	message: message
    	};
};

Ella se está acercando, finalmente (al menos es lo que piensa) usando este ejemplo: vendor/magento/module-catalog/view/adminhtml/web/component/image-size-field.js.

Si lo hiciste hasta ahora, te darás cuenta de que también en este caso no funcionó, dejando a nuestra desarrolladora sin otra opción que ir por la sobreescritura del core, específicamente vendor/magento/module-ui/view/base/web/js/lib/validation/rules.js agregando la regla de validación personalizada.

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

Su paso final fue hacer lo que era su primera opción:

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2013-2017 Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<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>

Y como ya habrán adivinado, damas y caballeros, ella finalmente logró cumplir su misión.

The end

Publicado por Andra Lungu

Magento community lover, started her experience with Magento 4+ years ago working as head of development at Buru-Buru, a big success startup merchant in Italy. Nowadays she works as backend developer at Bitbull, a fast growing company specialized in Magento 1 and Magento 2. She is more and more involved in the #realmagento community as delegate or speaker at events like Mage Unconference, Magento 2 seminar and MeetMagento Romania and also as organiser of Mage Titans Italy.