3

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?

2 Answers 2

6

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!

0

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

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.