django template tag for active CSS class

Navigation bar is an important part of the web application since it is the map that guides the users in different parts of the application. For better user experience navigation bars should indication which option is currently selected by the user. A common way to indicate which option is selected is by simply changing the CSS style of the selected option.

Vincent Foley has a very good article in his blog of how to implement a navigation bar in django. I include the latest paragraph of his post here, so if you are not familiar with url patterns and django template tags please read the full article of Vincent:

## tags.py
@register.simple_tag
def active(request, pattern):
    import re
    if re.search(pattern, request.path):
        return 'active'
    return ''

<!-- navigation -->

{% load tags %}
<div id="navigation">
    <a class="{% active request "^/$" %}" href="/">Home</a>
    <a class="{% active request "^/services/" %}" href="/services/">Services</a>
    <a class="{% active request "^/contact/" %}" href="/contact/">Contact</a>
</div>

This is a pretty good implementation but from my point of view it has one problem. It binds the url patterns with the template page. If you need to change the url pattern you must not forget to change the template also. Furthermore, what happens if you ship the application and the client decides to change the urls of the application.

Fortunately django has two features that allows to push this implementation one step further.
The first feature is that in django we can name the the url patterns, so the urls.py will look something like this:

## urls.py
urlpatterns += patterns('',
(r'/$', view_home_method, 'home_url_name'),
(r'/services/$', view_services_method, 'services_url_name'),
(r'/contact/$', view_contact_method, 'contact_url_name'),
)

The second feature is that we can create variables in the template page, the following code creates three variables (home, services and contact) these variables holds the url patterns for the respective names:

{% url home_url_name as home %}
{% url services_url_name as services %}
{% url contact_url_name as contact %}

Combining these two features of django we can modify the code of Vincent as follows:

{% load tags %}

{% url home_url_name as home %}
{% url services_url_name as services %}
{% url contact_url_name as contact %}

<div id="navigation">
    <a class="{% active request home %}" href="home">Home</a>
    <a class="{% active request services %}" href="services">Services</a>
    <a class="{% active request contact %}" href="contact">Contact</a>
</div>

As you can see we have totally decoupled the url patters from the template page. I know we have coupled it with the url names but there are no reasons whatsoever to change the url names since they are not visible anywhere, from now on we can change the url patterns as we wish.
Also note that the href tag has changed to the new variable that we created so it points to the correct url.

Important: There is not need to change the template tag.

Enjoy.

About these ads

8 thoughts on “django template tag for active CSS class

  1. Thanks for great example. I’ve been googling something like this for a while.

    To make it even better, I suggest the template tag is modified a little bit and you put:

    pattern = “^%s$” % pattern
    after import re

    And why that? My url structure has /appname/ as an application index page and every other page as /appname/foo/, /appname/bar/ etc. With the current version the index-page gets ‘active’ status every time because the path always contains ‘/appname/’.

    Also you might have typos in navigation div, I think you should use href=”{{ home }}” instead of href=”home”. :)

    • Thank you for a great post.

      The comment from ozkuu nailed it. That was exactly my problem .- that the “home” or index page was always getting hit and therefore put as active.

      Now I can sleep again – and continue doing some actual work on the site ;)

  2. Hi,
    very nice implementation indeed. It works pretty well when the request.method=”GET”, but when it comes to “POST”, I got some errors and the most relevant one is
    VariableDoesNotExist: Failed lookup for key [request] in u”[{u …

    I have the following in my url.py and views.py

    #url.py
    ...
    url(r'^feedback/$', show_feedback, name='feedback_url_name'),
    url(r'^thanks/$', do_thanks, name='thanks_url_name'),
    ...

    #view.py
    def do_feedback(request):
    if request.metho=='POST':
    #do stuff with the POST data
    #...
    return HttpResponseRedirect('/thanks/')
    else:
    return render_to_response( 'feedback.html', {}, context_instance=RequestContext(request) )

    def do_thanks(request):
    return render_to_response( 'thanks.html', {}, context_instance=RequestContext(request) )

    Do you know what might cause the problem? Thanks in advance.

    • Hi,

      you have a typo in your def do_feedback(request): if request.metho==’POST’: should be request.method

      try that maybe that’s the problem :)

  3. OK, I was almost sure the addition from ozkuu did what I needed, but I was mistaken.

    After playing with the code for a bit, I found out my url patterns were not “compatible” with this, so I change the “active” templatetag to:


    def active(request, pattern, return_value):
    if pattern == '/':
    if request.path == '/':
    return return_value
    else:
    return ''

    if re.search(pattern, request.path):
    return return_value
    return ''

    I know it’s not the nicest solution, but it makes “root” special, which it is :)

  4. Pingback: Creating base.html and improving the navigation bar. | Anthony Honstain SDE

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s