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:
- Half 1 – focuses on the undertaking setup together with the client-side
- 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.
Modal Element
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:
ModalComponent
is a Python class that extendselement.Element
class.- 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:
- We set
{% load viewcomponent_tags %}
on the prime so we are able to use the{% element %}
tags. {% element 'modal' as modal_comp %}
creates a modal element and assigns it to themodal_comp
variable.{% name modal_comp.modal_trigger %}
passes thecontent material
to themodal_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.
Modal Dimension
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:
- Now the element can settle for a
dimension
parameter. - We outlined
size_map
to map the scale to the CSS class together with asize_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:
- We put the tab content material and the respective panel content material collectively, to make the code extra readable.
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.
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:
Sequence:
- Half 1 – focuses on the undertaking setup together with the client-side
- Half 2 (this tutorial!) – focuses on the server-side