El módulo es un elemento estructural de Magento 2 - todo el sistema se basa en módulos. Normalmente, el primer paso para crear una personalización es construir un módulo.
- 1. Crear archivos de configuración
- 2. Crear rutas, controlador, template y layout
- 3. Ejecutar el script de instalación
- 4. Comprobar que el módulo está funcionando
- Crear Tablas
- Insertar información en las Tablas
- Actualizar Estructura de Tablas
- Actualizar Valores de la Tabla
- Actualizar Versión del Módulo
- 1. Rutas y Controladores
- 2. Menú
- 3. ACL (Access Control List)
- 4. Admin Grid
- 5. Formularios
- 6. MassActions
Para crear un módulo, se deben completar los siguientes pasos fundamentales:
- Crear archivos de configuración
- Crear rutas, controlador, template y layout
- Ejecutar el script de instalación. "bin/magento setup:upgrade" para instalar el nuevo módulo.
- Comprobar que el módulo está funcionando.
La carpeta deberá estar dentro de app/code. Cada nombre de módulo en Magento 2 consta de dos partes: el proveedor (Vendor) y el módulo en sí. En otras palabras, los módulos se agrupan en proveedores (Vendors). La ruta quedaría:
app/code/VENDOR/MODULE
En este caso el vendor será Tutorial y el módulo se llamará Example
app/code/Tutorial/Example
Este archivo contiene la siguiente información:
- Nombre del módulo
- Versión del módulo
- Dependencias
<?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="Tutorial_Example" setup_version="1.0.0">
<sequence>
<module name="Magento_Catalog"/>
</sequence>
</module>
</config>
Cada módulo debe tener este archivo, que le dice a Magento cómo localizar el módulo.
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Tutorial_Example',
__DIR__
);
Crear el archivo app/code/Tutorial/Example/etc/frontend/routes.xml para declarar las rutas:
<?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="example" frontName="example">
<module name="Tutorial_Example"/>
</route>
</router>
</config>
Crear el archivo app/code/Tutorial/Example/Controller/Index/Index.php
<?php
namespace Tutorial\Example\Controller\Index;
use Magento\Framework\App\Action\Context;
use Magento\Framework\View\Result\PageFactory;
class Index extends \Magento\Framework\App\Action\Action
{
/**
* @param Context $context
* @param PageFactory $resultPageFactory
*/
public function __construct(
Context $context,
PageFactory $resultPageFactory
)
{
parent::__construct($context);
$this->resultPageFactory = $resultPageFactory;
}
public function execute()
{
$resultPageFactory = $this->resultPageFactory->create();
// Add page title
$resultPageFactory->getConfig()->getTitle()->set(__('Example module'));
// Add breadcrumb
/** @var \Magento\Theme\Block\Html\Breadcrumbs */
$breadcrumbs = $resultPageFactory->getLayout()->getBlock('breadcrumbs');
$breadcrumbs->addCrumb('home',
[
'label' => __('Home'),
'title' => __('Home'),
'link' => $this->_url->getUrl('')
]
);
$breadcrumbs->addCrumb('tutorial_example',
[
'label' => __('Example'),
'title' => __('Example')
]
);
return $resultPageFactory;
}
}
Crear el archivo app/code/Tutorial/Example/view/frontend/layout/example_index_index.xml
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceContainer name="content">
<block class="Magento\Framework\View\Element\Template" name="tutorial_example_block" template="Tutorial_Example::index.phtml" />
</referenceContainer>
</body>
</page>
Crear el archivo app/code/Tutorial/Example/view/frontend/templates/index.phtml
<h1><?php echo __('This is an example module!') ?></h1>
Ejecutar este comando hace que el nuevo módulo esté activo, notificando a Magento de su presencia.
php bin/magento setup:upgrade
Para verificar que se ha reconocido, verifique el archivo /etc/config.php. Tiene una lista de módulos generados automáticamente que están activos. Ejecutar este comando desde la raíz del sitio
grep Tutorial_Example app/etc/config.php
El contenido del módulo podrá verse desde la ruta que se creo anteriormente:
http://sitio.com/example/index/index
o de forma abreviada
http://sitio.com/example
El formato de las rutas es de la siguiente forma:
http://sitio.com/route_name/controller/action
En donde:
- route_name: es un nombre único establecido en routes.xml.
- controller: es el nombre de la carpeta dentro de la carpeta Controller.
- action: es una clase con un método execute para procesar la petición.
En la base de datos, en la tabla setup_module es posible ver el nuevo módulo instalado:
SELECT * FROM magento.web_setup_module where module = 'Tutorial_Example';
Crear el archivo: app/code/Tutorial/Example/Setup/InstallSchema.php para crear y declarar la nueva tabla:
<?php
namespace Tutorial\Example\Setup;
use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\DB\Ddl\Table;
class InstallSchema implements InstallSchemaInterface
{
public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
{
$installer = $setup;
$installer->startSetup();
// Get tutorial_example table
$tableName = $installer->getTable('tutorial_example');
// Check if the table already exists
if ($installer->getConnection()->isTableExists($tableName) != true) {
// Create tutorial_example table
$table = $installer->getConnection()
->newTable($tableName)
->addColumn(
'id',
Table::TYPE_INTEGER,
null,
[
'identity' => true,
'unsigned' => true,
'nullable' => false,
'primary' => true
],
'ID'
)
->addColumn(
'title',
Table::TYPE_TEXT,
null,
['nullable' => false, 'default' => ''],
'Title'
)
->addColumn(
'summary',
Table::TYPE_TEXT,
null,
['nullable' => false, 'default' => ''],
'Summary'
)
->addColumn(
'description',
Table::TYPE_TEXT,
null,
['nullable' => false, 'default' => ''],
'Description'
)
->addColumn(
'created_at',
Table::TYPE_DATETIME,
null,
['nullable' => false],
'Created At'
)
->addColumn(
'status',
Table::TYPE_SMALLINT,
null,
['nullable' => false, 'default' => '0'],
'Status'
)
->setComment('News Table')
->setOption('type', 'InnoDB')
->setOption('charset', 'utf8');
$installer->getConnection()->createTable($table);
}
$installer->endSetup();
}
}
Esta clase, deberá llamarse InstallSchema y deberá implementar la interface InstallSchemaInterface, además debera contener el método install.
public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
Este método debera iniciar y terminar con las lineas:
$setup->startSetup();
...
$setup->endSetup();
Crear el archivo: app/code/Tutorial/Example/Setup/InstallData.php
<?php
namespace Tutorial\Example\Setup;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
class InstallData implements InstallDataInterface
{
/**
* {@inheritdoc}
*/
public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
{
$setup->startSetup();
$setup->getConnection()->insert(
$setup->getTable('tutorial_example'),
[
'title' => 'How to create a simple module',
'summary' => 'The summary',
'description' => 'The description',
'created_at' => date('Y-m-d H:i:s'),
'status' => 1
]
);
$setup->getConnection()->insert(
$setup->getTable('tutorial_example'),
[
'title' => 'Create a module with custom database table',
'summary' => 'The summary',
'description' => '',
'created_at' => date('Y-m-d H:i:s'),
'status' => 1
]
);
$setup->endSetup();
}
}
Crear el archivo: app/code/Tutorial/Example/Setup/UpgradeSchema.php
<?php
namespace Tutorial\Example\Setup;
use Magento\Framework\Setup\UpgradeSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\DB\Ddl\Table;
class UpgradeSchema implements UpgradeSchemaInterface
{
/**
* {@inheritdoc}
*/
public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $context)
{
$setup->startSetup();
if (version_compare($context->getVersion(), '1.0.1', '<')) {
// Get tutorial_example table
$tableName = $setup->getTable('tutorial_example');
$setup->getConnection()->addColumn(
$setup->getTable($tableName),
'newcolumn',
[
'type' => Table::TYPE_TEXT,
'nullable' => true,
'comment' => 'New Column'
]
);
}
$setup->endSetup();
}
}
Crear el archivo: app/code/Tutorial/Example/Setup/UpgradeData.php
<?php
namespace Tutorial\Example\Setup;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\UpgradeDataInterface;
class UpgradeData implements UpgradeDataInterface
{
/**
* {@inheritdoc}
*/
public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
{
$setup->startSetup();
if (version_compare($context->getVersion(), '1.0.1', '<')) {
// Get tutorial_example table
$tableName = $setup->getTable('tutorial_example');
// Update Row
$setup->getConnection()->update(
$setup->getTable($tableName),
[
'description' => 'New description'
],
$setup->getConnection()->quoteInto('id = ?', 2)
);
// Add new row
$data = [
[
'title' => 'How to create another module',
'summary' => 'The new summary',
'description' => 'The description',
'created_at' => date('Y-m-d H:i:s'),
'status' => 1
],
[
'title' => 'Create another module with custom database table',
'summary' => 'The other summary',
'description' => 'The description',
'created_at' => date('Y-m-d H:i:s'),
'status' => 1
]
];
// Insert data to table
foreach ($data as $item) {
$setup->getConnection()->insert($tableName, $item);
}
}
$setup->endSetup();
}
}
<?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="Tutorial_Example" setup_version="1.0.1">
<sequence>
<module name="Magento_Catalog"/>
</sequence>
</module>
</config>
Crear archivo Model/ResourceModel/Item.php, el cual contendrá la clase Item que extenderá de la clase AbstractDb y en su método constructor se seleccionara la tabla que utilizará
<?php
namespace Tutorial\Example\Model\ResourceModel;
use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
class Item extends AbstractDb
{
protected function _construct()
{
$this->_init('tutorial_example', 'id');
}
}
Crear archivo Model/Item.php el cual contendrá la clase Item y en este caso extenderá de la clase AbstractModel y en su método constructor se indicará que se utilizará la clase creada anteriormente.
<?php
namespace Tutorial\Example\Model;
use Magento\Framework\Model\AbstractModel;
class Item extends AbstractModel
{
protected function _construct()
{
$this->_init(\Tutorial\Example\Model\ResourceModel\Item::class);
}
}
Por último se creará el archivo Model/ResourceModel/Item/Collection.php que contiene la clase Collection la cual extiende de la clase de AbstractCollection y en su método constructor se pasan las 2 clases creadas anteriormente.
<?php
namespace Tutorial\Example\Model\ResourceModel\Item;
use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;
use Tutorial\Example\Model\Item;
use Tutorial\Example\Model\ResourceModel\Item as ItemResource;
class Collection extends AbstractCollection
{
protected $_idFieldName = 'id';
protected function _construct()
{
$this->_init(Item::class, ItemResource::class);
}
}
Blocks Crear el archivo Block/index.php el cual obtendrá los valores de la base de datos utilizando el Modelo que se creo anteriormente, específicamente la clase Collection. El método getItems() regresará los valores.
<?php
namespace Tutorial\Example\Block;
use Magento\Framework\View\Element\Template;
use Tutorial\Example\Model\ResourceModel\Item\Collection;
use Tutorial\Example\Model\ResourceModel\Item\CollectionFactory;
class Index extends Template
{
private $collectionFactory;
public function __construct(
Template\Context $context,
CollectionFactory $collectionFactory,
array $data = []
) {
$this->collectionFactory = $collectionFactory;
parent::__construct($context, $data);
}
/**
* @return \Tutorial\Example\Model\Item[]
*/
public function getItems()
{
return $this->collectionFactory->create()->getItems();
}
}
Layout En el caso del archivo view/frontend/layout/example_index_index.php que ya existía, se modificará el atributo class de la etiqueta block para indicar que utilice el bloque Index.php que creamos.
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceContainer name="content">
<block class="Tutorial\Example\Block\Index" name="tutorial_example_block" template="Tutorial_Example::index.phtml" />
</referenceContainer>
</body>
</page>
Templates En el caso del archivo view/frontend/templates/index.phtml que ya existía, le agregaremos las líneas que toman la información del Bloque Index.php; mediante un foreach() interactuamos entre cada registro devuelto para obtener los campos de la tabla.
<h1><?php echo __('This is an example module!') ?></h1>
<?php
/** @var \Tutorial\Example\Block\Index $block */
?>
<?php foreach ($block->getItems() as $item): ?>
<h2><b><?php echo $item->getTitle(); ?></b></h2>
<p><?php echo $item->getSummary(); ?></p>
<p><?php echo $item->getDescription(); ?></p>
<p>Fecha: <?php echo $item->getCreated_at(); ?></p>
<hr>
<?php endforeach; ?>
En el caso del archivo Controller/Index/Index.php que ya existía, no sufrirá cambios.
<?php
namespace Tutorial\Example\Controller\Index;
use Magento\Framework\App\Action\Context;
use Magento\Framework\View\Result\PageFactory;
class Index extends \Magento\Framework\App\Action\Action
{
/**
* @param Context $context
* @param PageFactory $resultPageFactory
*/
public function __construct(
Context $context,
PageFactory $resultPageFactory
)
{
parent::__construct($context);
$this->resultPageFactory = $resultPageFactory;
}
public function execute()
{
$resultPageFactory = $this->resultPageFactory->create();
// Add page title
$resultPageFactory->getConfig()->getTitle()->set(__('Example module'));
// Add breadcrumb
/** @var \Magento\Theme\Block\Html\Breadcrumbs */
$breadcrumbs = $resultPageFactory->getLayout()->getBlock('breadcrumbs');
$breadcrumbs->addCrumb('home',
[
'label' => __('Home'),
'title' => __('Home'),
'link' => $this->_url->getUrl('')
]
);
$breadcrumbs->addCrumb('tutorial_example',
[
'label' => __('Example'),
'title' => __('Example')
]
);
return $resultPageFactory;
}
}
En esta sección se verá como crear la parte del administrador:
- Rutas y Controladores.
- Menú.
- ACL (Access Control Lists).
- Admin Grid
- Formularios
El archivo que administra las rutas del administrador lo crearemos en la siguiente ruta:
Tutorial/Example/etc/adminhtml/routes.xml
Y el contenido queda de la siguiente manera:
<?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="example" frontName="example">
<module name="Tutorial_Example"/>
</route>
</router>
</config>
El archivo del controlador quedará en la siguiente ruta:
Tutorial/Example/Controller/Adminhtml/Index/Index.php
Y el contenido queda de la siguiente manera:
<?php
namespace Tutorial\Example\Controller\Adminhtml\Index;
use Magento\Framework\Controller\ResultFactory;
class Index extends \Magento\Backend\App\Action
{
public function execute()
{
$resultPage = $this->resultFactory->create(ResultFactory::TYPE_PAGE);
$resultPage->getConfig()->getTitle()->prepend((__('Modulo Example')));
return $resultPage;
}
}
Para poder acceder a la vista creada, se deberá teclear la siguiente ruta en el explorador:
http://sitio.com/admin/example/index
El archivo se deberá crear en la siguiente ruta:
Tutorial/Example/etc/adminhtml/menu.xml
Y el contenido será el siguiente:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd">
<menu>
...
</menu>
</config>
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd">
<menu>
<add id="Tutorial_Example::index" title="Tutorial Example" module="Tutorial_Example" sortOrder="51" resource="Tutorial_Example::index"/>
<add id="Tutorial_Example::post" title="Manage Posts" module="Tutorial_Example" sortOrder="10" action="example/index" resource="Tutorial_Example::post" parent="Tutorial_Example::index"/>
<add id="Tutorial_Example::configuration" title="Configuration" module="Tutorial_Example" sortOrder="99" parent="Tutorial_Example::index" action="adminhtml/system_config/" resource="Tutorial_Example::configuration"/>
</menu>
</config>
En este ejemplo, creamos un elemento de nivel 0 llamado “Tutorial Example” y dos sub-menus llamados “Manage Posts” y “Configuration”. El archivo menu.xml tiene una colección de etiquetas <add> dentro del nodo <menu> que son las que definen cada opción del menú. Los atributos de dicha etiqueta son los siguientes:
- Atributo
id
es el identificador único del elemento. Tiene el formato: {Vendor_ModuleName}::{menu_description}. - Atributo
title
es el texto que se mostrará en el menú. - Atributo
module
define el módulo al que pertenece dicho menú. - Atributo
sortOrder
indica la posición dentro del menú. Entre más pequeño sea el valor, mas arriba aparecerá en el menú. - Atributo
parent
es el ID de otro elemento del menú, que indica que éste es un submenú del ID indicado. - Atributo
action
para definir la url de la página que vincula este elemento de menú. La url sigue este formato {router_name}{controller_folder}{action_name}. - En este ejemplo, este menú tiene un link al módulo Tutorial_Example, controlador Index y acción Index. - Atributo
resource
es usado para definir las reglas de ACL que el administrador debe definir en los Roles.
El archivo se deberá crear en la siguiente ruta:
Tutorial/Example/etc/acl.xml
Y el contenido será:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd">
<acl>
<resources>
<resource id="Magento_Backend::admin">
<resource id="Tutorial_Example::index" title="Tutorial" translate="title" sortOrder="900">
<resource id="Tutorial_Example::post" title="Posts" sortOrder="10"/>
<resource id="Tutorial_Example::configuration" title="Configuration" sortOrder="99" />
</resource>
</resource>
</resources>
</acl>
</config>
Nuestro recurso se colocará como hijo de Magento_Backend::admin
. Cada recurso deberá tener los atributos Id, title y sortOrder
:
- Id: es el identificador del recurso. Se puede usar este Id cuando se defina un recurso en el menú de Administración para limitar el acceso al módulo. Es un string único y con el siguiente formato: Vendor_ModuleName::resource_name.
- Title: es la etiqueta o texto que se mostrará en el árbol de recursos (System / User Roles / Role Resources).
- sortOrder: indica la posición del recurso en el árbol de recursos.
Hay algunos lugares en donde se puede establecer la regla de ACL para limitar el acceso:
Admin menu: Colocar el recurso ACL para ocultar el menú en el siguiente archivo:
app/code/Tutorial/Example/etc/adminhtml/menu.xml
Contenido:
<add id="Tutorial_Example::index" title="Tutorial Example" module="Tutorial_Example" sortOrder="51" resource="Tutorial_Example::index"/>
System configuration: Colocar el recurso ACL para limitar el acceso a la sección de esta página, en el siguiente archivo.
File: app/code/Tutorial/Example/etc/adminhtml/system.xml
Contenido:
<section id="example" translate="label" sortOrder="130" showInDefault="1" showInWebsite="1" showInStore="1">
….
<resource>Tutorial_Example::configuration</resource>
….
</section>
En admin controllers: Magento provee un tipo abstracto Magento\Framework\AuthorizationInterface
que se puede usar para validar al usuario actualmente logueado. Se puede llamar a este objeto usando la variable : $this->_authorization
. En el controlador, se tiene que escribir una función protegida para verificar el recurso:
Ejemplo. Archivo: vendor/magento/module-customer/Controller/Adminhtml/Index.php
protected function _isAllowed()
{
return $this->_authorization->isAllowed('Magento_Customer::manage');
}
Declarar el recurso en el archivo de inyección de dependencias (di.xml), el cual conectará con el modelo para obtener los datos de la cuadrícula (grid). El archivo lo creamos en la siguiente ruta:
Tutorial/Example/etc/di.xml
Y tendrá el siguiente contenido:
<?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\Framework\View\Element\UiComponent\DataProvider\CollectionFactory">
<arguments>
<argument name="collections" xsi:type="array">
<item name="tutorial_example_item_listing_data_source" xsi:type="string">
Tutorial\Example\Model\ResourceModel\Item\Grid\Collection</item>
</argument>
</arguments>
</type>
</config>
Este archivo declarará la clase Collection, la tabla y el "resourceModel" de la tabla. Este origen se llamará en el layout.
El archivo lo creamos en la siguiente ruta:
Tutorial/Example/Model/ResourceModel/Item/Grid/Collection.php
Y tendrá el siguiente contenido:
<?php
namespace Tutorial\Example\Model\ResourceModel\Item\Grid;
use Magento\Framework\Data\Collection\Db\FetchStrategyInterface as FetchStrategy;
use Magento\Framework\Data\Collection\EntityFactoryInterface as EntityFactory;
use Magento\Framework\Event\ManagerInterface as EventManager;
use Psr\Log\LoggerInterface as Logger;
class Collection extends \Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult
{
public function __construct(
EntityFactory $entityFactory,
Logger $logger,
FetchStrategy $fetchStrategy,
EventManager $eventManager,
$mainTable = 'tutorial_example',
$resourceModel = 'Tutorial\Example\Model\ResourceModel\Item'
) {
parent::__construct(
$entityFactory,
$logger,
$fetchStrategy,
$eventManager,
$mainTable,
$resourceModel
);
}
}
Este archivo contiene la clase Collection la cual extiende de la clase \Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult y tiene el atributo $mainTable la cual indica la tabla y el atributo $resourceModel que indica el Modelo.
Cuando creamos las rutas del admin, se definió la ruta:
admin/example/index/
Formado por el id del nodo <route>
<router id="admin">
<route id="example" frontName="example">
Por el nombre de la carpeta del controlador del admin y el nombre del controlador en si.
Controler
|- Adminhtml
|- Index
|- Index.php
Para esta action /example/index/index, crearemos un layuot con el nombre example_index_index.xml; Este archivo lo colocaremos en la siguiente ruta:
Tutorial/Example/view/adminhtml/layout/example_index_index.xml
Y tendrá el siguiente contenido:
<?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">
<uiComponent name="tutorial_example_item_listing"/>
</referenceContainer>
</body>
</page>
En este layout, declararemos un uiComponent para cargar el contenido de ésta página.
Como se declaro en el archivo de layout anterior, ahora crearemos el componente tutorial_example_item_listing.xml en la siguiente ruta
Tutorial/Example/view/adminhtml/ui_component/tutorial_example_item_listing.xml
Con el siguiente contenido:
<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
<argument name="data" xsi:type="array">
<item name="js_config" xsi:type="array">
<item name="provider" xsi:type="string">tutorial_example_item_listing.tutorial_example_item_listing_data_source</item>
<item name="deps" xsi:type="string">tutorial_example_item_listing.tutorial_example_item_listing_data_source</item>
</item>
<item name="spinner" xsi:type="string">spinner_columns</item>
<item name="buttons" xsi:type="array">
<item name="add" xsi:type="array">
<item name="name" xsi:type="string">add</item>
<item name="label" xsi:type="string" translate="true">Add New Item</item>
<item name="class" xsi:type="string">primary</item>
<item name="url" xsi:type="string">*/*/new</item>
</item>
</item>
</argument>
<dataSource name="nameOfDataSource">
<argument name="dataProvider" xsi:type="configurableObject">
<argument name="class" xsi:type="string">Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider</argument>
<argument name="name" xsi:type="string">tutorial_example_item_listing_data_source</argument>
<argument name="primaryFieldName" xsi:type="string">id</argument>
<argument name="requestFieldName" xsi:type="string">id</argument>
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="component" xsi:type="string">Magento_Ui/js/grid/provider</item>
<item name="update_url" xsi:type="url" path="mui/index/render"/>
<item name="storageConfig" xsi:type="array">
<item name="indexField" xsi:type="string">id</item>
</item>
</item>
</argument>
</argument>
</dataSource>
<columns name="spinner_columns">
<selectionsColumn name="ids">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="resizeEnabled" xsi:type="boolean">false</item>
<item name="resizeDefaultWidth" xsi:type="string">55</item>
<item name="indexField" xsi:type="string">id</item>
</item>
</argument>
</selectionsColumn>
<column name="id">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="filter" xsi:type="string">textRange</item>
<item name="sorting" xsi:type="string">asc</item>
<item name="label" xsi:type="string" translate="true">ID</item>
</item>
</argument>
</column>
<column name="title">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="filter" xsi:type="string">text</item>
<item name="editor" xsi:type="array">
<item name="editorType" xsi:type="string">text</item>
<item name="validation" xsi:type="array">
<item name="required-entry" xsi:type="boolean">true</item>
</item>
</item>
<item name="label" xsi:type="string" translate="true">Name</item>
</item>
</argument>
</column>
<column name="created_at" class="Magento\Ui\Component\Listing\Columns\Date">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="filter" xsi:type="string">dateRange</item>
<item name="component" xsi:type="string">Magento_Ui/js/grid/columns/date</item>
<item name="dataType" xsi:type="string">date</item>
<item name="label" xsi:type="string" translate="true">Created</item>
</item>
</argument>
</column>
</columns>
</listing>
En este archivo, se puede observar la siguiente estructura:
<listing>
<argument name="data" xsi:type="array"> ... </argument>
<dataSource name="nameOfDataSource"> ... </dataSource>
<columns name="spinner_columns"> ... </colums>
</listing>
En el nodo <argument> se especifica la colección declarada en el archivo di.xml
<item>tutorial_example_item_listing.tutorial_example_item_listing_data_source</item>
También se especifican los botones que contendrá el Grid.
En el nodo <columns> se especifican las columnas que tendra el Grid
**Crear listing toolbar ** Este Grid soporta algunas acciones para interactuar con dicha cuadrícula, como ordenar, filtrar borrar, actualizar, etc. Para agregar una barra de herramientas, colocaremos el nodo <listingToolbar> en el archivo ui_component que estamos creando:
<listing>
<argument name="data" xsi:type="array"> ... </argument>
<dataSource name="nameOfDataSource"> ... </dataSource>
<listingToolbar name="listing_top">
<bookmark name="bookmarks"/>
<columnsControls name="columns_controls"/>
<exportButton name="export_button"/>
<filterSearch name="fulltext"/>
<filters name="listing_filters"/>
<paging name="listing_paging"/>
</listingToolbar>
<columns name="spinner_columns"> ... </colums>
</listing>
El archivo lo crearemos en la siguiente ruta:
Tutorial/Example/Ui/DataProvider.php
Con el siguiente contenido:
<?php
namespace Tutorial\Example\Ui;
use Magento\Ui\DataProvider\AbstractDataProvider;
class DataProvider extends AbstractDataProvider
{
protected $collection;
public function __construct(
$name,
$primaryFieldName,
$requestFieldName,
$collectionFactory,
array $meta = [],
array $data = []
) {
parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data);
$this->collection = $collectionFactory->create();
}
public function getData()
{
$result = [];
foreach ($this->collection->getItems() as $item) {
$result[$item->getId()]['general'] = $item->getData();
}
return $result;
}
}
Los controladores serán los encargados de mostrar el formulario para crear un nuevo elemento y para guardarlo. El encargado de mostrar el formulario será el siguiente archivo:
Tutorial/Example/Controller/Adminhtml/Item/NewAction.php
El contenido será el siguiente:
<?php
namespace Tutorial\Example\Controller\Adminhtml\Item;
use Magento\Framework\Controller\ResultFactory;
class NewAction extends \Magento\Backend\App\Action
{
public function execute()
{
return $this->resultFactory->create(ResultFactory::TYPE_PAGE);
}
}
El encargado de guardar la información será el siguiente archivo:
Tutorial/Example/Controller/Adminhtml/Item/Save.php
El contenido será el siguiente:
<?php
namespace Tutorial\Example\Controller\Adminhtml\Item;
use Tutorial\Example\Model\ItemFactory;
class Save extends \Magento\Backend\App\Action
{
private $itemFactory;
public function __construct(
\Magento\Backend\App\Action\Context $context,
ItemFactory $itemFactory
) {
$this->itemFactory = $itemFactory;
parent::__construct($context);
}
public function execute()
{
$this->itemFactory->create()
->setData($this->getRequest()->getPostValue()['general'])
->save();
return $this->resultRedirectFactory->create()->setPath('example/index/index');
}
}
El layout que tendrá el contenido del formulario lo creamos con el siguiente archivo:
Tutorial/Example/view/adminhtml/layout/example_item_new.xml
El contenido será el siguiente:
<?xml version="1.0"?>
<page layout="admin-2columns-left" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceContainer name="content">
<uiComponent name="example_item_form"/>
</referenceContainer>
</body>
</page>
Este layout, lo único que hace es llamar a un uiComponent que será el siguiente archivo:
Tutorial/Example/view/adminhtml/ui_component/example_item_form.xml
El contenido será el siguiente:
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
<argument name="data" xsi:type="array">
<item name="js_config" xsi:type="array">
<item name="provider" xsi:type="string">example_item_form.example_item_form_data_source</item>
<item name="deps" xsi:type="string">example_item_form.example_item_form_data_source</item>
</item>
<item name="label" xsi:type="string" translate="true">General</item>
<item name="layout" xsi:type="array">
<item name="type" xsi:type="string">tabs</item>
<item name="navContainerName" xsi:type="string">left</item>
</item>
<item name="buttons" xsi:type="array">
<item name="save" xsi:type="array">
<item name="name" xsi:type="string">save</item>
<item name="label" xsi:type="string" translate="true">Guardar</item>
<item name="class" xsi:type="string">primary</item>
<item name="url" xsi:type="string">*/item/save</item>
</item>
</item>
</argument>
<dataSource name="example_item_form_data_source">
<argument name="dataProvider" xsi:type="configurableObject">
<argument name="class" xsi:type="string">Tutorial\Example\Ui\DataProvider</argument>
<argument name="name" xsi:type="string">example_item_form_data_source</argument>
<argument name="primaryFieldName" xsi:type="string">id</argument>
<argument name="requestFieldName" xsi:type="string">id</argument>
<argument name="collectionFactory" xsi:type="object">Tutorial\Example\Model\ResourceModel\Item\CollectionFactory</argument>
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="submit_url" xsi:type="url" path="example/item/save"/>
</item>
</argument>
</argument>
<argument name="data" xsi:type="array">
<item name="js_config" xsi:type="array">
<item name="component" xsi:type="string">Magento_Ui/js/form/provider</item>
</item>
</argument>
</dataSource>
<fieldset name="general">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="label" xsi:type="string" translate="true">General</item>
</item>
</argument>
<field name="title">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="label" xsi:type="string" translate="true">Título</item>
<item name="dataType" xsi:type="string">text</item>
<item name="formElement" xsi:type="string">input</item>
<item name="validation" xsi:type="array">
<item name="required-entry" xsi:type="boolean">true</item>
</item>
</item>
</argument>
</field>
<field name="summary">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="label" xsi:type="string" translate="true">Resumen</item>
<item name="dataType" xsi:type="string">text</item>
<item name="formElement" xsi:type="string">input</item>
</item>
</argument>
</field>
<field name="description">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="label" xsi:type="string" translate="true">Descripción</item>
<item name="dataType" xsi:type="string">text</item>
<item name="formElement" xsi:type="string">input</item>
</item>
</argument>
</field>
</fieldset>
</form>
Modificaremos el layout que contiene el Grid de elementos :
Tutorial/Example/view/adminhtml/ui_component/tutorial_example_item_listing.xml
En el nodo <listingToolbar> agregaremos el sub nodo <massaction> con el siguiente contenido:
<listing>
<argument name="data" xsi:type="array"> ... </argument>
<dataSource name="nameOfDataSource"> ... </dataSource>
<listingToolbar name="listing_top">
<bookmark name="bookmarks"/>
<columnsControls name="columns_controls"/>
<exportButton name="export_button"/>
<filterSearch name="fulltext"/>
<filters name="listing_filters"/>
<paging name="listing_paging"/>
<massaction name="listing_massaction">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="component" xsi:type="string">Magento_Ui/js/grid/tree-massactions</item>
</item>
</argument>
<action name="delete">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="type" xsi:type="string">delete</item>
<item name="label" xsi:type="string" translate="true">Delete</item>
<item name="url" xsi:type="url" path="example/item/massDelete"/>
<item name="confirm" xsi:type="array">
<item name="title" xsi:type="string" translate="true">Delete Post</item>
<item name="message" xsi:type="string" translate="true">Are you sure you wan't to delete selected items?</item>
</item>
</item>
</argument>
</action>
</massaction>
</listingToolbar>
<columns name="spinner_columns"> ... </colums>
</listing>
Esta parte agregará una lista de acciones (en este caso Delete) para poder aplicar a todos los elementos seleccionados.
Agregaremos el archivo :
Tutorial/Example/Controller/Adminhtml/Item/Delete.php
Con el contenido:
<?php
namespace Tutorial\Example\Controller\Adminhtml\Item;
use Magento\Backend\App\Action;
class Delete extends Action
{
protected $_model;
/**
* @param Action\Context $context
* @param \Tutorial\Example\Model\Item $model
*/
public function __construct(
Action\Context $context,
\Tutorial\Example\Model\Item $model
) {
parent::__construct($context);
$this->_model = $model;
}
/**
* {@inheritdoc}
*/
protected function _isAllowed()
{
return $this->_authorization->isAllowed('Tutorial_Example::item_delete');
}
/**
* Delete action
*
* @return \Magento\Framework\Controller\ResultInterface
*/
public function execute()
{
$id = $this->getRequest()->getParam('id');
/** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
$resultRedirect = $this->resultRedirectFactory->create();
if ($id) {
try {
$model = $this->_model;
$model->load($id);
$model->delete();
$this->messageManager->addSuccess(__('Item deleted'));
return $resultRedirect->setPath('example/index/index');
} catch (\Exception $e) {
$this->messageManager->addError($e->getMessage());
return $resultRedirect->setPath('*/*/edit', ['id' => $id]);
}
}
$this->messageManager->addError(__('Item does not exist'));
return $resultRedirect->setPath('*/*/');
}
}
También agregaremos el siguiente controlador :
Tutorial/Example/Controller/Adminhtml/Item/MassDelete.php
Con el contenido:
<?php
namespace Tutorial\Example\Controller\Adminhtml\Item;
use Magento\Backend\App\Action\Context;
use Magento\Ui\Component\MassAction\Filter;
use Magento\Framework\Controller\ResultFactory;
use Tutorial\Example\Model\ResourceModel\Item\Collection;
use Tutorial\Example\Model\ResourceModel\Item\CollectionFactory;
class MassDelete extends \Magento\Backend\App\Action
{
/**
* @var Filter
*/
protected $filter;
/**
* @var CollectionFactory
*/
protected $collectionFactory;
/**
* @param Context $context
* @param Filter $filter
* @param CollectionFactory $collectionFactory
*/
public function __construct(Context $context, Filter $filter, CollectionFactory $collectionFactory)
{
$this->filter = $filter;
$this->collectionFactory = $collectionFactory;
parent::__construct($context);
}
/**
* Execute action
*
* @return \Magento\Backend\Model\View\Result\Redirect
* @throws \Magento\Framework\Exception\LocalizedException|\Exception
*/
public function execute()
{
$collection = $this->filter->getCollection($this->collectionFactory->create());
$collectionSize = $collection->getSize();
foreach ($collection as $item) {
$item->delete();
}
$this->messageManager->addSuccess(__('A total of %1 record(s) have been deleted.', $collectionSize));
/** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
$resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
return $resultRedirect->setPath('example/index/index');
}
}
Estos archivos serán los encargados de eliminar los elementos seleccionados y redireccionar nuevamente al Grid.
El archivo Delete.php contiene el método _isAllowed() el cual permite verificar si dicha acción está permitida en el ACL del usuario.
protected function _isAllowed()
{
return $this->_authorization->isAllowed('Tutorial_Example::item_delete');
}
Para poder definir esta regla, tenemos que modificar el archivo:
Tutorial/Example/etc/acl.xml
Al que le agregaremos el siguiente nodo:
<resource id="Tutorial_Example::item_delete" title="Delete Item" sortOrder="95" />
El archivo quedaría de la siguiente forma:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd">
<acl>
<resources>
<resource id="Magento_Backend::admin">
<resource id="Tutorial_Example::index" title="Tutorial" translate="title" sortOrder="900">
<resource id="Tutorial_Example::post" title="Posts" sortOrder="10"/>
<resource id="Tutorial_Example::configuration" title="Configuration" sortOrder="99" />
<resource id="Tutorial_Example::item_delete" title="Delete Item" sortOrder="95" />
</resource>
</resource>
</resources>
</acl>
</config>
Para agregar una columna al Grid con la opción para editar el registro, se tiene que modigicar el archivo tutorial_example_item_listing.xml:
Tutorial/Example/view/adminhtml/ui_component/tutorial_example_item_listing.xml
Agregaremos dentro del nodo <columns> la sección <actionsColumn> con el siguiente contenido:
<columns>
<actionsColumn name="actions" class="Tutorial\Example\Ui\Component\Listing\Grid\Column\Action">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="resizeEnabled" xsi:type="boolean">false</item>
<item name="resizeDefaultWidth" xsi:type="string">107</item>
<item name="indexField" xsi:type="string">id</item>
</item>
</argument>
</actionsColumn>
</columns>
Dentro de esta sección, se puede observar el atributo class, el cual llama al componente que realizará la acción de editar:
<actionsColumn name="actions" class="Tutorial\Example\Ui\Component\Listing\Grid\Column\Action">
El archivo se crea en la siguiente ruta:
Tutorial\Example\Ui\Component\Listing\Grid\Column\Action.php
Con el contenido:
<?php
namespace Tutorial\Example\Ui\Component\Listing\Grid\Column;
use Magento\Framework\View\Element\UiComponent\ContextInterface;
use Magento\Framework\View\Element\UiComponentFactory;
use Magento\Ui\Component\Listing\Columns\Column;
use Magento\Framework\UrlInterface;
class Action extends Column
{
/** Url path */
const ROW_EDIT_URL = 'example/item/new';
/** @var UrlInterface */
protected $_urlBuilder;
/**
* @var string
*/
private $_editUrl;
/**
* @param ContextInterface $context
* @param UiComponentFactory $uiComponentFactory
* @param UrlInterface $urlBuilder
* @param array $components
* @param array $data
* @param string $editUrl
*/
public function __construct(
ContextInterface $context,
UiComponentFactory $uiComponentFactory,
UrlInterface $urlBuilder,
array $components = [],
array $data = [],
$editUrl = self::ROW_EDIT_URL
) {
$this->_urlBuilder = $urlBuilder;
$this->_editUrl = $editUrl;
parent::__construct($context, $uiComponentFactory, $components, $data);
}
/**
* Prepare Data Source.
*
* @param array $dataSource
*
* @return array
*/
public function prepareDataSource(array $dataSource)
{
if (isset($dataSource['data']['items'])) {
foreach ($dataSource['data']['items'] as &$item) {
$name = $this->getData('name');
if (isset($item['id'])) {
$item[$name]['edit'] = [
'href' => $this->_urlBuilder->getUrl(
$this->_editUrl,
['id' => $item['id']]
),
'label' => __('Edit'),
];
}
}
}
return $dataSource;
}
}