How I Improved Magento Category Page

Published Jul 30, 2017
How I Improved Magento Category Page

About me

I am a full-stack developer and I've been building e-commerce sites and apps with Magento 1.9 and Magento 2, which I am quite passionate about.

The problem I wanted to solve

In one of the projects I worked on, my task was to improve the Magento 1.9 default left layered navigation by enabling the ability to have all the categories with their children displayed in a tree-like menu. The idea is that the menu would stay open in the current category at each page refresh/reload.

What is this tree-like menu for?

The code I added to the category page template in Magento, together with some jQuery code and some nice images for the tree-like menu, provides a nice experience by having a left tree menu to navigate between categories and their children and staying in the same spot at page reload. Behind the scenes, this is a combination functionality between PHP and jQuery JavaScript.

Tech stack

First of all, for the theme package layout.xml, I declared my custom template used for the left navigation layer on the category page. The page uses a "page/2columns-left.phtml" template.Code input for local.xml:

<catalog_category_layered>
    <reference name="root">
      <action method="setTemplate"><template>page/2columns-left.phtml</template></action>
    </reference>
    <reference name="left">
      <block type="catalog/navigation" name="catalog.leftnav" template="catalog/navigation/custom_left.phtml" />
    </reference>
  </catalog_category_layered>

Here, I will show the contents of the custom_left.phtml file, which is located in app/design/frontend/themename/template/catalog/navigation directory:

<!-- List all categories and their second level subcategories -->
<div class="block block-list block-categories">
    <div id="block-categories" class="block-title active">
        <strong><span>Categories </span></strong>
    </div><div id="leftnav" class="block-content" style="display:block">
        <?php $helper = $this->helper('catalog/category') ?>
        <?php $activecategory = Mage::registry('current_category')->getId(); ?>
        <?php $categories = $this->getStoreCategories() ?>
        <?php if (count($categories) > 0): ?>
            <ul id="leftnav-tree" class="level0">
                <?php foreach($categories as $category): ?>
        <li class="level0<?php if ($category->getId() == $activecategory): ?> active<?php endif; ?>">
                        <label></label>
                       <a href="<?php echo $helper->getCategoryUrl($category) ?>">
                            <span><?php echo $this->escapeHtml($category->getName()) ?></span></a>
                        <?php //if ($this->isCategoryActive($category)): ?>
                        <?php $actualcategory = Mage::getModel('catalog/category')->load($category->getId());
                              $subcategories = $actualcategory->getChildrenCategories(); ?>
                        <?php if (count($subcategories) > 0): ?>
                            <ul id="leftnav-tree-<?php echo $category->getId() ?>" class="level1">
                                <?php foreach($subcategories as $subcategory): ?>
              <li class="level1<?php if ($subcategory->getId() == $activecategory): ?> active<?php endif; ?>">
                                    <label></label>
                                    <a href="<?php echo $helper->getCategoryUrl($subcategory) ?>"><?php echo $this->escapeHtml(trim($subcategory->getName(), '- ')) ?></a>
                                    <?php
                                        $actualcategorysubcategory = Mage::getModel('catalog/category')->load($subcategory->getId());
                                        $subsubcategories = $actualcategorysubcategory->getChildrenCategories(); ?>

                                    <?php if (count($subsubcategories) > 0): ?>
                                        <ul id="leftnav-tree-<?php echo $subcategory->getId() ?>" class="level2">
                                            <?php foreach($subsubcategories as $secondLevelSubcategory ): ?>
                                            <li class="level2<?php if ($secondLevelSubcategory->getId() == $activecategory): ?> active<?php endif; ?>">
                                                    <a href="<?php echo $helper->getCategoryUrl($secondLevelSubcategory ) ?>"><?php echo $this->escapeHtml(trim($secondLevelSubcategory ->getName(), '- ')) ?></a>
                                                </li>
                                            <?php endforeach; ?>
                                        </ul>
                                        <script type="text/javascript">decorateList('leftnav-tree-<?php echo $subcategory->getId() ?>', 'recursive')</script>
                                    <?php endif; ?>
                                    <?php endforeach; ?>
                            </ul>
                            <script type="text/javascript">decorateList('leftnav-tree-<?php echo $category->getId() ?>', 'recursive')</script>
                        <?php endif; ?>
                        <?php //endif; ?>
                    </li>
                <?php endforeach; ?>
            </ul>
            <script type="text/javascript">decorateList('leftnav-tree', 'recursive')</script>
        <?php endif; ?>
    </div>
</div>

And finally, I will publish the Javascript jQuery lines that take care of the menu behavior by making it open and close with elegant ease and also stay open at the current category/subcategory when page reload. This JavaScript file must be located in the theme skin directory and, of course, it must be loaded in the project header to work it's magic (sample path: skin/frontend/themename/default/js/file.js), provided jQuery is already loaded and present in the application header.

jQuery(document).ready(function(){
/*  Jewels Category Page   */

    if(jQuery('#leftnav-tree li.level0 ul').length > 0)
    {
        jQuery('#leftnav-tree li.level0 ul').parent().find('label').addClass('contentContainer');
    }

    if(jQuery('li.level2').length > 0)
    {
        jQuery('li.level2').parent().parent().find('label').removeClass('contentContainer').addClass('contentContainerSub');
    }

    if(jQuery('#leftnav-tree li.level0 ul li.level1').find('label').hasClass('contentContainer'))
    {
        jQuery('#leftnav-tree li.level0 ul li.level1').find('label').removeClass('contentContainer');
    }

    if(jQuery('#leftnav-tree li.level0.active').find('label').hasClass('contentContainer'))
    {
        jQuery('#leftnav-tree li.level0.active').addClass('opened');
    }

    if(jQuery('#leftnav-tree li.level0 > ul li.level1.active'))
    {
        jQuery('#leftnav-tree li.level0 > ul li.level1.active').parent().css('display', 'block');
        jQuery('#leftnav-tree li.level0 > ul li.level1.active').parent().parent().addClass('opened');

    }

    if(jQuery('#leftnav-tree li.level1 > ul li.level2.active'))
    {
        jQuery('#leftnav-tree li.level1 > ul li.level2.active').parent().css('display', 'block');
        if(jQuery('#leftnav-tree li.level1 > ul li.level2.active').parent().parent().find('label').hasClass('contentContainerSub'))
        {
            jQuery('#leftnav-tree li.level1 > ul li.level2.active').parent().parent().addClass('opened');
        }

        jQuery('#leftnav-tree li.level1 > ul li.level2.active').parent().parent().parent().css('display', 'block');
        jQuery('#leftnav-tree li.level1 > ul li.level2.active').parent().parent().parent().parent().addClass('opened');

    }

    jQuery('ul.level0 label.contentContainer').on('click', function(ev) {
        ev.preventDefault();
        jQuery(this).parent('li.level0').children('ul.level1').closest('ul.level1').slideToggle();
        jQuery(this).parent('li.level0').closest('li.level0').toggleClass('opened');

    });

    jQuery('ul.level1 label.contentContainerSub').on('click', function(ev) {
        ev.preventDefault();
        jQuery(this).parent('li.level1').children('ul.level2').closest('ul.level2').slideToggle();
        jQuery(this).parent('li.level1').closest('li.level1').toggleClass('opened');
    });
 });
    

And, of course there are some CSS rules that you can use to improve or customize according to your project needs. The images for the tree-like menu are 'toggle-small-expand.png' for the opened tree branch and 'toggle-small.png' for the closed tree-menu branch. These styles are just there for guidance, but some of them are mandatory for the tree-menu to behave accordingly:

.nav>li>a {
  position: relative;
  display: block;
  padding: 10px 10px;
}

/*
CATEGORY MENU FIX 
 */
.breadcrumbs ul
{
  list-style-type: none;
}
.breadcrumbs li
{
    display: inline;
}
.pages strong
{
    display: none;
}
ul#leftnav-tree
{
    padding: 10px 0 0 20px;
    width: 300px;
    text-align: left;
}

.active
{
    font-weight: 700;
}
ul#leftnav-tree li
{
    position: relative;
    margin-left: 0;
    list-style-type: none;
}
#leftnav-tree li
{
    padding-left: 15px;
}
#leftnav-tree label.contentContainer {
    background:url('../images/toggle-small-expand.png') no-repeat 0 5px;
    color: #FFFFFF;
    cursor: pointer;
  width: 16px;
  height: 17px;
}

 #leftnav-tree li.level0.opened label.contentContainer {
    background:url('../images/toggle-small.png') no-repeat 0 5px;
    color: #FFFFFF;
  width: 16px;
  height: 17px;
}
#leftnav-tree label.contentContainerSub {
    background:url('../images/toggle-small-expand.png') no-repeat 0 5px;
    color: #FFFFFF;
    cursor: pointer;
    width: 16px;
    height: 17px;
}
#leftnav-tree li.level1.opened label.contentContainerSub {
    background:url('../images/toggle-small.png') no-repeat 0 5px;
    color: #FFFFFF;
    width: 16px;
    height: 17px;
}

#leftnav-tree ul.level1, #leftnav-tree ul.level2
{
    display:none;
}
#leftnav-tree li.level0.active > ul.level1
{
  display:block;
}
ul.products-grid li
{
    list-style-type: none;
    display: inline-block; /*necessary*/
    margin-bottom: 7px;
}
.products-grid {
    height: auto; /*your fixed height*/
    -webkit-column-count: 3;
    -moz-column-count: 3;
    column-count: 3; /*3 in those rules is just placeholder -- can be anything*/
}

ul.products-list li
{
    list-style-type: none;
}
ul.add-to-links li
{
  display: inline-block;
}
.products-list li
{
    display: block; /*necessary*/
    margin-bottom: 7px;
}
.product-info .actions button.btn-cart {
     margin: 10px auto 10px;
     letter-spacing: 3px;
     width: 80%;
     display: block;
     border-radius: 30px;
     -moz-border-radius: 30px;
     -webkit-border-radius: 30px;
     border: 1px solid #666699;
     background: #666699;
     font-family: 'Questrial';
     font-size: 16px;
     text-transform: uppercase;
     color: #fff;
     padding: 15px 20px;
     transition: all 0.3s ease;
 }
.product-shop .action button.btn-cart {
    margin: 10px auto 10px;
    letter-spacing: 3px;
    width: 30%;
    display: block;
    border-radius: 30px;
    -moz-border-radius: 30px;
    -webkit-border-radius: 30px;
    border: 1px solid #666699;
    background: #666699;
    font-family: 'Questrial';
    font-size: 16px;
    text-transform: uppercase;
    color: #fff;
    padding: 15px 20px;
    transition: all 0.3s ease;
}

The process of building this feature

During this feature debugging, I faced quite some problems making the jQuery code work as expected, but it all worked perfectly together at the end.

Challenges I faced

It was a good challenge as there are not so many guides out there about how to achieve such a feature in Magento layered navigation. I felt very satisfied to see all this working together nicely.

Key learnings

I learnt about the power of combining PHP server language and front-end jQuery in an unexpected way.

Final thoughts and next steps

I look forward to sharing my experience from other inspiring projects in Magento 1.9 and Magento 2. I hope to get inspiration and guidance from other developers' works in these frameworks as well. And I am sorry if this page is not correctly displayed in the parts where it contains code 😦(

If you find this code useful please post your comments!

Discover and read more posts from Andrada
get started