JavaScript Component-Inspired Django Templates

Friday, July 19, 2019
Tags: technical cassettenest

When I was starting out writing Cassette Nest, I went back and forth with “real” code as well as prototypes using React and Vue.

I have a UI concept that I use throughout the app called a “card.” It’s a big, blocky interface that spans the width of the screen. It could show information about a camera, a roll of film, or a project (among other things).

Because I’m developing all of Cassette Nest to work without JavaScript, I needed to convert my prototypes to Django templates.

I built a template for a card that acts very much like a JavaScript component. Every type of card uses the same template, but its appearance will change depending on the values passed into it (similar to props in Vue or React).

Here are a few ways I include the template (note the Sass-inspired _ in the filename to indicate that it’s intended to be used as an include.):

{% include '_card.html' with name='Dashboard' location=dashboard_url %}

{% include '_card.html' with name='Ready to process' location=ready_url slot='count' count=rolls_ready_count %}

{% include '_card.html' with name='In storage' location=inventory_url slot='count' count=rolls_storage_count %}

{% include '_card.html' with name=roll.camera.name location=roll_detail slot='reminder' roll=roll %}

Here’s (a simplified version of) the template itself:

<section class="c-card c-card--{# ... #}">
  {% if project and film %}
    <form onsubmit="{# ... #}" action="{# ... #}" method="post">
      {# ... #}
      <input type="submit" value="Remove" />
    </form>
  {% endif %}
  <a href="{# ... #}">
    <header><h1>{{ name }}</h1></header>
    {% if slot == 'count' %}
      <div class="c-card__count" title="Number of rolls">
        <span>{# ... #}</span>
      </div>
    {% elif slot == 'reminder' %}
      <section class="c-reminder c-reminder--{# ... #}">
        <header>
          {% if roll.film.iso %}
            <h1 title="{{ roll.film.type|upper }}">
              {{ roll.film.iso }}
            </h1>
          {% endif %}
        </header>
      </section>
    {% endif %}
  </a>
</section>

(Please forgive any incorrect document outlines. I’m working on that.)

Note my use of OOCSS-ish / BEM-style classes. That makes writing nested Sass very nice.

.c-card {
    // ...
    &--film {
        // ...
    &__count {
        // ...

Here are some of the ways cards can look:

Inventory Item showing name, number of rolls, and type of film (background color indicating color negative film)
Inventory Item showing name, number of rolls, and type of film (background color indicating color negative film)
Project showing number of rolls of film
Project showing number of rolls of film
Loaded Camera with a “reminder” tab showing film type (background color indicating black and white) and ISO
Loaded Camera with a “reminder” tab showing film type (background color indicating black and white) and ISO

I’ve since expanded the concept to certain form fields, which hasn’t been quite as easy of a drop-in solution so far, but it’s a lot easier than duplicating a lot of HTML where it does work.

Look how clean this is!

{% for field in form %}
  {% include '_form-field.html' with field=field %}
{% endfor %}

And here’s how it’s defined in the template.

<div class="c-field {# ... #}">
  <label for="{{ field.id_for_label }}">{{ field.label }}</label>
  <div class="c-field__widget">
    {{ field }}
    {% if field.help_text %}
      <small>{{ field.help_text }}</small>
    {% endif %}
  </div>
</div>

Anyway, I’m liking this technique and wanted to share it! Is anybody else building pseudo-components in their back-end templates like this?