Add Grid In Admin Form Tabs – Grid Serializer

In this tutorial you will learn, how to properly put the magento admin grid inside the tabs on an admin form.

This tutorial is only for very advanced magento users, who already added a grid in admin tabs, and faced problems.
Attached is source code for this blog post
[dm]6[/dm]
Here is a screenshot of what exactly this blog is about See Screenshot
P.S: before reading this tutorial make sure you have a strong understanding of magento admin forms and grids. Refer to this blog.
To explain the Grid Serializer, we will take a client requirement as an example. Example scenario taken here is, we have to create an entity called Customer Manager. Customer Manager, is assigned a group of customers whom he will manage. So in admin, we need to be able to select few customers from the entire customer list, and them associate/save them to a particular customer manager. So, in the Customer Manager edit form, will show a grid which has all the customers. In this grid, we will have a column with checkboxes where we will select the customers. This is similar to the Related Products, Cross-sell Products in the edit product forms in admin.
The default and easy way to do this is: In the Tabs.php we create for the admin form, there is an addTab() function in which we pass the class’s path, of the forms we want to add. What we can do is simply, put the class of our grid here.
For example:

$this->addTab('form_section', array(
          'label'     => Mage::helper('test')->__('Sample Grid'),
          'title'     => Mage::helper('test')->__('Sample Grid'),
          'content'   => $this->getLayout()->createBlock('fav/adminhtml_fav_edit_tab_grid')->toHtml(),
      ));

This will show a Grid on the particular tab added. But there are many problems with this implementation. Problems will be faced in relation to add/delete of items, when doing searching/pagination.

Correct Method To Do This:
First in your Tabs.php file add this code

$this->addTab('form_section1', array(
         'label'     => Mage::helper('test')->__('Customers'),
         'title'     => Mage::helper('test')->__('Customers'),
         'url'       => $this->getUrl('*/*/customer', array('_current' => true)),
         'class'     => 'ajax',
      ));

What this is does is, when you click on Customers Tab, it will call the url mentioned using ajax. So, next in the URL, will display the grid.
In our controller, in the customerAction() will put this code.

public function customerAction(){
		$this->loadLayout();
		$this->getLayout()->getBlock('customer.grid')
		->setCustomers($this->getRequest()->getPost('customers', null));
		$this->renderLayout();
	}

What this is does is simple. Simple loads layout and then renders it.So, the main part is written in our adminhtml layout file for the module. This is what we put in the layout file.

 <test_adminhtml_test_customer>
        <block type="core/text_list" name="root" output="toHtml">
            <block type="test/adminhtml_test_edit_tab_grid" name="customer.grid"/>
            <block type="adminhtml/widget_grid_serializer" name="grid_serializer">
                <reference name="grid_serializer">
                    <action method="initSerializerBlock">
                        <grid_block_name>customer.grid</grid_block_name>
                        <data_callback>getSelectedCustomers</data_callback>
                        <hidden_input_name>links[customers]</hidden_input_name>
                        <reload_param_name>customers</reload_param_name>
                    </action>
                    <action method="addColumnInputName">
                        <input_name>position</input_name>
                    </action>                   
                </reference>
            </block>
        </block>
    </test_adminhtml_test_customer>

The first block, is simple the it points to the block class of our grid. The second block is important, it adds a grid serializer. What this does is, it creates a hidden input type, where the values of your checkboxes which you check/uncheck are maintained.
Some explanation of the xml

<action method="initSerializerBlock">
                        <grid_block_name>customer.grid</grid_block_name>
                        <data_callback>getSelectedCustomers</data_callback>
                        <hidden_input_name>links[customers]</hidden_input_name>
                        <reload_param_name>customers</reload_param_name>
                    </action>
  • <grid_block> has the name of our grid
  • <data_callback> this is function defined inside our grid, where we will return the values of exists selected entries
  • <hidden_input_name> this is the name of the input type hidden created
  • <reload_param_name> not sure yet where this is used
<action method="addColumnInputName">
     <input_name>position</input_name>
</action>

This is the name of the column in grid called position (this needs to be a text field). Apparently to use grid serializer, you need a text field type column in your grid (Still researching alternatives)
Now, to look at the code of the Grid.php file. Grid file will mostly be same as your usual grid file, so I will only point out here new things to add.

	public function __construct()
	{
		parent::__construct();
		$this->setId('customerGrid');
		$this->setUseAjax(true); // Using ajax grid is important
		$this->setDefaultSort('entity_id'); 
		$this->setDefaultFilter(array('in_products'=>1)); // By default we have added a filter for the rows, that in_products value to be 1
		$this->setSaveParametersInSession(false);  //Dont save paramters in session or else it creates problems 
	}

Next, we will add a custom filter for the checkboxes we are showing.

protected function _addColumnFilterToCollection($column)
	{
		// Set custom filter for in product flag
		if ($column->getId() == 'in_products') {
			$productIds = $this->_getSelectedCustomers();
			if (empty($productIds)) {
				$productIds = 0;
			}
			if ($column->getFilter()->getValue()) {
				$this->getCollection()->addFieldToFilter('entity_id', array('in'=>$productIds));
			} else {
				if($productIds) {
					$this->getCollection()->addFieldToFilter('entity_id', array('nin'=>$productIds));
				}
			}
		} else {
			parent::_addColumnFilterToCollection($column);
		}
		return $this;
	}

as you can see from the code, the filter is self-explanatory. The functions used above are defined below.
Next we will add the checkbox column.

$this->addColumn('in_products', array(
                'header_css_class'  => 'a-center',
                'type'              => 'checkbox',
                'name'              => 'customer',
                'values'            => $this->_getSelectedCustomers(),
                'align'             => 'center',
                'index'             => 'entity_id'                
));

This function $this->_getSelectedCustomers() return the ids of all the existing select customers in the database, which are already associated to the customer manager.
Will also add an extra column called position,

$this->addColumn('position', array(
            'header'            => Mage::helper('catalog')->__('ID'),
            'name'              => 'position',
            'width'             => 60,
            'type'              => 'number',
            'validate_class'    => 'validate-number',
            'index'             => 'position',
            'editable'          => true,
            'edit_only'         => true
            ));

This column is added just to make the serializer work. This column name was given in the adminhtml layout file. This column will have an input type text, which is required for the serializer to work. In your table, we need to an extra column called position as well, you can always use this for sorting in frontend.
Next we will add two more functions.

protected function _getSelectedCustomers()   // Used in grid to return selected customers values.
	{
		$customers = array_keys($this->getSelectedCustomers());
		return $customers;
	}

	public function getSelectedCustomers() 
	{
		// Customer Data
		$tm_id = $this->getRequest()->getParam('id');
		if(!isset($tm_id)) {
			$tm_id = 0;
		}
		$customers = array(1,2); // This is hard-coded right now, but should actually get values from database.
		$custIds = array();

		foreach($customers as $customer) {
			foreach($customer as $cust) {
				$custIds[$cust] = array('position'=>$cust);
			}
		}
		return $custIds;
	}

Used in the layout xml as callback function. As you can see the array format returned here has position as well. This is required. Another function to add is the gridUrl function

public function getGridUrl()
	{
		return $this->_getData('grid_url') ? $this->_getData('grid_url') : $this->getUrl('*/*/customergrid', array('_current'=>true));
	}

this is function used in all grids which are ajax based. This URL is called when we search, paginate etc on the grid. For the above URL, we need add an action function the controller file.

public function customergridAction(){
		$this->loadLayout();
		$this->getLayout()->getBlock('customer.grid')
		->setCustomers($this->getRequest()->getPost('customers', null));
		$this->renderLayout();
	}

and the layout for this would be

<test_adminhtml_test_customergrid>
        <block type="core/text_list" name="root" output="toHtml">
            <block type="test/adminhtml_test_edit_tab_grid" name="customer.grid"/>
        </block>
</test_adminhtml_test_customergrid>

These steps are required to make the grid working. The grid should be working now properly. Just to troubleshoot or see if the grid serializer is working or not. Open your firebug html view, the see the html just after the grid <div> gets over. You should see an input type hidden there See Screenshot
When your select/deselect checkboxes the value in the input type will change. Next, step is when we save our form, the we need to get the value of all selected customers, so we can save them in our database table. To do that in your save action, write the below code

if(isset($data['links'])){
					$customers = Mage::helper('adminhtml/js')->decodeGridSerializedInput($data['links']['customers']); //Save the array to your database
				}

You can use the customers array to save in your database.