You are here
Django navigation bar (active link highlighting)
Alon Swartz - Thu, 2010/07/08 - 11:49 -
16 comments
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" %}
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:
Then in your CSS:
Nice and clean, but won't scale well if you have a dynamic site menu or have extensive menus.
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 :)
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:
Simple, isn't it? :)
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 :)
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.
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:
thanks
this works great, thanks!
Error while implementing this
Am getting the following error :
Nice Tutorial
Alon your tutorial is very nice and helpful, i also wrote similar tutorial, hope you like it. http://www.allphptricks.com/jquery-automatically-highlight-current-page/
How to, when arguments passed in urls.
Pages
Add new comment