8

Magento2 How to add row pattern/dynamic rows to the custom product attribute.

Here is the sample output

enter image description here

2
  • Thank you for the example. Helped me a lot. Can you use Magento\Eav\Model\Entity\Attribute\Backend\JsonEncoded instead of Magento\Eav\Model\Entity\Attribute\Backend\Serialized when create the product in your upgradeData. Example: $eavSetup->addAttribute( \Magento\Catalog\Model\Product::ENTITY, 'custom_attribute', [ 'group' => 'Custom Attribute Group', 'type' => 'varchar', 'backend' => '\Magento\Eav\Model\Entity\Attribute\Backend\JsonEncoded', 'frontend' => '', 'sort_order' => 8, 'label' => 'Label', 'input' => 'text', 'class' => '', 'source' => '', 'global' => \Magento\Eav\Model\Entity\Attribut Commented May 24, 2018 at 20:01
  • @FabioPelloso, The code posted by you is not containing the full data. It is stopped at global section, If you post full code will help others. thanks Commented Apr 8, 2020 at 15:17

2 Answers 2

17

Here are the high-level things we need to do.

Create Product custom attribute -> Which attribute you want to show the row pattern / dynamic rows

Create di.xml -> Add our custom Data provider into the product form

Create DataProvider -> Set row patter fields and data to the attribute

Create Observer (events.xml) -> Add/Update attribute value while saving the product in the Magento Admin.

Observer is optional because you can set backend model like "backend => Magento\Eav\Model\Entity\Attribute\Backend\Serialized.php" during create the product. So this is taken care of the save/load product with array serialized value.

In my case, this doesn't work throws the error when going to the catalog product so i remove the backend model and did the events.xml

Here are the steps we have to follow

Assume product custom attribute called "attraction_highlights"

You can create the custom product attribute through Magento Admin/ setup script.

The general structure of the di.xml file is:

File path like MAGENTO_ROOT\app\code\NAMESPACE\MODULE\etc\adminhtml\di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <virtualType name="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Pool">
        <arguments>
            <argument name="modifiers" xsi:type="array">
                <item name="attractionHighlights" xsi:type="array">
                    <item name="class" xsi:type="string">Born\Attractions\Ui\DataProvider\Product\Form\Modifier\Highlights</item>      
                    <item name="sortOrder" xsi:type="number">100</item>     
                </item>
            </argument>
        </arguments>
    </virtualType>   
</config>

Then you just create the data provider file (Highlights.php) :

File path like MAGENTO_ROOT\app\code\NAMESPACE\MODULE\Ui\DataProvider\Product\Form/Modifier/Highlights.php

Here is a sample code of the DataProvider (The data provider children you can load from the XML file also)

<?php

namespace Born\Attractions\Ui\DataProvider\Product\Form\Modifier;
 
use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier;
use Magento\Catalog\Controller\Adminhtml\Product\Initialization\StockDataFilter;
use Magento\Catalog\Model\Locator\LocatorInterface;
 
use Magento\CatalogInventory\Api\StockRegistryInterface;
use Magento\Framework\Stdlib\ArrayManager;
use Magento\CatalogInventory\Api\Data\StockItemInterface;
use Magento\CatalogInventory\Api\StockConfigurationInterface;
 
use Magento\Ui\Component\Container;
use Magento\Ui\Component\Form\Element\DataType\Number;
use Magento\Ui\Component\Form\Element\DataType\Text;
use Magento\Ui\Component\Form\Element\Textarea;
use Magento\Ui\Component\Form\Element\Input;
use Magento\Ui\Component\Form\Field;
use Magento\Ui\Component\Modal;
 
/**
 * Data provider for attraction highlights field
 */
class Highlights extends AbstractModifier
{
    const ATTRACTION_HIGHLIGHTS_FIELD = 'attraction_highlights';
 
    /**
     * @var LocatorInterface
     */
    private $locator;
 
    /**
     * @var ArrayManager
     */
    private $arrayManager;
 
    /**
     * @var array
     */
    private $meta = [];
 
    /**
     * @var string
     */
    protected $scopeName;   
 
    /**
     * @param LocatorInterface $locator
     * @param ArrayManager $arrayManager
     */
    public function __construct(
        LocatorInterface $locator,
        ArrayManager $arrayManager,
        $scopeName = ''
    ) {
        $this->locator = $locator;
        $this->arrayManager = $arrayManager;
        $this->scopeName = $scopeName;
    }
 
    /**
     * {@inheritdoc}
     */
    public function modifyData(array $data)
    {
        $fieldCode = self::ATTRACTION_HIGHLIGHTS_FIELD;
 
        $model = $this->locator->getProduct();
        $modelId = $model->getId();
 
        $highlightsData = $model->getAttractionHighlights();
 
        if ($highlightsData) {
            $highlightsData = json_decode($highlightsData, true);
            $path = $modelId . '/' . self::DATA_SOURCE_DEFAULT . '/'. self::ATTRACTION_HIGHLIGHTS_FIELD;
            $data = $this->arrayManager->set($path, $data, $highlightsData);
        }
        return $data;
    }
 
    /**
     * {@inheritdoc}
     */
    public function modifyMeta(array $meta)
    {
        $this->meta = $meta;
        $this -> initAttractionHighlightFields();
        return $this->meta;
    }
 
    /**
     * Customize attraction highlights field
     *
     * @return $this
     */
    protected function initAttractionHighlightFields()
    {
        $highlightsPath = $this->arrayManager->findPath(
            self::ATTRACTION_HIGHLIGHTS_FIELD,
            $this->meta,
            null,
            'children'
        );
         
        if ($highlightsPath) {
            $this->meta = $this->arrayManager->merge(
                $highlightsPath,
                $this->meta,
                $this->initHighlightFieldStructure($highlightsPath)
            );
            $this->meta = $this->arrayManager->set(
                $this->arrayManager->slicePath($highlightsPath, 0, -3)
                . '/' . self::ATTRACTION_HIGHLIGHTS_FIELD,
                $this->meta,
                $this->arrayManager->get($highlightsPath, $this->meta)
            );
            $this->meta = $this->arrayManager->remove(
                $this->arrayManager->slicePath($highlightsPath, 0, -2),
                $this->meta
            );
        }
 
        return $this;
    }   
 
 
    /**
     * Get attraction highlights dynamic rows structure
     *
     * @param string $highlightsPath
     * @return array
     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
     */
    protected function initHighlightFieldStructure($highlightsPath)
    {
        return [
            'arguments' => [
                'data' => [
                    'config' => [
                        'componentType' => 'dynamicRows',
                        'label' => __('Highlight Rows'),
                        'renderDefaultRecord' => false,
                        'recordTemplate' => 'record',
                        'dataScope' => '',
                        'dndConfig' => [
                            'enabled' => false,
                        ],
                        'disabled' => false,
                        'sortOrder' =>
                            $this->arrayManager->get($highlightsPath . '/arguments/data/config/sortOrder', $this->meta),
                    ],
                ],
            ],
            'children' => [
                'record' => [
                    'arguments' => [
                        'data' => [
                            'config' => [
                                'componentType' => Container::NAME,
                                'isTemplate' => true,
                                'is_collection' => true,
                                'component' => 'Magento_Ui/js/dynamic-rows/record',
                                'dataScope' => '',
                            ],
                        ],
                    ],
                    'children' => [
                        'title' => [
                            'arguments' => [
                                'data' => [
                                    'config' => [
                                        'formElement' => Input::NAME,
                                        'componentType' => Field::NAME,
                                        'dataType' => Text::NAME,
                                        'label' => __('Title'),
                                        'dataScope' => 'title',
                                        'require' => '1',
                                    ],
                                ],
                            ],
                        ],
 
                        'description' => [
                            'arguments' => [
                                'data' => [
                                    'config' => [
                                        'formElement' => Textarea::NAME,
                                        'componentType' => Field::NAME,
                                        'dataType' => Text::NAME,
                                        'label' => __('Description'),
                                        'dataScope' => 'description',
                                        'require' => '1',
                                    ],
                                ],
                            ],
                        ],
 
                        'icon' => [
                            'arguments' => [
                                'data' => [
                                    'config' => [
                                        'formElement' => Input::NAME,
                                        'componentType' => Field::NAME,
                                        'dataType' => Text::NAME,
                                        'label' => __('Icon Name'),
                                        'dataScope' => 'icon',
                                    ],
                                ],
                            ],
                        ],                                               
                         
                        'actionDelete' => [
                            'arguments' => [
                                'data' => [
                                    'config' => [
                                        'componentType' => 'actionDelete',
                                        'dataType' => Text::NAME,
                                        'label' => '',
                                    ],
                                ],
                            ],
                        ],
                    ],
                ],
            ],
        ];
    }   
}
?>

Then you just create the events.xml file (events.xml) :

File path like MAGENTO_ROOT\app\code\NAMESPACE\MODULE\etc\adminhtml\events.xml

Here is a sample code of the events.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">     
    <event name="catalog_product_save_before">
        <observer name="attraction_highlights_save_before" instance="Born\Attractions\Observer\SerializedAttractionHighlights" />
    </event>
</config>

Then you just create the observer file (SerializedAttractionHighlights.php) :

File path like MAGENTO_ROOT\app\code\NAMESPACE\MODULE\Observer/SerializedAttractionHighlights.php

Here is a sample code of the SerializedAttractionHighlights.php

<?php
/**
 * Copyright © 2017 BORN . All rights reserved.
 */
namespace Born\Attractions\Observer;
 
use \Magento\Framework\Event\Observer;
use \Magento\Framework\Event\ObserverInterface;
 
class SerializedAttractionHighlights implements ObserverInterface
{
    const ATTR_ATTRACTION_HIGHLIGHTS_CODE = 'attraction_highlights';
 
    /**
     * @var  \Magento\Framework\App\RequestInterface
     */
    protected $request;
 
    /**
     * Constructor
     */
    public function __construct(
        \Magento\Framework\App\RequestInterface $request
    )
    {
        $this->request = $request;
    }
 
    public function execute(Observer $observer)
    {
        /** @var $product \Magento\Catalog\Model\Product */
        $product = $observer->getEvent()->getDataObject();
        $post = $this->request->getPost();
        $post = $post['product'];
        $highlights = isset($post[self::ATTR_ATTRACTION_HIGHLIGHTS_CODE]) ? $post[self::ATTR_ATTRACTION_HIGHLIGHTS_CODE] : '';
        $product -> setAttractionHighlights($highlights);
    $requiredParams = ['title','description'];
        if (is_array($highlights)) {
            $highlights = $this -> removeEmptyArray($highlights, $requiredParams);
            $product -> setAttractionHighlights(json_encode($highlights));
        }
    }
 
    /**
    * Function to remove empty array from the multi dimensional array
    *
    * @return Array
    */
    private function removeEmptyArray($attractionData, $requiredParams){
 
        $requiredParams = array_combine($requiredParams, $requiredParams);
        $reqCount = count($requiredParams);
 
        foreach ($attractionData as $key => $values) {
            $values = array_filter($values);
            $inersectCount = count(array_intersect_key($values, $requiredParams));
            if ($reqCount != $inersectCount) {
                unset($attractionData[$key]);
            }  
        }
        return $attractionData;
    }
}

Magento2 Row Pattern Reference Link → http://devdocs.magento.com/guides/v2.2/pattern-library/getting-user-input/row_pattern/row_pattern.html

5
  • is this your question or answer? Commented Dec 15, 2017 at 12:05
  • In magento 2.1.* its not working properly..When I remove some row and click on save button then also old records are displaying as it is..In delete functionality it will not delete data from database.. Commented Oct 18, 2018 at 9:21
  • I cannot see the option 'Use Default Value' checkbox when changing the stores...How to activate this method? Commented Nov 12, 2018 at 5:56
  • I agree with Emipro technologies. But it works very well. the delete also works fine. For example, if you added three rows and saved the product. Then Remove all rows, then save the product, it will show only 1 record by default. Why it is showing one record, becoz, this is a required field Commented Apr 8, 2020 at 17:51
  • It's showing the required icon even field is not required. prnt.sc/11kcjaa Commented Apr 17, 2021 at 7:50
3

@raheem.unr note to your answer:

/etc/di.xml is not the correct file to put the modifiers, you should put it in /etc/adminhtml/di.xml else it wont work. (Magento 2.2.*)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.