Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reusable HTML component libraries in Django

I have a Django website with many pages all with their own unique content and markup (as opposed to a single template that is dynamically filled with data). These pages' templates are derived from a common "tool" template, which in turn derives from a "base" template. Each page also has a JS file which does the scripting for that page. Each pages data is spread around 3 or 4 locations:

  • the template
  • the page script (and maybe other page-specific statics)
  • a central "tool registry" which contains thing like the tool name, description and categories
  • for some tools, a set of template tags to do some HTML construction specific to that page (e.g. data to present in a table).

However, although each page has a unique layout and content, I still want to be able to share commonly used parametrised HTML "snippets" between pages. For example, one page has an input with a dropdown button that, using Bootstrap, looks like this:

<div class="input-group">
    <span class="input-group-btn">
        <a class="btn btn-default dropdown-toggle" data-toggle="dropdown" href="#">
            Common baudrates
            <span class="caret"></span>
        </a>
        <ul class="dropdown-menu pull-right" id="common-baudrate-list">
            <li><a href='#'>9600</a></li>
            <li><a href='#'>115200</a></li>
        </ul> 
    </span>           
    <input class="form-control" type="text" id="baudrate-input" val="115200">
</div>

I'd like to make a re-uasable parametrised function/template/tag which allows me to put in a structure like this given something like "group ID", "list of entries", "left/right button position", "default value".

The ways I see to do it so far are:

  • As a template, which has a fairly limited syntax, and make it hard to do something like a list of entries like this: ['Entry 1', 'Entry 2']
  • As a template, passing data into the context via the view (doesn't really appeal because I'd be spreading the pages contents around even more).
  • As a template tag, building the HTML as a big nasty string
  • As a template tag using an XML library like lxml, which is a pretty flexible method, but suffers from verbose syntax, over-complexity and I still have get data into the tag from the template!

None of these seem like a neat, re-usable and loosely coupled way to deal with this (for example, if I change up to Bootstrap 4 in future, I may need to re-write these components, and I'd rather have to do it just once). Having a library of components like this will also make future pages easier to construct. Is there a canonical "right" way to do this, or even a commonly used idiom for it?


Edit to show solution implementation

Using inclusion tags as answered below by Wolph, I avoided using a clunky construction like this

{% with [1,2,3] as items %}
    {% my_tag items.split %}
{% endwith %}

as follows:

@register.inclusion_tag('components/this_component.html')
def input_with_dropdown(dropdown_title, dropdown_items, default_value, group_id, button_left, *args, **kwargs):

    split_char = kwargs['split_char'] if 'split_char' in kwargs else ' '

    return {
        'dropdown_items': dropdown_items.split(split_char),
        'dropdown_title': dropdown_title,
        'group_id': group_id,
        'default_value' : default_value,
        'button_left': button_left
    }

And then passing in the variables like this:

{% input_with_dropdown 'Common baudrates' '9600 48000 115200' 115200 'baudrate-input' 0 %}
like image 436
Inductiveload Avatar asked Oct 06 '13 00:10

Inductiveload


1 Answers

Have you taken a look at inclusion tags? https://docs.djangoproject.com/en/dev/howto/custom-template-tags/#inclusion-tags

I believe that these would do exactly what you want, you can still specify parameters but you can just pass along a template to have it render really easily.

Example:

from django import template
register = template.Library()

@register.inclusion_tag('list_results.html')
def list_results(results):
    return {
        'results': results,
        'count': len(results),
    }

list_results.html:

Found {{ count }} results.<br>
<ul>
{% for result in results %}
    <li>{{ result }}</li>
{% endfor %}
</ul>
like image 127
Wolph Avatar answered Nov 19 '22 08:11

Wolph