>UI Components: How to use DynamicRows components in Magento 2

UI Components: How to use DynamicRows components in Magento 2

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:

How to use DynamicRows components in Magento 2
Visual swatch of product attribute

Or;

How to use DynamicRows components in Magento 2
Bundle items of Bundle product

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! 

< Previous Post
Next Post >