Tuesday, February 12, 2013

Create system config section with dynamic number of fields

Here is how you can create a section in System->Configuration with dynamic number of fields.
The following example will create a config section with yes/no dropdowns for each customer group.
For this you need to create a new module. Let's call it Easylife_Configsection. You can change the name to whatever you want but make sure you change the file names and references in the code.
The extension has the following files: app/etc/modules/Easylife_Configsection.xml - module declaration file
<?xml version="1.0"?>
<config>
    <modules>
        <Easylife_Configsection>
            <active>true</active>
            <codePool>local</codePool>
            <depends>
             <Mage_Adminhtml/>
            </depends>
        </Easylife_Configsection>
    </modules>
</config>
app/code/local/Easylife/Configsection/etc/config.xml - module config file
<?xml version="1.0"?>
<config>
 <modules>
  <Easylife_Configsection>
   <version>1.0.0</version>
  </Easylife_Configsection>
 </modules>
 <global>
  <blocks>
   <configsection>
    <class>Easylife_Configsection_Block</class>
   </configsection>
  </blocks>
  <helpers>
   <configsection>
    <class>Easylife_Configsection_Helper</class>
   </configsection>
  </helpers>
 </global>
</config>
app/code/local/Easylife/Configsection/Helper/Data.php - the module main helper
<?php 
class Easylife_Configsection_Helper_Data extends Mage_Core_Helper_Abstract{
 
}
app/code/local/Easylife/Configsection/etc/system.xml - this will add the section in the config area
<?xml version="1.0"?>
<config>
 <sections>
  <customer_groups_demo translate="label" module="configsection"><!-- this ads a new section in the config you can change the tab name to what you need-->
   <label>Customer groups demo</label>
            <tab>customer</tab><!-- the section is added in the customer tab. you can change to anything-->
            <frontend_type>text</frontend_type>
            <sort_order>200</sort_order>
            <show_in_default>1</show_in_default>
            <show_in_website>1</show_in_website>
            <show_in_store>1</show_in_store>
   <groups>
    <demo translate="label" module="configsection"><-- this add a group in the section you just created change the name to whatever you need -->
                    <label>Demo customer groups</label>
                    <frontend_type>text</frontend_type>
                    <frontend_model>configsection/adminhtml_system_config_form_fieldset_customer_groups</frontend_model><-- this is the model that renders all the fields -->
                    <sort_order>10</sort_order>
                    <show_in_default>1</show_in_default>
                    <show_in_website>1</show_in_website>
                    <show_in_store>1</show_in_store>
                </demo> 
   </groups>
  </customer_groups_demo>
 </sections>
</config>
app/code/local/Easylife/Configsection/etc/adminhtml.xml - this will add the acl for you section
<?xml version="1.0"?>
<config>
    <acl>
        <resources>
            <admin>
                <children>
                    <system>
                        <children>
                            <config>
                                <children>
                                    <customer_groups_demo translate="title" module="configsection"><-- this tag sould be named as the section you create -->
                                        <title>Customer groups demo</title>
                                    </customer_groups_demo>
                                </children>
                            </config>
                        </children>
                    </system>
                </children>
            </admin>
        </resources>
    </acl>
</config>
Now the fun part. app/code/local/Easylife/Configsection/Block/Adminhtml/System/Config/Form/Fieldset/Customer/Groups.php - this is the model that create the fields in the config section
<?php 
class Easylife_Configsection_Block_Adminhtml_System_Config_Form_Fieldset_Customer_Groups extends 
 Mage_Adminhtml_Block_System_Config_Form_Fieldset{
 protected $_dummyElement;
    protected $_fieldRenderer;
    protected $_values;

    public function render(Varien_Data_Form_Element_Abstract $element)
    {
        $html = $this->_getHeaderHtml($element);
 //here you cand loop through all the fields you want to add
 //for each element you neet to call $this->_getFieldHtml($element, $group);
        $groups = Mage::getModel('customer/group')->getCollection();

        foreach ($groups as $group) {
            $html.= $this->_getFieldHtml($element, $group);
        }
        $html .= $this->_getFooterHtml($element);

        return $html;
    }
    //this creates a dummy element so you can say if your config fields are available on default and website level - you can skip this and add the scope for each element in _getFieldHtml method
    protected function _getDummyElement()
    {
        if (empty($this->_dummyElement)) {
            $this->_dummyElement = new Varien_Object(array('show_in_default'=>1, 'show_in_website'=>1));
        }
        return $this->_dummyElement;
    }
    //this sets the fields renderer. If you have a custom renderer tou can change this. 
    protected function _getFieldRenderer()
    {
        if (empty($this->_fieldRenderer)) {
            $this->_fieldRenderer = Mage::getBlockSingleton('adminhtml/system_config_form_field');
        }
        return $this->_fieldRenderer;
    }
    //this is usefull in case you need to create a config field with type dropdown or multiselect. For text and texareaa you can skip it.
    protected function _getValues()
    {
        if (empty($this->_values)) {
            $this->_values = array(
                array('label'=>Mage::helper('adminhtml')->__('No'), 'value'=>0),
                array('label'=>Mage::helper('adminhtml')->__('Yes'), 'value'=>1),
            );
        }
        return $this->_values;
    }
    //this actually gets the html for a field
    protected function _getFieldHtml($fieldset, $group)
    {
        $configData = $this->getConfigData();
        $path = 'customer_groups_demo/demo/group_'.$group->getId();//this value is composed by the section name, group name and field name. The field name must not be numerical (that's why I added 'group_' in front of it)
        if (isset($configData[$path])) {
            $data = $configData[$path];
            $inherit = false;
        } else {
            $data = (int)(string)$this->getForm()->getConfigRoot()->descend($path);
            $inherit = true;
        }

        $e = $this->_getDummyElement();//get the dummy element

        $field = $fieldset->addField($group->getId(), 'select',//this is the type of the element (can be text, textarea, select, multiselect, ...)
            array(
                'name'          => 'groups[demo][fields][group_'.$group->getId().'][value]',//this is groups[group name][fields][field name][value]
                'label'         => $group->getCustomerGroupCode(),//this is the label of the element
                'value'         => $data,//this is the current value
                'values'        => $this->_getValues(),//this is necessary if the type is select or multiselect
                'inherit'       => $inherit,
                'can_use_default_value' => $this->getForm()->canUseDefaultValue($e),//sets if it can be changed on the default level
                'can_use_website_value' => $this->getForm()->canUseWebsiteValue($e),//sets if can be changed on website level
            ))->setRenderer($this->_getFieldRenderer());

        return $field->toHtml();
    }
}
That's about it. Clear the cache, logout of the admin and login again and you should be able to use this. You can get the value of a setting just like any other magento config setting:
$groupId = 1;
$val = Mage::getStoreConfig('customer_groups_demo/demo/group_'.$groupId);//section_name/group/field
Marius.

17 comments:

  1. VERY BIG THANKS!!!!!!!!
    Can't believe I finally found this. Was looking for 2 days.

    ReplyDelete
  2. How would one go about assigning a backend_model for a field created using this method? I've tried a number of things but haven't gotten it to work

    ReplyDelete
    Replies
    1. I don't think you can. When saving a section in the config this method is called Mage_Adminhtml_Model_Config_Data::save(). And in this method, in order to find the backend models the system.xml is parsed. And since the fields added this way are not in system.xml you cannot set a backend model.

      Delete
    2. @Adam @tzyganu, yes it is possible by using clone_fields. Actually it's doesn't need much effort. I've posted a tutorial on my blog http://www.mellority-report.com/magento-dynamic-config-fields-with-custom-backend-models/

      Delete
    3. It is possible, using the clone_fields element in the system.xml. Indeed it doesn't need much effort. I've posted a tutorial on http://www.mellority-report.com/magento-dynamic-config-fields-with-custom-backend-models/

      Delete
  3. Hi, this was just what i needed, but i can't get my fields saved, they area showed correctly, when i save, return the error

    An error occurred while saving this configuration: Notice: Trying to get property of non-object in /Users/andrefuhrman/Sites/teste/magento-1.7.0.2/app/code/core/Mage/Adminhtml/Model/Config/Data.php on line 149

    Any idea?

    ReplyDelete
    Replies
    1. What does it say on that line? In the version I have it's '->setGroupId($group)' that should work just fine. the method is called on an instance of 'Mage_Core_Model_Config_Data' and if something is wrong it should have crashed 2 lines above.

      Delete
    2. I had the same issue, just fixed the Data.php with the 1.8 version.

      /**
      * Get field backend model
      */
      $backendClass = (isset($fieldConfig->backend_model))? $fieldConfig->backend_model : false;
      if (!$backendClass) {
      $backendClass = 'core/config_data';
      }

      Delete
  4. Hi. Can this be used to add dynamic groups too?

    ReplyDelete
    Replies
    1. IN theory I think it's possible. I've never tried it, but a config section can use a 'frontend_model'. Check Mage_Adminhtml_Block_System_Config_Edit::initForm(). To see how the sections are generated check this method Mage_Adminhtml_Block_System_Config_Form::initForm(). This is the default frontend_model of the config section. Maybe you can create a new model following the structure of this one.
      Let me know if you come up with something.

      Delete
  5. HI ...

    i want to change the elements type to textbox,
    but the data cannot save

    $field = $fieldset->addField($collection->getId(), 'text',
    //this is the type of the element (can be text, textarea, select, multiselect, ...)
    array(
    'name' => 'collections[appearance][headersubcolor][entity_id'.$collection->getId().'][name]',
    //this is groups[group name][fields][field name][value]
    'class' => $collection->getName(),
    'label' => $collection->getName(),//this is the label of the element
    'value' => $data,//this is the current value
    //'values' => $this->_getValues(),//this is necessary if the type is select or multiselect
    'inherit' => $inherit,
    'comment' => 'Click inside to change a color of each Category',
    'can_use_default_value' => $this->getForm()->canUseDefaultValue($e),//sets if it can be changed on the default level
    'can_use_website_value' => $this->getForm()->canUseWebsiteValue($e),//sets if can be changed on website level
    ))->setRenderer($this->_getFieldRenderer());

    ReplyDelete
  6. Hi Marius,

    Thank u for share this post how can change group value drop down to text field. plz help me

    ReplyDelete
  7. Hi..

    Am not get any link to admin section.. can you help me?

    ReplyDelete
  8. Hi am not get any like to admin section. am using magento version 1.9-master, please help me.

    ReplyDelete