Magento 2 Dynamic Rows components are a list of records where users can add new records, edit their records, change their display positions, and navigate through the collection. We can see the DynamicRows in some of the default pages in Magento 2 Admin Panel. Here is one example:
Or;
In this article, let’s discuss how to create DynamicRows with UI Components in Magento 2 backend.
5 Steps to Create Magento 2 Dynamic Rows with UI Components
Table of Contents
Step 1: Create Module BSS_DynamicRows
First, create a module preparing components to perform database operations which include a new table, Model, Resource Model, and Collection. The path of the module is in the app/code/Bss/DynamicRows.
- Create file registration.php
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Bss_DynamicRows',
__DIR__
);
- Create file etc/module.xml
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Bss_DynamicRows" setup_version="1.0.0">
</module>
</config>
Step 2: Create Model and Database
- Create table bss_dynamic_rows with columns: row_id, row_name, birthday, sex, position
First off, in Setup/InstallSchema.php
<?php
namespace Bss\DynamicRows\Setup;
use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\DB\Ddl\Table as Table;
class InstallSchema implements InstallSchemaInterface
{
public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
{
$installer = $setup;
$installer->startSetup();
$table = $installer->getConnection()->newTable(
$installer->getTable('bss_dynamic_rows')
)->addColumn(
'row_id',
Table::TYPE_INTEGER,
null,
['identity' => true, 'nullable' => false, 'primary' => true],
'ID'
)->addColumn(
'row_name',
Table::TYPE_TEXT,
null,
['nullable' => false],
'Row Name'
)->addColumn(
'birthday',
Table::TYPE_DATE,
null,
['nullable' => false],
'Date of birth'
)->addColumn(
'sex',
Table::TYPE_SMALLINT,
null,
['nullable' => false],
'Sex'
)->addColumn(
'position',
Table::TYPE_INTEGER,
null,
['nullable' => false],
'Position'
)->setComment(
'Bss Dynamic Rows'
);
$installer->getConnection()->createTable($table);
$installer->endSetup();
}
}
- Create Model
In Model/DynamicRows.php
<?php
namespace Bss\DynamicRows\Model;
use Magento\Framework\Model\AbstractModel;
class DynamicRows extends AbstractModel
{
const CACHE_TAG = 'bss_dynamic_rows';
protected $_cacheTag = 'bss_dynamic_rows';
protected $_eventPrefix = 'bss_dynamic_rows';
protected function _construct()
{
$this->_init('Bss\DynamicRows\Model\ResourceModel\DynamicRows');
}
}
- Create Resource Model
In Model/ResourceModel/DynamicRows.php
<?php
namespace Bss\DynamicRows\Model\ResourceModel;
use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
class DynamicRows extends AbstractDb
{
protected function _construct()
{
$this->_init('bss_dynamic_rows', 'row_id');
}
public function deleteDynamicRows()
{
$connection = $this->getConnection();
$connection->delete(
$this->getMainTable(),
['row_id > ?' => 0]
);
}
}
Note: Function deleteDynamicRows() is used to delete all existing items
- Create Collection
In Model/ResourceModel/DynamicRows/Collection.php
<?php
namespace Bss\DynamicRows\Model\ResourceModel\DynamicRows;
use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;
class Collection extends AbstractCollection
{
protected $_idFieldName = 'row_id';
protected function _construct()
{
$this->_init(
'Bss\DynamicRows\Model\DynamicRows',
'Bss\DynamicRows\Model\ResourceModel\DynamicRows'
);
}
}
Step 3: Create Controller
- Create menu:
In etc/adminhtml/menu.xml
<?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="Bss_DynamicRows::dynamic_rows"
title="Dynamic Rows"
module="Bss_DynamicRows"
sortOrder="99"
action="bss/row/index/scope/stores"
parent="Magento_Backend::system_convert"
resource="Bss_DynamicRows::dynamic_rows" />
</menu>
</config>
- Create the permissions file for admin controllers
In etc/acl.xml
<?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="Magento_Backend::system">
<resource id="Magento_Backend::convert">
<resource id="Bss_DynamicRows::dynamic_rows" title="Dynamic Rows"/>
</resource>
</resource>
</resource>
</resources>
</acl>
</config>
- Create a new controller
+ Declare Routes
In etc/adminhtml/routes.xml
<?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="bss" frontName="bss">
<module name="Bss_DynamicRows"/>
</route>
</router>
</config>
+ Create file layout and reference UI form in which “dynamic_rows” is the name of UI form
In view/adminhtml/layout/bss_row_index.xml
<?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="dynamic_rows"/>
</referenceContainer>
</body>
</page>
+ Create index controller to show the form:
In Controller/Adminhtml/Row/Index.php
<?php
namespace Bss\DynamicRows\Controller\Adminhtml\Row;
use Magento\Framework\Controller\ResultFactory;
class Index extends \Magento\ImportExport\Controller\Adminhtml\Export\Index
{
public function execute()
{
$resultPage = $this->resultFactory->create(ResultFactory::TYPE_PAGE);
$resultPage->setActiveMenu('Bss_DynamicRows::dynamic_rows');
$resultPage->getConfig()->getTitle()->prepend(__('Dynamic Rows'));
$resultPage->getConfig()->getTitle()->prepend(__('Dynamic Rows'));
$resultPage->addBreadcrumb(__('Dynamic Rows'), __('Dynamic Rows'));
return $resultPage;
}
protected function _isAllowed()
{
return $this->_authorization->isAllowed('Bss_DynamicRows::dynamic_rows');
}
}
Step 4: Create UI Form
To begin, declare UI Form in view/adminhtml/ui_component/dynamic_rows.xml
<?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">dynamic_rows.dynamic_rows_data_source</item>
<item name="deps" xsi:type="string">dynamic_rows.dynamic_rows_data_source</item>
</item>
<item name="label" xsi:type="string" translate="true">Dynamic Rows</item>
<item name="config" xsi:type="array">
<item name="dataScope" xsi:type="string">data</item>
<item name="namespace" xsi:type="string">dynamic_rows</item>
</item>
<item name="template" xsi:type="string">templates/form/collapsible</item>
<item name="buttons" xsi:type="array">
<item name="save" xsi:type="string">Bss\DynamicRows\Block\Adminhtml\DynamicRows\Edit\SaveButton</item>
</item>
</argument>
<dataSource name="dynamic_rows_data_source">
<argument name="dataProvider" xsi:type="configurableObject">
<argument name="class" xsi:type="string">Bss\DynamicRows\Model\DataProvider</argument>
<argument name="name" xsi:type="string">dynamic_rows_data_source</argument>
<argument name="primaryFieldName" xsi:type="string">row_id</argument>
<argument name="requestFieldName" xsi:type="string">scope</argument>
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="submit_url" xsi:type="url" path="bss/row/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="dynamic_rows_set">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="label" xsi:type="string" translate="true">Dynamic Rows</item>
<item name="sortOrder" xsi:type="number">10</item>
</item>
</argument>
<container name="dynamic_rows_container">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="component" xsi:type="string">Magento_Ui/js/dynamic-rows/dynamic-rows</item>
<item name="template" xsi:type="string">ui/dynamic-rows/templates/default</item>
<item name="componentType" xsi:type="string">dynamicRows</item>
<item name="recordTemplate" xsi:type="string">record</item>
<item name="addButtonLabel" xsi:type="string">Add Row</item>
<item name="deleteProperty" xsi:type="boolean">false</item>
</item>
</argument>
<container name="record">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="label" xsi:type="string" translate="true">Dynamic Rows</item>
<item name="component" xsi:type="string" translate="true">Magento_Ui/js/dynamic-rows/record</item>
<item name="isTemplate" xsi:type="boolean">true</item>
<item name="is_collection" xsi:type="boolean">true</item>
<item name="showFallbackReset" xsi:type="boolean">false</item>
</item>
</argument>
<field name="row_id">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="label" xsi:type="string" translate="true">ID</item>
<item name="visible" xsi:type="boolean">false</item>
<item name="dataType" xsi:type="string">text</item>
<item name="formElement" xsi:type="string">input</item>
<item name="dataScope" xsi:type="string">row_id</item>
</item>
</argument>
</field>
<field name="row_name">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="dataType" xsi:type="string">text</item>
<item name="label" xsi:type="string" translate="true">Name</item>
<item name="formElement" xsi:type="string">input</item>
<item name="dataScope" xsi:type="string">row_name</item>
<item name="showFallbackReset" xsi:type="boolean">false</item>
<item name="validation" xsi:type="array">
<item name="required-entry" xsi:type="boolean">true</item>
</item>
<item name="sortOrder" xsi:type="string">10</item>
</item>
</argument>
</field>
<field name="sex">
<argument name="data" xsi:type="array">
<item name="options" xsi:type="object">Bss\DynamicRows\Model\Source\Sex</item>
<item name="config" xsi:type="array">
<item name="dataType" xsi:type="string">text</item>
<item name="formElement" xsi:type="string">select</item>
<item name="component" xsi:type="string">Magento_Ui/js/form/element/select</item>
<item name="label" xsi:type="string" translate="true">Sex</item>
<item name="dataScope" xsi:type="string">sex</item>
<item name="default" xsi:type="string">0</item>
<item name="disabled" xsi:type="boolean">false</item>
<item name="showFallbackReset" xsi:type="boolean">false</item>
<item name="sortOrder" xsi:type="string">20</item>
</item>
</argument>
</field>
<field name="birthday">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="filter" xsi:type="string">dateRange</item>
<item name="dataType" xsi:type="string">date</item>
<item name="formElement" xsi:type="string">input</item>
<item name="component" xsi:type="string">Magento_Ui/js/form/element/date</item>
<item name="label" xsi:type="string" translate="true">Date of birth</item>
<item name="dataScope" xsi:type="string">birthday</item>
<item name="disabled" xsi:type="boolean">false</item>
<item name="options" xsi:type="array">
<item name="dateFormat" xsi:type="string">y-MM-dd</item>
</item>
<item name="sortOrder" xsi:type="string">30</item>
</item>
</argument>
</field>
<actionDelete>
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="componentType" xsi:type="string">actionDelete</item>
<item name="dataType" xsi:type="string">text</item>
<item name="fit" xsi:type="boolean">false</item>
<item name="label" xsi:type="string">Actions</item>
<item name="additionalClasses" xsi:type="string">data-grid-actions-cell</item>
<item name="template" xsi:type="string">Magento_Backend/dynamic-rows/cells/action-delete</item>
</item>
</argument>
</actionDelete>
<field name="position">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="dataType" xsi:type="string">number</item>
<item name="formElement" xsi:type="string">input</item>
<item name="componentType" xsi:type="string">field</item>
<item name="label" xsi:type="string" translate="true">Position</item>
<item name="dataScope" xsi:type="string">position</item>
<item name="visible" xsi:type="boolean">false</item>
</item>
</argument>
</field>
</container>
</container>
</fieldset>
</form>
In this UI form, there are some points to note such as:
+ Button save: Used to save rows in the form which will redirect to Save action
In Block/Adminhtml/DynamicRows/Edit/SaveButton.php
<?php
namespace Bss\DynamicRows\Block\Adminhtml\DynamicRows\Edit;
use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;
use Magento\CatalogRule\Block\Adminhtml\Edit\GenericButton;
class SaveButton extends GenericButton implements ButtonProviderInterface
{
public function getButtonData()
{
$url = $this->getUrl('bss/row/save');
return [
'label' => __('Save Rows'),
'class' => 'save primary',
'on_click' => "setLocation('". $url ."'')",
'sort_order' => 90,
];
}
}
+ Data Provider: Fill data into rows
In Model/DataProvider.php
<?php
namespace Bss\DynamicRows\Model;
class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider
{
protected $loadedData;
protected $rowCollection;
public function __construct(
$name,
$primaryFieldName,
$requestFieldName,
\Bss\DynamicRows\Model\ResourceModel\DynamicRows\Collection $collection,
\Bss\DynamicRows\Model\ResourceModel\DynamicRows\CollectionFactory $collectionFactory,
array $meta = [],
array $data = []
) {
$this->collection = $collection;
$this->rowCollection = $collectionFactory;
parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data);
}
public function getData()
{
if (isset($this->loadedData)) {
return $this->loadedData;
}
$collection = $this->rowCollection->create()->setOrder('position', 'ASC');
$items = $collection->getItems();
foreach ($items as $item) {
$this->loadedData['stores']['dynamic_rows_container'][] = $item->getData();
}
return $this->loadedData;
}
}
+ Fields with the basic type: input, date, select
With type select, we will create a source file to create options. In Model/Source/Sex.php
<?php
namespace Bss\DynamicRows\Model\Source;
class Sex implements \Magento\Framework\Option\ArrayInterface
{
public function toOptionArray()
{
$yesNoArray[] = [
'label' => 'Male',
'value' => 0,
];
$yesNoArray[] = [
'label' => 'Female',
'value' => 1,
];
return $yesNoArray;
}
}
+ Action delete: delete row
+ Position: Saves data about the order of the row when the user performs the drag / drop operation
Step 5: Create Action Processing Data
In Controller/Adminhtml/Row/Save.php
<?php
namespace Bss\DynamicRows\Controller\Adminhtml\Row;
class Save extends \Magento\Backend\App\Action
{
protected $dynamicRow;
protected $dynamicRowResource;
public function __construct(
\Magento\Backend\App\Action\Context $context,
\Bss\DynamicRows\Model\DynamicRowsFactory $dynamicRowFactory,
\Bss\DynamicRows\Model\ResourceModel\DynamicRowsFactory $dynamicRowResource
) {
parent::__construct($context);
$this->dynamicRow = $dynamicRowFactory;
$this->dynamicRowResource = $dynamicRowResource;
}
public function execute()
{
try {
$dynamicRowResource = $this->dynamicRowResource->create();
$dynamicRowData = $this->getRequest()->getParam('dynamic_rows_container');
$dynamicRowResource->deleteDynamicRows();
if (is_array($dynamicRowData) && !empty($dynamicRowData)) {
foreach ($dynamicRowData as $dynamicRowDatum) {
$model = $this->dynamicRow->create();
unset($dynamicRowDatum['row_id']);
$model->addData($dynamicRowDatum);
$model->save();
}
}
$this->messageManager->addSuccessMessage(__('Rows have been saved successfully'));
} catch (\Exception $e) {
$this->messageManager->addErrorMessage(__($e->getMessage()));
}
$this->_redirect('*/*/index/scope/stores');
}
protected function _isAllowed()
{
return $this->_authorization->isAllowed('Bss_DynamicRows::dynamic_rows');
}
}
Important to note: The data will be processed by deleting all items in the table and then re-adding those items and the new items. The purpose of this step is to store items in the correct position previously sorted by the users. It will only apply if your database is small to medium, and for large databases, this solution will slow down the saving process. In that case, you can investigate some of the dynamic rows already mentioned earlier.
Conclusion
In conclusion, Magento 2 Dynamic Rows component offers an efficient way to manage and display complex data sets in a user-friendly manner. By implementing this feature, you can allow administrators to add, edit, and delete multiple rows of data directly in the backend, improving the flexibility and functionality of your Magento store. Follow the steps outlined above by BSS Commerce to integrate and customize Dynamic Rows for a more streamlined data management experience.
In near future, we will continue publishing articles about more advanced dynamic rows – Don’t miss out!