Adding conditional Links to the top navigation

One of my today’s tasks was to add conditional links to our shop’s top navigation. Let me show you how we achieved that – it’s easy!

The very Basics

Adding new links is archieved quite easily by inserting following markup to your local.xml (app/design/frontend/{yourpackage}/{yourtheme}/layout/local.xml) or custom module template file, as we will do later on:

<?xml version="1.0"?>
<layout version="0.1.0">
<!-- ... -->
    <default>
        <reference name="top.links">
            <action method="addLink" translate="label title">
                <label>My link</label>
                <url>fancy/url</url>
                <title>My link</title>
                <prepare />
                <urlParams />
                <position>100</position>
                <aParams>
                    <class>top-link-mylink</class>
                </aParams>
            </action>
        </reference>
    </default>
<!-- ... -->
</layout>

As Magento comes with <customer_logged_in> and <customer_logged_out> handles, we can easily add links that are supposed to only show up for logged in (or only logged out) customers:

<?xml version="1.0"?>
<layout version="0.1.0">
     <customer_logged_in>
        <reference name="top.links">
            <action method="addLink" translate="label title">
                <label>My new link</label>
                <url>path/to/wherever</url>
                <title>My new link</title>
                <prepare />
                <urlParams />
                <position>50</position>
                <aParams>
                    <class>top-link-mynewlink</class>
                </aParams>
            </action>
        </reference>
    </customer_logged_in>
</layout>

In order to make already existing links, like “My account”, only show up for logged in customers, we have to remove them first. Also, in case you’re new to this, pay attention to the preferred way of getting an internal URL: instead of writing <url>customer/account</url> we want the available helper to fetch the URL:

<?xml version="1.0"?>
<layout version="0.1.0">
    <default>
        <reference name="top.links">
            <action method="removeLinkByUrl"><url helper="customer/getAccountUrl" /></action>
        </reference>
    </default>
    <customer_logged_in>
        <reference name="top.links">
            <action method="addLink" translate="label title">
                <label>My Account</label>
                <url helper="customer/getAccountUrl"/>
                <title>My Account</title>
                <prepare />
                <urlParams />
                <position>50</position>
                <aParams>
                    <class>top-link-account</class>
                </aParams>
            </action>
        </reference>
    </customer_logged_in>
</layout>

Conditional Links

Lets assume we want certain Links only to be displayed when our customer belongs to a certain customer group like, say, “Wholesale”… or really, any condition which isn’t being covered by an already existing handle.

Some days ago I came across a stackexchange thread where the accepted reply suggested to manage this by letting a top.link’s custom child block inject links to its parent…  While this approach actually works, it’s not how it’s supposed to be done. I’d want a handle (like its name suggests) handling the layout – meaning: I want to create <customer_group_[group_name]> handles.

So what we do is letting an Observer add our custom handlers. Observers listen to a certain event that they’re attached to. So, we want our Observer to listen to an event that’s for one dispatched globally and, for two, linked to the layout (I found this overview to be helpful). The event we want to observe is called controller_action_layout_load_before.

While we didn’t necessarily need to create a module for our previous layout updates, we need one now in order to set up our Observer. I created a module which is handling all things concerning updates on our shop’s navigation layout.

1. Creating a module

(just skip this if you already know how to create modules)

A. Architecture

Our module needs following files:

  • app/etc/modules/Montareno_Navigation.xml
  • app/local/Montareno/Navigation/etc/config.xml
  • app/local/Montareno/Navigation/Model/Observer.xml
  • app/design/frontend/montareno/default/layout/montareno_navigation.xml

Montareno = a custom name for your own pack of modules. Make sure this name isn’t already taken by community or core modules.
Navigation = your module’s name
montareno = your theme’s package name
default = your theme’s name

Always pay attention to Magento’s case sensitivity. Uppercase the first letter of your pack and module, but only the first – I haven’t tried it myself but some folks reported problems with uppercase letters following the first letter. The templates’s xml filename is the only exception as its name will be set in the config.xml and can have any name. However, it’s good habit to always name it by its pack’s and module’s name, so you will always know where it belongs to.

B. Registration & Configuration

Any layout updates concerning the Navigation can now move into the module’s own layout file. However, at this point Magento will ignore this and any other file belonging to our module. First, we want to make Magento be aware of our module and tell what our module offers (Magento is kind of a diva  😉 ).

The first file on our architecture’s list is a “registry file” (well, that’s what I call it) – it tells Magento “Hey, look, here’s another module, you’ll find it at Montareno/Navigation in the ‘local’ code directory”:

<?xml version="1.0" encoding="utf-8"?>
<config>
    <modules>
        <Montareno_Navigation>
            <active>true</active>
            <codePool>local</codePool>
            <depends>
                <Mage_Checkout />
                <Mage_Customer />
                <Mage_Wishlist />
            </depends>
        </Montareno_Navigation>
    </modules>
</config>

Note: You can remove the depends-Tag. However, in case you’re planning to extent your Navigation module, I find them to be a good start.

If Magento can locate the module we advertised, the first thing it does is looking for the modules configuration files located within etc. In our case it’ll only be one configuration file: config.xml. This file tells Magento where else to look, so obviously, this is the place where you tell Magento that your module comes with its own layout file – you write following lines into your config.xml:

<?xml version="1.0"?>
<config>
    <modules>
        <Montareno_Navigation>
            <version>0.1.0</version>
        </Montareno_Navigation>
    </modules>
    <frontend>
        <layout>
            <updates>
                <Montareno_Navigation>
                    <file>montareno_navigation.xml</file>
                </Montareno_Navigation>
            </updates>
        </layout>
    </frontend>
</config>

Remember to adjust “Montareno_Navigation” to the pack & module name you’ve chosen (see above). Remember: case sensitivity!

2. Creating the observer

Now its time to open up the Observer.php and write our class Montareno_Navigation_Model_Observer:

class Montareno_Navigation_Model_Observer
{
/**
     * Adds layout handles customer_group_<group-name> 
     *
     * Event: controller_action_layout_load_before
     *
     * @param Varien_Event_Observer $observer
     */
    public function addCustomerGroupHandle(Varien_Event_Observer $observer){

        $customer = Mage::getSingleton('customer/session');
        if ($customer->isLoggedIn()) {
         
            $groupId = $customer->getCustomerGroupId();
            $groupName = preg_replace('#[^a-z0-9_]#', '', 
                str_replace(' ', '_',
                    strtolower(
                    Mage::getModel('customer/group')->load($groupId)->getCustomerGroupCode() 
                    ) 
                ) 
            );
            
            /* @var $update Mage_Core_Model_Layout_Update */
            $update = $observer->getEvent()->getLayout()->getUpdate();
            $update->addHandle('customer_group_' . $groupName);
            
        }
        
        return $this;
    }
}

I don’t think I have to explain what’s happening here and I also think you know what we have to do next: attach our Oberserver class to the event controller_action_layout_load_before

3. Attaching our observer to the event

Of course we do this in our module’s config.xml, too – simply add following lines after the closing modules tag:

   <!-- ... </modules> -->
    <global>
        <events>
            <controller_action_layout_load_before>
                <observers>
                    <customer_group_handle>
                        <class>Montareno_Navigation_Model_Observer</class>
                        <method>addCustomerGroupHandle</method>
                    </customer_group_handle>
                </observers>
            </controller_action_layout_load_before>
        </events>
    </global>
    <!-- <frontend> ... -->

These lines of xml are pretty straight forward. I chose to name my oberserver <customer_group_handle> but you can name it whatever you like – however, once again, I suggest you choose one that makes sense and, most importantly, isn’t already taken by other modules.

4. Yippie yay!

Yes, that’s it! Just clear the cache and there it is, our custom Navigation~

Leave a Reply

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