2010年9月13日 星期一

Magento - How to add category filter to product grid

After a long time investigation, I finally find a solution of how to add category filter to product grid.

The first thing we need to do is to tell magento that we'd like to add a category filter to product grid just like any other attributes. So in the file app/code/core/Mage/Adminhtml/Block/Catalog/Product/Grid.php, add the following line to _prepareCollection function:

->addAttributeToSelect('category_ids')

You can either call this method in a function chain or use it directly to the product collection $collection.

This is to tell magento that category_ids is a product attribute that later we want the product list to filter by.

After that, we need to visually display this category filter column on the front end. In the same Grid.php file, add the code snippet below into _prepareColumns function:


$categories = Mage::getResourceModel('catalog/category_collection')


->addFieldToFilter(array(array('attribute'=>'level','nin'=>array('0','1'))));

$category_sets = array();

foreach($categories as $category){

$id = $category->getEntityId();

$cat_mod = Mage::getModel('catalog/category')->load($id);

$category_sets[$id] = $cat_mod->getName();

}

$this->addColumn('category_ids',

array(

'header'=> Mage::helper('catalog')->__('Categories'),

'index' => 'category_ids',

'width' => '100px',

'type' => 'options',

'options' => $category_sets

));
 
To add a dropdown type of filter, we need to specify type=>options and options=>$sets when calling addColumn on the grid object. $sets is an array of which elements should be in the format id=>value. The above code snippet use categories that are not super category and root categories [addFieldToFilter(array(array('attribute'=>'level','nin'=>array('0','1')))].
 
If you refresh your product admin page right now, you should be able to see that category filter column shows. But it's only a beginning. There's no corresponding category value in the column for any product!
 
To accomplish that, we will need to find the right place to have category values rendered for products. If you see grid's template grid.phtml and look for the place where column values are displayed, you would guess that in the file app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Options.php, function render is being called.
 
Within this function, getData on the product object is called. That's why we don't get category ids because category_ids isn't actually a product field. Replace the original line:
 
$value = $row->getData($this->getColumn()->getIndex());
 
With the following:
 
$attr = $this->getColumn()->getIndex();


if($attr == 'category_ids'){

$value = $row->getCategoryIds($row);

}else{

$value = $row->getData($attr);

}
 
Refresh the page again, we can see category values displayed in the column. Now comes the most important part: adding category filter logic. Why are we doing this?  When we call $collection->addAttributeToSelect('*') and print all column names in this function, we can see category_ids is listed here but it's not actually a product field. So if corresponding logic is not added for this field and we try to use the category filter, we shoul be able to see the error output on the page.
 
Patiently trace down how product grid is rendered, you would find that in app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Collection.php,
product collection is filtered in the function addAttributeToFilter. Add an elseif block in the last if-else block.
 
elseif(is_string($attribute) && $attribute == 'category_ids'){


if(isset($condition['eq'])){

$this->getSelect()->join(

array('category' => $this->getTable('catalog/category_product')),

'category.product_id=e.entity_id AND category.category_id='.$condition['eq'],

array()

);

}

return $this;

}
 
This is to filter products with the exact same category id with the category filter request.
 
Last but not least, add this statement block to addAttributeToSort function
 
if ($attribute == 'category_ids') {


$this->getSelect()->order('e.entity_id ' . $dir);

return $this;

}
 
This is to perform an action when you click on the filter header to make a descent/ascendent sorting. I only give a sorting method as the id sorter here.
 
Now, let's celebrate! Haha!
 
This solution should work but since I am still new in magento, I would love to share any thoughts about it with anyone!