Search code examples
phpmagentoadminhtml

Magento Adminhtml: Category Attribute with category chooser


I'm trying to implement a category attribute meant to link the current categories with other categories for a project specific usage.

For that I created a varchar attribute meant to store a coma separated list of category IDs, but I'd like to have the small picker icon next to the field which would display the category chooser, like the one in the condition section of the promotion admin screen.

Category chooser from Promotion section

I don't really see the kind of renderer I should implement to achieve that, I hope you guys will give me a hint. Thanks for helping.


Solution

  • I made it - finally - so here it goes:

    FYI: My category attribute is named "category_top_searches" and is a coma separated list of category IDs. The picker is meant to help contributing this list.

    1 - Add a tab using an observer

    A . Observer declaration

    <adminhtml_catalog_category_tabs>
        <observers>
            <add_topsearches_tab>
                <class>mymodule/observer</class>
                <method>addTopSearchesTab</method>
            </add_topsearches_tab>
        </observers>
    </adminhtml_catalog_category_tabs>
    

    B . Observer implementation

    public function addTopSearchesTab(Varien_Event_Observer $observer)
    {
        $tabs = $observer->getTabs();
        $tabs->addTab(
            'top_searches_tab', 'mymodule/catalog_category_edit_tab_topsearches'
        );
    }
    

    2 - Define the block for the Tab

    Need to implement the tab interface methods and the methods to fetch checked categories when initializing the tree

    class MyNamespace_Adminhtml_Block_Catalog_Category_Edit_Tab_Topsearches
        extends Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Categories
        implements Mage_Adminhtml_Block_Widget_Tab_Interface
    {
    
        /**
         * Specify template to use
         */
        public function __construct()
        {
            parent::__construct();
            $this->setTemplate('catalog/category/edit/categorypicker.phtml');
        }
    
        /**
         * Checks when this block is readonly
         *
         * @return bool
         */
        public function isReadonly()
        {
            return false;
        }
    
        /**
         * getCategoryIds method
    
         * @return array
         */
        protected function getCategoryIds()
        {
            $category = Mage::registry('current_category');
            $ids = explode(',', $category->getCategoryTopSearches());
            return $ids;
        }
    
    
        /**
         * getIdsString
         *
         * @return string
         */
        public function getIdsString()
        {
            $category = Mage::registry('current_category');
            return $category->getCategoryTopSearches();
        }
    
        /**
         * Return Tab label
         *
         * @return string
         */
        public function getTabLabel()
        {
            return $this->__('Top Searches');
        }
    
        /**
         * Return Tab title
         *
         * @return string
         */
        public function getTabTitle()
        {
            return $this->__('Top Searches');
        }
    
        /**
         * Can show tab in tabs
         *
         * @return boolean
         */
        public function canShowTab()
        {
            return true;
        }
    
        /**
         * Tab is hidden
         *
         * @return boolean
         */
        public function isHidden()
        {
            return false;
        }
    }
    

    3 - Define the tree template

    I copy pasted the core template of the category tree of product edit page app/design/adminhtml/default/default/template/catalog/product/edit/categories.phtml into app/design/adminhtml/default/mytheme/template/catalog/category/edit/categorypicker.phtml and made some slight changes :

    Added a text field to display my IDs:

    <div class="entry-edit">
        <div class="entry-edit-head">
            <h4 class="icon-head head-edit-form fieldset-legend"><?php echo Mage::helper('catalog')->__('Linked Categories') ?></h4>
        </div>
        <fieldset id="linked_cat">
            <input type="text" name="linked_categories_list" id="linked_categories_list" value="<?php echo $this->getIdsString() ?>">
        </fieldset>
    </div>
    

    And modified the tree header to be more adequate with my context

    <div class="entry-edit">
        <div class="entry-edit-head">
            <h4 class="icon-head head-edit-form fieldset-legend"><?php echo Mage::helper('catalog')->__('Category picker') ?></h4>
        </div>
        <fieldset id="grop_fields">
            <input type="hidden" name="linked_categories" id="linked_categories" value="<?php echo $this->getIdsString() ?>">
            <div id="product-categories" class="tree"></div>
        </fieldset>
    </div>
    

    I had to modify slightly the beforeLoad function to set the id param:

    categoryLoader.on("beforeload", function(treeLoader, node) {
        treeLoader.baseParams.category = node.attributes.id;
        treeLoader.baseParams.id = node.attributes.id;
    });
    

    I added a function to implement a array_unique feature

    Array.prototype.unique = function(){
        'use strict';
        var im = {}, uniq = [];
        for (var i=0;i<this.length;i++){
            var type = (this[i]).constructor.name,
            //          ^note: for IE use this[i].constructor!
                val = type + (!/num|str|regex|bool/i.test(type)
                        ? JSON.stringify(this[i])
                        : this[i]);
            if (!(val in im)){uniq.push(this[i]);}
            im[val] = 1;
        }
        return uniq;
    }
    

    And then modified the categoryAdd and categoryRemove functions to update my text field with the newly checked/unchecked ID

    function categoryAdd(id) {
        var ids = $('linked_categories').value.split(',');
        ids.push(id);
        var idList = ids.unique().join(',').replace(/^,/, '');
        document.getElementById("linked_categories_list").value = idList;
        $('linked_categories').value = ids.join(',');
    }
    function categoryRemove(id) {
        var ids = $('linked_categories').value.split(',');
        // bug #7654 fixed
        while (-1 != ids.indexOf(id)) {
            ids.splice(ids.indexOf(id), 1);
        }
        var idList = ids.unique().join(',').replace(/^,/, '');
        document.getElementById("linked_categories_list").value = idList;
        $('linked_categories').value = ids.join(',');
    }
    

    4 - Implement an observer to the category save event to inject the new value into the real attribute

    A. Declaration

    <catalog_category_save_before>
        <observers>
            <set_top_searches>
                <class>mymodule/observer</class>
                <method>setTopSearches</method>
            </set_top_searches>
        </observers>
    </catalog_category_save_before>
    

    B. Implementation

    /**
     * takes the fake attribute value and insert it into the real category_top_searches attribute
     *
     * @param Varien_Event_Observer $observer Observer
     *
     * @return void
     */
    public function setTopSearches(Varien_Event_Observer $observer)
    {
        /** @var $category Mage_Catalog_Model_Category */
        $category = $observer->getEvent()->getCategory();
    
        $params = Mage::app()->getRequest()->getParams();
        $ids = $params['linked_categories_list'];
        $category->setData('category_top_searches', $ids);
    }
    

    5 - Finally I hid my original attribute by updating it in a setup script: (I changed its group otherwise its dedicated tab would be empty but still displayed)

    $installer->updateAttribute("catalog_category", "category_top_searches", 'group', "General");
    $installer->updateAttribute("catalog_category", "category_top_searches", 'is_visible', false);
    

    Dunno if anybody will use it since I'm pretty alone on this thread but I would had loved to find such a post when trying to achieve this feature... :D

    If anyone is interested, I also implemented this category picker in configuration fields as a new frontend_type, it's pretty great !