Wednesday, July 3, 2024
HomePythonReusable Elements in Django with Stimulus and Tailwind CSS - Half 2

Reusable Elements in Django with Stimulus and Tailwind CSS – Half 2


Welcome to half 2. Within the first a part of this tutorial sequence, we arrange Django, configured python-webpack-boilerplate, and used Stimulus and Tailwind CSS to construct reusable modal and tab parts. On this second half, we’ll take a look at methods to use django-viewcomponent to construct server-side parts, which may also help us reuse the code in a extra environment friendly manner.


Sequence:

  1. Half 1 – focuses on the undertaking setup together with the client-side
  2. Half 2 (this tutorial!) – focuses on the server-side

If frontend instruments and applied sciences is not actually your factor, and also you wish to hold it easy, take a look at Constructing Reusable Elements in Django, which offers solely with constructing server-side UI parts with Django.

If we check out the modal element HTML, there’s nonetheless some further HTML we have to enter if we wish to add a modal to our web page every time:

<div data-controller="modal">

  <!-- Modal toggle -->
  <button data-action="click->modal#openModal" class="btn-blue" kind="button">
    Toggle modal
  </button>

  <div data-modal-target="container" tabindex="-1" aria-hidden="true" class="hidden modal-container">
    <div class="relative p-4 w-full max-w-2xl max-h-full">

      <div class="modal-content">
        <!-- Modal header -->
        <div class="modal-header">
          <h3>
            Modal Title
          </h3>
        </div>

        <!-- Modal physique -->
        <div class="modal-body">
          <p class="text-base leading-relaxed text-gray-500">
            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer in lectus et ipsum eleifend consequat. Aenean
            pellentesque tortor velit, non molestie ex ultrices in. Nulla at neque eu nulla imperdiet mattis vel sit amet
            neque. In ac mollis augue, ac iaculis purus. Donec nisl massa, gravida pharetra euismod nec, ultrices ut quam.
            Vivamus efficitur bibendum hendrerit. In iaculis sagittis elementum. Sed sit amet dolor ultrices, mollis nisl
            sed, cursus eros. Suspendisse sollicitudin quam nulla, at dignissim ex scelerisque non. Mauris ac porta nisl.
          </p>
        </div>

        <!-- Modal footer -->
        <div class="modal-footer">
          <button data-action="click->modal#closeModal" kind="button" class="btn-blue">
            Shut
          </button>
        </div>

      </div>
    </div>
  </div>
</div>

Can we reuse a few of the template code and solely go the header, physique, and footer content material to the template?

We will use django-viewcomponent to assist with this.

django-viewcomponent

django-viewcomponent is a Django library that gives a approach to create reusable parts in your Django undertaking.

First time with django-viewcomponent? Take a look at Constructing Reusable Elements in Django.

Add it to the necessities.txt file:

django-viewcomponent==1.0.5

Set up:

(venv)$ pip set up -r necessities.txt

Then add the app to INSTALLED_APPS in settings.py:

INSTALLED_APPS = [

    ...

    "django_viewcomponent",
]

Modify the TEMPLATES part of settings.py like so:

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": ["django_component_app/templates"],
        "APP_DIRS": False,  # up to date
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
            "loaders":[(  # new
                "django.template.loaders.cached.Loader", [
                    "django.template.loaders.filesystem.Loader",
                    "django.template.loaders.app_directories.Loader",
                    "django_viewcomponent.loaders.ComponentLoader",
                ]
            )],
        },
    },
]

Replace tailwind.config.js:

const Path = require("path");
const pwd = course of.env.PWD;

// We will add present undertaking paths right here
const projectPaths = [
  Path.join(pwd, "./frontend/src/**/*.js"),
  Path.join(pwd, "./django_component_app/templates/**/*.html"),
  // django-viewcomponent
  Path.join(pwd, "./components/**/*.py"),          // new
  Path.join(pwd, "./components/**/*.html"),        // new
  // add js file paths if you need
];

const contentPaths = [...projectPaths];
console.log(`tailwindcss will scan ${contentPaths}`);

module.exports = {
  content material: contentPaths,
  theme: {
    lengthen: {},
  },
  plugins: [],
}

We added Path.be part of(pwd, "./parts/**/*.py"), and Path.be part of(pwd, "./parts/**/*.html"), in order that manner the Tailwind CSS lessons will probably be detected and added to the ultimate CSS file.

Create a brand new file known as parts/modal/modal.py:

from django_viewcomponent import element
from django_viewcomponent.fields import RendersOneField


@element.register("modal")
class ModalComponent(element.Element):
    modal_trigger = RendersOneField(required=True)
    modal_header = RendersOneField(required=True)
    modal_body = RendersOneField(required=True)
    modal_footer = RendersOneField(required=True)

    template_name = "modal/modal.html"

Notes:

  1. ModalComponent is a Python class that extends element.Element class.
  2. We used RendersOneField to outline a slot subject for the modal element.

Create a brand new file known as parts/modal/modal.html, which is the template file for the element:

<div data-controller="modal">

  {{ self.modal_trigger.worth }}

  <div data-modal-target="container" tabindex="-1" aria-hidden="true" class="hidden modal-container">
    <div class="relative p-4 w-full max-w-2xl max-h-full">

      <div class="modal-content">
        <div class="modal-header">
          {{ self.modal_header.worth }}
        </div>
        <div class="modal-body">
          {{ self.modal_body.worth }}
        </div>
        <div class="modal-footer">
          {{ self.modal_footer.worth }}
        </div>
      </div>
    </div>
  </div>
</div>

{{ self.modal_trigger.worth }} renders a slot worth of the modal_trigger subject.

Replace django_component_app/templates/index.html:

{% load webpack_loader static %}
{% load viewcomponent_tags %}

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta identify="viewport" content material="width=device-width, initial-scale=1.0">
  <title>Modal Instance</title>
  {% stylesheet_pack 'app' %}
  {% javascript_pack 'app' attrs='defer' %}
</head>
<physique>
  {% element 'modal' as modal_comp %}
    {% name modal_comp.modal_trigger %}
      <button data-action="click->modal#openModal" class="btn-blue" kind="button">
        Toggle modal
      </button>
    {% endcall %}

    {% name modal_comp.modal_header %}
      <h3>
        Modal Title
      </h3>
    {% endcall %}

    {% name modal_comp.modal_body %}
      <p class="text-base leading-relaxed text-gray-500">
        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer in lectus et ipsum eleifend consequat. Aenean
        pellentesque tortor velit, non molestie ex ultrices in. Nulla at neque eu nulla imperdiet mattis vel sit amet
        neque. In ac mollis augue, ac iaculis purus. Donec nisl massa, gravida pharetra euismod nec, ultrices ut quam.
        Vivamus efficitur bibendum hendrerit. In iaculis sagittis elementum. Sed sit amet dolor ultrices, mollis nisl
        sed, cursus eros. Suspendisse sollicitudin quam nulla, at dignissim ex scelerisque non. Mauris ac porta nisl.
      </p>
    {% endcall %}

    {% name modal_comp.modal_footer %}
      <button data-action="click->modal#closeModal" kind="button" class="btn-blue">
        Shut
      </button>
    {% endcall %}
  {% endcomponent %}
</physique>
</html>

Notes:

  1. We set {% load viewcomponent_tags %} on the prime so we are able to use the {% element %} tags.
  2. {% element 'modal' as modal_comp %}creates a modal element and assigns it to the modal_comp variable.
  3. {% name modal_comp.modal_trigger %} passes the content material to the modal_trigger slot of the modal element.

To check, run the Django dev server in a single terminal window:

(venv)$ python handle.py runserver

And run the webpack dev server in a distinct window:

Navgate to http://127.0.0.1:8000/ and ensure the modal nonetheless works as anticipated.

Whilst you’re newly created server-side modal element works nice, say your designer involves you and says, “I would like an even bigger modal”. To deal with that, begin by updating parts/modal/modal.py like so:

from django_viewcomponent import element
from django_viewcomponent.fields import RendersOneField


@element.register("modal")
class ModalComponent(element.Element):
    modal_trigger = RendersOneField(required=True)
    modal_header = RendersOneField(required=True)
    modal_body = RendersOneField(required=True)
    modal_footer = RendersOneField(required=True)

    template_name = "modal/modal.html"

    size_map = {
        "md": "max-w-2xl",
        "lg": "max-w-4xl",
    }

    def __init__(self, dimension=None, **kwargs):
        self.dimension = dimension

    @property
    def size_css(self):
        # return the category primarily based on the scale, if not, return the default dimension
        return self.size_map.get(self.dimension, self.size_map["md"])

Notes:

  1. Now the element can settle for a dimension parameter.
  2. We outlined size_map to map the scale to the CSS class together with a size_css property to return the CSS class primarily based on the scale parameter.

Then, replace parts/modal/modal.html:

<div data-controller="modal">

  {{ self.modal_trigger.worth }}

  <div data-modal-target="container" tabindex="-1" aria-hidden="true" class="hidden modal-container">
    <div class="relative p-4 w-full {{ self.size_css }} max-h-full m-auto">

      <div class="modal-content">
        <div class="modal-header">
          {{ self.modal_header.worth }}
        </div>
        <div class="modal-body">
          {{ self.modal_body.worth }}
        </div>
        <div class="modal-footer">
          {{ self.modal_footer.worth }}
        </div>
      </div>
    </div>
  </div>
</div>

Object-Oriented Programming helps hold the logic within the Python class whereas permitting the template file to do the work that it is suppossed to do — i.e., render the HTML.

Then within the django_component_app/templates/index.html file, we are able to change the modal dimension like so:

{% element 'modal' dimension='lg' as modal_comp %}

...

{% endcomponent %}

As you’ll be able to see, the element could be very extendable, logic has been separated from the template, and the template code could be very clear and straightforward to learn, since we don’t want to write down many if-else statements within the template file.

Tab Element

Subsequent, let’s create a server-side tab element.

parts/tab/tab.py:

from django_viewcomponent import element
from django_viewcomponent.fields import RendersManyField


@element.register("tab")
class TabComponent(element.Element):
    tabs = RendersManyField(required=True)
    panels = RendersManyField(required=True)

    template_name = "tab/tab.html"

Right here, we used RendersManyField in order that the the slot subject can settle for a group of things.

Add the parts/tab/tab.html template:

<div data-controller="tabs">
  <div class="mb-4 border-b border-gray-200 darkish:border-gray-700">
    <ul class="flex flex-wrap -mb-px text-sm font-medium text-center">
      {% for tab in self.tabs.worth %}
      <li class="me-2" position="presentation">
        <button class="inline-block p-4 border-b-2 rounded-t-lg hover:text-gray-600 hover:border-gray-300 "
                data-action="click->tabs#showContent" data-tabs-target="{{ forloop.counter }}" kind="button">
          {{ tab }}
        </button>
      </li>
      {% endfor %}
    </ul>
  </div>
  <div>
    {% for panel in self.panels.worth %}
    <div class="hidden p-4 rounded-lg bg-gray-50 darkish:bg-gray-800" data-panel="{{ forloop.counter }}" position="tabpanel">
      {{ panel }}
    </div>
    {% endfor %}
  </div>
</div>

Replace django_component_app/templates/index.html:

{% load webpack_loader static %}
{% load viewcomponent_tags %}

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta identify="viewport" content material="width=device-width, initial-scale=1.0">
  <title>Tab Instance</title>
  {% stylesheet_pack 'app' %}
  {% javascript_pack 'app' attrs='defer' %}
</head>
<physique>

  {% element 'tab' as element %}

    {% name element.tabs %}
      Profile
    {% endcall %}
    {% name element.panels %}
      <p class="text-sm text-gray-500 darkish:text-gray-400">That is some placeholder content material the <robust
              class="font-medium text-gray-800 ">Profile tab's related content material</robust>. Clicking
        one other tab will toggle the visibility of this one for the following. The tab JavaScript swaps lessons to regulate the
        content material visibility and styling.</p>
    {% endcall %}

    {% name element.tabs %}
      Dashboard
    {% endcall %}
    {% name element.panels %}
      <p class="text-sm text-gray-500 darkish:text-gray-400">That is some placeholder content material the <robust
              class="font-medium text-gray-800 ">Dashboard tab's related content material</robust>. Clicking
        one other tab will toggle the visibility of this one for the following. The tab JavaScript swaps lessons to regulate the
        content material visibility and styling.</p>
    {% endcall %}

    {% name element.tabs %}
      Settings
    {% endcall %}
    {% name element.panels %}
      <p class="text-sm text-gray-500 darkish:text-gray-400">That is some placeholder content material the <robust
              class="font-medium text-gray-800 ">Settings tab's related content material</robust>. Clicking
        one other tab will toggle the visibility of this one for the following. The tab JavaScript swaps lessons to regulate the
        content material visibility and styling.</p>
    {% endcall %}

  {% endcomponent %}

</physique>
</html>

Notes:

  1. We put the tab content material and the respective panel content material collectively, to make the code extra readable.
  2. RendersManyField is a robust characteristic. It permits us to outline a slot subject, and go content material to the element slot a number of occasions.

http://127.0.0.1:8000/:

Tab Component

Conclusion

On this article, we moved the HTML for our parts to the server-side. The server-side parts make the code extra maintainable, reusable, and testable. I hope it may make it easier to construct a greater Django undertaking.

If you wish to know extra about django-viewcomponent, overview the official documentation. I additionally encourage you to take a look at the next sections from the Constructing Reusable Elements in Django article:

  1. Previewing Elements
  2. Frontend Belongings
  3. Element Library
  4. Different Element Options
  5. Assets

Sequence:

  1. Half 1 – focuses on the undertaking setup together with the client-side
  2. Half 2 (this tutorial!) – focuses on the server-side



RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments