Layered navigation, but not quite

Layered navigation, but not quite

Recently I got a chance to rewrite Magento’s layered navigation standard functionality. The request was very specific as the client wanted to keep all of the “filters” visible all the time. For example if  you wish to filter your results by Color (let’s say you have yellow, green, red, blue and magenta) products are filtered but the layered navigation displays all filters. This way a costumer can re-filter the products in current category without the need to return to the category view.

Files that are used for layered navigation are situated in app/design/frontend/base/default/template/catalog/layer/ folder. File used for layered navigation is view.phtml – it shows us all of the filters when we click on a category. File used for active state is state.phtml – when we click on one of the filters it is responsible for the results – so we’re gonna edit this one. So copy the state.phtml from base to your package or theme.

This is the original code in state.phtml:

<?php $_filters = $this->getActiveFilters() ?>
<?php if(!empty($_filters)): ?>
<div class="currently">
<p class="block-subtitle"><?php echo $this->__('Currently Shopping by:') ?></p>
<ol>
<?php foreach ($_filters as $_filter): ?>
<li>
<a href="<?php echo $_filter->getRemoveUrl() ?>" title="<?php echo $this->__('Remove This Item') ?>" class="btn-remove"><?php echo $this->__('Remove This Item') ?></a>
<span class="label"><?php echo $this->__($_filter->getName()) ?>:</span> <?php echo $this->stripTags($_filter->getLabel()) ?>
</li>
<?php endforeach; ?>
</ol>
<div class="actions"><a href="<?php echo $this->getClearUrl() ?>"><?php echo $this->__('Clear All') ?></a></div>
</div>
<?php endif; ?>

Since we’re going to need url path of current category add this code before “currently” div block

<?php $obj = new Mage_Catalog_Block_Navigation(); ?>
<?php $_current_category=$obj->getCurrentCategory()->getUrlPath(); ?> //getting url path of current category
<?php $subs = $obj->getCurrentCategory()->getAllChildren(true); ?> //getting ids of subcategories of current category

Now we’re going to edit “currently” div, i renamed my to state, since it doesn’t show just current filter state anymore. Experienced Magento developers will notice that not all of the code is programmed Magento-way, but when I programmed it (Magento CE 1.4.2. ) Magento still didn’t have needed functions. I guess they didn’t expect from someone to use layered navigation this way. So, here we go!

<?php if(!empty($_filters)): ?>
<div class="state">
<p class="block-subtitle"><?php echo $this->__('Currently Shopping by:') ?></p>
<dl>
<?php foreach ($_filters as $_filter): ?>
<dd>
<ol>
<?php $attributemodel=$_filter->filter->_data["attribute_model"]; ?> // getting attribute model that has all filter options in it from currently active filter
<?php $attroptions=$attributemodel->getSource()->getAllOptions();?> // getting attribute options (filters) from attribute model
<?php $_categ= Mage::getModel('catalog/category');?> // object containing all categories and their information
 
<?php foreach($subs as $cat_id): ?>
<?php $_categ->load($cat_id)?> //get the subcategory you need
<?php $collection = $_categ->getProductCollection(); ?> //get the product collection (object containing all product information)
<?php $collection->addAttributeToSelect('color')?> // get the desired attribute
<?php endforeach; ?>

The next thing that needs to be done is to extract the information from attribute model and assemble links. Each of attribute options ($attroptions) contains attribute value (id) and attribute label.

<?php foreach($attroptions as $attr): ?> // get value and label of each attribute
<?php $count=0; ?>
<?php if($attr["value"]!=""): ?>
<?php $val=$attr["value"] ?>
<?php $collection->addFieldToFilter(array(array('attribute'=>'themes','gt'=>10)))?> // collection of attribute values and labels for all values
//greater then 10 (in this case attribute values range was 18-39)
<?php $proddata=$collection->getData() ?> // get product data for all attribute values
<?php if($attr["label"]!= $this->stripTags($_filter->getLabel())): ?> // make a nice looking label
<?php foreach($proddata as $prod):?>
<?php if($prod["type_id"]=="configurable"): ?> // in this store all products were configurable
<?php $split=split(",", $prod["color"]);?> // get the attribute values that correspond with one product (a product may have more
// then one attribute value and they're separated by commas that's why we split the string with "," as deliminator)

Even thought you set your attribute to Filterable(with results) you still have to count the products in order to output only attribute values that have product count >0 .

<?php foreach($split as $color): ?> //check out how many products have the same attribute value
<?php if($color==$attr["value"]): ?>
<?php $count++;?>
<?php endif; ?>
<?php endforeach; ?>
 
<?php endif;?>
 
<?php endforeach; ?>
 
<?php if($count>0):?> // check if any product has that attribute value
<li><a href="<?php echo $this->getUrl('').$_current_category ?>?color=<?php echo $attr["value"]?>" ><?php echo $attr["label"]; ?></a></li> // if not currently active filter make a link
<?php endif; ?>
 
<?php else:?>
<li class="current"> <?php echo $this->stripTags($_filter->getLabel()); ?></li> // if currently active filter write out the label
<?php endif;?>
<?php endif; ?>
<?php endforeach; ?>
 
<?php endforeach; ?> // ending the first for loop (foreach($filters as $filter))
</ol>
 
</dd>
</dl>
<a class="all" style="float:right;" href="<?php echo $this->getClearUrl()?>">All</a> // show all products, return from current state back to category view
</div>
<?php endif; ?>

And that’s it. Our task of keeping the filters visible all the time is finished 🙂 . Now where ever you go using layered navigation you’ll be able to simply re-filter the products in current category without the need to go back to category view. And for the end a brief warning – some code here isn’t programmed Magento-way, because needed functions weren’t available. I hope I explained the problem and solution well enough :).
Cheers!

You made it all the way down here so you must have enjoyed this post! You may also like:

How To Connect Google Analytics 4 To Magento 2 Bojan Mareljic
Bojan Mareljic, | 36

How To Connect Google Analytics 4 To Magento 2

3 best open-source eCommerce platforms in 2021 Zrinka Antolovic
Zrinka Antolovic, | 8

3 best open-source eCommerce platforms in 2021

eCommerce Returns Management – a simple solution from the Fashion industry Zrinka Antolovic
Zrinka Antolovic, | 12

eCommerce Returns Management – a simple solution from the Fashion industry

38 comments

  1. Hi used its working fine. I want on page load all attribute filter clear. Please help how can i do.

  2. You’re a damn fraud. If anyone reads this article carefully, they’ll realize the code is very specific and wouldn’t have worked unless you mirrored the author’s exact store configuration, even if you tried using it on Magento 1.4.

    It’s hack code at best. Just another fluff article to try and legitimize their company and for others to advertise their crappy plugins. Cancerous.

    1. Calm down, it was relevant in 2011. Doesn’t take a second to look at the date and realise…

      This website has helped many Magento developers over the years. They don’t owe it to anyone to remove old content, and all of their help has been free of charge, so show some respect.

  3. Hi,

    I am also getting a similar error:

    Fatal error: Call to a member function getBackend() on a non-object in F:\work\DH\branches\magento1.8.1.0\app\code\core\Mage\Eav\Model\Entity\Abstract.php on line 816

    Please help.
    Thank you.

  4. I am getting this error

    Call to a member function getSource() on a non-object in /home3/gstpro/public_html/developer/grocerymarket/app/design/frontend/groc2013/default/template/catalog/layer/state.phtml on line 70

    Please help.

  5. Hi! for filter need little change in this code
    template/catalog/layer/state.phtml

    <?php $_filters = $this->getActiveFilters() ?>
    <?php if(!empty($_filters)): ?>
    <?php $obj = new Mage_Catalog_Block_Navigation(); ?>
    <?php $_current_category=$obj->getCurrentCategory()->getUrlPath(); ?> 
    <?php $subs = $obj->getCurrentCategory()->getAllChildren(true); ?> 
    <div class="state">
        <p class="block-subtitle"><?php echo $this->__('Currently Shopping by:') ?></p>
          <?php foreach ($_filters as $_filter): ?>
    
                <dd>
                    <ol>
                        <?php $attributemodel=$_filter->filter->_data["attribute_model"]; ?>
                        <?php $attroptions=$attributemodel->getSource()->getAllOptions();?>     
                        <?php $_categ= Mage::getModel('catalog/category');?>   
    
                    <?php foreach($subs as $cat_id): ?>
    
                            <?php $_categ->load($cat_id)?>          
                            <?php $collection = $_categ->getProductCollection(); ?>   
                            <?php $collection->addAttributeToSelect('color')?>    
                    <?php endforeach; ?>
    
                    <?php foreach($attroptions as $attr): ?>   
    
                            <?php $count=0; ?>
                            <?php if($attr["value"]!=""): ?>
    
                                    <?php $val=$attr["value"] ?>
    
                                    <?php   $collection->addFieldToFilter(array(array('attribute'=>'color','gt'=>10)))?>
    
                                    <?php $proddata=$collection->getData() ?>  
                                    <?php if($attr["label"]!= $this->stripTags($_filter->getLabel())): ?>   
    
                                        <?php foreach($proddata as $prod):?>
    
                                            <?php //if($prod["type_id"]=="configurable"): ?>   
    
                                                <?php $split=split(",", $prod["color"]);?>      
    
                                                <?php foreach($split as $color): ?>   
                                                    <?php if($color==$attr["value"]): ?>
                                                        <?php $count++;?>
                                                    <?php endif; ?>
                                                <?php endforeach; ?>
                                            <?php //endif;?>
                                        <?php endforeach; ?>
                                        <?php if($count>0):?>   
                                            <li><a href="<?php echo $this->getUrl('').$_current_category ?>?color=<?php echo $attr["value"]?>" ><?php echo  $attr["label"]; ?></a></li> 
                                        <?php endif; ?>
                                    <?php else:?>
                                        <li class="current"> <?php echo $this->stripTags($_filter->getLabel()); ?></li>  
                                    <?php endif;?>
                            <?php endif; ?>
    <?php endforeach;  ?>
    <?php endforeach; ?>  
                    </ol>
                </dd>
        </dl>
        <a class="all" style="float:right;" href="<?php echo $this->getClearUrl()?>">All</a>   
    </div>
    <?php endif; ?>
  6. Hi! The thing you’ve described is rather popular now. Actually we needed the same for our electronics shop and were searching for different solutions. First we wanted to program it by ourselves, but then took a look at the appropriate extensions on Magento Connect. In the end we purchased Improved Navigation module from Amasty, and it’s really the best sollution for layered navigation. You can try it if you want http://amasty.com/improved-navigation.html

  7. The below is the Kid on updated code in the tutorial above but I found it so slow I switched the the previous code above….

    $model = Mage::getModel(‘catalog/product’);
    $collection = $model->getCollection();
    $collection->addFieldToFilter(‘make’,array(‘eq’=>’BMW’));
    $collection->addFieldToFilter(‘model’,array(‘eq’=>’1 SERIES’));
    echo $collection->getSelect();
    echo “xxxxxxx”;
    */

    /*
    $cat = Mage::getModel(‘catalog/product’)->setId(23);
    $collection = Mage::getModel(‘catalog/product’)
    ->getCollection()
    ->addCategoryFilter($cat)
    ->addAttributeToSelect(“*”);
    //echo $collection->getSelect();
    */

    /*
    $categories_collection = Mage::getModel(‘catalog/category’)->getCollection();
    $categories_collection->addAttributeToSelect(‘name’)->setStoreId(Mage::app()->getStore()->getId())->load();

    foreach($categories_collection as $category){
    if($_REQUEST[“make”]==$category->getName()) {
    $searhcat = $category->getId();
    }
    echo $category->getName().””;

    }
    */

  8. I got a newer version of this tutorial working but I found it was so slow width 1K of products I had to find an alternative

  9. // I did this a little differently in the end… some of this may help
    //categories

    $showthismake = array(
    “APRILIA” =>”73″,
    “BENELLI” =>”74″,
    “BIMOTA” =>”75″,
    “BMW” =>”76″,
    “BUELL” =>”77″,
    “DUCATI” =>”78″,
    “HARLEY DAVIDSON” =>”79″,
    “HONDA” =>”80″,
    “KAWASAKI” =>”81″,
    “KTM” =>”82″,
    “MOTO GUZZI” =>”83″,
    “MV AUGUSTA” =>”84″,
    “PEUGEOT” =>”100″,
    “PIGGIO” =>”99″,
    “SUZUKI” =>”101″,
    “TRIUMPH” =>”86″,
    “YAMAHA” =>”87″,
    );

    //——————-
    if (isset($_REQUEST[“make”])) {

    foreach ($showthismake as $key => $value) {
    if($_REQUEST[“make”]==$key) {
    $searhcat=$value;
    }
    }

    }
    //———————————————
    $category1 = Mage::getModel(‘catalog/category’)->load($searhcat);

    $collection = Mage::getResourceModel(‘catalog/product_collection’);
    $collection = Mage::getModel(‘catalog/product’)->getCollection()->addCategoryFilter($category1)->addAttributeToSelect(‘*’)->load();
    //—————————
    //then
    $currentmakearray=array();
    $attributes = Mage::getModel(‘catalogsearch/advanced’)->getAttributes();
    $attributeArray=array();
    foreach($attributes as $myattribute){

    if($myattribute->getAttributeCode() == ‘make’){
    foreach($myattribute->getSource()->getAllOptions(false) as $option){
    $attributeArray[$option[‘value’]] = $option[‘label’];
    $currentmake = strtoupper($attributeArray[$option[‘value’]]);
    $currentmakearray[$currentmake]=”false”;
    }
    }

    }
    //——————————————
    $tolookfor=strtoupper($product->load($product->getId())->getAttributeText(‘make’));
    //————————————–
    foreach ($currentmakearray as $key => $value) {
    if ($tolookfor==strtoupper($key)) {
    $currentmakearray[$key]=”true”;

    }
    }

  10. Hi ,

    I tried this but i got error.Please give me a clear code.

    I need to display multiple select option after select any single attribute selection.Please help me…..

  11. How can i only get the filters to show active options?

    At the moments my filter Color is showing every color, most of them empty and showing no results.
    Please how can i only get the colors, that are active in the category?

  12. hi, this code is not working on my magento 1.6
    The thing is i wanted to show an attribute on every page but without of any filtration, can any body help with this….!

  13. Hi Iva, hi everyone,
    I’m really stuck trying to acomplish the same thing.

    I managed to place Magentos layered navigation into drop downs, but now I want to keep all of the “filters” in the drop down visible all the time.

    Have you managed to do so???
    When I paste this code I get a conflict with “apparel_color_value”. It seems to be the name of one custom attribute? What if I have multiple attributes ..???

    Any help is appreciated.
    Best regards from Germany

    Michaela

  14. I would like to use it on Category instead of Color but I cannot get it to work.

    Currently we use layered navigation based on 2 options only;
    – category
    – model (hidden in [layer/view.phtml] but needed for filtering)

    Users are able to select the model value using a “Toestelzoeker” ak “Brand/Model searchform”.
    After their Model selection they can filter the results on category only.

    I need the filtered categories always visible so that users can simply change the category.

    Is is even possible?
    A tickbox in front of the category would be even nicer.

    I’ve seen mods like Amasty but they seems to do their fancy work on attributes only, not on categories.

  15. @John: It’s possible to do for more attributes, but the approach would have to be different. It’s unwieldy to do heavy coding in phtml.

  16. @Iva:

    Thanks for the link.

    It seems that http://www.uncommonlycute.com this site only has one attribute and it is filtered by it.

    Is it possible to do so for multiple attributes?

    @kiat:

    Thanks for the link.

    It only works for color attribute. Can’t we do same for other attributes also?

  17. Hi guys! sorry, I’ve been busy a bit. The working web store:
    http://www.uncommonlycute.com

    @John try using:

    <?php $attributemodel=$_filter->filter->_data["attribute_model"]; ?

    for me it didn’t work if I used getFilter() function so I had to do it Zend way.

    Also make sure you have this line at the beginning:

    <?php $_filters = $this->getActiveFilters() ?>

    sorry, it’s a bit hard for me to help you without seeing your code.
    Cheers,
    Iva

  18. Can anyone post the URL of such site which has implemented this feature? I am not getting the clear idea.

  19. I think that’s because, like me, you also have the category filter. So you need to bypass this. Note, apparel_color is a custom attribute, and definitely will be different in your case. Instead of checking for attribute option code, which is a number, I have set it to check for the option label: apparel_color_value.

  20. It might have come from this line:-

    $attributeModel = $_filter->getFilter()->getAttributeModel();

    This might be returning nothing, and I am getting the error in this line:-

    $attrOptions = $attributeModel->getSource()->getAllOptions();

    Is it working fine in your installation?

  21. My code is based on ver 1.4.1.1, I need to bypass the category filter. You can remove the check and see if it works for you.

  22. @Iva: I am getting “The attribute model is not defined” error from your code.

    @kiat: Nothing changes with your code. I think the if condition is not satisfied.

    if(get_class($_filter->getFilter()) == ‘Mage_Catalog_Model_Layer_Filter_Attribute’)

    The above condition is not satisfied. Can anyone propose any solution?

  23. Can you please post some working example website link? I tried the code but nothing changes in my layered navigation? I am using Magento 1.4.2.0.

    Thanks.

  24. I think there is a bug in getting the product collections from the subcategories.

    <?php foreach($subs as $cat_id): ?>

    and later the $collection here {code] addFieldToFilter(array(array(‘attribute’=>’themes’,’gt’=>10)))?>

     will only reference the last collection.  
    
    Also, the code will run slowly for large collection of products.
    
    I come up with a different approach by getting only the distinct colors from the collection [code]$collection->addAttributeToSelect('apparel_color_value')
                                  ->getSelect()->group('color');

    and store all the colors in string. Details in http://pastebin.com/Q6uHp9ZF

  25. Thanks for the Post, Iva.
    Our clients had change requests for the layered navigation more than once, so I guess it’s time for Magento to extend the functionality in the core! 😉

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <blockquote cite=""> <code> <del datetime=""> <em> <s> <strike> <strong>. You may use following syntax for source code: <pre><code>$current = "Inchoo";</code></pre>.

Tell us about your project

Drop us a line. We'd love to know more about your project.