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!