Magento – Extending Magento’s topLinks to Include Nested/Dropdown Child Menu’s

magento-1.7

We've extended the Magento class Mage_Page_Block_Template_Links using the method outlined by Sonassi here.

Editing the _construct function to include our custom template:-

protected function _construct()
{
    $this->setTemplate('page/template/links_nested.phtml');
}   

Editing the function addLink to include $childMenu variable and allow this to be used in XML layout:-

public function addLink($label, $url = '', $title = '', $prepare = false, $urlParams = array(), $position = null, $liParams = null, $aParams = null, $beforeText = '', $afterText = '', $childMenu = false)
{
    if (is_null($label) || false === $label) {
        return $this;
    }

    $link = new Varien_Object(array(
        'label' => $label,
        'url' => ($prepare ? $this->getUrl($url, (is_array($urlParams) ? $urlParams : array())) : $url),
        'title' => $title,
        'li_params' => $this->_prepareParams($liParams),
        'a_params' => $this->_prepareParams($aParams),
        'before_text' => $beforeText,
        'after_text' => $afterText,
        'child_menu' => ($childMenu ? $this->getLayout()->getBlock($childMenu) : '')
    ));

    $this->_links[$this->_getNewPosition($position)] = $link;
    if (intval($position) > 0) {
        ksort($this->_links);
    }

    return $this;
}

Then we wish to include <childMenu> parameter in local.xml for an item in topLinks:-

<reference name="top.links">
    <action method="addLink" translate="label title before_text">
    <label>Account</label>
    <url />
    <title>Account</title>
    <prepare />
    <urlParams />
    <position>10</position>
    <liParams>id="account-dropdown"</liParams>
    <aParams />
    <before_text />
    <after_text />
    <childMenu>account-submenu</childMenu>
    </action>
</reference>

And then construct the childMenu as account-submenu:-

<reference name="top.links">
<block type="page/template_links" name="submenu" as="submenu">
    <action method="setName">
    <name>account-submenu</name>
</action>
<action method="addLink" translate="label title before_text">
    <label>Contact Us</label>
    <url />
    <title>Contact Us</title>
    <prepare />
    <urlParams />
    <position>110</position>
    <liParams />
    <before_text />
    <after_text />
</action>
</block>
</reference>

Then we've made a few modifications to the template file to render childMenu if declared:-

<?php $_links = $this->getLinks(); ?>
<?php if(count($_links)>0): ?>
<ul class="links nav"<?php if($this->getName()): ?> id="<?php echo $this->getName() ?>"<?php endif;?>>
    <?php foreach($_links as $count=>$_link): ?>
        <?php if ($_link instanceof Mage_Core_Block_Abstract):?>
            <?php echo $_link->toHtml() ?>
        <?php else: ?>
            <li<?php if($_link->getIsFirst()||$_link->getIsLast()||$count): ?> class="<?php if($_link->getIsFirst()): ?>first<?php endif; ?><?php if($_link->getIsLast()): ?> last<?php endif; ?> link-<?php echo $count ?>"<?php endif; ?> <?php echo $_link->getLiParams() ?>>
                <?php echo $_link->getBeforeText() ?><a href="<?php echo $_link->getUrl() ?>" title="<?php echo $_link->getTitle() ?>" <?php echo $_link->getAParams() ?>><?php echo $_link->getLabel() ?></a><?php echo $_link->getAfterText() ?>
                <?php var_dump($_link->getChildMenu()); ?>
                <?php echo ($_link->getChildMenu()) ? $_link->getChildMenu()->toHtml() : ''; ?>
            </li>
        <?php endif;?>
    <?php endforeach; ?>
</ul>
<?php endif; ?>

Everything is working as expected apart from the fact that childMenu doesn't render anything at all in the front end, so the 'My Account' top link contains no submenu.

Is there something incorrect with the call for childMenu in the template file ?

<?php echo ($_link->getChildMenu()) ? $_link->getChildMenu()->toHtml() : ''; ?>

Code snippets sourced from sonassi.com

Best Answer

There appears to be two issues with your code.

  1. The name of the sub menu item must match that of the parent's node,

  2. You must define your child menu item before the parent in the xml.

It appears that the call to $this->getLayout()->getBlock($childMenu) will return false if the child follows the parent menu item in your XML. If you update your XML as follows then it should render.

<default>
    <reference name="top.links">
        <block type="page/template_links" name="submenu" as="submenu">
            <action method="setName">
                <name>account-submenu</name>
            </action>
            <action method="addLink" translate="label title before_text">
                <label>Contact Us</label>
                <url />
                <title>Contact Us</title>
                <prepare />
                <urlParams />
                <position>110</position>
                <liParams />
                <before_text />
                <after_text />
            </action>
        </block>
    </reference>
    <reference name="top.links">
        <action method="addLink" translate="label title before_text">
            <label>Account</label>
            <url />
            <title>Account</title>
            <prepare />
            <urlParams />
            <position>10</position>
            <liParams>id="account-dropdown"</liParams>
            <aParams />
            <before_text />
            <after_text />
            <childMenu>submenu</childMenu>
        </action>
    </reference>
</default>