In Magento selectable grids, each time after a grid operation is performed (whether it’s filtering the results, pagination or something else), the selected values get lost. In other words, Magento by default keeps only values selected after the grid is initialized in the beginning and loses them each time the grid gets reloaded by Ajax call. Fortunately, Magento has a built-in library called Grid Serializer to solve this issue.
To make it simple, Grid Serializer is nothing more than a container made of two parts: a grid and a hidden input field that are distinctly separated. Hidden input is used for storing serialized selected id’s and is populated with Javascript each time a row is selected. As it’s separated from the grid, the hidden input content is preserved each time the grid is reloaded and is used for auto-selecting the rows each time the grid is reloaded.
Before we start with the code, as this is a bit more advanced grid, it is assumed that you’ve already mastered the basics of Magento grids since only differences will be shown how to create ajax-like grids.
Structure
It is already said that Grid Serializer splits the grid in two parts where only the grid gets reloaded, leaving the rest of the page intact as opposite to default action where the entire tab is reloaded. In order to make that happen, layout and controller has to distinguish those two parts. As it can be seen on the image below, we have to separate the entire tab content, which initially includes the a hidden input type and the grid itself, and the grid that will be replaced.
For this we’ll need to have two actions defined in both layout file and controller: customersTab and customersGrid (or the tab and grid content).
Visible in the code below, the tab content defined by the <adminhtml_ajaxgrid_customerstab> handle includes both the grid content and the hidden input type blocks, while the grid-only content defined by the <adminhtml_ajaxgrid_customersgrid> handle is a stripped version of the tab content, containing only the grid block.
<?xml version="1.0"?>
<layout>
<adminhtml_ajaxgrid_edit>
<reference name="left">
<block type="inchoo/adminhtml_edit_tabs" name="ajaxgrid.edit.tabs" />
</reference>
<reference name="content">
<block type="inchoo/adminhtml_edit" name="ajaxgrid.index" />
</reference>
</adminhtml_ajaxgrid_edit>
<adminhtml_ajaxgrid_customerstab>
<block type="core/text_list" name="root" output="toHtml">
<block type="inchoo/adminhtml_edit_tab_customers" name="inchoo.tab.customers"/>
<block type="adminhtml/widget_grid_serializer" name="inchoo.serializer.customers">
<action method="initSerializerBlock">
<grid_block_name>inchoo.tab.customers</grid_block_name>
<data_callback>getSelectedCustomers</data_callback>
<hidden_input_name>customer_ids</hidden_input_name>
<reload_param_name>customers</reload_param_name>
</action>
</block>
</block>
</adminhtml_ajaxgrid_customerstab>
<adminhtml_ajaxgrid_customersgrid>
<block type="core/text_list" name="root" output="toHtml">
<block type="inchoo/adminhtml_edit_tab_customers" name="inchoo.tab.customers"/>
</block>
</adminhtml_ajaxgrid_customersgrid>
</layout>
In order to make Grid Serializer work, all those things have to be linked somehow, which is why the hidden input field has a few parameters left to be defined.
grid_block_name: name of the grid block
data_callback: method in the grid block that returns selected id’s on grid initialization
hidden_input_name: name of the hidden input type
reload_param_name: name of the parameter used in URL
Having the layout defined, controller has to add some actions to those handles. The tab action (customersTabAction) is supposed to collect the id’s, which is usually a collection that returns the id’s as array, to load the block defined in the layout and to set the selected id’s.
The grid action (customersGridAction) does the similar thing to the tab action, again, but the difference is that the selected id’s are read from the URL parameter which is defined in the layout (reload_param_name).
In the save action selected id’s are taken from the hidden input field from the parameter name defined in the layout (hidden_input_name). Grid Serializer comes with a handy method called decodeGridSerializedInput() which decodes a string into an array.
class Inchoo_Module_Adminhtml_AjaxgridController
extends Mage_Adminhtml_Controller_Action
{
public function editAction()
{
$this->loadLayout()
->_title($this->__('Customer Grid'))
->renderLayout();
}
public function customersTabAction()
{
// used for selecting customers on tab load
$saved_customer_ids = array(); // your load logic here
$this->loadLayout()
->getLayout()
->getBlock('inchoo.tab.customers')
->setSelectedCustomers($saved_customer_ids);
$this->renderLayout();
}
public function customersGridAction()
{
$this->loadLayout()
->getLayout()
->getBlock('inchoo.tab.customers')
->setSelectedCustomers($this->getRequest()->getPost('customers', null));
$this->renderLayout();
}
public function saveAction()
{
if ($customersIds = $this->getRequest()->getParam('customer_ids', null)) {
$customersIds = Mage::helper('adminhtml/js')->decodeGridSerializedInput($customersIds);
}
// your save logic here
}
}
Blocks
Having the structure of the tab defined, there are only a few things left to change in the blocks. Let’s start with the Tab block first.
In the _beforeToHtml() method, where the tabs are usually defined, several parameters have to be changed. The first thing would be to omit the content parameter, that is usually set by default, and to replace it with the “url” parameter. That parameter has to define an url for the tab content. The other thing would be to add a class parameter with the value “ajax”.
protected function _beforeToHtml()
{
$this->addTab('customers', array(
'label' => $this->__('Customers'),
'title' => $this->__('Customers'),
'url' => $this->getUrl('*/*/customerstab', array('_current' => true)),
'class' => 'ajax'
));
return parent::_beforeToHtml();
}
The last file that has to be changed is the grid block itself. One thing would be to use a variable containing the selected id’s, which is set by the controller (getSelectedCustomers() in this example). The other thing would be to define an URL of the grid content, which is why a getGridUrl() method is defined. Make sure to have setUseAjax(true) variable defined as well.
class Inchoo_Module_Block_Adminhtml_Edit_Tab_Customers
extends Mage_Adminhtml_Block_Widget_Grid
{
public function __construct()
{
parent::__construct();
$this->setSaveParametersInSession(false);
$this->setUseAjax(true);
$this->setId('inchoo_customers');
}
protected function _prepareCollection()
{
$collection = Mage::getResourceModel('customer/customer_collection')
->addNameToSelect();
$this->setCollection($collection);
return parent::_prepareCollection();
}
protected function _prepareColumns()
{
$this->addColumn('selected_customers', array(
'header' => $this->__('Popular'),
'type' => 'checkbox',
'index' => 'entity_id',
'align' => 'center',
'field_name'=> 'selected_customers',
'values' => $this->getSelectedCustomers(),
));
$this->addColumn('name', array(
'header' => $this->__('Name'),
'index' => 'name',
'align' => 'left',
));
return parent::_prepareColumns();
}
public function getGridUrl()
{
return $this->getUrl('*/*/customersGrid', array('_current' => true));
}
}
The result
Not a lot of changes are needed to create ajax-like grids in Magento by using Grid Serializer while a lot of things can have benefit of that. With this library selected id’s are preserved on grid reloads which is why pages can be changed, filters applied, sorting changed without losing the selection.
Additionally, the grid won’t be populated and collections won’t be run until the tab is selected. Having that in mind, it has to be extra careful when saving id’s since it has to distinguish whether none of the id’s are selected and when the tab is not loaded at all.
There’s a small glitch that I have found when working on this. When viewing a grid on smaller screens where the grid can be scrolled up and down, each time a row is selected the page jumps to the top. It’s a bit irritating, but looking around in Magento I have found a few of the pages that use Grid Serializer natively and that share the same problem (some of the pages have custom solutions), but that’s really a tiny thing compared to what the library offers.