DynamicRows 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.
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. In near future, we will continue publishing articles about more advanced dynamic rows – Don’t miss out!
Will it work for Magento 1.9
The answer is No since this code is for Magento 2.x version only.
Can you please write DataProvider File .
Because i have Made one module based on your tutorial . Its working fine for displaying dynamic row and save data.
but on page load saved data not display.
Also same to same copy from your tutorial doesnt work. its throw error like
Fatal error: Method Magento\Ui\TemplateEngine\Xhtml\Result::__toString() must not throw an exception, caught Error: Call to a member function addFieldToFilter() on null in /public_html/vendor/magento/module-ui/Component/Wrapper/UiComponent.php on line 0
Hello,
DataProvider file content is missing so I am unable to load the module properly.
Can you send us proper data for DataProvider.php
Hello
Can you send us the custom module link or github link
I have added this extension to Github, check it out
https://github.com/bbakalovGlobal/Bss_DynamicRows
Hello ,
I need to check this functionality on urgent basis. Can you share the working code.
Dataprovider.php file it is not working.
When we complie it is showing error:
“Incompatible argument type “
Hello,
I have tried your code but not able to generate multiple rows it is showing single row on clcik to Add New.
It is very urgent can you guide me for the same or send me working code
Thanks for your feedback!
We have updated the post so that every function works now. Please check it again. Let us know if you need any further support.