An important part of a usable web site is a well-designed navigation bar. Something that any navigation bar should have is an indication of where the user is. Common practices for this include using a bold face for the link, a different color or adding a small icon next to the current section link. To do that, you could include your navigation code in every page and use an “active” class name on the a element to distinguish it from the other links.
<div id="navigation">
<a href="/">Home</a>
<a href="/services/">Services</a>
<a class="active" href="/contact/">Contact</a>
</div>
Writing this in every template quickly becomes a pain in the neck. With Django, an easy solution would be to move this section to a different file, _nav.html for instance, and include it in every page. Another, even better way, would be to make sure that every template extends a base template (a very common practice with Django) and to add the navigation to that base template.
We face a little problem however: how do we know which page we’re on? If you use the render_to_response shortcut, you can give an extra keyword argument, context_instance. If you use the RequestContext class, you can pass more data to the template (the generic views use RequestContext by default, so you don’t have to worry about them.) Here’s how to do it:
## settings.py
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.request',
)
## views.py
from django.template import ContextRequest
def home(request):
return render_to_request('home.html', {}, context_instance=RequestContext(request))
You can now use the request template variable in home.html and its parent templates, which should include your base template. The HttpRequest object has an attribute, path, which contains the current URL (starting after the host name). We can now add the active class to the link for the current section:
<div id="navigation">
<a class="{% ifequal request.path "/" %}active{% endifequal %}" href="/">Home</a>
<a class="{% ifequal request.path "/services/" %}active{% endifequal %}" href="/services/">Services</a>
<a class="{% ifequal request.path "/contact/" %}active{% endifequal %}" href="/contact/">Contact</a>
</div>
This code has two problems: (1) it’s really ugly (it’s probably all that should matter to make you want to rework this code), and (2) imagine that there are subsections under the services section, /services/web-hosting/, /services/tech-support/, etc. When you’re under one of those subsections, the link to the services section will not be highlighted. We’ll fix that by using a custom template tag that will check if request.path begins with a given string.
## tags.py
@register.simple_tag
def active(request, pattern):
if request.path.startswith(pattern):
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>
Ah, the code is much nicer and now if we go to /services/web-hosting/, the services link will still be highlighted. Another link will be highlighted unfortunately: the home link will ALWAYS be highlighted, because every path begins with a slash. Let’s change the implementation of the active tag slightly to use regular expressions and we’ll also change the calls to {% active %}
## 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>
Nice! Now it’s how we want it. Home will be highlighted only when we’re viewing the / URL, the subsections still work and the code is still pretty cute.
September 14, 2007 at 3:35 pm |
Thats a neat solution. I just worked around the same problem just this morning by writing a custom middleware (to intercept process_view), a context processor func and a decorator to decorate every view func. Your solution is a whole lot simpler and elegant.
September 17, 2007 at 10:57 am |
Very neat. Uncannily similar to a solution I came up with for Rails. (I’m familiar with Rails but plan to learn Django too).
September 17, 2007 at 7:32 pm |
Intermediate templates and inheritance are what you want; then you don’t need any tags at all :)
September 17, 2007 at 8:06 pm |
James: I don’t see how that would help out. I still need to be aware of the page I’m currently on. Hence the need for a tag.
October 31, 2007 at 3:27 am |
Hello,
I just found your post, here is my solution:
http://www.martin-geber.com/weblog/2007/10/25/breadcrumbs-django-templates/
(By the way, I that’s what James means.)
Cheers.
November 10, 2007 at 6:34 pm |
[...] Highlight active menu option: use {{ request.path }} to know requested URL and compare it with menu options * [more info] [...]
February 23, 2008 at 4:28 pm |
A fix:
## views.py
from django.template import RequestContext
March 19, 2008 at 3:03 pm |
[...] new and improved {% active %} tag A few months ago, I described a Django template tag that I wrote to identify the currently active [...]
April 23, 2008 at 6:35 pm |
This made some code go way in templates! Thanks
June 1, 2008 at 8:56 pm |
This is very nice for my main navigation.
But what about the Side navigation that keeps track of active liknks like:
/events/1
/events/2
where each links is active depending on the current event beign displayed.
I can’t really do {% active request “/events/” + event.id %} here :-(
June 1, 2008 at 9:13 pm |
drozzy: Just do {% active request “/events/\d+” %} :)
September 22, 2008 at 8:40 pm |
Thanks, this is an intuitive and insightful intro to some fundamental functionality.
October 14, 2008 at 11:26 pm |
Thanks for the write-up. I was looking all over for something like this and couldn’t find it in the docs.
November 8, 2008 at 10:40 am |
Thanks for an excellent and simple example!
My tags.py file ended up looking like this:
from django import template
register = template.Library()
@register.simple_tag
def active(request, pattern):
import re
if re.search(pattern, request.path):
return ‘active’
return ”
November 8, 2008 at 10:40 am |
but with indentation obviously…
November 23, 2008 at 1:52 am |
Thanks, this was almost exactly what I was planning on writing. You not only you saved me the time but also made a more flexible version by using regular expressions.
December 27, 2008 at 10:32 pm |
Can you tell me, where do you put the code? Does it belong in the view.py file? If it belongs in its own file, where does that file go?
Thank you very much.
January 25, 2009 at 7:03 am |
[...] 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: [...]
January 25, 2009 at 8:48 am |
This is a good implementation. The only problem I see is that the url pattern is coupled with the template page. To decouple it you can use url names in the urls.py and variables in the template page. I have a post on my blog on how to do the decoupling, it based on your article obviously.
110j.wordpress.com/2009/01/25/django-template-tag-for-active-class
February 16, 2009 at 1:17 am |
This is a great solution. I haven’t seen anything similar posted anywhere else.
Some argue that this is a job for base_* and some CSS but I disagree. What happens when you are using a mix of normal views and flatpages? regex is at the heart of this tag which makes it so much more flexible than most other solutions.
Thanks!
March 12, 2009 at 7:21 am |
Just for the record, I’m using Django 1.0.2 and it seems that ‘request’ is available by default in every template, so there’s no need to do the ‘RequestContext’ thing.
April 26, 2009 at 7:12 am |
That was neat. Helped me implement simple navigation.
Thanks
May 29, 2009 at 3:47 am |
Thanks for a good post, but I found some ways to imporve further. It would be much better to use the reverse method to get the urls that you need and instead use the path of the methods.
#tags.py:
from django import template
from string import Template
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
register = template.Library()
@register.simple_tag
def active(request, url, title):
s = Template(‘$title‘)
import re
print reverse(url), request.path
#if re.search(reverse(url), request.path):
if reverse(url) == request.path:
active = ‘active’
else:
active = ”
return s.substitute(active=active, link=reverse(url), title=unicode(_(title)))
#in the template
{% active request “mmp.form.views.default” “Home” %}
This tag gets the right url from urls.py, so if you change it, you just have to change it in urls.py and nowhere else. The tag also uses djangos methods for translation at the same time.
Cheers!
July 10, 2009 at 4:51 am |
I found this extremly useful, thanx
my variation:
from django.template import Library
from django.core.urlresolvers import reverse
import re
register = Library()
@register.simple_tag
def active(request, pattern, return_value):
import re
if re.search(pattern, request.path):
return return_value
return “
@register.simple_tag
def reverse_active(request, url, return_value):
print reverse(url), request.path
if reverse(url) == request.path:
return return_value
return “
# template
class=”top-menu-element{% reverse_active request “catalog.views.browse” ” top-menu-element-active” %}”
class=”top-menu-element{% active request “/help/” ” top-menu-element-active” %}”
August 25, 2009 at 4:16 am |
I get the following error:
‘DebugParser’ object has no attribute ‘path’
September 23, 2009 at 1:27 pm |
Electrical appliances with, camera of Motorola?Another cannon to, include wholegrain breads.And therefore are, any weight loss.Car stocks for COULD NOT CREATE HTTPREQUEST, this encrypt all and save it.On the processes, game especially in.,
October 20, 2009 at 8:10 am |
What tamplate do you use in your blog? Very interesting articles