Customize Product Attribute Input Renderer in Magento 2

adminhtmlblocksmagento2renderer

I'm facing to a very ridiculous trouble, I added a custom product attribute with source model, backend model as a select box. Everything works fine (my custom attribute will get the values from a custom table) except the input_renderer part.

Here is what I tried, (running on default mode)

Setup\MySetup.php

public function getDefaultEntities()
{
    return [
        'catalog_product' => [
            'entity_model' => 'Magento\Catalog\Model\ResourceModel\Product',
            'attribute_model' => 'Magento\Catalog\Model\ResourceModel\Eav\Attribute',
            'table' => 'catalog_product_entity',
            'additional_attribute_table' => 'catalog_eav_attribute',
            'entity_attribute_collection' => 'Magento\Catalog\Model\ResourceModel\Product\Attribute\Collection',
            'attributes' => [
                'custom_ids' => [
                    'type' => 'static',
                    'input' => 'select',
                    'label' => 'Custom Attribute',
                    'global' => ScopedAttributeInterface::SCOPE_GLOBAL,
                    'backend' => 'Vendor\Module\Model\Product\Attribute\Backend\CustomAttr',
                    'source' => 'Vendor\Module\Model\Product\Attribute\Source\CustomAttr',
                    'input_renderer' => 'Vendor\Module\Block\Adminhtml\Product\Helper\Form\CustomAttr',
                    'required' => false,
                    'sort_order' => 9,
                    'visible' => true,
                    'group' => 'General',
                    'is_used_in_grid' => false,
                    'is_visible_in_grid' => false,
                    'is_filterable_in_grid' => false,
                ],
    ];
}

Block\Adminhtml\Product\Helper\Form\CustomAttr.php

class CustomAttr extends \Magento\Framework\Data\Form\Element\Select
{
    public function getAfterElementHtml()
    {
        if (!$this->isAllowed()) {
            return '';
        }

        $htmlId = $this->getHtmlId();
        $suggestPlaceholder = __('start typing to search custom attribute');
        $selectorOptions = $this->jsonEncoder->encode($this->getSelectorOptions());
        $backendUrl = $this->backendData->getUrl('vendor_module/customattr_index/index/suggestCustomAttr');

        $return
            = <<<HTML
   /** Some HTML and JavaScript here **/
HTML;

        return $return;
    }
}

I tried to die() inside getAfterElementHtml() method, but nothing happens. I even go to the database and change the value of column frontend_input_renderer in table catalog_eav_attribute to something doesn't exists.

But there is NO ERROR at all !!! Magento 2 just simply to denied to call my input renderer 🙂

Anyone here have any suggestions or a best pratices for this?

Thank you.

Best Answer

TL;DR: If you need to customise your custom attribute in product edit form Magento 2, use PHP modifier in UI components instead of input renderer.

I found the solution myself.

As we all knew that Magento 2 using UI components, the solution I'm talking about here is PHP modifier in UI components.

From Magento 2 devdocs:

PHP modifiers that are the server-side part of UI components configuration. Using modifiers is optional and might be necessary when static declaration in XML configuration files is not suitable for the tasks. For example, in cases when additional data should be loaded from database. Or the other specific example is the default product creation form, for which the modifier is a place where validations are added to display only certain fields for certain product types.

By default, Magento 2 using PHP modifiers to render EAV attribute. They never use input_renderer to do so. If you are reading here, I assume that you need few examples, so here you are :)

Ui\DataProviders\Product\Form\Modifier\CustomAttr

<?php
namespace Vendor\Module\Ui\DataProvider\Product\Form\Modifier;

use Magento\Catalog\Api\Data\ProductAttributeInterface;
use Magento\Catalog\Model\Locator\LocatorInterface;
use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier;
use Magento\Framework\Stdlib\ArrayManager;
use Magento\Framework\UrlInterface;
use Magento\Ui\Component\Form\Field;

/**
 * Data provider for "Custom Attribute" field of product page
 */
class CustomAttr extends AbstractModifier
{
    const SUGGEST_FILTER_URI = 'vendor_module/something/suggestCustomAttr';

    /**
     * @param LocatorInterface            $locator
     * @param UrlInterface                $urlBuilder
     * @param ArrayManager                $arrayManager
     */
    public function __construct(
        LocatorInterface $locator,
        UrlInterface $urlBuilder,
        ArrayManager $arrayManager
    ) {
        $this->locator = $locator;
        $this->urlBuilder = $urlBuilder;
        $this->arrayManager = $arrayManager;
    }

    /**
     * {@inheritdoc}
     */
    public function modifyMeta(array $meta)
    {
        $meta = $this->customiseCustomAttrField($meta);

        return $meta;
    }

    /**
     * {@inheritdoc}
     */
    public function modifyData(array $data)
    {
        return $data;
    }

    /**
     * Customise Custom Attribute field
     *
     * @param array $meta
     *
     * @return array
     */
    protected function customiseCustomAttrField(array $meta)
    {
        $fieldCode = 'custom_ids'; //your custom attribute code
        $elementPath = $this->arrayManager->findPath($fieldCode, $meta, null, 'children');
        $containerPath = $this->arrayManager->findPath(static::CONTAINER_PREFIX . $fieldCode, $meta, null, 'children');

        if (!$elementPath) {
            return $meta;
        }

        $meta = $this->arrayManager->merge(
            $containerPath,
            $meta,
            [
                'arguments' => [
                    'data' => [
                        'config' => [
                            'label'         => __('Custom Attribute'),
                            'dataScope'     => '',
                            'breakLine'     => false,
                            'formElement'   => 'container',
                            'componentType' => 'container',
                            'component'     => 'Magento_Ui/js/form/components/group',
                            'scopeLabel'    => __('[GLOBAL]'),
                        ],
                    ],
                ],
                'children'  => [
                    $fieldCode => [
                        'arguments' => [
                            'data' => [
                                'config' => [
                                    'component'     => 'Vendor_Module/js/components/your-select-js',
                                    'componentType' => Field::NAME,
                                    'formElement'   => 'select',
                                    'elementTmpl'   => 'ui/grid/filters/elements/ui-select',
                                    'disableLabel'  => true,
                                    'multiple'      => false,
                                    'options'       => $this->getOptions(),
                                    'filterUrl'     => $this->urlBuilder->getUrl(
                                        self::SUGGEST_FILTER_URI,
                                        ['isAjax' => 'true']
                                    ),
                                    'config'           => [
                                        'dataScope' => $fieldCode,
                                        'sortOrder' => self::FIELD_ORDER,
                                    ],
                                ],
                            ],
                        ],
                    ]
                ]
            ]
        );

        return $meta;
    }

    /**
     * Retrieve custom attribute collection
     *
     * @return array
     */
    protected function getOptions()
    {
        // Get your options
    }
}

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="custom-attribute" xsi:type="array">
                    <item name="class" xsi:type="string">Vendor\Module\Ui\DataProvider\Product\Form\Modifier\CustomAttr</item>
                    <item name="sortOrder" xsi:type="number">10</item>
                </item>
            </argument>
        </arguments>
    </virtualType>
</config>

Above is example of two important file which ping Magento 2 to render your custom attribute fields in product edit form to whatever you want. You will need to customise with javascript if you want it plays smoothly.

And yes, welcome to Magento 2 most painful's world - UI components.

Related Topic