| published by | Software Crafts |
|---|---|
| in blog | Software Crafts |
| original entry | Django Template components are slowly coming |
The problem before me last week was to build out the navigation for a section of the new startup application. I was about to install a template component library (django-bird was the choice...and I still might!), when I noticed in an issue on django-bird that coming in Django 5.2 is the simple_block_tag template tag. After reviewing the example in the docs I thought it would be a perfect fit for my use case. The result was 3 template tags that allow me to build out a navigation that has toggles with sub items and tracking the currently active page.
First here is the final template in which they are used:
{% navitem url_name='thing-dashboard' %}Dashboard{% endnavitem %}
{% togglenavitem toggle_name="THINGS" %}
{% subnavitem url_name='thing-list' %}Overview{% endsubnavitem %}
{% for thin in things %}
{% subnavitem url_name=account.get_absolute_url %}Thing {{ thin.pk }}{% endsubnavitem %}
{% endfor %}
{% subnavitem url_name='thing-create' %}
New thing
{% endsubnavitem %}
{% endtogglenavitem %}
This has a top-level navigation item and a toggle which lists a list of "things" with option of creating a new thing at the end. Let's start simple with the main nav item. The template tag is as follows:
@register.simple_block_tag(takes_context=True)
def navitem(context, content, url_name):
url = resolve_url(url_name)
is_active = context['request'].path == url
format_kwargs = {
"content": content,
"url": url,
"is_active_classes": "bg-blue-50" if is_active else "bg-gray-50 hover:bg-blue-50"
}
return get_template("nav/item.html").render(format_kwargs)
and the referenced template being:
<li>
<a href="{{url}}"
class="block rounded-md py-2 pl-10 pr-2 text-sm/6 font-semibold text-gray-700 {{is_active_classes}}">{{content}}</a>
</li>
It's really quite simple, almost like a sub-view? We simple construct the necessary variables for the template and then render it! The other two template tags are very similar as shown below for completeness.
@register.simple_block_tag(takes_context=True)
def subnavitem(context, content, url_name):
url = resolve_url(url_name)
is_active = context['request'].path == url
format_kwargs = {
"content": content,
"url": url,
"is_active_classes": "bg-blue-50 is_active" if is_active else "bg-gray-50 hover:bg-blue-50"
}
return get_template("nav/subitem.html").render(format_kwargs)
@register.simple_block_tag(takes_context=True)
def togglenavitem(context, content, toggle_name):
format_kwargs = {
"content": content,
"toggle_name": toggle_name,
"toggle_checked": "checked" if 'is_active' in content else ""
}
return get_template("nav/togglenavitem.html").render(format_kwargs)
The subnavitem template:
<li>
<a href="{{url}}"
class="block rounded-md py-2 pl-9 pr-2 text-sm/6 text-gray-700 {{is_active_classes}}">{{content}}</a>
</li>
The togglenavitem template:
<li>
<div>
<input type="checkbox" id="toggle_{{toggle_name}}" class="peer hidden" {{toggle_checked}} />
<label for="toggle_{{toggle_name}}"
class="flex w-full items-center gap-x-3 rounded-md p-2 text-left text-sm/6 font-semibold text-gray-700 hover:bg-gray-50 peer-checked:[&>svg]:rotate-90 peer-checked:[&>svg]:text-gray-500"
aria-controls="sub-menu-1"
aria-expanded="false">
<svg class="size-5 shrink-0 text-gray-400 transition-transform duration-200"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
data-slot="icon">
<path fill-rule="evenodd" d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" />
</svg>
{{toggle_name}}
</label>
<ul class="mt-1 px-2 overflow-hidden max-h-0 peer-checked:max-h-96 transition-all duration-200" id="sub-menu-1">
{{content}}
</ul>
</div>
</li>
We use a hidden checkbox to store the state if the toggle is open and the CSS class is_active to track if a subnavitem is the active page to keep the relevant toggle open.
Some final thoughts, first thanks to Jake Howard for proposing and implementing this change! Second he has proposed a similar tag for inclusion tags which would reduce the small amount of boilerplate in the template rendering. I do wonder however if it's possible to ditch writing any python templatetags at all? Could I specify the formatting in the template itself? That's probably where a third-party package comes in for now, which I will be happy to do, but until then I'm going to see how far I can go with the simple_block_tag!