Login

Template tag to handle navigation item selection

Author:
SmileyChris
Posted:
September 15, 2009
Language:
Python
Version:
1.1
Score:
0 (after 0 ratings)

Handles navigation item selection.

See the nav docstring for details.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
from django import template
from django.utils.encoding import smart_str


register = template.Library()


class NavNode(template.Node):
    def __init__(self, item=None, var_name=None):
        self.item = item
        self.var_name = var_name or 'nav'

    def render(self, context):
        # If the nav variable is already set (to a non-empty value), don't do
        # anything.
        if context.get(self.var_name):
            return ''
        # If self.item was blank, just set the nav variable to the context
        # (useful to put the nav in a higher context stack)
        if not self.item:
            context[self.var_name] = {}
            return ''
        item = self.item.resolve(context)
        item = item and smart_str(item)
        if not item:
            return ''
        value = True
        for part in reversed(item.split('.')):
            new_item = {}
            new_item[part] = value
            value = new_item
        # The nav variable could have been set (as an empty dict) on a higher
        # context stack. Try getting it from the context, otherwise set it to
        # the current context stack.
        nav = context.get(self.var_name)
        if not isinstance(nav, dict):
            nav = {}
            context[self.var_name] = nav
        nav.update(new_item)
        return ''

    def __repr__(self):
        return "<Nav node>"


@register.tag
def nav(parser, token):
    """
    Handles navigation item selection.
    
    Example usage::
    
        {# Set up the variable for use across context-stacking tags #}
        {% nav %} or {% nav for mynav %}
        
        {# Set the context so {{ nav.home }} (or {{ mynav.home }}) is True #}
        {% nav "home" %} or {% nav "home" for mynav %}
    
    The most basic (and common) use of the tag is to call ``{% nav [item] %}``,
    where ``[item]`` is the item you want to check is selected.
    
    By default, this tag creates a ``nav`` context variable. To use an
    alternate context variable name, call ``{% nav [item] for [var_name] %}``.

    To use this tag across ``{% block %}`` tags (or other context-stacking
    template tags such as ``{% for %}``), call the tag without specifying an
    item.

    Your HTML navigation template should 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 override this in a child template, you'd do::
    
        {% include "base.html" %}
        {% load nav %}
    
        {% block nav %}
        {% nav "about" %}
        {{ block.super }}
        {% endblock %}
    
    This works for multiple levels of template inheritance, due to the fact
    that the tag only does anything if the ``nav`` context variable does not
    exist. So only the first ``{% nav %}`` call found will ever be processed.
    
    To create a sub-menu you can check against, simply dot-separate the item:: 
    
        {% nav "about_menu.info" %}
    
    This will be pass for both ``{% if nav.about_menu %}`` and
    ``{% if nav.about_menu.info %}``.
    """
    bits = token.split_contents()
    if len(bits) > 2:
        var_name = bits.pop()
        for_bit = bits.pop()
        if for_bit != 'for' or len(bits) > 2:
            raise template.TemplateSyntaxError('Unexpected format for %s tag' %
                                               bits[0])
    else:
        var_name = 'nav'
    if len(bits) > 1:
        item = parser.compile_filter(bits[1])
    else:
        item = None
    return NavNode(item, var_name)

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 9 months ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 9 months, 1 week ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 4 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 4 months ago
  5. Help text hyperlinks by sa2812 1 year, 5 months ago

Comments

the_ac (on February 20, 2011):

Thanks for that SmileyChris!

Whilst I can understand how to use this code, based on your documentation, I am not sure where I should paste it?

Can it just go anywhere, or does it need to go somewhere specific like [appname]/models.py?

#

homunq (on November 4, 2011):

for extra sugar replace lines 27-31:

    value = {'selected':'selected'}
    for part in reversed(item.split('.')):
        new_item = dict(value)
        new_item[part] = value
        value = new_item

So you can just say class="{{ nav.somesection.selected }}". You should probably change the docstring too:

or as a shortcut:

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

#

Please login first before commenting.