Skip to main content

Magento: Custom menu configuration section using a field array

    Build your next product with a team of experts

    Upload file

    Our Happy Clients

    I have worked with Itera Research for many years on numerous projects. During this time, the team always exceeds my expectations, producing amazing tools for our customers.

    Founder, eDoctrina
    Founder, eDoctrina

    To find out more, see our Expertise and Services

    Engagement Models

    Staff Augmentation

    Avoid the overhead costs of internal hires by adding Itera Research experts to your existing team


    Software Outsoursing

    Focus on your core business while we handle the development and delivery of your software product


    IT Consulting

    Leverage our CTO-as-a-service to strategize and solve your biggest technical challenges

    Magento: Custom menu configuration section using a field array

    As we already know, we can add custom sections with input fields to the system configuration page in admin by using a system.xml config file in our module. The Magento Adminhtml module provides different types of form fields that we can use, one of which is Form Field Array. This form field allows you to create multiple rows with input fields.

    This is a great alternative to admin grids, because when we want to use the grid, it assumes that we have data collection, model, and related database table. A field array is a simple way to store multiple items without using collections. It would be more user-friendly to edit footer links in this way rather than editing links in CMS block. Let’s create a custom module Ir_Menu as an example. The module config files would be like this:

    file: app/code/local/Ir/Menu/etc/config.xml

    <?xml version="1.0"?>
    <config>
        <modules>
            <Ir_Menu>
                <version>0.0.1</version>
            </Ir_Menu>
        </modules>
        <global>
            <blocks>
                <menu>
                    <class>Ir_Menu_Block</class>
                </menu>
            </blocks>
        </global>
    </config> 
    

    file: app/code/local/Ir/Menu/etc/config.xml

    <?xml version="1.0"?>
    <config>
        <sections>
            <design>
                <groups>
                    <main_menu>
                        <label>Main Menu</label>
                        <frontend_type>text</frontend_type>
                        <sort_order>301</sort_order>
                        <show_in_default>1</show_in_default>
                        <show_in_website>1</show_in_website>
                        <show_in_store>1</show_in_store>
                        <fields>
                            <items translate="label">
                                <label>Menu Items</label>
                                <frontend_model>menu/adminhtml_form_field_items</frontend_model>
                                <backend_model>menu/system_config_backend_items</backend_model>
                                <sort_order>100</sort_order>
                                <show_in_default>1</show_in_default>
                                <show_in_website>0</show_in_website>
                                <show_in_store>0</show_in_store>
                            </items>
                        </fields>
                    </main_menu>
                </groups>
            </design>
        </sections>
    </config>
    

    As we can see in the system.xml config, we have a option,  <frontend_model> which actually defines a view block for the field. Implementation of the Ir_Menu_Block_Adminhtml_Form_Field_Items class would be like this:

    file: app/code/local/Ir/Menu/Block/Adminhtml/Form/Field/Items.php

    class Ir_Menu_Model_System_Config_Backend_Items extends Mage_Adminhtml_Model_System_Config_Backend_Serialized_Array
    {
        /**
         * Process data after load
         */
        protected function _afterLoad()
        {
            //$value = $this->getValue();
            //$this->setValue($value);
            parent::_afterLoad();
        }
    
        /**
         * Prepare data before save
         */
        protected function _beforeSave()
        {
            //$value = $this->getValue();
            //$this->setValue($value);
            parent::_beforeSave();
        }
    }
    

    So we need to extend Mage_Adminhtml_Block_System_Config_Form_Field_Array_Abstract class and override_prepareToRender method where we can add our custom columns. This would be enough when we use only text input fields in all columns. But in our case we want to use drop down select with CMS pages. And in this example we have additional custom method _getPageRenderer. As you can see this method creates another layout block. This block then passed as renderer parameter of the addColumn method. This is the way we can customize field array columns. Next step is implementation. This model responsible for data preparing when we save and load configuration on system > configuration page in admin. Here we can specify default Magento classMage_Adminhtml_Model_System_Config_Backend_Serialized_Array which do the job. But in our example we implement custom model Ir_Menu_Model_System_Config_Backend_Items which extendsMage_Adminhtml_Model_System_Config_Backend_Serialized_Array and override methods _afterLoad and_beforeSave like this:

    file: app/code/local/Ir/Menu/Model/System/Config/Backend/Items.php

    class Ir_Menu_Model_System_Config_Backend_Items extends
    Mage_Adminhtml_Model_System_Config_Backend_Serialized_Array
    {
        /**
         * Process data after load
         */
        protected function _afterLoad()
        {
            //$value = $this->getValue();
            //$this->setValue($value);
            parent::_afterLoad();
        }
    
        /**
         * Prepare data before save
         */
        protected function _beforeSave()
        {
            //$value = $this->getValue();
            //$this->setValue($value);
            parent::_beforeSave();
        }
    }
    

    Also, don’t forget to change config.xml so it looks like this:

    file: app/code/local/Ir/Menu/etc/config.xml

    <?xml version="1.0"?>
    <config>
        <modules>
            <Ir_Menu>
                <version>0.0.1</version>
            </Ir_Menu>
        </modules>
        <global>
    	  <models>
                <menu>
                    <class>Ir_Menu_Model</class>
                </menu>
            </models>
            <blocks>
                <menu>
                    <class>Ir_Menu_Block</class>
                </menu>
            </blocks>
        </global>
    </config>
    

    As we can see, it just serializes the data and saves it to DB. Therefore, we have the following in the core_config_data table:

    Let’s get back to the _getPageRenderer method of Ir_Menu_Block_Adminhtml_Form_Field_Items class:
    file: app/code/local/Ir/Menu/Block/Adminhtml/Form/Field/Items.php…

    ...    
        protected function _getPageRenderer()
        {
            if (!$this->_pageRenderer) {
                $this->_pageRenderer = $this->getLayout()->createBlock(
                    'menu/adminhtml_form_field_page', '',
                    array('is_render_to_js_template' => true)
                );
                $this->_pageRenderer->setClass('cms_page_select');
                $this->_pageRenderer->setExtraParams('style="width:120px"');
            }
            return $this->_pageRenderer;
        }
    ...
    

    Since we want to customize the field array column with dropdown selection where we can choose the CMS page, we must implement the 'menu/adminhtml_form_field_page' block class:
    file: app/code/local/Ir/Menu/Block/Adminhtml/Form/Field/Page.php

    {
        private $_cmsPages;
        protected function _getCmsPages($pageId = null)
        {
            if (is_null($this->_cmsPages)) {
                $this->_cmsPages = array();
                $collection = Mage::getResourceModel('cms/page_collection')->load();
                foreach ($collection as $item) {
                    $this->_cmsPages[$item->getIdentifier()] = $item->getTitle();
                }
            }
            if (!is_null($pageId)) {
                return isset($this->_cmsPages[$pageId]) ? $this->_cmsPages[$pageId] : null;
            }
            return $this->_cmsPages;
        }
    
        public function setInputName($value)
        {
            return $this->setName($value);
        }
    
        public function _toHtml()
        {
            if (!$this->getOptions()) {
                $this->addOption('', 'not selected');
                foreach ($this->_getCmsPages() as $pageId => $pageLabel) {
                    $this->addOption($pageId, addslashes($pageLabel));
                }
            }
            return parent::_toHtml();
        }
    } 
    

    All we are doing here is extending one of the default Magento core blocks Mage_Core_Block_Html_Select and overridingthe _toHtml method where we can add selection options, and load a collection of CMS pages. At this point, we should see the following in the Magento admin system > configuration > design > Main Menu:

    Finally, we need to render our menu on the front end. This will be done by adding an appropriate block class and its template:
    file: app/code/local/Ir/Menu/Block/View.php

    class Ir_Menu_Block_View extends Mage_Core_Block_Template
    {
        protected $_items;
        public function __construct()
        {
            parent::__construct();
            $this->setTemplate('menu/view.phtml');
        }
    
    
        public function getItems()
        {
            if (!$this->_items) {
                $this->_items = unserialize(Mage::getStoreConfig('design/main_menu/items'));
            }
            return $this->_items;
        }
    }
    

    Since we have the serialized data stored in our config 'design/main_menu/items', we should unserialize it to an array like this:$this->_items = unserialize(Mage::getStoreConfig('design/main_menu/items')); $this->_items  array will look like this:

    All we have to do now is create a menu.phtml template and add a layout update in our custom theme. It could be something like this:
    file: app/design/frontend/our_package/our_theme/template/menu/view.phtml

    <ul>
        <?php foreach($this->getItems() as $item): ?>
        <li>
           <a href="<?php echo $item['link']; ?>"><?php echo $item['title']; ?></a>
        </li>
        <?php endforeach; ?>
    </ul>
    

    file: app/design/frontend/our_package/our_theme/layout/menu.xml

    <layout version="0.1.0">
    <default>
      <reference name="header">
         <block type="menu/view" name="main_menu"/>
      </reference>        
    </default>
    

    And don’t forget to add the following to config.xml if we use our own layout update file:
    file: app/code/local/Ir/Menu/etc/config.xml

    <?xml version="1.0"?>
    <config>
        ...
        <frontend>
           <layout>
                <updates>
                    <menu module="Ir_Menu">
                        <file>menu.xml</file>
                    </menu>
                </updates>
            </layout>
        </frontend>
    </config>
    

    Sincerely, Itera Research Team

    Next Post
    Welcome to Itera Research Blog
    Next Post
    Which CMS will be crowned Content King in 2015?