Повторно используемые компоненты в Django с помощью Stimulus и Tailwind CSS — Часть 2
Добро пожаловать в часть 2. В первой части этой серии руководств мы настроили Django, настроили python-webpack-boilerplate и использовали Stimulus и Tailwind CSS для создания повторно используемых модальных компонентов и компонентов табуляции. В этой второй части мы рассмотрим, как использовать django-viewcomponent для создания серверных компонентов, которые могут помочь нам более эффективно использовать код повторно.
Серия:
- Часть 1 - посвящена настройке проекта вместе с клиентской частью
- Часть 2 (это руководство!) - посвящена серверной части
Если интерфейсные инструменты и технологии вам не по душе, и вы хотите, чтобы все было просто, ознакомьтесь с Создание повторно используемых компонентов в Django, в котором речь идет только о создании серверных компонентов пользовательского интерфейса с помощью Django..
Модальный компонент
Если мы взглянем на HTML-код модального компонента, то увидим, что нам все еще нужно вводить дополнительный HTML-код, если мы хотим каждый раз добавлять модальный код на нашу страницу:
<div data-controller="modal">
<!-- Modal toggle -->
<button data-action="click->modal#openModal" class="btn-blue" type="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 body -->
<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" type="button" class="btn-blue">
Close
</button>
</div>
</div>
</div>
</div>
</div>
Можем ли мы повторно использовать часть кода шаблона и передавать в шаблон только содержимое верхнего, основного и нижнего колонтитулов?
Мы можем использовать django-viewcomponent, чтобы помочь с этим.
django-viewcomponent
компонент django-view - это библиотека Django, которая предоставляет способ создания повторно используемых компонентов для вашего проекта Django.
Впервые используете компонент django-view? Ознакомьтесь с Созданием повторно используемых компонентов в Django.
Добавьте его в requirements.txt файл:
django-viewcomponent==1.0.5
Установка:
(venv)$ pip install -r requirements.txt
Затем добавьте приложение в INSTALLED_APPS
в settings.py:
INSTALLED_APPS = [
...
"django_viewcomponent",
]
Измените раздел TEMPLATES
в разделе settings.py следующим образом:
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": ["django_component_app/templates"],
"APP_DIRS": False, # updated
"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",
]
)],
},
},
]
Обновить tailwind.config.js:
const Path = require("path");
const pwd = process.env.PWD;
// We can add current project paths 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: contentPaths,
theme: {
extend: {},
},
plugins: [],
}
Мы добавили Path.join(pwd, "./components/**/*.py"),
и Path.join(pwd, "./components/**/*.html"),
, чтобы CSS-классы Tailwind были обнаружены и добавлены в окончательный CSS-файл.
Создайте новый файл с именем components/modal/modal.py:
from django_viewcomponent import component
from django_viewcomponent.fields import RendersOneField
@component.register("modal")
class ModalComponent(component.Component):
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"
Примечания:
ModalComponent
это класс Python, который расширяет классcomponent.Component
.- Мы использовали
RendersOneField
для определения поля slot для модального компонента.
Создайте новый файл с именем components/modal/modal.html , который является файлом шаблона для компонента:
<div data-controller="modal">
{{ self.modal_trigger.value }}
<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.value }}
</div>
<div class="modal-body">
{{ self.modal_body.value }}
</div>
<div class="modal-footer">
{{ self.modal_footer.value }}
</div>
</div>
</div>
</div>
</div>
{{ self.modal_trigger.value }}
отображает значение интервала в поле modal_trigger
.
Обновить django_component_app/templates/index.html:
{% load webpack_loader static %}
{% load viewcomponent_tags %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Modal Example</title>
{% stylesheet_pack 'app' %}
{% javascript_pack 'app' attrs='defer' %}
</head>
<body>
{% component 'modal' as modal_comp %}
{% call modal_comp.modal_trigger %}
<button data-action="click->modal#openModal" class="btn-blue" type="button">
Toggle modal
</button>
{% endcall %}
{% call modal_comp.modal_header %}
<h3>
Modal Title
</h3>
{% endcall %}
{% call 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 %}
{% call modal_comp.modal_footer %}
<button data-action="click->modal#closeModal" type="button" class="btn-blue">
Close
</button>
{% endcall %}
{% endcomponent %}
</body>
</html>
Примечания:
- Мы устанавливаем
{% load viewcomponent_tags %}
вверху, чтобы мы могли использовать теги{% component %}
. {% component 'modal' as modal_comp %}
создает модальный компонент и присваивает его переменнойmodal_comp
.{% call modal_comp.modal_trigger %}
передаетcontent
вmodal_trigger
слот модального компонента.
Для тестирования запустите сервер разработки Django в одном окне терминала:
(venv)$ python manage.py runserver
И запустите сервер разработки webpack в другом окне:
$ npm run start
Перейдите к http://127.0.0.1:8000/ и убедитесь, что режим по-прежнему работает должным образом.
Модальный размер
Пока ваш недавно созданный серверный модальный компонент работает отлично, допустим, ваш дизайнер приходит к вам и говорит: "Мне нужен модал побольше". Чтобы справиться с этим, начните с обновления components/modal/modal.py вот так:
from django_viewcomponent import component
from django_viewcomponent.fields import RendersOneField
@component.register("modal")
class ModalComponent(component.Component):
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, size=None, **kwargs):
self.size = size
@property
def size_css(self):
# return the class based on the size, if not, return the default size
return self.size_map.get(self.size, self.size_map["md"])
Примечания:
- Теперь компонент может принимать параметр
size
. - Мы определили
size_map
для сопоставления размера классу CSS вместе со свойствомsize_css
, возвращающим класс CSS на основе параметра size.
Затем обновите components/modal/modal.html:
<div data-controller="modal">
{{ self.modal_trigger.value }}
<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.value }}
</div>
<div class="modal-body">
{{ self.modal_body.value }}
</div>
<div class="modal-footer">
{{ self.modal_footer.value }}
</div>
</div>
</div>
</div>
</div>
Объектно-ориентированное программирование помогает сохранить логику в классе Python, позволяя файлу шаблона выполнять ту работу, которую он должен выполнять, т.е. отображать HTML.
Затем в файле django_component_app/templates/index.html мы можем изменить модальный размер следующим образом:
{% component 'modal' size='lg' as modal_comp %}
...
{% endcomponent %}
Как вы можете видеть, компонент очень легко расширяем, логика была отделена от шаблона, а код шаблона очень чистый и легко читаемый, поскольку нам не нужно писать много инструкций if-else в файле шаблона.
Вкладочный компонент
Далее, давайте создадим вкладочный компонент на стороне сервера.
components/tab/tab.py:
from django_viewcomponent import component
from django_viewcomponent.fields import RendersManyField
@component.register("tab")
class TabComponent(component.Component):
tabs = RendersManyField(required=True)
panels = RendersManyField(required=True)
template_name = "tab/tab.html"
Здесь мы использовали RendersManyField, чтобы поле "слот" могло принимать набор элементов.
Добавьте шаблон components/tab/tab.html:
<div data-controller="tabs">
<div class="mb-4 border-b border-gray-200 dark:border-gray-700">
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center">
{% for tab in self.tabs.value %}
<li class="me-2" role="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 }}" type="button">
{{ tab }}
</button>
</li>
{% endfor %}
</ul>
</div>
<div>
{% for panel in self.panels.value %}
<div class="hidden p-4 rounded-lg bg-gray-50 dark:bg-gray-800" data-panel="{{ forloop.counter }}" role="tabpanel">
{{ panel }}
</div>
{% endfor %}
</div>
</div>
Обновить django_component_app/templates/index.html:
{% load webpack_loader static %}
{% load viewcomponent_tags %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tab Example</title>
{% stylesheet_pack 'app' %}
{% javascript_pack 'app' attrs='defer' %}
</head>
<body>
{% component 'tab' as component %}
{% call component.tabs %}
Profile
{% endcall %}
{% call component.panels %}
<p class="text-sm text-gray-500 dark:text-gray-400">This is some placeholder content the <strong
class="font-medium text-gray-800 ">Profile tab's associated content</strong>. Clicking
another tab will toggle the visibility of this one for the next. The tab JavaScript swaps classes to control the
content visibility and styling.</p>
{% endcall %}
{% call component.tabs %}
Dashboard
{% endcall %}
{% call component.panels %}
<p class="text-sm text-gray-500 dark:text-gray-400">This is some placeholder content the <strong
class="font-medium text-gray-800 ">Dashboard tab's associated content</strong>. Clicking
another tab will toggle the visibility of this one for the next. The tab JavaScript swaps classes to control the
content visibility and styling.</p>
{% endcall %}
{% call component.tabs %}
Settings
{% endcall %}
{% call component.panels %}
<p class="text-sm text-gray-500 dark:text-gray-400">This is some placeholder content the <strong
class="font-medium text-gray-800 ">Settings tab's associated content</strong>. Clicking
another tab will toggle the visibility of this one for the next. The tab JavaScript swaps classes to control the
content visibility and styling.</p>
{% endcall %}
{% endcomponent %}
</body>
</html>
Примечания:
- Мы объединили содержимое вкладки и соответствующей панели, чтобы сделать код более читабельным.
RendersManyField
это мощная функция. Это позволяет нам определять поле слота и передавать содержимое в слот компонента несколько раз.
Заключение
В этой статье мы перенесли HTML-код для наших компонентов на серверную сторону. Серверные компоненты делают код более удобным для обслуживания, повторного использования и тестирования. Я надеюсь, что это поможет вам создать лучший проект на Django.
Если вы хотите узнать больше о django-viewcomponent, ознакомьтесь с официальной документацией. Я также рекомендую вам ознакомиться со следующими разделами статьи Создание повторно используемых компонентов в Django:
- Предварительный просмотр компонентов
- Ресурсы внешнего интерфейса
- Библиотека компонентов
- Другие компонентные решения
- Ресурсы
Серия:
- Часть 1 - посвящена настройке проекта вместе с клиентской частью
- Часть 2 (это руководство!) - посвящена серверной части