Question

I am trying to add category chooser in my custom admin form without ui component but unfortunately I am getting error in system.log

[2019-03-22 09:27:47] main.CRITICAL: Vendor\Module\Block\Adminhtml\Chooser doesn'n extend \Magento\Framework\Data\Form\Element\AbstractElement [] []

My form field is like:

$fieldset->addField(
    'category_ids',
    'Vendor\Module\Block\Adminhtml\Chooser',
    [
        'name' => 'category_ids',
        'label' => __('Categories'),
        'title' => __('Categories'),
        'required' => true
    ]
);

And Chooser.php is like:

<?php
namespace Vendor\Module\Block\Adminhtml;
class Chooser extends \Magento\Catalog\Block\Adminhtml\Category\Widget\Chooser
{
    protected function _elementHtml()
    {
        $element = $this->_element;
        $htmlId  = $element->getId();
        $data   = $element->getData();

        $data['after_element_js']   = $this->_afterElementJs();
        $data['after_element_html'] = $this->_afterElementHtml();
        $data['readonly']           = 'readonly';

        $objectManager  = \Magento\Framework\App\ObjectManager::getInstance();
        $elementFactory = $objectManager->get('\Magento\Framework\Data\Form\Element\Factory');

        $htmlItem = $elementFactory->create('text', ['data' => $data]);
      $htmlItem
            ->setId("{$htmlId}")
            ->setForm($element->getForm())
            ->addClass('required-entry')
            ->addClass('entities');

        $return  = <<<HTML
            <div id="{$htmlId}-container" class="chooser_container">
                {$htmlItem->getElementHtml()}
            </div>
HTML;

        return $return;
    }

    protected function _afterElementHtml()
    {
        $element = $this->_element;
        $htmlId  = $element->getId();

        $return  = <<<HTML
            <a href="javascript:void(0)" onclick="MultiCategoryChooser.displayChooser('{$htmlId}-container')" class="widget-option-chooser" title="{__('Open Chooser')}">
                <img src="{$this->getViewFileUrl('images/rule_chooser_trigger.gif')}" alt="{__('Open Chooser')}" />
            </a>
            <a href="javascript:void(0)" onclick="MultiCategoryChooser.hideChooser('{$htmlId}-container')" title="{__('Apply')}">
                <img src="{$this->getViewFileUrl('images/rule_component_apply.gif')}" alt="{__('Apply')}">
            </a>

            <div class="chooser"></div>
HTML;

        return $return;
    }

    protected function _afterElementJs()
    {
        $chooserUrl = $this->getUrl('adminhtml/widget_instance/categories', []);
        $element    = $this->_element;
        $htmlId     = $element->getId();

        $return = <<<HTML
            <script>
                require([
                    'jquery',
                    'Magento_Ui/js/modal/alert',
                    "prototype"
                ], function (jQuery, alert) {
                    var MultiCategoryChooser = {
                        displayChooser : function(chooser) {
                            chooser  = $(chooser).down('div.chooser');
                            entities = chooser.up('div.chooser_container').down('input[type="text"].entities').value;
                            postParameters = {selected: entities};
                            url = '{$chooserUrl}';

                            if (chooser && url) {
                                if (chooser.innerHTML == '') {
                                    new Ajax.Request(url, {
                                        method  : 'post',
                                        parameters : postParameters,
                                        onSuccess  : function(transport) {
                                            try {
                                                if (transport.responseText) {
                                                    Element.insert(chooser, transport.responseText);
                                                    chooser.removeClassName('no-display');
                                                    chooser.show();
                                                }
                                            } catch (e) {
                                                alert({
                                                   content: 'Error occurs during loading chooser.'
                                                });
                                            }
                                        }
                                    });
                                } else {
                                    chooser.removeClassName('no-display');
                                    chooser.show();
                                }
                            }
                       },
                        hideChooser : function(chooser) {
                            chooser = $(chooser).down('div.chooser');
                            if (chooser) {
                                chooser.addClassName('no-display');
                                chooser.hide();
                            }
                        },
                        checkCategory : function(event) {
                            node    = event.memo.node;
                            container = event.target.up('div.chooser_container');
                            value   = container.down('input[type="text"].entities').value.strip();
                            if (node.attributes.checked) {
                                if (value) ids = value.split(',');
                                else ids = [];
                                if (-1 == ids.indexOf(node.id)) {
                                    ids.push(node.id);
                                    container.down('input[type="text"].entities').value = ids.join(',');
                                }
                            } else {
                                ids = value.split(',');
                                while (-1 != ids.indexOf(node.id)) {
                                    ids.splice(ids.indexOf(node.id), 1);
                                    container.down('input[type="text"].entities').value = ids.join(',');
                                }
                            }
                        }
                  }

                    window.MultiCategoryChooser = MultiCategoryChooser;
                    jQuery(function() {
                        var container = $('{$htmlId}-container');
                        if (container) {
                            container.up(0).down('.control-value').hide();
                        }

                        Event.observe(document, 'node:changed', function(event){
                            MultiCategoryChooser.checkCategory(event);
                        });
                        Event.observe(document, 'category:beforeLoad', function(event) {
                            container = event.target.up('div.chooser_container');
                            value   = container.down('input[type="text"].entities').value.strip();
                            event.memo.treeLoader.baseParams.selected = value;
                        });
                    });
                });
            </script>
HTML;

        return $return;
    }
}

Can anyone help me to resolve this issue?

Was it helpful?

Solution

I have solved the issue by following codes: Adding field by following code:

$fieldset->addField(
    'category_ids',
    '\Vendor\Module\Block\Adminhtml\Chooser',
    [
        'name' => 'category_ids',
        'label' => __('Categories'),
        'title' => __('Categories'),
        'required' => true
    ]
);

The code for

app/code/Vendor/Module/Block/Adminhtml/Chooser.php

is like below:

<?php
namespace Vendor\Module\Block\Adminhtml;

use Magento\Catalog\Model\Category as CategoryModel;
use Magento\Framework\AuthorizationInterface;
use Magento\Framework\Data\Form\Element\CollectionFactory;
use Magento\Framework\Data\Form\Element\Factory;
use Magento\Framework\Data\Form\Element\Multiselect;
use Magento\Framework\Escaper;
use Magento\Framework\UrlInterface;

class Chooser extends Multiselect
{

    public $collectionFactory;

    public $authorization;

    protected $_urlBuilder;

    public function __construct(
        Factory $factoryElement,
        CollectionFactory $factoryCollection,
        Escaper $escaper,
        \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory $collectionFactory,
        AuthorizationInterface $authorization,
        UrlInterface $urlBuilder,
        array $data = []
    ) {
        $this->collectionFactory = $collectionFactory;
        $this->authorization = $authorization;
        $this->_urlBuilder = $urlBuilder;

        parent::__construct($factoryElement, $factoryCollection, $escaper, $data);
    }

    public function getElementHtml()
    {
        $html = '<div class="admin__field-control admin__control-grouped">';
        $html .= '<div id="topbanner-category-select" class="admin__field" data-bind="scope:\'topbannerCategory\'" data-index="index">';
        $html .= '<!-- ko foreach: elems() -->';
        $html .= '<input name="category_ids" data-bind="value: value" style="display: none"/>';
        $html .= '<!-- ko template: elementTmpl --><!-- /ko -->';
        $html .= '<!-- /ko -->';
        $html .= '</div></div>';

        $html .= $this->getAfterElementHtml();

        return $html;
    }

    public function getCategoriesTree()
    {
        /* @var $collection \Magento\Catalog\Model\ResourceModel\Category\Collection */
        $collection = $this->collectionFactory->create()->addAttributeToSelect('name')->addAttributeToSort('position','asc');

        $categoryById = [
            CategoryModel::TREE_ROOT_ID => [
                'value'    => CategoryModel::TREE_ROOT_ID,
                'optgroup' => null,
            ],
        ];

        foreach ($collection as $category) {
            foreach ([$category->getId(), $category->getParentId()] as $categoryId) {
                if (!isset($categoryById[$categoryId])) {
                    $categoryById[$categoryId] = ['value' => $categoryId];
                }
            }

            $categoryById[$category->getId()]['is_active'] = 1;
            $categoryById[$category->getId()]['label'] = $category->getName();
            $categoryById[$category->getParentId()]['optgroup'][] = &$categoryById[$category->getId()];
        }

        return $categoryById[CategoryModel::TREE_ROOT_ID]['optgroup'];
    }

    public function getValues()
    {
        $values = $this->getValue();
        if (!is_array($values)) {
            $values = explode(',', $values);
        }

        if (!sizeof($values)) {
            return [];
        }

        $collection = $this->collectionFactory->create()
            ->addIdFilter($values);

        $options = [];
        foreach ($collection as $category) {
            $options[] = $category->getId();
        }

        return $options;
    }

    public function getAfterElementHtml()
    {
        $html = '<script type="text/x-magento-init">
            {
                "*": {
                    "Magento_Ui/js/core/app": {
                        "components": {
                            "topbannerCategory": {
                                "component": "uiComponent",
                                "children": {
                                    "select_category": {
                                        "component": "Vendor_Module/js/components/new-category",
                                        "config": {
                                            "filterOptions": true,
                                            "disableLabel": true,
                                            "chipsEnabled": true,
                                            "levelsVisibility": "1",
                                            "elementTmpl": "ui/grid/filters/elements/ui-select",
                                            "options": ' . json_encode($this->getCategoriesTree()) . ',
                                            "value": ' . json_encode($this->getValues()) . ',
                                            "listens": {
                                                "index=create_category:responseData": "setParsed",
                                                "newOption": "toggleOptionSelected"
                                            },
                                            "config": {
                                                "dataScope": "select_category",
                                                "sortOrder": 10
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        </script>';

        return $html;
    }

}

And finally added js file to the below location:

app/code/Vendor/Module/view/adminhtml/web/js/components/new-category.js

with below content:

define([
    'underscore',
    'Magento_Catalog/js/components/new-category'
], function (_, Category) {
    'use strict';

    function flattenCollection(array, separator, created) {
        var i = 0,
            length,
            childCollection;

        array = _.compact(array);
        length = array.length;
        created = created || [];

        for (i; i < length; i++) {
            created.push(array[i]);

            if (array[i].hasOwnProperty(separator)) {
                childCollection = array[i][separator];
                delete array[i][separator];
                flattenCollection.call(this, childCollection, separator, created);
            }
        }

        return created;
    }

    return Category.extend({
        /**
         * Set option to options array.
         *
         * @param {Object} option
         * @param {Array} options
         */
        setOption: function (option, options) {
            var parent = parseInt(option.parent);
            if (_.contains([0, 1], parent)) {
                options = options || this.cacheOptions.tree;
                options.push(option);

                var copyOptionsTree = JSON.parse(JSON.stringify(this.cacheOptions.tree));
                this.cacheOptions.plain = flattenCollection(copyOptionsTree, this.separator);
                this.options(this.cacheOptions.tree);
            } else {
                this._super(option, options);
            }
        }
    });
});

Thats it! Now everything works great!

OTHER TIPS

Piggybacking off of the accepted answer:

In the getElementHtml() function outlined above, I recommend changing

$html .= '<input name="category_ids" data-bind="value: value" style="display: none"/>';

to

$html .= '<input name="'.$this->_id.'" data-bind="value: value" style="display: none"/>';

This will allow your subsequent ->addField() to honor

'name'     => 'name_of_field',

and pass that in your $_POST with the specified field name. Tested working with Magento 2.3.6

Licensed under: CC-BY-SA with attribution
Not affiliated with magento.stackexchange
scroll top