Source, frontend and backend models in Magento system settings

In the previous article we have described how the different settings can be applied to a custom extension. There’s nothing difficult for the simple settings such as text field or text area. But, if you look at the drop down, multiselect or editable items list you’ll notice that these items have additional fields, e.g. source_model, backend_model, frontend_model. Let’s focus on these models and find out more info about creating settings that require data models.

Source Model – a model class that serves to get existing values (stored in the db or somewhere else) for further displaying inside the setting’s field.

Frontend Model – as a rule, it’s a block’s class. Methods of this class return html of setting’s field. To be more specific, the block had to have method _getElementHtml() described inside the class which returns the raw html of setting’s field.

Backend Model – a class which allows to operate with configuration data on the different stages (save, load). It contains three major methods respectively for each event: _afterLoad(), _beforeSave() and _afterSave().

If you are interested in, you can always find the working examples of each of them in the Magento core itself. But firstly, let’s try to look on some custom examples of these models. The following part describes how to create a drop down menu with the custom values:

Init setting’s field in your system.xml:

<color>
    <label>Color</label>
    <frontend_type>select</frontend_type>
    <source_model>Atwix_Shoppingbar_Model_Adminhtml_System_Config_Source_Color</source_model>
    <sort_order>2</sort_order>
    <show_in_default>1</show_in_default>
    <show_in_website>1</show_in_website>
    <show_in_store>1</show_in_store>
</color>

Then, create the source model that was specified above:

class Atwix_Shoppingbar_Model_Adminhtml_System_Config_Source_Color
{
   public function toOptionArray()
   {
       $themes = array(
           array('value' => 'green', 'label' => 'Green'),
           array('value' => 'blue', 'label' => 'Blue'),
           array('value' => 'beige', 'label' => 'Beige'),
       );

       return $themes;
   }
}

As you can see, there’s only one method toOptionArray(). It returns an array with the items (value -> label) for the drop down menu. Nothing difficult is here :) Let’s look onto another example to find out how to use frontend and source models. The best point is to review editable items list setting since it has both. The setting described below operates with the text items. It allows to add/remove email addresses:

<addresses>
    <label>Blocked Email Addresses</label>
    <frontend_model>atwix_emailblocker/adminhtml_addresses</frontend_model>
    <backend_model>adminhtml/system_config_backend_serialized</backend_model>
    <sort_order>2</sort_order>
    <show_in_default>1</show_in_default>
    <show_in_website>0</show_in_website>
    <show_in_store>0</show_in_store>
    <can_be_empty>1</can_be_empty>
</addresses>

Frontend model is a block:

class Atwix_Emailblocker_Block_Adminhtml_Addresses extends Mage_Adminhtml_Block_System_Config_Form_Field
{
   protected $_addRowButtonHtml = array();
   protected $_removeRowButtonHtml = array();

   /**
    * Returns html part of the setting
    *
    * @param Varien_Data_Form_Element_Abstract $element
    * @return string
    */
   protected function _getElementHtml(Varien_Data_Form_Element_Abstract $element)
   {
       $this->setElement($element);

       $html = '<div id="emailblocker_addresses_template" style="display:none">';
       $html .= $this->_getRowTemplateHtml();
       $html .= '</div>';

       $html .= '<ul id="emailblocker_addresses_container">';
       if ($this->_getValue('addresses')) {
           foreach ($this->_getValue('addresses') as $i => $f) {
               if ($i) {
                   $html .= $this->_getRowTemplateHtml($i);
               }
           }
       }
       $html .= '</ul>';
       $html .= $this->_getAddRowButtonHtml('emailblocker_addresses_container',
           'emailblocker_addresses_template', $this->__('Add New Email'));

       return $html;
   }

   /**
    * Retrieve html template for setting
    *
    * @param int $rowIndex
    * @return string
    */
   protected function _getRowTemplateHtml($rowIndex = 0)
   {
       $html = '<li>';

       $html .= '<div style="margin:5px 0 10px;">';
       $html .= '<input style="width:100px;" name="'
           . $this->getElement()->getName() . '[addresses][]" value="'
           . $this->_getValue('addresses/' . $rowIndex) . '" ' . $this->_getDisabled() . '/> ';

       $html .= $this->_getRemoveRowButtonHtml();
       $html .= '</div>';
       $html .= '</li>';

       return $html;
   }

   protected function _getDisabled()
   {
       return $this->getElement()->getDisabled() ? ' disabled' : '';
   }

   protected function _getValue($key)
   {
       return $this->getElement()->getData('value/' . $key);
   }

   protected function _getSelected($key, $value)
   {
       return $this->getElement()->getData('value/' . $key) == $value ? 'selected="selected"' : '';
   }

   protected function _getAddRowButtonHtml($container, $template, $title='Add')
   {
       if (!isset($this->_addRowButtonHtml[$container])) {
           $this->_addRowButtonHtml[$container] = $this->getLayout()->createBlock('adminhtml/widget_button')
               ->setType('button')
               ->setClass('add ' . $this->_getDisabled())
               ->setLabel($this->__($title))
               ->setOnClick("Element.insert($('" . $container . "'), {bottom: $('" . $template . "').innerHTML})")
               ->setDisabled($this->_getDisabled())
               ->toHtml();
       }
       return $this->_addRowButtonHtml[$container];
   }

   protected function _getRemoveRowButtonHtml($selector = 'li', $title = 'Delete')
   {
       if (!$this->_removeRowButtonHtml) {
           $this->_removeRowButtonHtml = $this->getLayout()->createBlock('adminhtml/widget_button')
               ->setType('button')
               ->setClass('delete v-middle ' . $this->_getDisabled())
               ->setLabel($this->__($title))
               ->setOnClick("Element.remove($(this).up('" . $selector . "'))")
               ->setDisabled($this->_getDisabled())
               ->toHtml();
       }
       return $this->_removeRowButtonHtml;
   }
}

It’s pretty huge. We have described the full version to show what’s going on there. You can simple extend your block from Mage_GoogleCheckout_Block_Adminhtml_Shipping_Merchant and override the necessary methods.

Backend model, in this case, it’s a standard Magento model:

class Mage_Adminhtml_Model_System_Config_Backend_Serialized extends Mage_Core_Model_Config_Data
{
    protected function _afterLoad()
    {
        if (!is_array($this->getValue())) {
            $value = $this->getValue();
            $this->setValue(empty($value) ? false : unserialize($value));
        }
    }

    protected function _beforeSave()
    {
        if (is_array($this->getValue())) {
            $this->setValue(serialize($this->getValue()));
        }
    }
}

As you can see, it simply unserializes/serializes field’s values before load/save respectively. That’s all. You are always able to improvise with the models described below to get the best result. If you want non-trivial form element – use your own fronted model. To get available values for settings you should use the source model. And if you need to save/load data in your format or make some other actions on these events – feel free to use the backend model.
Good luck and graceful solutions :)

You may also want to read: