Creating custom product filter

In this tutorial I show how in easy way create the module that will allow user to browse products by attribute options and categories. This will be done using custom controller and attributes passed by URL which will make our site more SEO friendly.

Let’s create module directories:

  • app/code/local/MageDev/Filter – location of filter module files
  • app/code/local/MageDev/Filter/etc – config data
  • app/code/local/MageDev/Filter/controllers – custom controllers
  • app/code/local/Block – here goes blocks logic
  • app/code/local/Helper – general functionality

Configuration data goes to config.xml file inside etc directory:

<?xml version="1.0"?>
<config>
  <modules>
    <MageDev_Filter>
      <version>0.0.1</version>
    </MageDev_Filter>
  </modules>
 
  <global>
    <blocks>
      <MageDev_Filter>
        <class>MageDev_Filter_Block</class>
      </MageDev_Filter>
    </blocks>
 
    <helpers>
      <MageDev_Filter>
        <class>MageDev_Filter_Helper</class>
      </MageDev_Filter>
    </helpers>
  </global>
 
  <frontend>
    <routers>
      <filter>
        <use>standard</use>
        <args>
          <module>MageDev_Filter</module>
          <frontName>filter</frontName>
        </args>
      </filter>
    </routers>
 
      <layout>
        <updates>
           <MageDev_Filter>
              <file>MageDev_Filter.xml</file>
           </MageDev_Filter>
        </updates>
      </layout>    
  </frontend>
</config>

Let’s assume the module will filter products by category and color attribute. The block code goes into app/code/local/MageDev/Filter/Block/Form.php

<?php
class MageDev_Filter_Block_Form extends Mage_Core_Block_Template 
{
  public function getForm()
  {
    $form = new Varien_Data_Form();
    $form->setId('filter-form')
       ->setMethod('GET')
       ->setAction($this->getUrl('*/*/result'))
       ->setUseContainer(true);
 
    $form->addElement($this->getAttributeSelect('color')->setValue($this->getRequest()->getParam('filterColor', 0)));
    $form->addElement($this->getCategorySelect()->setValue($this->getRequest()->getParam('filterCategory', 0)));
    $submit = new Varien_Data_Form_Element_Submit(array('value' => 'Filter'));
    $submit->setId('submit-filter');
    $form->addElement($submit);
    return $form;
  }
 
  public function getAttributeSelect($attributeCode = null, $attributeLabel = null)
  {
    if (null === $attributeCode || !count($options = Mage::helper('MageDev_Filter')->getAttributeOptions($attributeCode))) {
      return null;
    }
 
    $element = new Varien_Data_Form_Element_Select();
 
    $label = null !== $attributeLabel ? $attributeLabel : $attributeCode;                            
 
    $element->setName('filter' . ucfirst($attributeCode))  
        ->setId('filter' . ucfirst($attributeCode));
 
    array_unshift($options, array('label' => ' -- All --', 'value' => 0));
 
    $element->setValues($options)
        ->setLabel(ucfirst($label));
 
    return $element;
  }
 
  public function getCategorySelect()
  {
    if (!count($options = Mage::helper('MageDev_Filter')->getCategoryOptions())) {
      return null;
    }
 
    $element = new Varien_Data_Form_Element_Select();
    $element->setName('filterCategory')
        ->setId('filterCategory');
 
    array_unshift($options, array('label' => ' -- All --', 'value' => 0));
 
    $element->setValues($options)
          ->setLabel('Category');
 
    return $element;
  }
 
  public function _toHtml()
  {
    return $this->getForm()->toHtml();
  }
}

Note that _toHtml method is defined in the block so it does not require template to generate HTML output. The getAttributeOptions and getCategoryOptions methods used to fill appropriate select elements are defined in app/code/local/MageDev/Filter/Helper/Data.php

<?php
class MageDev_Filter_Helper_Data
{
    public function getAttributeOptions($attribute_code) {
      $attribute = Mage::getModel('catalog/entity_attribute');
        $attribute->setStoreId(Mage::app()->getStore()->getId())->loadByCode('catalog_product', $attribute_code);
        $options = array();
       if ($attribute->getData('is_visible')) {
         $options = Mage::getResourceModel('eav/entity_attribute_option_collection')
           ->setAttributeFilter($attribute->getId())
           ->setStoreFilter()
           ->load()
           ->toOptionArray();
      }  
 
           return $options;
   }
 
   protected function _addCategoryOption($categoryData, $levelPrefix = '&raquo;', $startAtLevel = 2)
   {
       if ($categoryData['level'] >= $startAtLevel) {
         return array(
           'value'  => $categoryData['entity_id'],
           'label'  => str_repeat($levelPrefix, $categoryData['level'] - $startAtLevel) . $categoryData['name']
         );
       }
 
       return null;
   }
 
   public function getCategoryOptions()
   {
    $options = array();
       $categoryCollection = Mage::getModel('catalog/category')->load(Mage::app()->getStore()->getRootCategoryId())->getCollection()->addAttributeToSelect('name');
    foreach ($categoryCollection as $categoryData) {
      if ($option = $this->_addCategoryOption($categoryData)) {
        $options[] = $option;
      }
    }
 
    return $options;
   }
}

_addCategoryOption method accepts three arguments:

  • $categoryData – associative array containing data of currently processed category
  • $levelPrefix – string added as a prefix of each level of category tree
  • $startAtLevel – integer defining level from which category tree elements should be added to options. By default is set to 2 in order to omit root category (at level 1).

Now it’s time to define actions responsible for displaying and processing filter form. Below is the code of module’s index controller located in app/code/local/MageDev/Filter/controller/IndexController.php file:

<?php
class MageDev_Filter_IndexController extends Mage_Core_Controller_Front_Action
{
  public function indexAction()
  {
    $this->loadLayout();
    $this->renderLayout();
  }
 
  public function resultAction()
  {
    $this->loadLayout();
    $this->renderLayout();
  }
}

Peace of cake – it just displays blocks defined in /app/design/frontend/[PackageName]/[ThemeName]/layout/MageDev_Filter.xml

<?xml version="1.0"?>
<layout version="0.0.1">  
  <filter_index_index>
      <reference name="root">
            <action method="setTemplate"><template>page/1column.phtml</template></action>
        </reference>
    <reference name="content">
      <block type="MageDev_Filter/Form" name="filter_form" />
    </reference>
  </filter_index_index>
 
  <filter_index_result>
    <reference name="content">
      <block type="MageDev_Filter/Form" name="filter_form" />
      <block type="MageDev_Filter/Result" name="filter_result" template="catalog/product/list.phtml" />
    </reference>
  </filter_index_result>
</layout>

As you can see, on results page I used default product list so it will follow general store design. The block code also uses product list functionality by extending Mage_Catalog_Block_Product_List class (app/code/local/MageDev/Filter/Block/Result.php)

<?php
class MageDev_Filter_Block_Result extends Mage_Catalog_Block_Product_List
{
  protected function _getProductCollection()
  {
    if (is_null($this->_productCollection)) {
      $collection = Mage::getResourceModel('catalog/product_collection');
      Mage::getModel('catalog/layer')->prepareProductCollection($collection);
 
      $colorId = $this->getRequest()->getParam('filterColor', 0);
      $categoryId = $this->getRequest()->getParam('filterCategory', 0);
 
    if (!$colorId && !$categoryId) {
    $this->_redirect('*/*/index');
    }
 
      if ($colorId) {
        $collection->addAttributeToFilter('color', $colorId);
      }
 
      if ($categoryId) {
        $category = Mage::getModel('catalog/category')->load($categoryId);
        $collection->addCategoryFilter($category, true);
      }
 
      $this->_productCollection = $collection;
    }
 
    return $this->_productCollection;
  }
}

That’s it – you obviously can add as many attributes as you wish. Methods responsible for getting options of attribute and categories might also be useful for other purposes, so you can reuse the helper class in your own modules.

26 thoughts on “Creating custom product filter”

  1. it doesnt seem to work on my part.. i was adding a block in a content but there is no result being shown.. this is the block of code.. {{block type=“gebana_filter/result“ name=“filter_result“ template=“catalog/product/list.phtml“}}

  2. Hello Jason. I assume you use the code to insert block into cms page. Please ensure you call this page with custom parameters (filterColor or filterCategory). You can do it by redirecting form to your cms page or by preparing url to specify these attributes. You can also check if Magento sees your block by adding following method:

    public function __construct()
    {
        echo 'Test'; die;
    }

    to your Gebana_Filter_Block_Result class.

  3. hi man ,
    It’s possible to filter the attribute’s value ?
    For example : I have a attribute called a ,it’s number, on the frontend page I wanna filter the attribute great then 300(it’s value) not id, any ideas ???
    THANKS!

  4. Hi,

    Here’s the code that does the trick:

        $collection = Mage::getResourceModel('catalog/product_collection');
        Mage::getModel('catalog/layer')->prepareProductCollection($collection);
        $collection->addAttributeToFilter('a', array('gt' => 300));
  5. Thanks for great tutorial

    Do you have any idea how to filter attribute’s value like this:
    I have a brand’s names (attribute code = brand ) and
    wanted to add to filter only brands which relate to existing products in DB
    (so if there are no products assigned to attribute, i don’t want include this attribute to filter)…

    For categories filter i use getProductCount() function, but i don’t have any idea how to get same for attributes…

  6. Hi Controle_r,

    It’s quite easy thanks to Magento layout system. To display form in left column change layout file content to:

    <?xml version="1.0"?>
    <layout version="0.0.1">  
      <filter_index_index>
          <reference name="root">
                <action method="setTemplate"><template>page/2columns-left.phtml</template></action>
            </reference>
        <reference name="left">
          <block type="MageDev_Filter/Form" name="filter_form" />
        </reference>
      </filter_index_index>
     
      <filter_index_result>
        <reference name="content">
          <block type="MageDev_Filter/Form" name="filter_form" />
          <block type="MageDev_Filter/Result" name="filter_result" template="catalog/product/list.phtml" />
        </reference>
      </filter_index_result>
    </layout>

    You can find more information related to themeing in Designer’s Guide to Magento

  7. Hi Igor,

    You can try to use CatalogIndex module and get counts by direct query to catalogindex_eav table or by the use of Mage_CatalogIndex_Model_Attribute::getCount() method. This might not be 100% accurate as it depends on the state of catalog index.

    Hope that helps

  8. Hi wonder if you can help me.
    i made an Attributes caled ringsize. it shows all avalible ringsize, in the shop by section on the front page,

    i like it but, its not the best place for it,.
    so i have disabled it there,

    what i want is, it to show the ringsize elswhere, in a static block.
    or in the shop by but only when i have navigated to the rings section,

    best regards ole

  9. Hi Ole,

    If you already have a block (static or dynamic) that you are happy with and just want to display it in specific categories, go to Catalog->Manage Categories, select rings category and in Custom Layout Update tab place the ringsize block. You can also specify where this block should be visible by ‘Apply to’ option.

  10. hi,

    it doesn’t seem to work for me on Magento 1.4.1 … it’s ok for the search form (need to declare the module in app/etc/module)

    but I have error (404 not found) for the result page : if I insert search form in the content of my category, url defined for the form is catalog/category/resultt/?filterColor=7&filterCategory=3&=Filter

    I don’t change anything in this original piece of code and I use the default catalog/product/list.phtml template param for result

    If someone can help me with a step by step explanation …

    Thanks a lot !!!

  11. it works if I replace

    setAction($this->getUrl(‘*/*/result’))

    by

    setAction($this->getUrl(‘filter/index/result’))

    If someone can help me to understand … I’m newbie with magento

  12. Just another question … if someone can help me …

    How can I add checkbox filter ? ex. if I want to have color attribute in checkbox and not select list … so I can choose 2 or 3 colors in my filter

    Thanks a lot !!!

  13. hi,
    I tried to add this module, but I have one problem. I dont know how to get result, whent I submit my form. I always get “404 eror,page not found”. I think that I missed someone. Anyone can help? Thanks.

  14. hi i followed the instructions you said,but i cant see this filter at my home page. is there anything else have to do to display this filter? can u plz help me

  15. good tutorial

    public function testAction()
    {
    var_dump(
    (string)
    Mage::getModel(‘catalog/product’)
    ->getCollection()
    ->addFieldToFilter(‘sku’,’your sku’)
    ->getSelect());
    }
    may also return the qury!!

    check it out

  16. I have followed the instructions but do not see any filter, is this include the display code as well or i have to add something?

    Thanks.

  17. I’m using your module for filtration on product listing page in 1column layout but when I’m submitting the form it redirects to 404 page.

    the url which comes is “(mydomain)/catalog/category/result/?filterTypeType=5&filterCategory=0&=filter”

    can please help me out

  18. Hi,

    nice tutorial, exactly what i need. :-)

    But, I dont get any output :-/ I tried it on Magento 1.4.1.1

    Did you get any idea?

    I already cleared the cache and created a xml in /app/etc/modules/ … I also read about confix.xml adminhtml.xml … but I dont get it running : /

    Any hints are appreaciated :-)

    Cheers,
    Matthias

  19. Hy,
    Thanks for the elaborate instructions.
    Can I add a browsable custom filter using the Magento forms from the backend rather than using code.

    Looking forward to getting some light on this.

    Best,
    Amit

  20. hi, i’m newbie , can you point out how to use it?
    i did’t understand the your replaying as flow , i’m stuck here , i’m trying to understand what it was?

    “Hello Jason. I assume you use the code to insert block into cms page. Please ensure you call this page with custom parameters (filterColor or filterCategory). You can do it by redirecting form to your cms page or by preparing url to specify these attributes. You can also check if Magento sees your block by adding following method:

    public function __construct()
    {
    echo ‘Test’; die;
    }

    to your Gebana_Filter_Block_Result class.

Comments are closed.