Как компоновать фрагменты шаблонов Django с Alpine.js для создания многократно используемых серверно-рендерных компонентов?
Короче говоря, я пытаюсь создать многократно используемый компонент Alpine с его дочерними элементами, определенными в шаблонном фрагменте Django.
Пример с кодом, который работает не так, как задумано - Карусель
У меня такая структура шаблона: cities_page.html
(шаблон) / city.html
(сниппет) / carousel.html
(сниппет + Alpine) / carousel_item.html
(сниппет).
cities_page
рендерит множество cities
, а каждый carousel
рендерит множество carousel_items
. Весь рендеринг происходит на стороне сервера.
cities_page.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Cities Page</title>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.13.7/dist/cdn.min.js"></script>
</head>
<body>
{% for city in cities %}
{% include "myapp/snippets/city.html" with city=city %}
{% endfor %}
</body>
</html>
city.html
<div>
<h2>{{ city.name }}</h2>
{% include "myapp/snippets/carousel.html" with images=city.images %}
</div>
carousel.html
<div x-data="carousel">
<template x-for="item in items" :key="item">
<div x-html="item.itemHtml"></div>
</template>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data(`carousel`, () => ({
items: [
{% for image in images %}
{
itemHtml: `{% include "myapp/snippets/carousel_item.html" with image=image %}`,
},
{% endfor %}
]
}));
});
</script>
carousel_item.html
<p>
Item: {{ image }}
</p>
views.py
def cities_page(request):
context = {
"cities": [
{
"name": "London",
"images": ["london1.jpg", "london2.jpg", "london3.jpg"]
},
{
"name": "Paris",
"images": ["paris1.jpg", "paris2.jpg", "paris3.jpg"]
},
]
}
return render(request, "myapp/cities_page.html", context=context)
Причина, по которой я хочу, чтобы Alpine управлял элементами моей карусели, заключается в том, что я хочу разработать взаимодействия, которые манипулируют этими элементами. Это могут быть такие методы, как reverseOrder
и removeItem
.
Результатом этого является:
Лондон
Item: paris1.jpg
Item: paris2.jpg
Item: paris3.jpg
Париж
Item: paris1.jpg
Item: paris2.jpg
Item: paris3.jpg
Обратите внимание, что в Лондоне есть изображения Парижа. Проблема в том, что сценарий, определенный для карусели Парижа, перезаписывает сценарий для Лондона.
Моя лучшая попытка исправить это - переписать карусель следующим образом
<div>
<template x-for="item in items" :key="item">
<div x-html="item.itemHtml"></div>
</template>
</div>
<script>
(() => {
const genRanHex = size => [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join('');
const id = genRanHex(5)
const currentScript = document.currentScript;
const previousElement = currentScript.previousElementSibling;
previousElement.setAttribute("x-data", `carousel${id}`)
document.addEventListener('alpine:init', () => {
Alpine.data(`carousel${id}`, () => ({
items: [
{% for image in images %}
{
itemHtml: `{% include "myapp/snippets/carousel_item.html" with image=image %}`,
},
{% endfor %}
]
}));
});
})();
</script>
Обратите внимание, что я
- Удалено
x-data
- Сгенерировали случайный идентификатор и присвоили его имени компонента Alpine, чтобы сделать его глобально уникальным .
- Установил
x-data
с помощью скрипта .
- Убедились, что все, что определено в скрипте, не загрязняет глобальную область видимости (и другие версии этого скрипта, включенные в каждый экземпляр карусели), обернув его анонимной функцией .
Это похоже на действительно халтурный способ создания многократно используемых компонентов.
Поэтому мой вопрос заключается в следующем: Какова лучшая практика в написании таких компонентов? Не слишком ли далеко я зашел с Alpine и не стоит ли мне рассмотреть возможность использования чего-то вроде Vue/React для достижения такой функциональности?
Благодарю вас за любую помощь или за то, что поделились соответствующими ресурсами.
У меня есть нечто похожее. Это может помочь, а может и не помочь.
https://github.com/tochy97/djangoAPI
Я использую загрузчик для вызова каждого содержимого Js по мере необходимости по их пути.
https://github.com/tochy97/djangoAPI/blob/main/static/components/common/js/loader.js
Индекс.js работает как инициализатор для всего.
https://github.com/tochy97/djangoAPI/blob/main/static/components/common/js/index.js