Введение

Привет! Меня зовут Павел и я Magento 2 бэкенд-разработчик. В прошлых частях саги о Magento 2 UI Components мы получили общие сведения о UI-компонентах, а также рассмотрели наиболее важные аспекты их конфигурации. Сегодня подробно коснемся некоторых вопросов модификации UI компонентов под свои нужды: изменение внешнего вида, поведения клиентской части, поведения серверной части и пр. Погнали!

Изменение внешнего вида

Начнем с наиболее простой части — изменение внешнего вида. У всех компонентов, имеющих какое либо визуальное представление, внешний вид задается шаблоном. Для примера возьмем шаблон компонента Input:

<input class="admin__control-text" type="text"
    data-bind="
        event: {change: userChanges},
        value: value,
        hasFocus: focused,
        valueUpdate: valueUpdate,
        attr: {
            name: inputName,
            placeholder: placeholder,
            'aria-describedby': noticeId,
            id: uid,
            disabled: disabled,
            maxlength: 255
    }"/>

Попробуем изменить его, для чего в нашем модуле создадим файл input.html в /view/adminhtml/web/templates/form/element/ нашего модуля и поместим туда следующий текст:

<span>White Rabbit</span><!-- добавим элемент для наглядности -->
<input class="admin__control-text" type="text"
    data-bind="
        event: {change: userChanges},
        value: value,
        hasFocus: focused,
        valueUpdate: valueUpdate,
        attr: {
            name: inputName,
            placeholder: placeholder,
            'aria-describedby': noticeId,
            id: uid,
            disabled: disabled,
            maxlength: 255
    }"/>

После чего в xml конфигурации, где объявлен наш компонент, укажем новый шаблон для компонента примерно таким образом:

<field name="white_rabbit" sortOrder="10" formElement="input">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            ...
            <item name="elementTmpl" xsi:type="string">RSHB_WhiteRabbit/form/element/input</item>
            ...
        </item>
    </argument>
</field>

Очистим кэш и посмотрим, как изменился внешний вид компонента:

Измененный шаблон компонента Input
Измененный шаблон компонента Input

Вместе с кастомизацией шаблона мы можем расширить функционал UI-компонента, например, использовать в шаблоне knockout конструкции, которые будут обращаться к кастомным методам JS-компонента.

Кастомизация JS-компонента

Перейдем к более интересному моменту, а именно — к управлению поведением компонента в браузере пользователя. Как мы помним из предыдущей статьи, одной из частей UI-компонента является JS-виджет, который обеспечивает интерактивность компонента и отвечает за его поведение. Многие простейшие компоненты, такие как рассмотренный ранее input, не имеют своей реализации JS-компонента, вместо этого используя общую для всех реализацию abstract (которую можно найти в <magento_root>/vendor/magento/module-ui/view/base/web/js/form/element/abstract.js).

Поэтому, посмотрим на один из самых простых специализированных JS-компонентов — компонент Textarea:

define([
    './abstract'
], function (Abstract) {
    'use strict';
    return Abstract.extend({
        defaults: {
            cols: 15,
            rows: 2,
            elementTmpl: 'ui/form/element/textarea'
        }
    });
});

Как мы видим, данный компонент расширяет реализацию abstract и вносит минимум изменений, а именно, задает размеры поля по умолчанию и его шаблон (тот самый, который мы обсуждали ранее).

У нас есть два пути модификации JS-компонента.

В первом случае, если мы хотим кардинально изменить поведение компонента, то можно написать новый JS-компонент, и указать его в xml конфигурации UI-компонента вместо использующегося по умолчанию. Для этого в нашем модуле создадим файл textarea.js по адресу view/adminhtml/web/js/form/element/, поместим туда модифицированный JS-компонент, а затем укажем его в xml конфигурации следующим образом:

<field name="white_rabbit" sortOrder="10" formElement="textarea">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            ...
            <item name="component" xsi:type="string">RSHB_WhiteRabbit/js/form/element/textarea</item>
            ...
        </item>
    </argument>
</field>

Стоит заметить, что если нет нужды переписывать весь компонент, то достаточно его расширить, если вносимые изменения незначительны:

define([
    'Magento_Ui/js/form/element/textarea'
], function (MagentoTextarea) {
    'use strict';
    return MagentoTextarea.extend({
        sayHello: function () {
            alert('Hello!');
            return this;
        },
    });
});

Так мы добавили нашему компоненту функцию sayHello которую можно использовать по своему смотрению.

Альтернативой полной замене JS-компонента может служить миксин к базовому JS-компоненту. В отличие от описанного ранее, данный метод поменяет поведение всех экземпляров компонента в системе, это следует держать в голове при выборе способа модификации JS-компонента. 

Чтобы изменить поведение компонента при помощи миксина, необходимо создать миксин и указать его в requirejs. Для примера, применим миксин к базовому компоненту form, для чего поместим файл requirejs-config.js в /view/base/ нашего модуля:

var config = {
    'config': {
        'mixins': {
            'Magento_Ui/js/form/form': {
                'RSHB_WhiteRabbit/form-hook': true
            }
        }
    }
};

И далее разместим в /view/base/web/form-hook.js нашего модуля код мисксина:

define([], function () {
    'use strict';
    return function (Form) {
        return Form.extend({
            initialize: function () {
                this._super();
                console.log('The mixin from White Rabbit!');
            }
        });
    }
});

Таким образом, мы дополнили функцию инициализации компонента Form нашим кодом, и теперь каждый раз при выводе компонента Form в консоли будет выводиться строка “The mixin from White Rabbit!”.

PHP-классы

Теперь рассмотрим модификацию серверной части UI-компонента. Для этой цели служат кастомные классы компонентов и PHP-модификаторы (о последних поговорим позже).

Представим ситуацию, когда в базе у нас лежит значение int, которое определяет, включен некий параметр, или нет (значения 1 или 0, аналогично булевым true-false). При использовании стандартного класса column в гриде, мы увидим в колонке значения 1 или 0, в зависимости от значения в базе. Если мы хотим выводить эти значения как “Включено” или “Выключено”, нам необходимо подменить класс компонента, например:

<?php
...
class OnOff extends \Magento\Ui\Component\Listing\Columns\Column
{
    /**
     * OnOff constructor
     *
     * @param ContextInterface $context
     * @param UiComponentFactory $uiComponentFactory
     * @param array $components
     * @param array $data
     */
    public function __construct(
        ContextInterface $context,
        UiComponentFactory $uiComponentFactory,
        array $components = [],
        array $data = []
    ) {
        parent::__construct($context, $uiComponentFactory, $components, $data);
    }

    /**
     * @param array $dataSource
     * @return array
     */
    public function prepareDataSource(array $dataSource)
    {
        if (isset($dataSource['data']['items'])) {
            foreach ($dataSource['data']['items'] as $id => $item) {
                $whiteRabbit = $item['white_rabbit'];//Находим поле с нужным именем whi
                $item['white_rabbit'] = (bool)$whiteRabbit ? 'On' : 'Off';
                $dataSource['data']['items'][$id] = $item;
            }
        }

        return $dataSource;
    }
}

Как мы видим, кастомный класс компонента меняет значения 1 и 0 на “Включено”  и “Выключено”. 

Стоит помнить, что при изменении значения в колонке нужно также изменять значения в фильтрах (если для колонки предусмотрен фильтр). Для этого следует указать Source класс со значениями, которые соответствуют значениям “Включено” и “Выключено” в нашем примере. Код Source класса:

<?php
...
class OnOff implements \Magento\Framework\Option\ArrayInterface
{
    /**
    * @return array
    */
    public function toOptionArray(): array
    {
        return [['value' => 1, 'label' => __('On')], ['value' => 0, 'label' => __('Off')]];
    }

    /**
    * @return array
    */
    public function toArray(): array
    {
        return [0 => __('Off'), 1 => __('On')];
    }
}

Кстати, в М2 есть ряд предопределенных Source классов для наиболее распространенных задач, например, значения Yes/No, перечень категорий товаров и пр.

PHP-модификаторы

Еще одним любопытным способом изменить или дополнить поведение серверной части UI-компонента (а именно, его дата-провайдера) — PHP модификаторы. Они применяются в случаях, когда статическое объявление PHP класса в XML конфигурации по каким-то причинам не подходит, или может вызвать избыточное дублирование кода.

PHP модификатор должен реализовывать Magento\Ui\DataProvider\Modifier\ModifierInterface и должен содержать два метода: modifyData() и modifyMeta(). Посмотрим пример такого модификатора:

<?php
...
class WhiteRabbitModifier implements Magento\Ui\DataProvider\Modifier\ModifierInterface
{
    /**
    * {@inheritdoc}
    */
    public function modifyMeta(array $meta)
    {
        $meta['test_fieldset_name'] = [
            'arguments' => [
                'data' => [
                    'config' => [
                        'label' => __('Label For Fieldset'),
                        'sortOrder' => 50,
                        'collapsible' => true
                    ]
                ]
            ],
            'children' => [
                'test_field_name' => [
                    'arguments' => [
                        'data' => [
                            'config' => [
                                'formElement' => 'select',
                                'componentType' => 'field',
                                'options' => [
                                    ['value' => 'test_value_1', 'label' => 'Test Value 1'],
                                    ['value' => 'test_value_2', 'label' => 'Test Value 2'],
                                    ['value' => 'test_value_3', 'label' => 'Test Value 3'],
                                ],
                                'visible' => 1,
                                'required' => 1,
                                'label' => __('Label For Element')
                            ]
                        ]
                    ]
                ]
            ]
        ];

        return $meta;
    }

    /**
     * {@inheritdoc}
     */

    public function modifyData(array $data)
    {
        return $data;
    }
}

Как мы видим, при помощи модификатора можно изменить данные и мета-данные UI-компонента.

Для того, чтобы модификатор заработал, его нужно добавить в пул модификаторов дата-провайдера через di.xml таким способом:

<virtualType name="RSHB\WhiteRabbit\DataProvider\Modifier\WhiteRabbitPool" type="Magento\Ui\DataProvider\Modifier\Pool">
     <arguments>
         <argument name="modifiers" xsi:type="array">
             <item name="modifier_name" xsi:type="array">
                 <item name="class" xsi:type="string">RSHB\WhiteRabbit\Modifier\WhiteRabbitModifier</item>
                 <item name="sortOrder" xsi:type="number">10</item>
             </item>
         </argument>
     </arguments>
</virtualType>

<type name="RSHB\WhiteRabbit\Ui\DataProvider\WhiteRabbitDataProvider">
    <arguments>
        <argument name="pool" xsi:type="object">RSHB\WhiteRabbit\DataProvider\Modifier\WhiteRabbitPool</argument>
    </arguments>
</type>

Заключение

Мы рассмотрели основные аспекты модификации UI-компонентов под свои нужды. На этом цикл статей об UI-компонентах я считаю закрытым, возникающие вопросы по нюансам работы можно задавать в комментариям к соответствующим статьям.

Пишите код с удовольствием! До встречи.

Комментарии (0)