Dodanie własnego przycisku akcji do strony w adminie w Magento 2
W dzisiejszej notce opiszę jakimi sposobami możemy dodać przyciski do istniejącej strony w adminie. W Magento 2 znajdziemy na to parę sposobów.
Dodanie przycisku akcji do strony z gridem możemy wykonać na parę sposobów. Omówię tutaj konfigurację dla strony z modułu Magento_Newsletter. Dodamy 5 nowych przycisków akcji i zobaczymy, w jakiej kolejności zostaną wyświetlone.
Przykład ze stroną Newsletter Subscribers
Strona edycji subskrybentów do newslettera znajduje się w Marketing ➤ Newsletter Subscribers.
Grid ten nie jest komponentem UI. Jest zbudowany na blokach.
Jak się podpiąć? Na początek potrzebujemy znaleźć plik layoutu, który jest za to odpowiedzialny. Strona, o której mówimy, ma taki URL: admin/newsletter/subscribe/index. Odpowiadający plik layoutu to newsletter_subscribe_index.xml, który znajdziemy w katalogu vendor\magento\module-newsletter\view\adminhtml\layout. Konfiguracja wygląda następująco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?xml version="1.0"?> <!-- /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ --> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <update handle="formkey"/> <update handle="newsletter_subscriber_block"/> <body> <referenceContainer name="content"> <block class="Magento\Newsletter\Block\Adminhtml\Subscriber" name="adminhtml.newsletter.subscriber.container"/> </referenceContainer> </body> </page> |
Po sprawdzeniu jest to handler newsletter_subscriber_block, czyli plik newsletter_subscriber_block.xml. To jest miejsce gdzie mamy całą konfigurację dla grida:
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 |
<?xml version="1.0"?> <!-- /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ --> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceBlock name="adminhtml.newsletter.subscriber.container"> <block class="Magento\Newsletter\Block\Adminhtml\Subscriber\Grid" name="adminhtml.newslettrer.subscriber.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">subscriberGrid</argument> <argument name="dataSource" xsi:type="object" shared="false">Magento\Newsletter\Model\ResourceModel\Subscriber\Grid\Collection</argument> <argument name="default_sort" xsi:type="string">subscriber_id</argument> <argument name="default_dir" xsi:type="string">desc</argument> <argument name="use_ajax" xsi:type="string">1</argument> </arguments> <block class="Magento\Backend\Block\Widget\Grid\Massaction" name="adminhtml.newslettrer.subscriber.grid.massaction" as="grid.massaction"> <arguments> <argument name="massaction_id_field" xsi:type="string">subscriber_id</argument> <argument name="form_field_name" xsi:type="string">subscriber</argument> <argument name="use_select_all" xsi:type="string">1</argument> <argument name="options" xsi:type="array"> <item name="unsubscribe" xsi:type="array"> <item name="label" xsi:type="string" translate="true">Unsubscribe</item> <item name="url" xsi:type="string">*/*/massUnsubscribe</item> </item> <item name="delete" xsi:type="array"> <item name="label" xsi:type="string" translate="true">Delete</item> <item name="url" xsi:type="string">*/*/massDelete</item> <item name="confirm" xsi:type="string" translate="true">Are you sure you want to delete the selected subscriber(s)?</item> </item> </argument> </arguments> </block> <block class="Magento\Backend\Block\Widget\Grid\Export" name="adminhtml.newslettrer.subscriber.grid.export" as="grid.export"> <arguments> <argument name="exportTypes" xsi:type="array"> <item name="csv" xsi:type="array"> <item name="urlPath" xsi:type="string">*/*/exportCsv</item> <item name="label" xsi:type="string" translate="true">CSV</item> </item> <item name="excel" xsi:type="array"> <item name="urlPath" xsi:type="string">*/*/exportXml</item> <item name="label" xsi:type="string" translate="true">Excel XML</item> </item> </argument> </arguments> </block> <block class="Magento\Backend\Block\Widget\Grid\ColumnSet" name="adminhtml.newslettrer.subscriber.grid.columnSet" as="grid.columnSet"> <arguments> <argument name="id" xsi:type="string">problemGrid</argument> </arguments> <block class="Magento\Backend\Block\Widget\Grid\Column" name="adminhtml.newslettrer.subscriber.grid.columnSet.subscriber_id" as="subscriber_id"> <arguments> <argument name="header" xsi:type="string" translate="true">ID</argument> <argument name="index" xsi:type="string">subscriber_id</argument> <argument name="header_css_class" xsi:type="string">col-id</argument> <argument name="column_css_class" xsi:type="string">col-id</argument> </arguments> </block> <block class="Magento\Backend\Block\Widget\Grid\Column" name="adminhtml.newslettrer.subscriber.grid.columnSet.email" as="email"> <arguments> <argument name="header" xsi:type="string" translate="true">Email</argument> <argument name="index" xsi:type="string">subscriber_email</argument> <argument name="header_css_class" xsi:type="string">col-email</argument> <argument name="column_css_class" xsi:type="string">ccol-email</argument> </arguments> </block> <!-- ( ...) --> </block> </referenceBlock> </body> </page> |
Główna klasa, która odpowiada za grida to Magento\Newsletter\Block\Adminhtml\Subscriber\Grid. Naszym celem jest dodanie do bloku Toolbar nowego przycisku akcyjnego. Pokaże, jak można to zrobić na parę sposobów.
Użycie konfiguracji layoutu
Jeśli jest to możliwe, korzystajmy z konfiguracji layoutu. We wstępie, po analizie modułu Magento_Newsletter wiemy, że będzie interesowało nas stworzenie dodatkowej konfiguracji w pliku layoutu newsletter_subscriber_block.xml. Zanim odniesiemy się do danej klasy bloku, stwórzmy najpierw klasę bloku dla przycisku akcyjnego:
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 |
<?php declare(strict_types=1); namespace Anna\Newsletter\Block\Adminhtml\Widget; use Magento\Backend\Block\Widget\Button; use Magento\Backend\Block\Widget\Container; use Magento\Backend\Block\Widget\Context; class FirstCustomButton extends Container { protected const ACTION_ROUTE = 'customroute/custom/action1'; public function __construct( Context $context, array $data = [] ) { parent::__construct($context, $data); $this->initButton(); } protected function initButton(): void { $addButtonProps = [ 'id' => 'first_custom_button_subscribers', 'label' => __('First Custom Button'), 'onclick' => "setLocation('" . $this->getUrl(self::ACTION_ROUTE) . "')", 'class_name' => Button::class, 'class' => 'action-primary', ]; $this->buttonList->add('first_custom_button', $addButtonProps); } } |
Ścieżka do akcji, powinna być istniejącą ścieżką w systemie. Stworzona tu klasa to po prostu blok-kontener. Analogicznie została utworzona druga klasa, Anna\Newsletter\Block\Adminhtml\Widget\SecondCustomButton zawierająca odpowiednio zmodyfikowane parametry dla drugiego przycisku.
W zasadzie nie ma różnicy, gdzie dodamy ten blok, nie wyświetlamy bezpośrednio zawartości, nie definiujemy templaty, a jedynie dodajemy w kodzie przycisk akcji. Metoda addButton() klasy Magento\Backend\Block\Widget\Container pozwala określić między innymi obszar, w którym dany przycisk ma się wyświetlić, domyślna wartość to toolbar, która nas interesuje:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/** * Public wrapper for the button list * * @param string $buttonId * @param array $data * @param integer $level * @param integer $sortOrder * @param string|null $region That button should be displayed in ('toolbar', 'header', 'footer', null) * @return $this */ public function addButton($buttonId, $data, $level = 0, $sortOrder = 0, $region = 'toolbar') { $this->buttonList->add($buttonId, $data, $level, $sortOrder, $region); return $this; } |
Stworzone klasy możemy dodać do odpowiedniego bloku. Konfiguracja została zdefiniowana w przykładowym module w view/adminhtml/layout/newslatter_subscriber_block.xml:
1 2 3 4 5 6 7 8 9 10 11 12 |
<?xml version="1.0"?> <layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd"> <referenceBlock name="adminhtml.newsletter.subscriber.container"> <block class="Anna\Newsletter\Block\Adminhtml\Widget\FirstCustomButton" name="custom.newsletter.subscribers.first.button"/> </referenceBlock> <referenceBlock name="page.actions.toolbar"> <block class="Anna\Newsletter\Block\Adminhtml\Widget\SecondCustomButton" name="custom.newsletter.subscribers.second.button"/> </referenceBlock> </layout> |
Pierwszy przycisk został umieszczony w referencji bloku-kontenera grida, drugi bezpośrednio do bloku toolbar’a, gdzie oryginalnie dodawane są przyciski. Sama klasa layoutu dla toolbar’a nie jest bezpośrednio skonfigurowana w newsletter_subscriber_block.xml, tylko tworzona wewnątrz bloku-rodzica. Mając dostęp do obiektu klasy grida, możemy użyć następującego kodu, aby wyciągnąć nazwę dla takiego bloku:
1 |
$result->getToolbar()->getNameInLayout(); |
Do jakiego bloku byśmy się nie podpięli, klasa kontenera doda przycisk do odpowiedniego obszaru. Przyciski zostają dodane w kolejności, w jakiej zostały zainicjalizowane z konfiguracji pliku xml, co widzimy na poniższym obrazku:
Użycie pluginu
Dobrym pomysłem jest również użycie pluginu. W odpowiednim momencie wystarczy odnieść się do klasy Toolbar’a. Pokaże tutaj dwa sposoby. Pierwszy to użycie dedykowanej klasy grida, zaś w drugim sposobie odnosimy się ogólnie do instancji Toolbar’a, a tym samym mamy możliwość kontroli większej ilości obszarów gdzie dany przycisk może się pokazywać.
Użycie pluginu na klasie Grida
Dla klasy bloku Magento\Newsletter\Block\Adminhtml\Subscriber\Grid obsługującej grid definiujemy plugin. Na początek określmy definicję pluginu, dodając do konfiguracji w etc/adminhtml/di.xml:
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:ObjectManager/etc/config.xsd"> <type name="Magento\Newsletter\Block\Adminhtml\Subscriber\Grid"> <plugin name="newsletter_add_third_button" type="Anna\Newsletter\Plugin\Newsletter\Subscriber\Grid\AddThirdCustomButton" disabled="false" /> </type> </config> |
Jak teraz dodać przycisk? Sam kod, który doda przycisk do Toolbar’a znajdziemy na przykład w klasie Magento\Newsletter\Block\Adminhtml\Template, gdzie metoda _prepareLayout() ustawia przycisk „Add New Template„. Fragment kodu, który za to odpowiada, jest następujący:
1 2 3 4 5 6 7 8 9 |
$this->getToolbar()->addChild( 'add_button', \Magento\Backend\Block\Widget\Button::class, [ 'label' => __('Add New Template'), 'onclick' => "window.location='" . $this->getCreateUrl() . "'", 'class' => 'add primary add-template' ] ); |
Niestety, metoda _prepareLayout() jest typu protected, a do stworzenia pluginu potrzebujemy znaleźć publiczną metodę. Klasa Magento\Framework\View\Element\AbstractBlock posiada metodę setLayout(), która wywołuje _prepareLayout():
1 2 3 4 5 6 7 8 9 10 11 12 |
/** * Set layout object * * @param \Magento\Framework\View\LayoutInterface $layout * @return $this */ public function setLayout(\Magento\Framework\View\LayoutInterface $layout) { $this->_layout = $layout; $this->_prepareLayout(); return $this; } |
Stwórzmy plugin after dla tej metody. Nasza klasa Anna\Newsletter\Plugin\Newsletter\Subscriber\Grid\AddThirdCustomButton, która ustawia dodatkowy przycisk, wygląda następująco:
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 |
<?php declare(strict_types=1); namespace Anna\Newsletter\Plugin\Newsletter\Subscriber\Grid; use Magento\Backend\Block\Widget\Button; use Magento\Framework\View\LayoutInterface; use Magento\Newsletter\Block\Adminhtml\Subscriber\Grid; class AddThirdCustomButton { protected const ACTION_ROUTE = 'customroute/custom/action3'; public function afterSetLayout(Grid $subject, Grid $result, LayoutInterface $layout) { $result->getToolbar()->addChild( 'custom.newsletter.subscribers.third.button', Button::class, [ 'label' => __('Add Third Button'), 'onclick' => "window.location='" . $result->getUrl(self::ACTION_ROUTE) . "'", 'class' => 'action-primary' ] ); return $result; } } |
Wykorzystujemy tutaj obiekt $result, który posiada już zmiany dokonane na Toolbarze i podobnie jak w przedstawiony przykładzie dla Magento\Newsletter\Block\Adminhtml\Template dodajemy nowy przycisk do bloku Toolbar. Na końcu zwracamy obiekt $result.
Użycie pluginu na klasie Toolbar
Jeśli interesuje nas wyświetlenie przycisku w różnych miejscach — to ten sposób będzie nas interesował. Tak, jak poprzednio, zaczniemy od dodania potrzebnej konfiguracji do etc/adminhtml/di.xml:
1 2 3 4 5 6 7 8 |
<?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="Magento\Backend\Block\Widget\Button\ToolbarInterface"> <plugin name="newsletter_add_fourth_button" type="Anna\Newsletter\Plugin\Newsletter\Subscriber\Grid\AddFourthCustomButton"/> </type> </config> |
Jeśli sprawdzimy di.xml modułu Magento_Backend, to <preference> wskazuje na klasę Magento\Backend\Block\Widget\Button\Toolbar. Posiada ona specjalną metodę, pushButtons(), która ustawia przyciski:
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 |
/** * {@inheritdoc} */ public function pushButtons( \Magento\Framework\View\Element\AbstractBlock $context, \Magento\Backend\Block\Widget\Button\ButtonList $buttonList ) { foreach ($buttonList->getItems() as $buttons) { /** @var \Magento\Backend\Block\Widget\Button\Item $item */ foreach ($buttons as $item) { $containerName = $context->getNameInLayout() . '-' . $item->getButtonKey(); $container = $this->createContainer($context->getLayout(), $containerName, $item); if ($item->hasData('name')) { $item->setData('element_name', $item->getName()); } if ($container) { $container->setContext($context); $toolbar = $this->getToolbar($context, $item->getRegion()); $toolbar->setChild($item->getButtonKey(), $container); } } } } |
Używamy pluginu before, aby nasz przycisk również został dodany, zanim zostanie przypisanie listy do Toolbar’a:
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 |
<?php declare(strict_types=1); namespace Anna\Newsletter\Plugin\Newsletter\Subscriber\Grid; use Magento\Backend\Block\Widget\Button\ButtonList; use Magento\Backend\Block\Widget\Button\ToolbarInterface; use Magento\Framework\View\Element\AbstractBlock; class AddFourthCustomButton { protected const ACTION_ROUTE = 'customroute/custom/action4'; public function beforePushButtons( ToolbarInterface $subject, AbstractBlock $context, ButtonList $buttonList ): void { $fullActionName = $context->getRequest()->getFullActionName(); if ($this->canShow($fullActionName)) { $buttonList->add('custom.newsletter.subscribers.fourth.button', [ 'label' => __('Add Fourth Button'), 'onclick' => "window.location='" . $context->getUrl(self::ACTION_ROUTE) . "'", 'class' => 'action-primary' ]); } } private function canShow(string $fullActionName): bool { return $fullActionName === 'newsletter_subscriber_index'; } } |
Klasa Toolbar’a jest ogólnie używana w przypadku budowania grida, dlatego potrzebujemy dodatkowo sprawdzić, czy w danym miejscu wyświetlić przycisk. Sprawdzamy na podstawie akcji, a samą akcję pobieramy z obiektu Request:
1 |
$fullActionName = $context->getRequest()->getFullActionName() |
Tym sposobem, został wyświetlony czwarty przycisk, efekt poniżej:
Użycie preference
Użycie preference. Oczywiście ta opcja też jest możliwa, tylko tym sposobem narażamy się na ewentualne konflikty, które mogą wyniknąć w przypadku, gdy inny moduł również będzie nadpisywał taką samą klasę właśnie przez preference. Nie mniej i ten sposób zamieszczam. Proponowane tu rozwiązaniem najlepiej zastosować gdy możemy dokonać bezpośredniej modyfikacji klasy.
Ponownie sięgamy do klasy grida modułu Magento_Newsletter i dodajemy wpis w adminhtml/di.xml:
1 2 3 4 5 6 |
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <!-- ... --> <preference for="Magento\Newsletter\Block\Adminhtml\Subscriber\Grid" type="Anna\Newsletter\Block\Adminhtml\Subscriber\Grid" /> </config> |
Następnym korkiem jest nadpisanie metody _prepareLayout(), gdzie należy umieścić kod odpowiedzialny za dodanie przycisku akcyjnego do bloku Toolbar:
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 |
<?php declare(strict_types=1); namespace Anna\Newsletter\Block\Adminhtml\Subscriber; use Magento\Backend\Block\Widget\Button; class Grid extends \Magento\Newsletter\Block\Adminhtml\Subscriber\Grid { protected const ACTION_ROUTE = 'customroute/custom/action5'; /** * @return $this */ protected function _prepareLayout() { $this->getToolbar()->addChild( 'fifth_custom_button_subscribers', Button::class, [ 'label' => __('Fifth button'), 'onclick' => "window.location='" . $this->getUrl(self::ACTION_ROUTE). "'", 'class' => 'action-primary' ] ); return parent::_prepareLayout(); } } |
Po zastosowaniu tego sposobu widzimy, że piąty przycisk pojawia się jako pierwszy:
Pluginy w Magento 2 - Web Programming
4 grudnia 2023 @ 02:58
[…] Dodanie przycisku akcyjnego do grida. […]