TurnKey Linux Virtual Appliance Library

Django navigation bar (active link highlighting)

Every web application needs a navigation bar. Common practice is to indicate to the user where he or she is, and is usually implemented by using a visual aid such as a bold type-face, different color or an icon.
 
I wanted an elegant, generic, extendable solution to "highlight" a link on the navigation bar without hardcoding URLs, using ifequals, or using template block inheritance by specifying a navbar block on each and every template (you'd be surprised, but the above mentioned are recommend often).
 
The solution I came up with is quite simple.
  • No need to hardcode URLs (using urlconf names).
  • Navbar is only specified in the base template (actually a separate template loaded by the base template).
  • By using a simple template tag and the request context processor, "active" will be returned if the "link" should be active.
  • Supports multiple URLs for each link.
  • CSS is used to highlight the active link.

You can see the above in action on the TurnKey Hub.

On to the code

First up, we need to enable the request context processor.


settings.py

TEMPLATE_CONTEXT_PROCESSORS = (
    ...
    'django.core.context_processors.request',
)
 
Next, create the template tag navactive.
 
Note: the navigation sitemap I'm using is flat, but you can tweak the code to support multiple levels quite easily.

apps/<myapp>/templatetags/base_extras.py

from django import template
from django.core.urlresolvers import reverse

register = template.Library()

@register.simple_tag
def navactive(request, urls):
    if request.path in ( reverse(url) for url in urls.split() ):
        return "active"
    return ""
 
Now for a little CSS styling...

media/css/style.css

.navbar .active {
    font-weight: bold;
}
 
With all the above in place, we can create the navigation bar template code.

templates/navigation.html

{% load base_extras %}

<div id="topbar">
    <div class="navbar">
        <div class="navbar-side navbar-left"></div>
        <div class="navbar-content">
        {% if user.is_authenticated %}
            <a class="{% navactive request 'servers help_server' %}"
                href="{% url servers %}">Servers</a>

            <a class="{% navactive request 'account_clouds help_registeraccount
                href="{% url account_clouds %}">Cloud Accounts</a>

            <a class="{% navactive request 'account_details' %}"
                href="{% url account_details %}">User Profile</a>

            <a href="{% url auth_logout %}">Logout</a>
        {% else %}
            <a href="{% url auth_login %}">Login</a> or
            <a href="{% url registration_register %}"><b>Sign up</b></a>
        {% endif %}
        </div>
        <div class="navbar-side navbar-right"></div>
    </div>
</div>
 
And finally, include the navigation bar in the base template so the navigation bar shows up on each page.
templates/base.html

{% include "navigation.html" %}

You can get future posts delivered by email or good old-fashioned RSS.
TurnKey also has a presence on Google+, Twitter and Facebook.

Comments

Have you thought of just using CSS and a body id/class?

I prefer to just use plain old CSS and an ID or class configured on the body element:

<body id="page-{% block page_id %}contact{% endblock %}">
  <ul class="menu">
    <li><a class="services" href="{% url services %}">Services</a></li>
    <li><a class="contact" href="{% url contact %}">Contact Us</a></li>
  </ul>
</body>

Then in your CSS:

#page-services ul.menu .services,
#page-contact ul.menu .contact {
  background-color: #a40000;
}

 

Nice and clean, but won't scale well if you have a dynamic site menu or have extensive menus.

Alon Swartz's picture

I was trying to avoid hardcoding

If I understand correctly, you are specifying the "page" by use of the page_id block, which is what I was trying to avoid. I mentioned this approach in the blog post as template block inheritance by specifying a navbar block on each and every template.

The approach works and is simple, but requires hardcoding, and won't scale very well for dynamic sites as you mentioned.

No need to hardcode

Put an id in the context and write that id in the body-tag of the base template: <body id="{% id %}">  (Or use an inclusion tag so you can have a fallback, until class-based views (and thus an inheritable, automatic id) is the norm). Have the navblock in the same template. Problem solved.

Using jquery

jquery to the rescue :)

$('#menu a[href*="' + location.pathname.split("/")[2] + '"][class!="noselect"]').addClass('selected');

Where the [2] on the location.pathname.split is the depth level you want to use. That'd be something.com/xxx/

The noselect class is just there if you don't want some element to be highlighted.

And for the CSS:

.selected { 
	font-weight: bold; 
}

Simple, isn't it? :)

Alon Swartz's picture

Clever use of JQuery, interesting approach.

Clever use of JQuery, interesting approach.

I think the complexity would slowly scale up when adding features, such as support for multiple URLs for each link in the navigation bar.

I do like your approach for really simple implementations though, thanks for sharing!

Hi. I'm fairly new with

Hi. I'm fairly new with making apps in django and I recently ran into this kind of problem. I would like to ask for a bit of assistance of it's not too much bother, about where and how this jquery code is implemented in my django templates? My main navigations has been taken care of, but the problem now is my sub-navigations. Thank you in advance.

As an if/else tag

You see the template tag solution pop up over and over. Kind of an annoying problem, but a template tag is the best solution I've seen. I sort of wish there was a built-in in Django template tag to handle this kind of thing.

I rendered it as an if tag: http://gist.github.com/476361

(PS: you should add your blog to http://www.djangoproject.com/community/ if you haven't :)

Alon Swartz's picture

Thanks for sharing!

Thanks for sharing! I just sent off an email to Jacob requesting he add the blog to the Django community planet.

JQuery

Antonio's JQuery solution is beautiful! I was waiting to use JQuery for something other than UI decoration and there it is. I hadn't got to the part of my project where I had to sort out the menu but that has given me a great headstart into a problem I was blindly walking into!

Does anyone know a good way to solve this other problem I have. My site shows menus based on a users privs. I have a function that returns the privs as a dictionary as below:

return {"manage_entries":manage_entries, "manage_members":manage_members, "manage_something_else":manage_something_else}

I passed in each priv to my base template that includes the navigation bar and use simple {% if priv to decide if I am going to show the menu item or not. It works fine except...

I need to pass the privs in the context of every view as they all include the base.html template and thus menu. There must be a better way!

Cheers

Rich

using resolve() instead of reverse()

reverse won't work for url patterns with arguments (those with things like (.*)). I found that going the other way around, i.e. using urlresolvers.resolve(), works better.

@register.simple_tag
def nav_active(request, views):
    views = ["myapp.views." + view for view in views.split()]
    try:
        view = urlresolvers.resolve(request.path).url_name
        if view in views:
            return "active"
        else:
            return ""
    except urlresolvers.Resolver404:
        return ""

I'm appending "myapp.views" to all view names here, because I don't want to repeat it in the template.

problem with this

I'm having issues with decorator wrapped views and this code.

view = resolve(request.path).url_name == decorators.wrapper

using django and jquery with no new template tags

This has worked for me.

Include this in the head of your base template:

 

<script type='text/javascript'>

$(document).ready(function () {

      $('.navlink:eq({% block navnum %}-1{% endblock navnum %})').addClass('highlight');

});

</script>

and in the html body:
<ul>

    <li class='navlink'>Home</li>

    <li class='navlink'>News</li>

etc.

</ul>
 
In the css, have the 'highlight' class do whatever highlighting you want to do on the menu item for the current page.
Then in the News template (which extends the base), have the following:
{% block navnum %}1{% endblock navnum %}
In the Home template, do this:
	{% block navnum %}0{% endblock navnum %}
where the number between the tags is the index of the <li> inside the <ul>.
 
Cheers!

thanks

this works great, thanks!

Post new comment

The content of this field is kept private and will not be shown publicly. If you have a Gravatar account, used to display your avatar.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <p> <span> <div> <h1> <h2> <h3> <h4> <h5> <h6> <img> <map> <area> <hr> <br> <br /> <ul> <ol> <li> <dl> <dt> <dd> <table> <tr> <td> <em> <b> <u> <i> <strong> <font> <del> <ins> <sub> <sup> <quote> <blockquote> <pre> <address> <code> <cite> <strike> <caption>

More information about formatting options

Leave this field empty. It's part of a security mechanism.
(Dear spammers: moderators are notified of all new posts. Spam is deleted immediately)