Magento 2: dynamiczna inicjalizacja komponentu UI
W Magento 2 nie tylko w adminie, ale i na frontendzie wykorzystywane są elementy UI. W Jaki sposób możemy przekazywać im dane? Jak zainicjalizować komponent ui? Na przykładzie prostego komponentu, select-ui: Magento_Ui/js/form/element/ui-select przestawię sposoby jak tego dokonać. Rozpoczynając od przykładu z kodem w phtml-u, do bardziej rozbudowanej inicjalizacji, za pomocą której możemy dostarczyć konfigurację nawet z innego modułu!
- Sposoby na przekazywanie danych
- Uruchomienie przykładu
- Bazowe pliki i klasy do przykładów
- Najprostszy przykład
- Przykład z użyciem ko template – getTemplate
- Przykład z użyciem layoutu, konfiguracja argumentu jsLayout
- Nadpisanie metody getJsLayout bloku
- Nadpisanie metody getJsLayout bloku – dodanie LayoutProcessors
Sposoby na przekazywanie danych
Jeśli chcemy przekazać dynamiczne dane, z php, do komponentu ui robimy to przez dodanie kodu w templacie .phtml. Przekazywane dane potrzebujemy zserializować do formatu JSON. Dane do komponentu ui możemy przekazywać poprzez:
- własną, zdefiniowaną metodę bloku,
- przekazanie części danych z użyciem view modelu,
- poprzez ustawienie argumentu jsLayout dla bloku w konfiguracji layoutu,
- nadpisanie metody bloku getJsLayout().
Dla danego posobu zmodyfikujemy odpowiednio kod .phtml, przekazując przez inicjalizację w znaczniku <script> typu text/x-magento-init.
Jeśli chodzi o template do komponentu ui, to możemy ją stworzyć zarówno w pliku .phtml jak i w standardowym typie plików dla templat KnockoutJS – pliku .html. Krok po kroku przedstawię jak przejść z prostej do bardziej zaawansowanej konfiguracji.
Uruchomienie przykładu
Kiedy mamy zakodowany przykład, to żeby go uruchomić potrzebujemy wykonać w konsoli następujące komendy:
1 2 3 4 5 6 7 |
bin/magento setup:upgrade # below command we need to use with parameter -f in developer mode: bin/magento setup:static-content:deploy -f # clear the cache: bin/magento cache:flush |
Bazowe pliki i klasy do przykładów
Na początek potrzebujemy stworzyć nowy moduł. Poniżej przedstawiam konfigurację dla modułu Anna_KnockoutSelect.
Plik etc/module.xml:
1 2 3 4 5 |
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> <module name="Anna_KnockoutSelect" /> </config> |
Plik registration.php:
1 2 3 4 5 6 |
<?php \Magento\Framework\Component\ComponentRegistrar::register( \Magento\Framework\Component\ComponentRegistrar::MODULE, "Anna_KnockoutSelect", __DIR__ ); |
Konfiguracja routingu i akcji
W module tworzymy plik etc/frontend/routes.xml o następującej zawartości:
1 2 3 4 5 6 7 8 9 |
<?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="standard"> <route id="koselect" frontName="koselect"> <module name="Anna_KnockoutSelect" /> </route> </router> </config> |
Stwórzmy teraz bazową klasę dla akcji.
W module, w katalogu Controller/Select stwórz plik BaseExample.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<?php declare(strict_types=1); namespace Anna\KnockoutSelect\Controller\Select; use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\App\ActionInterface; use Magento\Framework\View\Result\PageFactory; abstract class BaseExample implements ActionInterface, HttpGetActionInterface { /** * @var PageFactory */ protected $pageFactory; /** * @param PageFactory $pageFactory */ public function __construct( PageFactory $pageFactory ) { $this->pageFactory = $pageFactory; } /** * {@inheritdoc} */ public function execute() { return $this->pageFactory->create(); } } |
Każda klasa akcji przestawiona w przykładzie, będzie dziedziczyć po klasie Anna\KnockoutSelect\Controller\Select\BaseExample.
Dla 5 przykładów są to kolejno klasy:
1 2 3 4 5 |
Anna\KnockoutSelect\Controller\Select\Exampleone Anna\KnockoutSelect\Controller\Select\Exampletwo Anna\KnockoutSelect\Controller\Select\Examplethree Anna\KnockoutSelect\Controller\Select\Examplefour Anna\KnockoutSelect\Controller\Select\Examplefive |
Tym sposobem, w katalogu view/frontend/layout będziemy mieć 5 konfiguracji xml dla layoutu:
1 2 3 4 5 |
koselect_select_exampleone.xml koselect_select_exampletwo.xml koselect_select_examplethree.xml koselect_select_examplefour.xml koselect_select_examplefive.xml |
Poszczególne akcje będą więc dostępne pod url-em:
1 2 3 4 5 |
https://<<magento_url>>/koselect/select/exampleone https://<<magento_url>>/koselect/select/exampletwo https://<<magento_url>>/koselect/select/examplethree https://<<magento_url>>/koselect/select/examplefour https://<<magento_url>>/koselect/select/examplefive |
Klasa modelu zwracająca opcje dla elementu select
Dla uproszczenia została stworzona klasa, która zwraca nam odpowiednie dane dla elementu <select>. Klasa Anna\KnockoutSelect\Model\Source\SelectOptions:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<?php declare(strict_types=1); namespace Anna\KnockoutSelect\Model\Source; use Magento\Framework\Data\OptionSourceInterface; class SelectOptions implements OptionSourceInterface { /** * {@inheritdoc} */ public function toOptionArray(): array { return [ [ 'value' => 'first-example', 'label' => __('First Example') ], [ 'value' => 'second-example', 'label' => __('Second Example') ], [ 'value' => 'third-example', 'label' => __('Third Example') ], [ 'value' => 'fourth-example', 'label' => __('Fourth Example') ], [ 'value' => 'fifth-example', 'label' => __('Fifth Example') ], ]; } } |
Minimalne stylowanie
Ponieważ każdy przykład zawiera po dwa elementy <select> na stronie, dodałam proste stylowanie.
W katalogu view/frontend/web/css/source/_module.less dodaj poniższy kod:
1 2 3 |
.example-div { margin-bottom: 20px; } |
Klasa Modelu Widoku
Klasa modelu widoku (ang. view model) przyda nam się do przekazania przetworzonych danych do pliku widoku (pliku phtml):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
<?php declare(strict_types=1); namespace Anna\KnockoutSelect\ViewModel; use Anna\KnockoutSelect\Model\Source\SelectOptions; use Magento\Framework\Serialize\Serializer\Json; use Magento\Framework\View\Element\Block\ArgumentInterface; class SelectData implements ArgumentInterface { /** @var Json */ protected $jsonSerializable; /** @var SelectOptions */ protected $selectOptions; /** * @param Json $jsonSerializable * @param SelectOptions $selectOptions */ public function __construct( Json $jsonSerializable, SelectOptions $selectOptions ) { $this->jsonSerializable = $jsonSerializable; $this->selectOptions = $selectOptions; } /** * @return string */ public function selectOptionsJson(): string { $data = $this->selectOptions->toOptionArray(); return $this->jsonSerializable->serialize($data); } } |
Przykłady, które wykorzystują model widoku, będą miały odpowiednią konfiguracją w pliku layoutu.
Najprostszy przykład
Pierwszy bazowy przykład zawiera większość kodu w pliku .phtml. Dodatkowe dane, tutaj opcje dla elementu <select>, przekazywane są za pomocą modelu widoku. Konfiguracja pliku layoutu koselect_select_exampleone.xml jest następująca:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?xml version="1.0"?> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="content"> <block class="Magento\Framework\View\Element\Template" name="select_example_one" template="Anna_KnockoutSelect::select/example_one.phtml"> <arguments> <argument name="example_number" xsi:type="string">one</argument> <argument name="select_view_model" xsi:type="object">Anna\KnockoutSelect\ViewModel\SelectData</argument> </arguments> </block> </referenceContainer> </body> </page> |
Na podstawie tej konfiguracji, potrzebujemy stworzyć plik example_one.phtml w podkatalogu modułu view/frontend/templates/select. Wykorzystujemy standardową klasę bloku oraz ustawiamy dla niej dodatkowo dwa argumenty. W pliku .phtml możemy się odnieść do nich w następujący sposób:
1 2 3 4 5 6 7 8 |
<?php /** @var Magento\Framework\View\Element\Template $block */ /** @var Anna\KnockoutSelect\ViewModel\SelectData $viewModel */ $viewModel = $block->getSelectViewModel(); $exampleNr = $block->getExampleNumber(); // or $viewModel = $block->getData('select_view_model'); $exampleNr = $block->getData('example_number'); |
Następnym krokiem jest utworzenie templaty dla komponentu ui.
Użycie data-bind
Do pliku view/frontend/templates/select/example_one.phtml dodajmy poniższy kod:
1 2 3 4 |
<div data-bind="scope: 'custom-select-<?= $exampleNr ?>'" class="example-div"> <select data-bind="options: selectOptions, optionsText: 'label', optionsValue: 'value', value: selectedValue"></select> </div> |
Wewnątrz elementu <div> za pomocą data-bind mamy określony tak zwany scope, który będzie odnosił się do zasięgu dla definicji komponentu ui, który zaraz dodamy. Wartość scope dla pierwszego przykładu to oczywiście custom-select-one. Jeśli chodzi o element <select>, to w nim bezpośrednio, za pomocą data-bind ustawiamy poniższe parametry:
- options — jako wartość podajemy nazwę zmiennej, która zawiera tablicę z listą opcji. W omawianym przykładzie potrzebujemy zainicjalizować komponentowi ui zmienną selectOptions,
- optionsText — podajemy nazwę klucza w tablicy, który zawiera nazwę dla wyświetlanej opcji w elemencie <option>. W zwracanej przez nas tablicy jest to klucz label.
- optionsValue — określamy klucz, pod którym przechowywana jest wartość dla atrybutu value elementu <option>.
- value — wartość, odpowiadającej wartości value wybranej opcji, która będzie domyślnie zaznaczona.
To, co będziemy potrzebowali zainicjalizować dla komponentu select-ui to:
- selectOptions — dla którego dane, w formacie JSON, otrzymamy z modelu widoku,
- selectedValue — dla pierwszego przykładu, będzie ona miała wartość „first-example„.
Do inicjalizacji komponentu ui, Magento 2 wprowadziło obsługę skryptów typu text-x-magento-init. Do pliku .phtml dodajemy poniższy kod:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<script type="text/x-magento-init"> { "*": { "Magento_Ui/js/core/app": { "components": { "custom-select-<?= $exampleNr ?>": { "component": "Magento_Ui/js/form/element/ui-select", "selectOptions": <?= /* @noEscape */ $viewModel->selectOptionsJson(); ?>, "selectedValue": "first-example" } } } } } </script> |
Wyróżniona linia numer 6, zawiera nazwę, która odpowiada wartości scope, która została wcześniej ustawiona dla elementu <div>. Oprócz ustawienia wspomnianych wcześniej wartości selectOptions i selectedValue została również określona wartość dla component, dla którego wskazujemy ścieżkę do komponentu ui, który chcemy użyć.
Pełen kod pliku phtml
Dodam jeszcze jeden podstawowy przykład, mianowicie działa identycznie, jedyną różnicą jest podanie konfiguracji dla komponentu. Mamy dodatkowy element config, do którego podajemy parametry do ustawienia elementu <select>. Pełen kod pliku .phtml, wraz z alternatywną wersją konfiguracji poniżej:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
<?php /** @var Magento\Framework\View\Element\Template $block */ /** @var Anna\KnockoutSelect\ViewModel\SelectData $viewModel */ $viewModel = $block->getSelectViewModel(); $exampleNr = $block->getExampleNumber(); ?> <h1>Example <?= $exampleNr ?></h1> <script type="text/x-magento-init"> { "*": { "Magento_Ui/js/core/app": { "components": { "custom-select-<?= $exampleNr ?>": { "component": "Magento_Ui/js/form/element/ui-select", "selectOptions": <?= /* @noEscape */ $viewModel->selectOptionsJson(); ?>, "selectedValue": "first-example" }, "custom-select-<?= $exampleNr ?>-second": { "component": "Magento_Ui/js/form/element/ui-select", "config" : { "selectOptions": <?= /* @noEscape */ $viewModel->selectOptionsJson(); ?>, "selectedValue": "first-example" } } } } } } </script> <div data-bind="scope: 'custom-select-<?= $exampleNr ?>'" class="example-div"> <select data-bind="options: selectOptions, optionsText: 'label', optionsValue: 'value', value: selectedValue"></select> </div> <div data-bind="scope: 'custom-select-<?= $exampleNr ?>-second'" class="example-div"> <select data-bind="options: selectOptions, optionsText: 'label', optionsValue: 'value', value: selectedValue"></select> </div> |
Przykład z użyciem ko template – getTemplate
Plik layoutu koselect_select_exampletwo.xml (konfiguracja podobna jak do przykładu pierwszego):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?xml version="1.0"?> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="content"> <block class="Magento\Framework\View\Element\Template" name="select_example_two" template="Anna_KnockoutSelect::select/example_two.phtml"> <arguments> <argument name="example_number" xsi:type="string">two</argument> <argument name="select_view_model" xsi:type="object">Anna\KnockoutSelect\ViewModel\SelectData</argument> </arguments> </block> </referenceContainer> </body> </page> |
Stworzony kod z pierwszego przykładu dodajmy do pliku example_two.phtml i zmodyfikujmy go odpowiednio.
W poprzednim przykładzie templata dla elementu ui została ustawiona w pliku example_one.phtml w ten sposób. Przenieśmy kod dla komponentu ui, tutaj kod dla elementu <select>, do osobnego pliku z rozszerzeniem .html.
1 2 3 4 |
<div data-bind="scope: 'custom-select-<?= $exampleNr ?>'" class="example-div"> <select data-bind="options: selectOptions, optionsText: 'label', optionsValue: 'value', value: selectedValue"></select> </div> |
Plik templaty html
Kod, który jest dla elementu <select> dodajmy do osobnej templaty. Dla KnockoutJs templaty definiowane są w plikach .html. Pliki te umieszczamy w katalogu view/frontend/web/template. Stwórz plik example_select.html o następującej zawartości:
1 |
<select data-bind="options: selectOptions, optionsText: 'label', optionsValue: 'value', value: selectedValue"></select> |
Wykorzystanie ko getTempate()
W miejsce przenosionego elementu <select> dodaj kod wyróżniony poniżej:
1 2 3 |
<div data-bind="scope: 'custom-select-<?= $exampleNr ?>'" class="example-div"> <!-- ko template: getTemplate() --><!-- /ko --> </div> |
Następnym krokiem jest dodanie informacji, że korzystamy z templaty html. Potrzebujemy zdefiniować właściwość template dla konfiguracji komponentu. Odniesienie do templaty, które potrzebujemy tu ustawić to Anna/KnockoutSelect/example_select. Odpowiada ono ścieżce Anna/KnockoutSelect/view/frontend/web/template/example_select.html.
Pełen kod pliku phtml, z wyróżnionymi zmianami:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
<?php /** @var Magento\Framework\View\Element\Template $block */ /** @var Anna\KnockoutSelect\ViewModel\SelectData $viewModel */ $viewModel = $block->getSelectViewModel(); $exampleNr = $block->getExampleNumber(); ?> <h1>Example <?= $exampleNr ?></h1> <script type="text/x-magento-init"> { "*": { "Magento_Ui/js/core/app": { "components": { "custom-select-<?= $exampleNr ?>": { "component": "Magento_Ui/js/form/element/ui-select", "selectOptions": <?= /* @noEscape */ $viewModel->selectOptionsJson(); ?>, "selectedValue": "second-example", "template": "Anna_KnockoutSelect/example_select" }, "custom-select-<?= $exampleNr ?>-second": { "component": "Magento_Ui/js/form/element/ui-select", "config" : { "selectOptions": <?= /* @noEscape */ $viewModel->selectOptionsJson(); ?>, "selectedValue": "second-example", "template": "Anna_KnockoutSelect/example_select" } } } } } } </script> <div data-bind="scope: 'custom-select-<?= $exampleNr ?>'" class="example-div"> <!-- ko template: getTemplate() --><!-- /ko --> </div> <div data-bind="scope: 'custom-select-<?= $exampleNr ?>-second'" class="example-div"> <!-- ko template: getTemplate() --><!-- /ko --> </div> |
Przykład z użyciem layoutu, konfiguracja argumentu jsLayout
W poprzednich przykładach wykorzystywaliśmy domyślną klasę bloku Magento\Framework\View\Element\Template. Klasa ta dziedziczy po klasie abstrakcyjnej Magento\Framework\View\Element\AbstractBlock. Wśród argumentów zdefiniowanych dla parametru konstruktora $data, sprawdzane jest, czy w konfiguracji layoutu, tablicy, został ustawiony klucz jsLayout:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
<?php /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ declare(strict_types=1); namespace Magento\Framework\View\Element; use Magento\Framework\App\DeploymentConfig; use Magento\Framework\App\ObjectManager; use Magento\Framework\Cache\LockGuardedCacheLoader; use Magento\Framework\Config\ConfigOptionsListConstants; use Magento\Framework\DataObject\IdentityInterface; /** * Base class for all blocks. * * Avoid inheriting from this class. Will be deprecated. * * Marked as public API because it is actively used now. * * phpcs:disable Magento2.Classes.AbstractApi * @api * @SuppressWarnings(PHPMD.ExcessivePublicCount) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.NumberOfChildren) * @since 100.0.2 */ abstract class AbstractBlock extends \Magento\Framework\DataObject implements BlockInterface /** (...) */ /** * JS layout configuration * * @var array */ protected $jsLayout = []; /** (...) */ /** * Constructor * * @param \Magento\Framework\View\Element\Context $context * @param array $data */ public function __construct( \Magento\Framework\View\Element\Context $context, array $data = [] ) { $this->_request = $context->getRequest(); $this->_layout = $context->getLayout(); $this->_eventManager = $context->getEventManager(); $this->_urlBuilder = $context->getUrlBuilder(); $this->_cache = $context->getCache(); $this->_design = $context->getDesignPackage(); $this->_session = $context->getSession(); $this->_sidResolver = $context->getSidResolver(); $this->_scopeConfig = $context->getScopeConfig(); $this->_assetRepo = $context->getAssetRepository(); $this->_viewConfig = $context->getViewConfig(); $this->_cacheState = $context->getCacheState(); $this->_logger = $context->getLogger(); $this->_escaper = $context->getEscaper(); $this->filterManager = $context->getFilterManager(); $this->_localeDate = $context->getLocaleDate(); $this->inlineTranslation = $context->getInlineTranslation(); $this->lockQuery = $context->getLockGuardedCacheLoader(); if (isset($data['jsLayout'])) { $this->jsLayout = $data['jsLayout']; unset($data['jsLayout']); } parent::__construct($data); $this->_construct(); } /** * Retrieve serialized JS layout configuration ready to use in template * * @return string */ public function getJsLayout() { return json_encode($this->jsLayout); } /** (...) */ } |
W konstruktorze jest inicjalizowana zmienna klasowa $jsLauout, która jest kodowana do JSON przy wywołaniu metody getJsLayout(). Super!
W związku z tym możemy tak zrobić, by blok zwracał całą konfiguracją dla ui componentu. W nowym pliku templaty example_three.phtml możemy uprościć kod do następującego:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<?php /** @var Magento\Framework\View\Element\Template $block */ $exampleNr = $block->getExampleNumber(); ?> <h1>Example <?= $exampleNr ?></h1> <script type="text/x-magento-init"> { "*": { "Magento_Ui/js/core/app": <?= /* @escapeNotVerified */ $block->getJsLayout();?> } } </script> <div data-bind="scope: 'custom-select-<?= $exampleNr ?>'" class="example-div"> <!-- ko template: getTemplate() --><!-- /ko --> </div> <div data-bind="scope: 'custom-select-<?= $exampleNr ?>-second'" class="example-div"> <!-- ko template: getTemplate() --><!-- /ko --> </div> |
Rezygnujemy tutaj z wykorzystania modelu widoku. W tym przykładzie chce pokazać strukturę w xml-u, która pozwoli zwrócić nam konfigurację dla komponentu UI. Plik koselect_select_examplethree.xml, który ustawia potrzebne dane, jest następujący:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
<?xml version="1.0"?> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="content"> <block class="Magento\Framework\View\Element\Template" name="select_example_three" template="Anna_KnockoutSelect::select/example_three.phtml"> <arguments> <argument name="example_number" xsi:type="string">three</argument> <argument name="jsLayout" xsi:type="array"> <item name="components" xsi:type="array"> <item name="custom-select-three" xsi:type="array"> <item name="config" xsi:type="array"> <item name="component" xsi:type="string">Magento_Ui/js/form/element/ui-select</item> <item name="template" xsi:type="string">Anna_KnockoutSelect/example_select</item> <item name="selectOptions" xsi:type="array"> <item name="0" xsi:type="array"> <item name="value" xsi:type="string">first-example</item> <item name="label" translate="true" xsi:type="string">First Example</item> </item> <item name="1" xsi:type="array"> <item name="value" xsi:type="string">second-example</item> <item name="label" translate="true" xsi:type="string">Second Example</item> </item> <item name="2" xsi:type="array"> <item name="value" xsi:type="string">third-example</item> <item name="label" translate="true" xsi:type="string">Third Example</item> </item> <item name="3" xsi:type="array"> <item name="value" xsi:type="string">fourth-example</item> <item name="label" translate="true" xsi:type="string">Fourth Example</item> </item> <item name="4" xsi:type="array"> <item name="value" xsi:type="string">fifth-example</item> <item name="label" translate="true" xsi:type="string">Fifth Example</item> </item> </item> <item name="selectedValue" xsi:type="string">third-example</item> </item> </item> <item name="custom-select-three-second" xsi:type="array"> <item name="component" xsi:type="string">Magento_Ui/js/form/element/ui-select</item> <item name="template" xsi:type="string">Anna_KnockoutSelect/example_select</item> <item name="selectOptions" xsi:type="array"> <item name="0" xsi:type="array"> <item name="value" xsi:type="string">first-example</item> <item name="label" translate="true" xsi:type="string">First Example</item> </item> <item name="1" xsi:type="array"> <item name="value" xsi:type="string">second-example</item> <item name="label" translate="true" xsi:type="string">Second Example</item> </item> <item name="2" xsi:type="array"> <item name="value" xsi:type="string">third-example</item> <item name="label" translate="true" xsi:type="string">Third Example</item> </item> <item name="3" xsi:type="array"> <item name="value" xsi:type="string">fourth-example</item> <item name="label" translate="true" xsi:type="string">Fourth Example</item> </item> <item name="4" xsi:type="array"> <item name="value" xsi:type="string">fifth-example</item> <item name="label" translate="true" xsi:type="string">Fifth Example</item> </item> </item> <item name="selectedValue" xsi:type="string">third-example</item> </item> </item> </argument> </arguments> </block> </referenceContainer> </body> </page> |
Nie stworzyliśmy nowej klasy bloku, a możemy zainicjalizować komponent! Mimo wszystko chcemy by opcje do elementu <select> były dostarczane z kodu php dynamicznie. Przedsławie dwa sposoby jak możemy to zrobić. Kolejne przykłady będą wymagały utworzenia dodatkowo klasy bloku.
Nadpisanie metody getJsLayout bloku
W poprzednim przykładzie stworzyliśmy konfigurację layoutu, która pozwalała na zainicjalizowaniu komponentu po wywołaniu metody getJsLayout() w pliku .phtml. Dodajmy nową konfigurację layoutu, usuniemy z jej wartości dla elementu <select> oraz dodajmy definicję nowego bloku:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<?xml version="1.0"?> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="content"> <block class="Anna\KnockoutSelect\Block\Select\ExampleFour" name="select_example_four" template="Anna_KnockoutSelect::select/example_four.phtml"> <arguments> <argument name="example_number" xsi:type="string">four</argument> <argument name="jsLayout" xsi:type="array"> <item name="components" xsi:type="array"> <item name="custom-select-four" xsi:type="array"> <item name="config" xsi:type="array"> <item name="component" xsi:type="string">Magento_Ui/js/form/element/ui-select</item> <item name="template" xsi:type="string">Anna_KnockoutSelect/example_select</item> <item name="selectedValue" xsi:type="string">fourth-example</item> </item> </item> <item name="custom-select-four-second" xsi:type="array"> <item name="component" xsi:type="string">Magento_Ui/js/form/element/ui-select</item> <item name="template" xsi:type="string">Anna_KnockoutSelect/example_select</item> <item name="selectedValue" xsi:type="string">fourth-example</item> </item> </item> </argument> </arguments> </block> </referenceContainer> </body> </page> |
Templata example_four.phtml ma identyczną zawartość jak templata example_three.phtml. Dodajmy teraz samą klasę bloku.
Potrzebujemy przekazać listę opcji, w tym celu tablicę zawierającą konfigurację, której budowa musi odpowiadać zagnieżdżeniu elementów, jakie były konfiguracji layoutu koselect_select_examplethree.xml.
Poniżej kod klasy bloku Anna/KnockoutSelect/Block/Select/ExampleFour:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<?php declare(strict_types=1); namespace Anna\KnockoutSelect\Block\Select; use Anna\KnockoutSelect\Model\Source\SelectOptions; use Magento\Framework\View\Element\Template; class ExampleFour extends Template { /** @var SelectOptions */ protected $selectOptions; public function __construct( Template\Context $context, SelectOptions $selectOptions, array $data = [] ) { $this->selectOptions = $selectOptions; parent::__construct($context, $data); } /** * {@inheritdoc} */ public function getJsLayout(): string { $exampleNr = $this->getData('example_number'); $customSelectNr = sprintf('custom-select-%s', $exampleNr); $this->jsLayout['components'][$customSelectNr]['config']['selectOptions'] = $this->selectOptions->toOptionArray(); $customSelectNrSecond = sprintf('custom-select-%s-second', $exampleNr); $this->jsLayout['components'][$customSelectNrSecond]['selectOptions'] = $this->selectOptions->toOptionArray(); return parent::getJsLayout(); } } |
Oczywiście nic nie stoi na przeszkodzie, aby stworzyć po prostu dowolną metodę i zwrócić całą taką konfigurację w tablicy. Następnie taką tablicę zserializować do formatu JSON.
Nadpisanie metody getJsLayout bloku – dodanie LayoutProcessors
Layout koselect_select_examplefive.xml dla naszego piątego i ostatniego przykładu (analogicznie jak do poprzedniego przykładu) będzie następujący:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<?xml version="1.0"?> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="content"> <block class="Anna\KnockoutSelect\Block\Select\ExampleFive" name="select_example_five" template="Anna_KnockoutSelect::select/example_five.phtml"> <arguments> <argument name="example_number" xsi:type="string">five</argument> <argument name="jsLayout" xsi:type="array"> <item name="components" xsi:type="array"> <item name="custom-select-five" xsi:type="array"> <item name="config" xsi:type="array"> <item name="component" xsi:type="string">Magento_Ui/js/form/element/ui-select</item> <item name="template" xsi:type="string">Anna_KnockoutSelect/example_select</item> <item name="selectedValue" xsi:type="string">fifth-example</item> </item> </item> <item name="custom-select-five-second" xsi:type="array"> <item name="component" xsi:type="string">Magento_Ui/js/form/element/ui-select</item> <item name="template" xsi:type="string">Anna_KnockoutSelect/example_select</item> <item name="selectedValue" xsi:type="string">fifth-example</item> </item> </item> </argument> </arguments> </block> </referenceContainer> </body> </page> |
Zajmiemy się teraz przeniesieniem logiki dodawania opcji do elementu <select> z klasy bloku do osobnej klasy. W tym celu potrzebujemy umożliwić dodanie zawartości do argumentu jsLayout za pomocą innych klas. Na początek zdefiniujmy interfejs, który te klasy będą musiały implementować.
Interfejs Anna\KnockoutSelect\Block\Select\LayoutProcessorInterface
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?php declare(strict_types=1); namespace Anna\KnockoutSelect\Block\Select; /** * Layout processor interface. * * Can be used to provide a custom logic for example select JS layout preparation. * * @see \Anna\KnockoutSelect\Block\Select\ExampleFive */ interface LayoutProcessorInterface { /** * Process js Layout of block */ public function process(array $jsLayout): array; } |
Nowa wersja logiki, która została zastosowana w bloku Anna\KnockoutSelect\Block\Select\ExampleFive jest następująca:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<?php declare(strict_types=1); namespace Anna\KnockoutSelect\Block\Select; use Magento\Framework\View\Element\Template; class ExampleFive extends Template { /** @var array|LayoutProcessorInterface[] */ protected $layoutProcessors; /** * @param Template\Context $context * @param array|LayoutProcessorInterface[] $layoutProcessors * @param array $data */ public function __construct( Template\Context $context, array $layoutProcessors = [], array $data = [] ) { $this->layoutProcessors = $layoutProcessors; parent::__construct($context, $data); } /** * {@inheritdoc} */ public function getJsLayout(): string { foreach ($this->layoutProcessors as $processor) { $this->jsLayout = $processor->process($this->jsLayout); } return parent::getJsLayout(); } } |
Do konsturktora dodajemy argument $layoutProcessors, który jest tablicą. Elementy tablicy to obiekty implementujące interfejs Anna\KnockoutSelect\Block\Select\LayoutProcessorInterface. W Metodzie getJsLayout() dodajemy pętle, która przegląda listę i wywołuje metodę określoną przez interfejs.
Pora na stworzenie klasy, której zadaniem będzie dodanie konfiguracji dla opcji elementu <select>. Kod klasy Anna\KnockoutSelect\Block\Select\ExampleFive\SelectDataProcessor:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<?php declare(strict_types=1); namespace Anna\KnockoutSelect\Block\Select\ExampleFive; use Anna\KnockoutSelect\Block\Select\LayoutProcessorInterface; use Anna\KnockoutSelect\Model\Source\SelectOptions; class SelectDataProcessor implements LayoutProcessorInterface { /** @var SelectOptions */ protected $selectOptions; /** * @param SelectOptions $selectOptions */ public function __construct( SelectOptions $selectOptions ) { $this->selectOptions = $selectOptions; } /** * {@inheritdoc} */ public function process(array $jsLayout): array { $exampleNr = 'five'; $customSelectNr = sprintf('custom-select-%s', $exampleNr); $jsLayout['components'][$customSelectNr]['config']['selectOptions'] = $this->selectOptions->toOptionArray(); $customSelectNrSecond = sprintf('custom-select-%s-second', $exampleNr); $jsLayout['components'][$customSelectNrSecond]['selectOptions'] = $this->selectOptions->toOptionArray(); return $jsLayout; } } |
Logika, która wcześniej była w metodzie bloku, jest teraz w osobnej klasie.
Potrzebujemy teraz przekazać tę klasę do bloku, aby jej logika została wykonana. Stwórzmy więc konfigurację wstrzykującą do tablicy $layoutProcessors utworzoną klasę Anna\KnockoutSelect\Block\Select\ExampleFive\SelectDataProcessor.
W katalogu etc/frontend modułu stwórz plik di.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <type name="Anna\KnockoutSelect\Block\Select\ExampleFive"> <arguments> <argument name="layoutProcessors" xsi:type="array"> <item name="select_data" xsi:type="object">Anna\KnockoutSelect\Block\Select\ExampleFive\SelectDataProcessor</item> </argument> </arguments> </type> </config> |
I to wszystko. Mamy odciążoną klasę bloku od dodatkowych zależności. Możemy też później rozbudowywać o kolejne klasy, które mogą dostarczać konfigurację także do innych komponentów UI.