RequireJS en la configuración de nuestro módulo en Magento2

RequireJS

Si te quedaste enganchado con el articulo anterior, este te va a gustar mas. Vamos a incluir nuestro js en la pagina de configuración de nuestra extensión.

Capaz que te preguntás para qué, entonces vamos a poner un ejemplo.

Si entre los datos de la configuración de mi extensión tengo un combo por ejemplo de formas (cuadrado, rectángulo, circulo, triangulo, etc) y otro combo de colores (rojo, azul, amarillo, verde, etc), pero alguna lógica hace depender el color de la forma, por ejemplo hay círculos verdes y rojos, pero no azules ni amarillos. El segundo combo, depende del valor del primer combo (es tal cual países/estados).

Bien, implementar esta dependencia de combos en la configuración de tu extensión puede llegar a ser bastante difícil… acá te voy a explicar como hacerlo con RequireJS, javascript y ajax.

Para mayor comodidad de la explicacion, todo esta basado en un ejemplo que escribi que podes encontrar en https://github.com/gonzaloebiz/article2.

No voy a explicar, porque no es el objetivo, cómo crear una extensión, o cómo definir el system.xml, solo me voy a centrar en cómo hacer para que un combo en la configuración de tu extensión dependa de otro.

1. El system.xml

Vamos a lo importante, veamos el system.xml donde se definen los campos de la configuración de mi extensión.

Ahí tenemos un campo para la forma:

<field id="shape" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1">
    <label>Shape</label>
    <source_model>Gonzalezuy\Article2\Model\Config\Source\Shape</source_model>
    <depends>
        <field id="*/*/active">1</field>
    </depends>
</field>

Y un campo para el color:

<field id="color" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1">
    <label>Color</label>
    <source_model>Gonzalezuy\Article2\Model\Config\Source\Color</source_model>
    <depends>
        <field id="*/*/active">1</field>
    </depends>
</field>

Como podemos ver no tiene nada de especial la definición, son dos combos que simplemente tienen source_model para cargar el combo.

¿Que cosas vamos a rescatar de acá? 4 lineas:

<section id="article2" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="1"> 
<group id="general" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
<field id="shape" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1">
<field id="color" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1">

Lo que nos interesa de esas lineas son los id de cada una (los vamos a necesitar mas adelante para nuestro javascript). Magento usa esos id para armar el id de cada campo en el admin, así para el campo de shape, el id en el admin va a ser la concatenación de los ids de sus padres mas el de él, o sea article2_general_shape.

Lo otro que vamos a ver del system.xml, son estas lineas:

<group id="hint" translate="label" type="text" sortOrder="0" showInDefault="1" showInWebsite="1" showInStore="1">
    <frontend_model>Gonzalezuy\Article2\Block\Adminhtml\System\Config\Fieldset\Hint</frontend_model>
</group>

Estas líneas lo que hacen es incluir un bloque en la configuración de nuestra extensión.

En este bloque podés poner lo que quieras, nosotros lo vamos a usar para incluir la inicialización de nuestro javascript.

2. El bloque oculto

Vamos a ver como definir el bloque oculto que pusimos en nuestro system.xml.

Para eso miramos esta linea:

<frontend_model>Gonzalezuy\Article2\Block\Adminhtml\System\Config\Fieldset\Hint</frontend_model>

Y tenemos que definir el bloque que indica, es decir:

<?php
 
namespace Gonzalezuy\Article2\Block\Adminhtml\System\Config\Fieldset;
 
class Hint extends \Magento\Backend\Block\Template implements \Magento\Framework\Data\Form\Element\Renderer\RendererInterface
{
 
    /**
     * @var string
     */
    protected $_template = 'Gonzalezuy_Article2::system/config/fieldset/hint.phtml';
 
    /**
     * @param \Magento\Framework\Data\Form\Element\AbstractElement $element
     * @return mixed
     */
    public function render(\Magento\Framework\Data\Form\Element\AbstractElement $element)
    {
        return $this->toHtml();
    }
 
}

Acá lo único que hacemos es definir el tamplate, no hace falta mas nada.

3. RequireJS

Como ya hemos dicho, Magento2 usa RequireJS para cargar nuestros js, así que vamos a ver cómo usarlo.

Para eso definimos el archivo:

/view/adminhtml/requirejs-config.js

Acá la linea importante es:

    article2color: 'Gonzalezuy_Article2/js/color'

El primer valor es el nombre por el que vamos a referirnos a nuestro js y el segundo es la ubicación de nuestro js.

Así que tenemos que definir el js, este según la definición tiene que estar en view/adminhtml/web/js/color.js, en nuestro proyecto:

define([
        'jquery'
    ],
    function($){
        "use strict";
 
        $.widget('mage.article2color', {
            "options": {
                "colorUrl": ""
            },
 
            _init: function() {
                var self = this;
                $('#article2_general_shape').change(function() {
                    var shape = $('#article2_general_shape').find(':selected').val();
                    self._loadColor(shape);
                });
            },
 
            _loadColor: function(shape) {
                var self = this;
                var colorUrl = this.options.colorUrl;
                $('#article2_general_color').empty();
                $('#article2_general_color').append($('<option>', {
                    value: -1,
                    text : 'Select one color'
                }));
                $.ajax({
                        url: colorUrl,
                        data: {'form_key':  window.FORM_KEY, 'shape': shape},
                        type: 'POST',
                        dataType: 'json',
                        showLoader: true
                    }
                ).done(function(data) {
                    $.each(data , function(i,item) {
                        $('#article2_general_color').append($('<option>', {
                            value: item.id,
                            text : item.value
                        }));
                    });
                });
            }
        });
        return $.mage.article2color;
});

En principio vamos a señalar algunas lineas de este archivo.

Ésta:

$.widget('mage.article2color', {

Y ésta:

return $.mage.article2color;

Así hay que definir el widget y se tiene que llamar, tal cual le pusimos en el require-config.js en el primer parámetro.

Las otras lineas interesante son:

define([
        'jquery'
    ],

Acá podemos incluir cualquier otro js que el nuestro necesite, por su nombre tal cual vimos recién.

4. Inicializando nuestro javascript

Como ya dijimos antes, RequireJS no carga nuestro js a menos que lo necesitemos, y eso es inicializarlo.

Para eso pusimos nuestro bloque oculto, así que volvamos a él.

En el template que le asociamos esta toda la magia.

Ponemos un div oculto:

<div id="config_article2color" class="admin__page-section-item" style="display:none;"
     data-mage-init='{"article2color":{"colorUrl": "<?php echo $this->getUrl('article2/color/get');?>"}}'>
</div>

Y dentro de ese div inicializamos nuestro js.

     data-mage-init='{"article2color":{"colorUrl": "<?php echo $this->getUrl('article2/color/get');?>"}}'>

Acá lo que nos importa es el article2color que fue el nombre que le pusimos a nuestro js en el require-config.js y también en nuestro js propiamente (acordate el punto anterior donde lo vimos).

Lo otro importante es el parámetro colorUrl, si te fijás en:

"options": {
    "colorUrl": ""
},

Declaramos que nuestro js iba a tener un parámetro, bueno, este es el momento de pasarlo. ¿Y que le pasamos? La url del controlador que nos va a devolver los colores según la forma.

5. Nuestro controller

Como vimos en el punto anterior, necesitamos un controller para poder llamarlo por ajax y que nos devuelva los colores para una forma determinada.

Como siempre en Magento2 definimos nuestra ruta en /etc/adminthml/routes.xml:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="admin">
        <route id="article2" frontName="article2">
            <module name="Gonzalezuy_Article2" before="Magento_Backend" />
        </route>
    </router>
</config>

Eso no deberías tener problema en hacerlo y ya deberías saberlo.

Luego definimos nuestro controller en /Controller/Adminhtml/Color/Get.php:

<?php
 
namespace Gonzalezuy\Article2\Controller\Adminhtml\Color;
 
use Magento\Backend\App\Action;
use Magento\Framework\Controller\ResultFactory;
 
class Get extends Action
{
 
    /**
     * @var ResultFactory
     */
    protected $_resultFactory;
 
    /**
     * @var \Gonzalezuy\Article2\Helper\Data
     */
    protected $_helper;
 
    /**
     * Get constructor.
     * @param Action\Context $context
     * @param ResultFactory $resultFactory
     * @param \Gonzalezuy\Article2\Helper\Data $helper
     */
    public function __construct(
        Action\Context $context,
        ResultFactory $resultFactory,
        \Gonzalezuy\Article2\Helper\Data $helper
    )
    {
        parent::__construct($context);
        $this->_helper  = $helper;
        $this->_resultFactory = $resultFactory;
    }
 
    public function execute()
    {
        $params = $this->getRequest()->getParams();
        $shape = $params['shape'];
        $colors = $this->_helper->getColors($shape);
        $resultJson = $this->_resultFactory->create(ResultFactory::TYPE_JSON);
        $resultJson->setData($colors);
        return $resultJson;
    }
 
    protected function _isAllowed()
    {
        return true;
//        return $this->_authorization->isAllowed('Gonzalezuy_Article2::stores_edit');
    }
 
}

No importa mucho la lógica de como deducimos los colores, podes hacerlo como quieras, en mi caso uso un helper.

Lo interesante acá y que tenemos que tener en cuenta, es devolver los valores en un json, esto lo hacemos acá:

$resultJson = $this->_resultFactory->create(ResultFactory::TYPE_JSON);
$resultJson->setData($colors);
return $resultJson;

El parámetro de la forma lo obtenemos como en cualquier controller:

$params = $this->getRequest()->getParams();
$shape = $params['shape'];

6. Usando ajax en nuestro js

Ya tenemos nuestro controller como para poder llamarlo desde nuestro js, de forma de obtener los colores para una forma dada y poder cargar el combo de colores. Así que manos a la obra.

Queremos que el combo se cargue, cuando cambiamos la forma, así que usando jQuery es bastante fácil:

$('#article2_general_shape').change(function() {
    var shape = $('#article2_general_shape').find(':selected').val();
    self._loadColor(shape);
});

(Acordate de como te conté de como se nombraban los id en magento2, por eso uso article2_general_shape)

Acordate (también) que cuando inicializamos nuestro js, le pasamos la url del controller, así que lo único que tenemos que hacer, es llamarlo pasándole la forma como parámetro. Es una llamada ajax normal del tipo:

$.ajax({
        url: colorUrl,
        data: {'form_key':  window.FORM_KEY, 'shape': shape},
        type: 'POST',
        dataType: 'json',
        showLoader: true
    }
).done(function(data) {
    $.each(data , function(i,item) {
        $('#article2_general_color').append($('<option>', {
            value: item.id,
            text : item.value
        }));
    });
});

¿Que tenés que tener en cuanta acá? La url que ya la teníamos cuando inicializamos el js, y los parámetros, la form_key la tenés que pasar si o si, Magento2 usa esa form_key para validar que tengas permiso para ejecutar lo que querés, y obvio, la forma:

data: {'form_key':  window.FORM_KEY, 'shape': shape},

El resto es procesar la respuesta json del controller, y cargar el combo

).done(function(data) {
    $.each(data , function(i,item) {
        $('#article2_general_color').append($('<option>', {
            value: item.id,
            text : item.value
        }));
    });

No tiene mucho misterio.

Y con esto, tenés todo como para hacerlo, espero que te haya resultado claro, útil e interesante.

Publicado por Gonzalo Dominguez

Magento fanatic developer. Do not try and bend the spoon. That's impossible. Instead... only try to realize the truth. There is no spoon. @gonzalezuy