Question

I'm sure I've seen this question on Stack Overflow before, but I couldn't find it by my life, so here goes nothing.

I have a normal Django menu which uses the {% url %} tag and static names for the menu items. Now I want to have a different style for the menu item which has been selected. But the menu is being rendered in the base template, so how do I figure out which menu item it is?

Was it helpful?

Solution

You could surely do this with some ugly template code, but a better more globally known way is to use a CSS selector. This lets CSS do all of the work automatically for you.

Here's how it works:

You simply put an id in your body depending on which page you are on. Then in css you do something like this:

#section-aboutme #nav-aboutme,
#section-contact #nav-contact
/* ... put one of these per body/menu item ... */
{
    font-color: red;
}

You put the nav-aboutme, and nav-contact ids on each of your menu items.

The style will automatically be selected by CSS depending on which body id they are inside of.

OTHER TIPS

I normally do it the way Brian suggested, but to accommodate for a template which a designer gave me which used the more common class="selected" method, I wrote a {% nav %} template tag.

Your HTML navigation template will look something like:

{% block nav %}
<ul class="nav">
    <li{% if nav.home %} class="selected"{% endif %}><a href="/">Home</a></li>
    <li{% if nav.about %} class="selected"{% endif %}><a href="/about/">About</a></li>
</ul>
{% endblock %}

To set the navigation in a child template, do:

{% include "base.html" %}
{% load nav %}

{% block nav %}
{% nav "about" %}
{{ block.super }}
{% endblock %}

How about a custom tag which you use to generate your nav item?

The following takes the name of the url for which a nav item should be generated and the text it should display. It generates a li tag with a class of "selected" if the named url's path is the same as the current url (requires 'django.core.context_processors.request' in your TEMPLATE_CONTEXT_PROCESSORS). Within the li, it generates an a tag with the path of the url specified by the url_name. It has the contents specified by contents.

Obviously, this could be tweaked to generate different markup for the nav item, as required.

The rest can be done using CSS.

Advantages:

  • Easy to use

  • Little code required

  • DRY

  • Could be made to be more flexible

Disadvantages:

  • Requires 'django.core.context_processors.request'

  • Requires urls to be named e.g. urlpatterns = patterns('django.views.generic.simple', ... (r'^$', 'direct_to_template', {'template': 'index.html'}, 'index'), ... ). This could potentially be done differently (e.g. pass in url).

  • Doesn't cope with pages not exactly equal to the specified and therefore will not apply the selected class to the li when on a page lower in the url heirarchy. For example, if I'm on /products/, it will highlight the nav item directing to /products/. If I'm on /products/myProduct/, it will not highlight the /products/ link. This could be coded around, but it would force people to use sensible urls. For example, change the additionalAttrs assignment to additionalAttrs = ' class=selected' if (context['request'].path.startswith(path) and path != '/') or (context['request'].path == path) else ''.

Code:

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

register = template.Library()

class NavNode(template.Node):
    def __init__(self, url_name, contents):
        self.url_name = url_name
        self.contents = contents

    def render(self, context):
        path = reverse(self.url_name)
        additionalAttrs = ' class=selected' if path == context['request'].path else ''
        return '<li'+additionalAttrs+'><a href="'+path+'">'+self.contents+'</a></li>'

@register.tag
def nav_link(parser, token):
    bits = token.split_contents()
    if len(bits) == 3:
        contents = bits.pop()
        url_name = bits.pop()
    else:
        raise template.TemplateSyntaxError, "%r tag requires a single argument" % bits[0]

    if contents[0] == contents[-1] and contents[0] in ('"', "'"):
        contents = contents[1:-1]

    return NavNode(url_name, contents)                

You can pass request.path to your template

from django.shortcuts import render_to_response
from django.template import RequestContext
return render_to_response('templ.html', {'page':request.path}, context_instance=RequestContext(request))

then use an ugly if template tag to add a CSS class to your menu item

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top