HTML fragmentsยค
Fragments are pieces of HTML that are inserted into the page without a full page reload. Fragments are also known as partials or HTML-over-the-wire.
The usual flow is to:
- Make a server request
- Server responds with new HTML
- Insert the new HTML into the page
This example loads HTML fragments using different client-side techniques: vanilla JavaScript, AlpineJS, and HTMX.
In each of the 3 cases, when the fragment is loaded, this also runs the fragment's JS and CSS code.
Definitionยค
from typing import NamedTuple
from django_components import Component, register, types
DESCRIPTION = "Use HTML fragments (partials) with HTMX, AlpineJS, or plain JS."
@register("simple_fragment")
class SimpleFragment(Component):
"""A simple fragment with JS and CSS."""
class Kwargs(NamedTuple):
type: str
template: types.django_html = """
<div class="frag_simple">
Fragment with JS and CSS (plain).
<span id="frag-text"></span>
</div>
"""
js: types.js = """
document.querySelector('#frag-text').textContent = ' JavaScript has run.';
"""
css: types.css = """
.frag_simple {
background: #f0f8ff;
border: 1px solid #add8e6;
padding: 1rem;
border-radius: 5px;
}
"""
@register("alpine_fragment")
class AlpineFragment(Component):
"""A fragment that defines an AlpineJS component."""
class Kwargs(NamedTuple):
type: str
# The fragment is wrapped in `<template x-if="false">` so that we prevent
# AlpineJS from inserting the HTML right away. Instead, we want to load it
# only once this component's JS has been loaded.
template: types.django_html = """
<template x-if="false" data-name="frag">
<div
class="frag_alpine"
x-data="frag"
x-text="message"
x-init="() => {
document.querySelectorAll('#loader-alpine').forEach((el) => {
el.innerHTML = 'Fragment loaded!';
el.disabled = true;
});
}"
></div>
</template>
"""
js: types.js = """
Alpine.data('frag', () => ({
message: 'Fragment with JS and CSS (AlpineJS).',
}));
document.querySelectorAll('[data-name="frag"]').forEach((el) => {
el.setAttribute('x-if', 'true');
});
"""
css: types.css = """
.frag_alpine {
background: #f0fff0;
border: 1px solid #98fb98;
padding: 1rem;
border-radius: 5px;
}
"""
Exampleยค
To see the component in action, you can set up a view and a URL pattern as shown below.
views.py
ยค
from django.http import HttpRequest, HttpResponse
from django.utils.safestring import mark_safe
from django_components import Component, get_component_url, types
from .component import AlpineFragment, SimpleFragment
class FragmentsPage(Component):
class Media:
js = (
"https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio,container-queries",
mark_safe('<script defer src="https://unpkg.com/alpinejs"></script>'),
)
def get_template_data(self, args, kwargs, slots, context):
# Get URLs that points to the FragmentsPageView.get() method
alpine_url = get_component_url(FragmentsPage, query={"type": "alpine"})
js_url = get_component_url(FragmentsPage, query={"type": "js"})
htmx_url = get_component_url(FragmentsPage, query={"type": "htmx"})
return {
"alpine_url": alpine_url,
"js_url": js_url,
"htmx_url": htmx_url,
}
template: types.django_html = """
{% load component_tags %}
<html>
<head>
<title>HTML Fragments Example</title>
<script src="https://unpkg.com/htmx.org@2.0.7/dist/htmx.js"></script>
</head>
<body
class="bg-gray-100 p-8"
data-alpine-url="{{ alpine_url }}"
data-js-url="{{ js_url }}"
hx-boost="true"
>
<div class="max-w-4xl mx-auto bg-white p-6 rounded-lg shadow-md">
<h1 class="text-2xl font-bold mb-4">
HTML Fragments
</h1>
<p class="text-gray-600 mb-6">
This example shows how to load HTML fragments
using different client-side techniques.
</p>
<!-- Vanilla JS -->
<div class="mb-8 p-4 border rounded-lg">
<h2 class="text-xl font-semibold mb-2">
Vanilla JS
</h2>
<div id="target-js">Initial content</div>
<button
id="loader-js"
class="mt-2 px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"
>
Load Fragment
</button>
</div>
<!-- AlpineJS -->
<div
class="mb-8 p-4 border rounded-lg"
x-data="{
htmlVar: '<div id=\\'target-alpine\\'>Initial content</div>',
}"
>
<h2 class="text-xl font-semibold mb-2">
AlpineJS
</h2>
<div x-html="htmlVar"></div>
<button
id="loader-alpine"
@click="() => {
const alpineUrl = document.body.dataset.alpineUrl;
fetch(alpineUrl)
.then(r => r.text())
.then(html => {
htmlVar = html;
})
}"
class="mt-2 px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700"
>
Load Fragment
</button>
</div>
<!-- HTMX -->
<div class="p-4 border rounded-lg">
<h2 class="text-xl font-semibold mb-2">
HTMX
</h2>
<div id="target-htmx">Initial content</div>
<button
id="loader-htmx"
hx-get="{{ htmx_url }}"
hx-swap="outerHTML"
hx-target="#target-htmx"
class="mt-2 px-4 py-2 bg-purple-600 text-white rounded-md hover:bg-purple-700"
>
Load Fragment
</button>
</div>
</div>
<script>
document.querySelector('#loader-js').addEventListener('click', function () {
const jsUrl = document.body.dataset.jsUrl;
fetch(jsUrl)
.then(response => response.text())
.then(html => {
document.querySelector('#target-js').outerHTML = html;
});
});
</script>
</body>
</html>
"""
class View:
# The same GET endpoint handles rendering either the whole page or a fragment.
# We use the `type` query parameter to determine which one to render.
def get(self, request: HttpRequest) -> HttpResponse:
fragment_type = request.GET.get("type")
if fragment_type:
fragment_cls = AlpineFragment if fragment_type == "alpine" else SimpleFragment
return fragment_cls.render_to_response(
request=request,
deps_strategy="fragment",
kwargs={"type": fragment_type},
)
else:
return FragmentsPage.render_to_response(
request=request,
deps_strategy="fragment",
)