Добавить дерево категорий в произвольном расширении


23

Я пытаюсь добавить дерево категорий в пользовательское расширение, дерево категорий, которое находится на одной из вкладок страницы редактирования продукта.

Ответы:


27

Приготовься, это будет долго. Вот оно.
Вам понадобятся следующие файлы:

app/code/local/[Namespace]/[Module]/Block/Adminhtml/[Entity]/Edit/Tab/Categories.php - вкладка, которая будет отображать категории.

<?php
class [Namespace]_[Module]_Block_Adminhtml_[Entity]_Edit_Tab_Categories
    extends Mage_Adminhtml_Block_Catalog_Category_Tree {
    protected $_categoryIds = null;
    protected $_selectedNodes = null;
    public function __construct() {
        parent::__construct();
        $this->setTemplate('[namespace]_module/[entity]/edit/tab/categories.phtml');
        $this->_withProductCount = false;
    }
    public function get[Entity](){
        return Mage::registry('current_[entity]'); //use other registration key if you have one
    }

    public function getCategoryIds(){
        if (is_null($this->_categoryIds)){
            $categories = $this->get[Entity]()->getSelectedCategories();
                $ids = array();
                foreach ($categories as $category){
                    $ids[] = $category->getId();
                }
                $this->_categoryIds = $ids;
        }
        return $this->_categoryIds;
    }
    public function getIdsString(){
        return implode(',', $this->getCategoryIds());
    }
    public function getRootNode(){
        $root = $this->getRoot();
        if ($root && in_array($root->getId(), $this->getCategoryIds())) {
            $root->setChecked(true);
        }
        return $root;
    }

    public function getRoot($parentNodeCategory = null, $recursionLevel = 3){
        if (!is_null($parentNodeCategory) && $parentNodeCategory->getId()) {
            return $this->getNode($parentNodeCategory, $recursionLevel);
        }
        $root = Mage::registry('category_root');
        if (is_null($root)) {
            $rootId = Mage_Catalog_Model_Category::TREE_ROOT_ID;
            $ids = $this->getSelectedCategoryPathIds($rootId);
            $tree = Mage::getResourceSingleton('catalog/category_tree')
                ->loadByIds($ids, false, false);
            if ($this->getCategory()) {
                $tree->loadEnsuredNodes($this->getCategory(), $tree->getNodeById($rootId));
            }
            $tree->addCollectionData($this->getCategoryCollection());
            $root = $tree->getNodeById($rootId);
            Mage::register('category_root', $root);
        }
        return $root;
    }
    protected function _getNodeJson($node, $level = 1){
        $item = parent::_getNodeJson($node, $level);
        if ($this->_isParentSelectedCategory($node)) {
            $item['expanded'] = true;
        }
        if (in_array($node->getId(), $this->getCategoryIds())) {
            $item['checked'] = true;
        }
        return $item;
    }
    protected function _isParentSelectedCategory($node){
        $result = false;
        // Contains string with all category IDs of children (not exactly direct) of the node
        $allChildren = $node->getAllChildren();
        if ($allChildren) {
            $selectedCategoryIds = $this->getCategoryIds();
            $allChildrenArr = explode(',', $allChildren);
            for ($i = 0, $cnt = count($selectedCategoryIds); $i < $cnt; $i++) {
                $isSelf = $node->getId() == $selectedCategoryIds[$i];
                if (!$isSelf && in_array($selectedCategoryIds[$i], $allChildrenArr)) {
                    $result = true;
                    break;
                }
            }
        }
        return $result;
    }
    protected function _getSelectedNodes(){
        if ($this->_selectedNodes === null) {
            $this->_selectedNodes = array();
            $root = $this->getRoot();
            foreach ($this->getCategoryIds() as $categoryId) {
                if ($root) {
                    $this->_selectedNodes[] = $root->getTree()->getNodeById($categoryId);
                }
            }
        }
        return $this->_selectedNodes;
    }

    public function getCategoryChildrenJson($categoryId){
        $category = Mage::getModel('catalog/category')->load($categoryId);
        $node = $this->getRoot($category, 1)->getTree()->getNodeById($categoryId);
        if (!$node || !$node->hasChildren()) {
            return '[]';
        }
        $children = array();
        foreach ($node->getChildren() as $child) {
            $children[] = $this->_getNodeJson($child);
        }
        return Mage::helper('core')->jsonEncode($children);
    }
    public function getLoadTreeUrl($expanded = null){
        return $this->getUrl('*/*/categoriesJson', array('_current' => true));
    }
    public function getSelectedCategoryPathIds($rootId = false){
        $ids = array();
        $categoryIds = $this->getCategoryIds();
        if (empty($categoryIds)) {
            return array();
        }
        $collection = Mage::getResourceModel('catalog/category_collection');
        if ($rootId) {
            $collection->addFieldToFilter('parent_id', $rootId);
        }
        else {
            $collection->addFieldToFilter('entity_id', array('in'=>$categoryIds));
        }

        foreach ($collection as $item) {
            if ($rootId && !in_array($rootId, $item->getPathIds())) {
                continue;
            }
            foreach ($item->getPathIds() as $id) {
                if (!in_array($id, $ids)) {
                    $ids[] = $id;
                }
            }
        }
        return $ids;
    }
}

app/design/adminhtml/default/default/[namespace]_[module]/[entity]/tab/edit/categories.phtml - шаблон, необходимый для отображения категорий

<div class="entry-edit">
    <div class="entry-edit-head">
        <h4 class="icon-head head-edit-form fieldset-legend">
            <?php echo Mage::helper('[module]')->__('Categories') ?>
        </h4>
    </div>
    <fieldset id="grop_fields">
        <input type="hidden" name="category_ids" id="[entity]_categories" value="<?php echo $this->getIdsString() ?>">
        <div id="[entity]-categories" class="tree"></div>
    </fieldset>
</div>
<?php if($this->getRootNode() && $this->getRootNode()->hasChildren()): ?>
<script type="text/javascript">
    Ext.EventManager.onDocumentReady(function() {
        var categoryLoader = new Ext.tree.TreeLoader({
           dataUrl: '<?php echo $this->getLoadTreeUrl()?>'
        });
        categoryLoader.createNode = function(config) {
            config.uiProvider = Ext.tree.CheckboxNodeUI;
            var node;
            if (config.children && !config.children.length) {
                delete(config.children);
                node = new Ext.tree.AsyncTreeNode(config);
            }
            else {
                node = new Ext.tree.TreeNode(config);
            }
            return node;
        };
        categoryLoader.on("beforeload", function(treeLoader, node) {
            treeLoader.baseParams.category = node.attributes.id;
        });

        categoryLoader.on("load", function(treeLoader, node, config) {
            varienWindowOnload();
        });
        var tree = new Ext.tree.TreePanel('[entity]-categories', {
            animate:true,
            loader: categoryLoader,
            enableDD:false,
            containerScroll: true,
            rootUIProvider: Ext.tree.CheckboxNodeUI,
            selModel: new Ext.tree.CheckNodeMultiSelectionModel(),
            rootVisible: '<?php echo $this->getRootNode()->getIsVisible() ?>'
        });
        tree.on('check', function(node) {
            if(node.attributes.checked) {
                categoryAdd(node.id);
            } else {
                categoryRemove(node.id);
            }
            varienElementMethods.setHasChanges(node.getUI().checkbox);
        }, tree);
        var root = new Ext.tree.TreeNode({
            text: '<?php echo $this->jsQuoteEscape($this->getRootNode()->getName()) ?>',
            draggable:false,
            checked:'<?php echo $this->getRootNode()->getChecked() ?>',
            id:'<?php echo $this->getRootNode()->getId() ?>',
            disabled: <?php echo ($this->getRootNode()->getDisabled() ? 'true' : 'false') ?>,
            uiProvider: Ext.tree.CheckboxNodeUI
        });
        tree.setRootNode(root);
        bildCategoryTree(root, <?php echo $this->getTreeJson() ?>);
        tree.addListener('click', categoryClick.createDelegate(this));
        tree.render();
        root.expand();
    });
    function bildCategoryTree(parent, config){
        if (!config) {
            return null;
        }
        if (parent && config && config.length){
            for (var i = 0; i < config.length; i++){
                config[i].uiProvider = Ext.tree.CheckboxNodeUI;
                var node;
                var _node = Object.clone(config[i]);
                if (_node.children && !_node.children.length) {
                    delete(_node.children);
                    node = new Ext.tree.AsyncTreeNode(_node);

                }
                else {
                    node = new Ext.tree.TreeNode(config[i]);
                }
                parent.appendChild(node);
                node.loader = node.getOwnerTree().loader;
                if(config[i].children){
                    bildCategoryTree(node, config[i].children);
                }
            }
        }
    }
    function categoryClick(node, e){
        if (node.disabled) {
            return;
        }
        node.getUI().check(!node.getUI().checked());
        varienElementMethods.setHasChanges(Event.element(e), e);
    };
    function categoryAdd(id) {
        var ids = $('[entity]_categories').value.split(',');
        ids.push(id);
        $('[entity]_categories').value = ids.join(',');
    }
    function categoryRemove(id) {
        var ids = $('[entity]_categories').value.split(',');
        while (-1 != ids.indexOf(id)) {
            ids.splice(ids.indexOf(id), 1);
        }
        $('[entity]_categories').value = ids.join(',');
    }
</script>
<?php endif; ?>

В вашем файле формы, куда вы добавляете вкладки вашей пользовательской сущности, добавьте также:

    $this->addTab('categories', array(
        'label' => Mage::helper('[module]')->__('Associated categories'),
        'url'   => $this->getUrl('*/*/categories', array('_current' => true)),
        'class'    => 'ajax'
    ));

В контроллере администратора вашей пользовательской сущности эти 2 действия, которые будут обрабатывать запросы по категориям:

public function categoriesAction(){
    $this->_init[Entity]();
    $this->loadLayout();
    $this->renderLayout();
}
public function categoriesJsonAction(){
    $this->_init[Entity]();
    $this->getResponse()->setBody(
        $this->getLayout()->createBlock('[module]/adminhtml_[entity]_edit_tab_categories')
            ->getCategoryChildrenJson($this->getRequest()->getParam('category'))
    );
}

и убедитесь, что в том же контроллере этот метод существует:

protected function _init[Entity](){
    $[entity]Id  = (int) $this->getRequest()->getParam('id');
    $[enity]    = Mage::getModel('[module]/[entity]');

    if ($[entity]Id) {
        $[entity]->load($[entity]Id);
    }
    Mage::register('current_[entity]', $[entity]);
    return $[entity];
}

В файле макета администратора вашего модуля добавьте этот дескриптор для действия категорий:

<adminhtml_[module]_[entity]_categories>
    <block type="core/text_list" name="root" output="toHtml">
        <block type="[module]/adminhtml_[entity]_edit_tab_categories" name="[entity].edit.tab.categories"/>
    </block>
</adminhtml_[module]_[entity]_categories>

Теперь перейдем к сохранению ваших данных.
Для этого вам понадобится следующее в одном из скриптов установки / обновления вашего модуля. Это создаст таблицу, в которой будут храниться связанные значения

$table = $this->getConnection()
    ->newTable($this->getTable('[module]/[entity]_category'))
    ->addColumn('rel_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'unsigned'  => true,
        'identity'  => true,
        'nullable'  => false,
        'primary'   => true,
        ), 'Relation ID')
    ->addColumn('[entity]_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), '[Entity] ID')
    ->addColumn('category_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Category ID')
    ->addColumn('position', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'nullable'  => false,
        'default'   => '0',
    ), 'Position')
    ->addIndex($this->getIdxName('[module]/[entity]_category', array('category_id')), array('category_id'))
    ->addForeignKey($this->getFkName('[module]/[entity]_category', '[entity]_id', '[module]/[entity]', 'entity_id'), '[entity]_id', $this->getTable('[module]/[entity]'), 'entity_id', Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->addForeignKey($this->getFkName('[module]/[entity]_category', 'category_id', 'catalog/category', 'entity_id'),    'category_id', $this->getTable('catalog/category'), 'entity_id', Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->addIndex(
    $this->getIdxName(
        '[module]/[entity]_category',
        array('[entity]_id', 'category_id'),
        Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE
    ),
    array('[entity]_id', 'category_id'),
    array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE))
    ->setComment('[Entity] to Category Linkage Table');
$this->getConnection()->createTable($table);

Объявите свой стол. Добавьте это config.xmlвнутри <[module]_resource><entities>тега

<[entity]_category>
    <table>[module]_[entity]_category</table>
</[entity]_category>

Вам понадобится модель для связи с категориями:
app/code/local/[Namespace]/[Module]/Model/[Entity]/Category.php

<?php

class [Namespace]_[Module]_Model_[Entity]_Category
    extends Mage_Core_Model_Abstract {
    protected function _construct(){
        $this->_init('[module]/[entity]_category');
    }
    public function save[Entity]Relation($[entity]){
        $data = $[entity]->getCategoriesData();
        if (!is_null($data)) {
            $this->_getResource()->save[Entity]Relation($[entity], $data);
        }
        return $this;
    }
    public function getCategoryCollection($[entity]){
        $collection = Mage::getResourceModel('[module]/[entity]_category_collection')
            ->add[Entity]Filter($[entity]);
        return $collection;
    }
}

и модель ресурсов app/code/local/[Namespace]/[Module]/Model/Resource/[Entity]/Category.php:

<?php

class [Namespace]_[Module]_Model_Resource_[Entity]_Category
    extends Mage_Core_Model_Resource_Db_Abstract {

    protected function  _construct(){
        $this->_init('[module]/[entity]_category', 'rel_id');
    }
    public function save[Entity]Relation($[entity], $data){
        if (!is_array($data)) {
            $data = array();
        }
        $deleteCondition = $this->_getWriteAdapter()->quoteInto('[entity]_id=?', $[entity]->getId());
        $this->_getWriteAdapter()->delete($this->getMainTable(), $deleteCondition);

        foreach ($data as $categoryId) {
            if (!empty($categoryId)){
                $this->_getWriteAdapter()->insert($this->getMainTable(), array(
                    '[entity]_id'      => $[entity]->getId(),
                    'category_id'     => $categoryId,
                    'position'      => 1
                ));
            }
        }
        return $this;
    }
}

и модель ресурсов коллекции: app/code/local/[Namespace]/[Module]/Model/Resource/[Entity]/Category/Collection.php

<?php
class [Namespace]_[Module]_Model_Resource_[Entity]_Category_Collection
    extends Mage_Catalog_Model_Resource_Category_Collection{
    protected $_joinedFields = false;
    public function joinFields(){
        if (!$this->_joinedFields){
            $this->getSelect()->join(
                array('related' => $this->getTable('[module]/[entity]_category')),
                'related.category_id = main_table.entity_id',
                array('position')
            );
            $this->_joinedFields = true;
        }
        return $this;
    }
    public function add[Entity]Filter($[entity]){
        if ($[entity] instanceof [Namespace]_[Module]_Model_[Entity]){
            $[entity] = $[entity]->getId();
        }
        if (!$this->_joinedFields){
            $this->joinFields();
        }
        $this->getSelect()->where('related.[entity]_id = ?', $[entity]);
        return $this;
    }
}

Теперь в saveAction вашего администратора добавить это прямо перед вызовом $[entity]->save()

$categories = $this->getRequest()->getPost('category_ids', -1);
if ($categories != -1) {
    $categories = explode(',', $categories);
    $categories = array_unique($categories);
    $[entity]->setCategoriesData($categories);
}

В вашей модели сущности добавьте это вверху вашего класса: protected $_categoryInstance = null;и эти методы везде:

protected function _afterSave() {
    $this->getCategoryInstance()->save[Entity]Relation($this);
    return parent::_afterSave();
}
public function getCategoryInstance(){
    if (!$this->_categoryInstance) {
        $this->_categoryInstance = Mage::getSingleton('[module]/[entity]_category');
    }
    return $this->_categoryInstance;
}
public function getSelectedCategories(){
    if (!$this->hasSelectedCategories()) {
        $categories = array();
        foreach ($this->getSelectedCategoriesCollection() as $category) {
            $categories[] = $category;
        }
        $this->setSelectedCategories($categories);
    }
    return $this->getData('selected_categories');
}
public function getSelectedCategoriesCollection(){
    $collection = $this->getCategoryInstance()->getCategoryCollection($this);
    return $collection;
}

Вот и все. Надеюсь, я ничего не пропустил. Код может потребовать некоторых изменений, потому что я не знаю, как именно построен ваш модуль, но основные идеи есть. С некоторой отладкой вы должны заставить это работать.


Примечание. Приведенный выше код был сгенерирован с использованием Ultimate Module Creator v1.9 .


Замечательный пост! Вам нужно немного навыков, чтобы продолжить, но если вы ищете такой ответ, у вас должно быть достаточно. Где-то есть опечатка (извините, я точно не помню, где именно) вызывается функция ... categoTy ... вместо categoRy. спасибо Мариус
Джузеппе

Почему вы не использовали API дерева категорий Magento, чтобы получить данные в своем модуле и передать их в поле зрения. Вы можете играть с ним очень легко.
Сураб Моди

@SourabhModi. Я воссоздал соединение категорий таким же образом, который используется для связывания категорий с продуктом в форме добавления / редактирования продукта. Я думал о том, чтобы сохранить его последовательным
Мариус

Я просто сохранил свои данные в поле модели с названием category_ids.
Джузеппе

1
@Marius Я понял вашу точку зрения, но дело в том, что если вы вызываете API Magento, то кода с вашей стороны будет намного меньше, и внутренне Magento делает то же самое, когда вызывается API, как они это делали, чтобы связать категорию в форме добавления / редактирования продукта. , Поэтому нет смысла переписывать этот код снова на вашем собственном конце, так как вы можете использовать ту же функцию, вызывая функцию (вызов API - это вызов функции), которая внутренне использует ту же требуемую функцию, что и в форме добавления / редактирования продукта.
Сураб Моди

2

По крайней мере, для Magento 1.9 вы должны быть уверены, что extJ загружен.
Используйте один из следующих методов, чтобы активировать использование extJS в бэкэнде:

  1. В вашем контроллере используйте это:

    $this->getLayout()->getBlock('head')->setCanLoadExtJs(true);
  2. В вашем макете XML используйте это:

    <reference name="head">
        <action method="setCanLoadExtJs">
            <value>1</value>
        </action>
    </reference>
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.