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 class
Mage_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