46
votes

I am building a webapp that has several main sections. Each section has several sub-sections. I have a main_nav.html file that holds the nav for the main section. This is added to the based HTML file with the {% include ... %} command in the base.html template. Further, I have several sub-section nav files, each of which are added to any given page with the same {% include ... %} command.

All the nav bars are very simple, just text with <a href...> tags.

I want to highlight the link for the current main section and the current sub-section. Since this webapp is rather big, I was hoping to somehow do this without adding page-specific information. Plus, I want it to just "work" as the webapp expands to include more sections and sub-sections. For example, could this be done by looking at the actual URL in use? I was hoping to put this could within the nav files themselves and not have to load some variable or whatever within every django view.

So, for example, the nav looks like this:

(main ->)            [Systems][Invoices][Work Orders][Admin]
(system sub-nav ->)  [Owner][Billing][Contacts]

So if I am in the Billing section of Systems, I want the link Systems in bold and the link Billing to be bold (or some other simple highlight)

Or:

(main ->)                 [Systems][Invoices][Work Orders][Admin]
(Work-Orders sub-nav ->)  [Create New][Outstanding]

If I am in the Outstanding section of Work Orders, the link Work Orders and the link Outstanding needs to be highlighted.

Any ideas?

6
There is a middleware that does this and it's very easy to plug... check out the django snippets siteM. Ryan
consider this one too github.com/hellysmile/django-activeurl (fork for django 3: github.com/timgates42/django-activeurl works great)Paolo

6 Answers

74
votes

Assuming you use render_to_response with RequestContext or use the render method or class-based views of Django 1.3, you'll have the request object available in your template. From there, it's a simple matter of just accessing the current path and comparing it with expected values:

<a href="/some/path/to/be/highlighted/"{% if request.path == '/some/path/to/be/highlighted/' %} class="active"{% endif %}>Some Link</a>

In Django 1.3, I like to save redundancy and use the as operator for the URL lookup:

{% url 'some_urlpattern_name' as url %}
<a href="{{ url }}"{% if request.path == url %} class="active"{% endif %}>Some Link</a>

Repeat as necessary for each link.

18
votes

Expanding on Chris' accepted answer, you can use the fact that the Django template language supports in for partial matching. So for example if you have URLs like

/people/directory
/people/profiles/joe
/people/profiles/edit

and you want the main "People" nav element highlighted for all of these cases, use something like:

{% if "/people/" in request.path %}class="active"{% endif %}
7
votes

Just thought I'd throw in my approach for others to consider if they find this in google.

In my templates I have something like this:

{% block pagename %}homepage{% endblock %}

Then in my main template, something like this (so that the page name from the inheriting template is available to the rendered page):

<span id="_pageName" style="display:none">{% block pagename %}{% endblock %}</span>

My links look like this:

<li data-link-name="homepage"><a href="{% url "pages:home" %}">Home</a></li>

All you need is a bit of javascript to apply your CSS class to the correct link when the page loads. Mine looks something like this:

$(function() {
    var pageName = document.getElementById('_pageName');
    if (pageName != null) { pageName = pageName.innerHTML; }
    else { pageName = ''; }
    if (pageName.length > 0) {
        $("li[data-link-name='" + pageName + "']").addClass('active');
    }
});

Very simple, gives you control over which link to highlight by adding a tiny block to your templates.

3
votes

In an other similar question, I've seen jpwatts', 110j's, nivhab's & Marcus Whybrow's answers, but they all seem to lack in something: what about the root path ? Why it's always active ?

So I've made an other way, easier, which make the "controller" decides by itself and I think it resolve most of the big problems.

Here is my custom tag:

## myapp_tags.py

@register.simple_tag
def nav_css_class(page_class):
    if not page_class:
        return ""
    else:
        return page_class

Then, the "controller" declares CSS classes needed (in fact, the most important is it declares its presence to the template)

## views.py

def ping(request):
    context={}
    context["nav_ping"] = "active"
    return render(request, 'myapp/ping.html',context)

And finally, I render it in my navigation bar:

<!-- sidebar.html -->

{% load myapp_tags %}
...

<a class="{% nav_css_class nav_home %}" href="{% url 'index' %}">
    Accueil
</a>
<a class="{% nav_css_class nav_candidats %}" href="{% url 'candidats' %}">
    Candidats
</a>
<a class="{% nav_css_class nav_ping %}" href="{% url 'ping' %}">
    Ping
</a>
<a class="{% nav_css_class nav_stat %}" href="{% url 'statistiques' %}">
    Statistiques
</a>
...

So each page has its own nav_css_class value to set, and if it's set, the template renders active: no need of request in template context, no URL parcing and no more problems about multi-URL pages or root page.

0
votes

Expanding on fabspro's answer, here's a way to do this based on page urls and with vanilla js.

<div id="navbar">
    <ul>
        <li data-link-name="home">
            <a href="{% url 'home' %}"><span>Home</span></a>
        </li>
        <li data-link-name="parent">
            <a href="{% url 'myapp:index' %}"><span>Parent</span></a>
        </li>
        <li data-link-name="child1">
            <a href="/myapp/child1/grandchild1"><span>Child 1</span></a>
        </li>
    </ul>
</div>

<script>
    var path = location.pathname;
    if (path == "/") {
        pageName = "home";
    } else if (path.startsWith("/myapp/child1")){
        pageName = "child1";
    } else if (path.startsWith("/myapp")) {
        pageName = "parent";
    }

    document.querySelector("li[data-link-name='" + pageName + "']").classList += "active";
</script>

There's a lot of flexibility here. For example, the Parent link above would be highlighted for all urls in it, except /child1. The Child 1 link would take the user to the /grandchild1 page, but the link would be shown active for all grandchildren in child1. Any kind of custom logic could be written really.

0
votes

If you want to handle complex urls with unicode do not forget to use iriencode (https://docs.djangoproject.com/en/dev/ref/templates/builtins/#iriencode)

{% url 'your_view_name_defined_in_urls.py' possible_attribute as url %}
<a class="{% if request.path|iriencode == url %}active{% endif %}" href="{{ url }}">
    Your label
</a>