2

Instead of categories I would like products to appear in the drop-down navigation menu similar to lowes.com. Is this possible? I am not paying for anything either :)

I've attempted to alter core/Mage/Catalog/Block/Navigation.php and try to 'fake' products as categories but the object requirement is very specific. Since the function to create the menu is recursive it will only work on actual categories and nothing else. Any ideas?

I know another option is to create 2nd level categories and name them as my products and then do a rewrite in .htaccess but this is not dynamic and very messy.

Jared Eitnier
  • 7,012
  • 12
  • 68
  • 123

3 Answers3

5

After a bit of experimenting I've got this working! Below is the new code to use in

app/code/core/Mage/Catalog/Block/Navigation.php

function _renderCategoryMenuItemHtml (swap 'local' for 'core' if localizing)

protected function _renderCategoryMenuItemHtml($category, $level = 0, $isLast = false, $isFirst = false, $isOutermost = false, $outermostItemClass = '', $childrenWrapClass = '', $noEventAttributes = false)
{
    if (!$category->getIsActive()) {
        return '';
    }
    $html = array();

    // get all children
    if (Mage::helper('catalog/category_flat')->isEnabled()) {
        $children = (array)$category->getChildrenNodes();
        $childrenCount = count($children);
    } else {
        $children = $category->getChildren();
        $childrenCount = $children->count();
    }
    $hasChildren = ($children && $childrenCount);

    // get products listing
    $cur_category = Mage::getModel('catalog/category')->load($category->getId());
    $_productCollection = Mage::getResourceModel('catalog/product_collection')->addCategoryFilter($cur_category)->setOrder('position','ASC');
    $k = 1;
    $hasProduct1 = $_productCollection->count();
    $phtmlChildren = '';
    if ($hasProduct1 >= 1) {
        $l = $level+1;
        foreach ($_productCollection as $_product) {
            $cur_product = Mage::getModel('catalog/product')->load($_product->getId());
            if ($cur_product->getStatus()) {
                $phtmlChildren .= '<li';
                $phtmlChildren .= ' class="level'.$l;
                $phtmlChildren .= ' nav-'.$this->_getItemPosition($l);
                if ($k == $hasProduct1) {
                    $phtmlChildren .= ' last';
                }
                $phtmlChildren .= '">'."\n";
                $phtmlChildren .= ' <a href="'.$cur_product->getProductUrl().'">'.$this->htmlEscape($cur_product->getName()).'</a>'."\n";
                $phtmlChildren .= '</li>';
                $k++;
            }
        }
    }

    // select active children
    $activeChildren = array();
    foreach ($children as $child) {
        if ($child->getIsActive()) {
            $activeChildren[] = $child;
        }
    }
    $activeChildrenCount = count($activeChildren);
    $hasActiveChildren = ($activeChildrenCount > 0);

    // prepare list item html classes
    $classes = array();
    $classes[] = 'level' . $level;
    $classes[] = 'nav-' . $this->_getItemPosition($level);
    if ($this->isCategoryActive($category)) {
        $classes[] = 'active';
    }
    $linkClass = '';
    if ($isOutermost && $outermostItemClass) {
        $classes[] = $outermostItemClass;
        $linkClass = ' class="'.$outermostItemClass.'"';
    }
    if ($isFirst) {
        $classes[] = 'first';
    }
    if ($isLast) {
        $classes[] = 'last';
    }
    if ($hasActiveChildren) {
        $classes[] = 'parent';
    }

    // prepare list item attributes
    $attributes = array();
    if (count($classes) > 0) {
        $attributes['class'] = implode(' ', $classes);
    }
    if ($hasActiveChildren && !$noEventAttributes) {
         $attributes['onmouseover'] = 'toggleMenu(this,1)';
         $attributes['onmouseout'] = 'toggleMenu(this,0)';
    }

    // assemble list item with attributes
    $htmlLi = '<li';
    foreach ($attributes as $attrName => $attrValue) {
        $htmlLi .= ' ' . $attrName . '="' . str_replace('"', '\"', $attrValue) . '"';
    }
    $htmlLi .= '>';
    $html[] = $htmlLi;

    $html[] = '<a href="'.$this->getCategoryUrl($category).'"'.$linkClass.'>';
    $html[] = '<span>' . $this->escapeHtml($category->getName()) . '</span>';
    $html[] = '</a>';

    // render 'product' children
    $htmlChildren = '';
    if ($hasChildren) {
        $j = 0;
        foreach ($children as $child) {
            if ($child->getIsActive()) {
                $htmlChildren .= $this->_renderCategoryMenuItemHtml($child, $level + 1, $j++ >= $k);
            }
        }
    }
    if ((!empty($htmlChildren)) || (!empty($phtmlChildren))) {
        $html[] = '<ul class="level'.$level.'">'."\n".$htmlChildren.$phtmlChildren.'</ul>';
    }

    // render children
    $htmlChildren = '';
    $j = 0;
    foreach ($activeChildren as $child) {
        $htmlChildren .= $this->_renderCategoryMenuItemHtml(
            $child,
            ($level + 1),
            ($j == $activeChildrenCount - 1),
            ($j == 0),
            false,
            $outermostItemClass,
            $childrenWrapClass,
            $noEventAttributes
        );
        $j++;
    }
    if (!empty($htmlChildren)) {
        if ($childrenWrapClass) {
            $html[] = '<div class="' . $childrenWrapClass . '">';
        }
        $html[] = '<ul class="level' . $level . '">';
        $html[] = $htmlChildren;
        $html[] = '</ul>';
        if ($childrenWrapClass) {
            $html[] = '</div>';
        }
    }

    $html[] = '</li>';

    $html = implode("\n", $html);       
    return $html;
}

Basically there are two new added sections. The first section builds the product collection to get relevant info (name, url, etc). The second section appends the new unordered list inside the existing root category list items. Hope this helps someone. Now you don't need to pay $99 for the extension that is out there :)

Tested on v.1.6.1

Jared Eitnier
  • 7,012
  • 12
  • 68
  • 123
  • Very helpful thanks. On the downside it'll break on updates if the file is modified since it's part of the core. – EasyCo Feb 28 '12 at 01:48
  • No it won't, just put it in a local folder. I just listed that specific code for clarity sake. – Jared Eitnier Feb 28 '12 at 02:29
  • Care to expand a little please? – EasyCo Feb 28 '12 at 02:51
  • Well what most people do to their Magento customization is create local folders for all their work. So for example, if you wanted to keep this particular core file intact, instead create a new Navigation.php inside app/local/core/Mage/Catalog/Block/Navigation.php. Magento looks for local folders first, then moves down the line for core, etc. – Jared Eitnier Feb 28 '12 at 12:52
  • Awesome thanks. I wasn't aware that it did that for the code. Thought it was limited to design, skin and such. – EasyCo Feb 29 '12 at 05:13
  • No problem. I'm glad it helped someone out. Yes you can do that with code also, it just gets more complicated if you want to override classes but if you ever get into that, check out Alan Storm's website (http://alanstorm.com/category/magento). – Jared Eitnier Feb 29 '12 at 12:58
1

Jared Eitnier's method works except on the log in, cart, and checkout pages - for example /customer/account/login the products' URL breaks it becomes /customer/account/login/product-seo-url

Oddly the products' URLs work on the CMS pages...

I am using Magento 1.6.2

I have changed this line -

$phtmlChildren .= ' <a href="'.$cur_product->getUrlPath().'">'.$this->htmlEscape($cur_product->getName()).'</a>'."\n";

With this -

$phtmlChildren .= ' <a href="'.$cur_product->getProductUrl().'">'.$this->htmlEscape($cur_product->getName()).'</a>'."\n";

And now it works on every page! Hopefully this helps somebody out.

Kayla
  • 133
  • 1
  • 3
  • 9
-1

Maybe one generic category call "All products", and link all products to it?

Then make only that category visible.

That is one solution at least, without braking the system.

ShaunOReilly
  • 2,186
  • 22
  • 34