Привет! Меня зовут Павел и я Magento 2 бэкенд-разработчик. Если спросить у любого практикующего M2 разработчика, какие аспекты М2 вызывают наиболее сильную боль, — уверен, что с большим отрывом будут лидировать UI компоненты (Ui Components). Это тот самый случай, когда их все используют, но немногие знают, как в действительности они устроены и как они работают. Сложившаяся ситуация отчасти вызвана достаточно скудным описанием устройства компонентов в документации, особенно если принять во внимание сложность и некоторую неочевидность устройства компонентов. Сегодня мы начнем цикл статей про M2 Ui Components, чтобы полностью закрыть данную тему. Хотя на хабре уже есть превосходные статьи, которые рассматривают тему Ui Components с того или иного ракурса, системно ее на Хабре не раскрывал еще никто. В общем, иди за мной Нео, и я покажу тебе, насколько глубока кроличья нора. Погнали!

Назначение

В первую очередь я предлагаю определиться, что такое Ui Components. Если говорить в общем, то это набор инструментов и решений, реализующих новый способ построения интерфейса, основанный на библиотеке унивесальных компонентов, имплементирующих логику, поведение и внешний вид, обладающих широкими возможностями кастомизации. Компоненты призваны унифицировать внешний вид и поведение интерфейса системы, а также обеспечить многократное использование как базовых компонентов, так и их наборов.

UiComponents изначально разрабатывались, чтобы заменить (полностью или частично) прежние способы построения интерфейса, основанные на блоках и разметке в лейаутах.

На сегодняшний момент (M2 версии 2.4.2) приведение всего базового интерфейса M2 к использованию компонентов все еще не завершено, однако работа идет, и многие разделы админки М2 уже полностью построены с использованием Ui Components. Документация M2 прямо указывает на приоритет использования Ui Components вместо устаревших способов построения интерфейса, поэтому те разработчики, которые соблюдают Magento Coding Standards, должны использовать компоненты при разработке новых решений. 

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

Тем не менее, системность подхода к реализации компонентов выводит данный способ в число наиболее предпочтительных способов построения интерфейса М2.

Набор представленных компонентов не может быть расширен сторонними модулями, однако имеющиеся в наборе компоненты (о них мы поговорим ниже) могут быть модифицированы под нужды разработчика.

Принцип действия

Что ж, с назначением разобрались, теперь давайте посмотрим в самых общих чертах, как работают Ui компоненты.

По своей природе компонент является обособленным JS виджетом. Если заглянуть в структуру сгенерированной страницы, на которой используются компоненты, мы найдем там вызов примерно такого вида:

<script type="text/x-magento-init">
    {"*": {"Magento_Ui/js/core/app": {...}}}
</script>

Так выглядит инициализация базового компонента (о которых чуть ниже).

Если посмотреть внутрь JSON объекта, мы увидим примерно такую структуру:

{
...
     "components": {
          "white_rabbit_listing": {
                    "children": {
                        "white_rabbit_listing": {
                            "children": {
                                "listing_top": {
                                    "type": "container",
                                    "name": "listing_top",
                                    "children": {
                                        "bookmarks": {...},
                                        "columns_controls": {...},
                                        "listing_filters": {...},
                                        "listing_massaction": {...},
                                        "listing_paging": {...}
…
}

Здесь мы видим несколько вложенных объектов, каждый из которых является отдельным Ui компонентом: white_rabbit_listing это компонент Listing, bookmarks — cоответственно, компонент Bookmarks и т.д. Каждый из этих компонентов может гибко конфигурироваться, использовать разные шаблоны и пр., причем, поскольку мы полностью лишены статической HTML разметки, которая рендерится вместе со страницей, мы можем управлять поведением и внешним видом компонента практически налету, просто используя knockout.js привязки!

Стоит также заметить, что компоненты имеют иерархическую структуру, где дочерние компоненты наследуют свойства предков и приобретают свои собственные (данный подход был позаимствован из ООП, и, как мы далее увидим, имеет свои корни в устройстве самих компонентов.)

Компоненты

Рассмотрим общий список компонентов. Поскольку новые компоненты мы добавлять не можем, знать о пределах возможностей Ui вдвойне важно.

Итак, как мы уже говорили ранее, все компоненты имеют иерархическую структуру. На вершине этой иерархии находится два компонента: Listing Component и Form Component. Как не сложно догадаться из названий, первый базовый компонент отвечает за списки, второй - за формы. Все остальные компоненты являются дочерними по отношению к указанным двум. 

Перечислим все компоненты, доступные для двух базовых. Рядом я буду давать краткие комментарии относительно назначения компонентов, а название компонента будет ссылкой на страницу документации (кстати, будьте внимательны — на страницах описания базовых компонентов Listing и Form в документации (на момент написания данной статьи) даны неполные списки дочерних компонентов, причем пропущена чуть ли не половина). Для наглядности я также добавлю иллюстрации компонентов (в тех случаях, когда компонент имеет какой-то внешний вид в административном интерфейсе M2), однако стоит учитывать, что иллюстрации будут справедливы только для интерфейса админки, на фронтэнде внешний вид компонентов нужно писывать самостоятельно при помощи CSS.

Listing Component

Наследники Listing Component c иллюстрациями и описанием (трафик!)

ActionsColumn — колонка операций над строкой: редактирование, удаление и пр.

Bookmarks — позволяет сохранить определенное отображение грида и в последствии возвращаться к нему

Button — базовая кнопка

Column — Базовый компонент для одной колонки

Columns — Базовый компонент для всех колонок в пределах грида

ColumnsControls — Позволяет выбрать количество отображаемых в гриде колонок

ColumnsEditor — позволяет редактировать данные прямо в гриде, не переходя на детальную страницу элемента

ColumnsEditorRecord — дочерний компонент ColumnsEditor, содержащий отдельную конфигурацию редактора, нужно, к примеру, для ColumnsEditor с полем редактирования типа Select или Multiselect

ColumnsEditorView — дочерний компонент ColumnsEditor, описывающий представление поля редактирования

ColumnsEditingBulk — функционал пакетного изменения для ColumnsEditor

ColumnsEditingClient — реализует функции сохранения отредактированной при помощи ColumnsEditor записи

ColumnsResize — реализует функции изменения ширины колонок в гриде

DateColumn — Колонка с датой

DragAndDrop — Позволяет менять сортировку строк в гриде перетаскиванием

Expandable — Позволяет отображать в ячейке несколько значений, разделенных каким либо символом; часть значений скрыта под ссылкой show more. При нажатии на ссылку отображается компонент Tooltip c полным списком значений

ExportButton — Кнопка экспорта грида в файл

Filters — Компонент фильтров грида

FiltersChips — элементы управления для компонента Filters

GridDataProvider — data provider для грида

ImagePreview — Превью изображения при клике на компонент ThumbnailColumn

InsertListing — позволяет динамически всталять компонент Listing в другой Ui компонент

LinkColumn — Колонка с ячейками, которые могут содержать ссылки

Masonry — очень интересный наследник Listing, позволяющий располагать элементы в виде плитки (или каменной кладки)

MassActions — Компонент массовых действий над элементами грида

MultiselectColumn — Колонка с чекбоксами, позволяющая выбрать несколько строк для применения действий MassActions

OnOffColumn — Позволяет выводить On/Off переключатель в ячейке

Paging — Компонент пагинации грида

Range — Компонент  полями типа “от” и “до”. В гридах обычно используется как одно из представлений фильтров грида. Может включать как базовые поля типа Input, так и их специализированных наследников (например, компонент Date)

Search — Поле текстового поиска по гриду

SelectColumn — Селект в фильтре колонки

Sizes — Компонент управления гридом, ограничивающий количество строк на одной странице

Sortby — добавляет заголовку колонки способность сортировать грид по содержимому колонки в ASC или DESC порядке

ThumbnailColumn — позволяет выводить маленькое изображение в ячейке колонки

TimelineColumns — позволяет организовать грид в виде таймлайна. В Magento 2 Commerce Edition таким образом организовано расписание запуска отложенных действий

ListingToolbar — компонент, который является контейнером для компонентов управления гридом: пагинация, количество выводимых строк и пр.

TreeMassActions — декоратор для компонента MassActions, добавляющий возможность вложенности массовых действий

UI-select — гибрид выпадающего списка и мульти-селекта, позволяющий последовательно выбрать из списка несколько элементов (его можно увидеть при выборе категорий в товарах)

Form Component

Наследники Form Component с иллюстрациями и описанием (трафик!)

ActionDelete — компонент для удаления элементов компонента DynamicRows

Checkbox — единичный чекбокс

CheckboxToggleNotice — пояснение, которое размещается под переключателем

Checkboxset — компонент для объединения несколькии компонентов Checkbox в группы

ColorPicker — компонент выбора цвета

Container — объединяет несколько дочерних компонентов в группу с поясняющим текстом

DataSource — источник данных для формы

Date — поле с виджетом выбора даты или диапазона дат

DynamicRows — интересный компонент, позволяющий динамически добавлять записи, состоящие из нескольких колонок. Каждая колонка может реализовывать один из Ui компонентов

DynamicRowsDragAndDrop — добавляет компоненту DynamicRows возможность менять сортировку перетаскиванием

DynamicRowsRecord — контейнер для записей компонента DynamicRows

Email — текстовое поле Input типа email

FieldSet — компонент для визуальной группировки дочерних элементов

File — текстовое поле Input типа file

FileUploader — элемент для загрузки файла (представляет собой адаптер для JQuery плагина JQuery-File-Upload)

FormDataProvider — компонент, реализующий взаимодействие с дата провайдером формы

Hidden — текстовое поле Input типа hidden

HtmlContent — компонент, позволяющий выводить блоки HTML

Input — базовый компонент, реализующий простое текстовое поле Input

InsertForm — позволяет вставлять формы в другие Ui компоненты

Modal — компонент, реализующий функцию модального окна

Multiline — компонент, объединяющий несколько компонентов в единую группу (можно встретить во многих местах M2 при вводе почтового адреса)

Multiselect — компонент, позволяющий осуществить множественный выбор элементов при зажатом ctrl на клавиатуре

Nav — реализует функционал Tab навигации. Содержит дочерние компоненты Tab

Radioset — псевдоним компонента Checkboxset

Select — базовый компонент типа Select

Tab — реализует Tab-страницу в составе компонента Nav

Text — компонент для выбора текстовой строки

Textarea — компонент, реализующий поле ввода типа textarea

UrlInput — реализует поле типа urlInput

Wysiwyg — компонент, реализующий поддержку TinyMCE для компонента textarea

Как мы видим, список компонентов довольно обширен и включает практически все, что нужно для построения стандартных интерфейсов М2. Также стоит обратить внимание, что некоторые компоненты являются декораторами к базовым компонентам, а некоторые - адаптерами к сторонним решениям. Подробнее о пользе подобного подхода можно почитать в описаниях паттернов Decorator и Adapter.

Устройство компонента

Теперь рассмотрим устройство компонента. В общих чертах, каждый компонент стоит из трех частей:

  1. Объявление компонента в виде XML-декларации.

  2. JS класс компонента, который обычно наследуется от базовых классов  UIElement, UIClass или UICollection

  3. Шаблон (один или несколько), ассоциированный с компонентом.

XML декларация представляет собой объявление компонента, а также его настроек. Пример типичного объявления компонента типа Input внутри базового компонента Form:

<field name="id" formElement="input">
   <argument name="data" xsi:type="array">
       <item name="config" xsi:type="array">
           <item name="source" xsi:type="string">white_rabbit</item>
       </item>
   </argument>
   <settings>
       <dataType>text</dataType>
       <dataScope>id</dataScope>
   </settings>
</field>

При парсинге XML файла вся декларация превращается в многомерный массив, который фактически является комплексной настройкой для базового компонента и всех его дочерних компонентов (об этом мы подробно поговорим в следующих статьях).

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

Шаблон компонента представляет собой HTML описание внешнего вида компонента с привязками поведения компонента к методам класса путем использования knockout.js.

Для примера рассмотрим шаблон компонента Checkbox:

<div class="admin__field admin__field-option">
   <input type="checkbox"
          class="admin__control-checkbox"
          simple-checked="checked"
          ko-disabled="disabled"
          ko-focused="focused"
          ko-value="value"
          attr="id: uid, name: inputName"/>
   <label class="admin__field-label" text="description" attr="for: uid"/>
</div>

Таким образом, у нас есть инструменты объявления и конфигурации компонента (XML декларация), класс компонента, который может быть расширен (что повлечет за собой расширение или изменение сценариев поведения компонента), а также шаблон компонента, который так же может быть при необходимости модифицирован. В целом такая система позволяет в широких пределах модицифировать компоненты и добиваться решения поставленных задач, несмотря на ограниченный и нерасширяемый набор компонентов.

Заключение

Сегодня мы расссмотрели базовые принципы и устройство Ui Components. Как можно заметить, при системном подходе в устройствах компонентов нет ничего особо сложного, вместе с тем, они позволяют решать большой круг задач, которые возникают при разработке интерфейса M2.=

В следующей статье мы детально рассмотрим вопрос конфигурации компонентов, а также попутно более подробно коснемся вопроса, как XML декларация превращается в интерактивный компонент на экране.

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