jdPerez Logo Header
9 minutos de lectura | 24/07/2024

Crear un componente de Knockout

Si nos vamos a cualquier template del Checkout de Magento2, por ejemplo, al template Magento_Checkout/shipping vemos en la primera línea del código html esto data-bind="fadeVisible: visible()", bien, el data-bind si leemos la documentación de Knockout nos dice lo siguiente:

Knockout’s declarative binding system provides a concise and powerful way to link data to the UI.

Un enlace declarativo entre nuestro UI y Knockout, a lo cual se traduce en un componente que Knockout puede interpretar para mejorar la experiencia de usuario. El ejemplo que expongo del template del shipping es un binding creado por Magento para hacer mas "amigable" la visualización de contenedores html, es más, podemos verlo en el fichero Magento_Ui/view/base/web/js/lib/knockout/bindings/fadeVisible.js.

En este artículo vamos a crear un binding custom para nuestro tema, el cual va a tener una funcionalidad muy sencilla, vamos a crear un binding para un input para las provincias de EEUU el cual se irá auto completando conforme vamos escribiendo en él.

Veamos cómo sería, nos creamos el directorio Vendor_Module/view/base/web/js/lib/knockout/bindings y creamos el fichero autoComplete.js (por ejemplo), el cual tendrá lo siguiente:

define([
    'jquery',
    'ko',
    'mage/url'
], function (
    $,
    ko,
    mageUrl
) {
    'use strict';
 
    let regions = [];
    const loadRegions = () => {
        $.ajax({
            url: mageUrl.build('perezdev/ajax/getRegions'),
            type: 'POST',
            dataType: 'json',
            data: {country: window.checkoutConfig.originCountryCode},
            success: function (response) {
                response.shift();
                regions = response.map((value) => {return value['title']});
            }
        });
    }
 
    ko.bindingHandlers.autoComplete = {
        init: function () {
            // Aquí ejecutamos lo que necesitemos al inicializarse el componente
            loadRegions();
        },
 
        update: function (element, valueAccessor) {
            // Aquí entrará siempre que value sea un observable que ha mutado
            // Después del `init` entrará en el `update`
            let value = valueAccessor(),
                valueUnwrapped = ko.unwrap(value).toLocaleLowerCase(),
                valueLength = valueUnwrapped.length,
                suggestion = document.getElementById(element.dataset.suggestion);
 
            if (valueLength) {
                let availableProvinces = regions.map(
                    (value) => {
                        if ((value.substring(0,valueLength).toLocaleLowerCase().includes(valueUnwrapped))) {
                            return value;
                        }
                    }).filter((value) => {return value !== undefined;});
 
                let firstProvince = availableProvinces.shift();
                if (firstProvince) {
                    suggestion.value = firstProvince;
                }
            } else {
                suggestion.value = '';
            }
        }
    };
});

Aquí vemos que tenemos ko.bindingHandlers.autoComplete, bien, aquí es donde añadimos el binding y le asignamos un nombre, si vemos la doc de KO nos dice esto:

ko.bindingHandlers.yourBindingName = {
    init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        // This will be called when the binding is first applied to an element
        // Set up any initial state, event handlers, etc. here
    },
    update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        // This will be called once when the binding is first applied to an element,
        // and again whenever any observables/computeds that are accessed change
        // Update the DOM element based on the supplied values here.
    }
};

Por lo cual tenemos nuestro método init, que en mi caso lo utilizo para cargar una array de provincias, y tenemos el método update, el cual observa el cambio observables/computeds, y en mi caso el update lo utilizo para detectar los cambios del input para recibir el valor y filtrar las provincias a mostrar.

Importante!. Debemos cargar por RequireJS nuestro componente:

var config = {
    deps: [
        'PerezDev_PostComponentKo/js/knockout/bindings/autoComplete'
    ]
};

Una vez tengamos todo esto ya es solo asignar el binding en un template:

<div class="custom-component">
    <label for="input-autocomplete" data-bind="i18n: 'Auto Complete'"></label>
    <input type="text" id="suggestion" disabled/>
    <input type="text" id="input-autocomplete" placeholder="Insert state..."
           data-suggestion="suggestion"
           data-bind="autoComplete: defaultValue(),
                      value: defaultValue,
                      valueUpdate: 'afterkeydown'"/>
</div>

Podemos ver que el input id="input-autocomplete" tiene data-bind="autoComplete: defaultValue()", con esto le asignamos el binding y le damos un observable con un valor por defecto, el observable va definido dentro del componente del template.

Veamos como ha quedado:

acortado.gif

Esto mismo también se podría utilizar, por ejemplo, para autocompletar mail, cuando un cliente vaya a escribir el mail detectar cuando ponga el @ y poner diferentes dominios de correos.