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.

  • Vishalsanwar

    hi,
    It is great tutorial, but  i m  facing a problem ,when i click on Customer tab.ajax loading image display but there is no grid display,plz give me the solution for these as soon as possible,

    Thanx

    Vishal

    • Manish Prakash

      Did you download and copy the entire module to your magento? Or are writing code yourself?

      • Vishalsanwar

         Hi manish

        yes i am writing my own code. i have a good news my grid is show and when i click on save it is go to saveAction().
        but i am not getting the value of all checked box.

        and as same as product i am also take the hidden fields..

        plz tell  me how to get the values of selected checked boxes..

        Thanx
        Vishal

        • Tim Dirckx

          For everybody who is looking for this answer:

          Add ‘field_name’ => ‘checboxname[]’ to the in_products array.

      • Nigam

        I am trying to download via facebook login but it seems show fatal error.

  • Ajay Makwana

    Hi
    I have override the catalog product admin tab and product controller for creating the tab.
    The tab and pagination is working fine but the issue that when I am trying to make pagination or sorting there is “too much recursion error” with prototype.js.
    So please help me on that.
    Regards,
    Ajay

    • Shaheer Ali

      add the code to your grid.php file

      public function getGridUrl()
      {
      return $this->getData(‘grid_url’)
      ? $this->getData(‘grid_url’)
      : $this->getUrl(‘*/*/productsgrid’, array(‘_current’ => true));
      }

  • Humayun

    Great post… I’m writing my own code and integrated your code in but when I click on the tab, it shows loading for few seconds and then do nothing (doesn’t even show customer grid). Can you tell where could be the problem? Also how can I change customer grid to product grid?

  • Haney

    At the moment one customer can be assigned to one record and when you add another item and go to customer list, it doesn’t show the previously ticked customer. What needs changing to assign one customer to multiple items?

    • Manish Prakash

      In the file

      Excellence_Manager_Model_Mysql4_Grid

      Change the function addGridPosition to below and let me know if this works.

      public function addGridPosition($collection,$manager_id){

      $table2 = $this->getMainTable();

      $cond = $this->_getWriteAdapter()->quoteInto(‘e.entity_id = t2.customer_id’,”);
      $collection->getSelect()->joinLeft(array(‘t2’=>$table2), $cond);

      $collection->getSelect()->group(‘e.entity_id’);

      // echo $collection->getSelect();

      }

  • Bart

    Excellent information, Manish. Thank you for posting.

    I’m working through a difficult module admin and have encountered a bottleneck: I’ve implemented my grid inside the Grid Container but unlike your example, clicking on one of the grid rows allows the user to edit a form. I’ve implemented this using a custom action “editform” and I’ve gotten this working as planned.

    Here’s the sticky wicket: When the edit form template displays, the product tabs don’t display in the left column (no left column at all actually). I’ve tried adding the in the layout xml and also injecting the block programmatically but it kills the page render every time – no error logs or reports either.

    I’m guessing that this is possible but I’m stumped. Does anyone know if it’s possible to display an instance of class Mage_Adminhtml_Block_Widget_Form_Container inside of the Tabbed product interface?

  • Haney

    On the customer grid page its showing ‘Total 1 record found’ and ‘1 of 1 pages’ on the top of the grid but there are more than 100 customers and they all are loading as well. Its just not showing the right count and cannot even navigate to the next page or record set.

    • Nirav

      In Excellence/Manager/Model/Mysql4/Grid.php file,

      Comment following line:

      $collection->getSelect()->group(‘e.entity_id’);

      By doing this your issue will be solved.

      Enjoy Coding……

  • Craig

    Not to worry… its in grid_manager and already in id form 😉 thanks for saving me a day or two’s work Manish

  • Rike

    Hi,

    with help of this tutorial I have a custom product grid with editable column and with serializer. But if I click to next page it takes long – and then opens a window with information,
    that a skript is busi or don’t answers and wheather I want to stop it.

    What could be wrong?? And where could I search??

  • Vishal

    How we set default selected ids in hidden variable on grid loading first time ?

  • varsha

    Nice Post

    But i have some issue. I need to remove position column from grid and there is no any other text field column in my grid. So if i remove position column, the proper data is not posted. so my feature is not worked perfectly. I even cant change the “addColumnInputName” value in xml file. So there is any solution to remove position column from this grid without effecting on post data??

  • Pradeep shyam

    Everything work perfectly except I got an error while loading next page in grid.

    RangeError: Maximum call stack size exceeded.

    Please help me

    • Frank Hernandez

      Did you ever resolve this? I have the same issue right now.

  • Jyoti Ranjan Singh

    Hi Manish,

    Thanks for the brilliant article this was a great help for creating a grid serializer for products.But I am stuck at one point . when i am clicking on the check box listed in the product grid.The hidden field is not getting populated.I am unable to get what is the reason for this problem .is it related to the layout.xml or with the block file.I am messed up at this. i will be really thankful if u would guide me about how the checkbox and hidden field are related…

  • Sanraj

    Hi i want to remove check box selection on clicking on any row of grid. My checking box is getting checked. Plz tell me solution over this. thnx.

  • Lenz Nice

    Hey Manish,

    great post and my serializer works fine now, but I
    cant get the sorting option by “position” working. Either in the
    frontend nor in backend grid. What exactly do you mean with “In your
    table, we need to an extra column called position as well, you can
    always use this for sorting in frontend.”?

    I added a
    column called “position” to both the parent and the child table but no
    values are beeing saved. Do I have to add something to the saveAction?

    Thanks in advance, I hope you still read those comments here .. 😉

  • Shaheer Ali

    if anyone have recursive error in pagination add the following code to your grid.php file

    add the code to your grid.php file

    public function getGridUrl()
    {
    return $this->getData(‘grid_url’)
    ? $this->getData(‘grid_url’)
    : $this->getUrl(‘*/*/productsgrid’, array(‘_current’ => true));
    }

  • Frank Hernandez

    The Pagination does not seem to work. it only shows you one page of results in the grid.

  • Son Leung

    Where Can I download the source code? thank you!

    source code

  • ND

    How to solve this problem Fatal error: Call to a member function setCustomers() in Controller

  • This article is really old but it is useful! I have already created my own module but I get a problem at the loading time: all the saved information in the database are loaded but their are not set as checked in the grid. Seems that the Javascript code is not fired at the “boot of the serialized grid”. Any idea? Thanks!

  • Trịnh Huy Hoàng

    Hi Manish, where exactly is the Grid Serializer implementation in Magento? I need to understand it so I can customize it? Thanks.

  • C

    I’m using this to display a product grid, how can I add the store view to this grid?

    • Manish Prakash

      If you want to add Store View to the grid, you need to follow some steps:

      In your module’s adminhtml edit/create form (__Block_Adminhtml__Edit_Tab_Form add the following in the _prepareForm function where you would like the store selection to show up:

      if (!Mage::app()->isSingleStoreMode()) {

      $fieldset->addField(‘store_id’, ‘multiselect’, array(

      ‘name’ => ‘stores[]’,

      ‘label’ => Mage::helper(”)->__(‘Store View’),

      ‘title’ => Mage::helper(”)->__(‘Store View’),

      ‘required’ => true,

      ‘values’ => Mage::getSingleton(‘adminhtml/system_store’)

      ->getStoreValuesForForm(false, true),

      ));

      }

      else {

      $fieldset->addField(‘store_id’, ‘hidden’, array(

      ‘name’ => ‘stores[]’,

      ‘value’ => Mage::app()->getStore(true)->getId()

      ));

      }

      (This adds the store view multiselect to your form if you have multiple stores setup, if not it adds a hidden field with the current stores id taking into consideration the posibility of more stores being added in the future.)

      Next in your module’s Controller file(__Adminhtml_Controller) add the following to your saveAction:

      if(isset($data[‘stores’])) {

      if(in_array(‘0’,$data[‘stores’])){

      $data[‘store_id’] = ‘0’;

      }

      else{

      $data[‘store_id’] = implode(“,”, $data[‘stores’]);

      }

      unset($data[‘stores’]);

      }

      (This sets store_id to 0 if “All Store Views” was selected or sets store_id as comma-seperated values representing the store selection.)

      In your modules Grid file (__Block_Adminhtml__Grid), add the following:

      protected function _prepareCollection(){

      $collection = Mage::getModel(‘_/’)->getCollection();

      foreach($collection as $link){

      if($link->getStoreId() && $link->getStoreId() != 0 ){

      $link->setStoreId(explode(‘,’,$link->getStoreId()));

      }

      else{

      $link->setStoreId(array(‘0’));

      }

      }

      $this->setCollection($collection);

      return parent::_prepareCollection();

      }

      (This parses the collections store_id’s and resets them as an Array, taking into consideration any that might not be assigned yet or any with a value of 0, which represents ‘All Store Views’)

      In the grids prepareColumns function add:

      if (!Mage::app()->isSingleStoreMode()) {

      $this->addColumn(‘store_id’, array(

      ‘header’ => Mage::helper(”)->__(‘Store View’),

      ‘index’ => ‘store_id’,

      ‘type’ => ‘store’,

      ‘store_all’ => true,

      ‘store_view’ => true,

      ‘sortable’ => true,

      ‘filter_condition_callback’ => array($this,

      ‘_filterStoreCondition’),

      ));

      }

      (If you have multiple stores setup this will add the “Store View” column with the store selection drop down, if no column is added as its not needed.)

      and add the following function:

      protected function _filterStoreCondition($collection, $column){

      if (!$value = $column->getFilter()->getValue()) {

      return;

      }

      $this->getCollection()->addStoreFilter($value);

      }

      (This function checks to see if a store filter has been selected and if so calls the function to add the filter to the collection.)

      Lastly in your modules Collection file(__Model_Mysql4__Collection) add this function:

      public function addStoreFilter($store, $withAdmin = true){

      if ($store instanceof Mage_Core_Model_Store) {

      $store = array($store->getId());

      }

      if (!is_array($store)) {

      $store = array($store);

      }

      $this->addFilter(‘store_id’, array(‘in’ => $store));

      return $this;

      }

    • Manish Prakash

      Hi Chris,
      You need to add join query in prepare collection to show store view in grid.

      Thanks