Skip to content

HTML fragments

Django-components provides a seamless integration with HTML fragments with AJAX (HTML over the wire), whether you're using jQuery, HTMX, AlpineJS, vanilla JavaScript, or other.

If the fragment component has any JS or CSS, django-components will:

  • Automatically load the associated JS and CSS
  • Ensure that JS is loaded and executed only once even if the fragment is inserted multiple times

Info

What are HTML fragments and "HTML over the wire"?

It is one of the methods for updating the state in the browser UI upon user interaction.

How it works is that:

  1. User makes an action - clicks a button or submits a form
  2. The action causes a request to be made from the client to the server.
  3. Server processes the request (e.g. form submission), and responds with HTML of some part of the UI (e.g. a new entry in a table).
  4. A library like HTMX, AlpineJS, or custom function inserts the new HTML into the correct place.

Document and fragment strategiesยค

Components support different "strategies" for rendering JS and CSS.

Two of them are used to enable HTML fragments - "document" and "fragment".

What's the difference?

Document strategyยค

Document strategy assumes that the rendered components will be embedded into the HTML of the initial page load. This means that:

  • The JS and CSS is embedded into the HTML as <script> and <style> tags (see Default JS / CSS locations)
  • Django-components injects a JS script for managing JS and CSS assets

A component is rendered as a "document" when:

Example:

MyTable.render(
    kwargs={...},
)

# or

MyTable.render(
    kwargs={...},
    deps_strategy="document",
)

Fragment strategyยค

Fragment strategy assumes that the main HTML has already been rendered and loaded on the page. The component renders HTML that will be inserted into the page as a fragment, at a LATER time:

  • JS and CSS is not directly embedded to avoid duplicately executing the same JS scripts. So template tags like {% component_js_dependencies %} inside of fragments are ignored.
  • Instead, django-components appends the fragment's content with a JSON <script> to trigger a call to its asset manager JS script, which will load the JS and CSS smartly.
  • The asset manager JS script is assumed to be already loaded on the page.

A component is rendered as "fragment" when:

Example:

MyTable.render(
    kwargs={...},
    deps_strategy="fragment",
)

Live examplesยค

For live interactive examples, start our demo project (sampleproject).

Then navigate to these URLs:

  • /fragment/base/alpine
  • /fragment/base/htmx
  • /fragment/base/js

Example - HTMXยค

1. Define document HTMLยค

This is the HTML into which a fragment will be loaded using HTMX.

[root]/components/demo.py
from django_components import Component, types

class MyPage(Component):
    template = """
        {% load component_tags %}
        <!DOCTYPE html>
        <html>
            <head>
                {% component_css_dependencies %}
                <script src="https://unpkg.com/htmx.org@1.9.12"></script>
            </head>
            <body>
                <div id="target">OLD</div>

                <button
                    hx-get="/mypage/frag"
                    hx-swap="outerHTML"
                    hx-target="#target"
                >
                  Click me!
                </button>

                {% component_js_dependencies %}
            </body>
        </html>
    """

    class View:
        def get(self, request):
            return self.component.render_to_response(request=request)

2. Define fragment HTMLยค

The fragment to be inserted into the document.

IMPORTANT: Don't forget to set deps_strategy="fragment"

[root]/components/demo.py
class Frag(Component):
    template = """
        <div class="frag">
            123
            <span id="frag-text"></span>
        </div>
    """

    js = """
        document.querySelector('#frag-text').textContent = 'xxx';
    """

    css = """
        .frag {
            background: blue;
        }
    """

    class View:
        def get(self, request):
            return self.component.render_to_response(
                request=request,
                # IMPORTANT: Don't forget `deps_strategy="fragment"`
                deps_strategy="fragment",
            )

3. Create view and URLsยค

[app]/urls.py
from django.urls import path

from components.demo import MyPage, Frag

urlpatterns = [
    path("mypage/", MyPage.as_view())
    path("mypage/frag", Frag.as_view()),
]

Example - AlpineJSยค

1. Define document HTMLยค

This is the HTML into which a fragment will be loaded using AlpineJS.

[root]/components/demo.py
from django_components import Component, types

class MyPage(Component):
    template = """
        {% load component_tags %}
        <!DOCTYPE html>
        <html>
            <head>
                {% component_css_dependencies %}
                <script defer src="https://unpkg.com/alpinejs"></script>
            </head>
            <body x-data="{
                htmlVar: 'OLD',
                loadFragment: function () {
                    const url = '/mypage/frag';
                    fetch(url)
                        .then(response => response.text())
                        .then(html => {
                            this.htmlVar = html;
                        });
                }
            }">
                <div id="target" x-html="htmlVar">OLD</div>

                <button @click="loadFragment">
                  Click me!
                </button>

                {% component_js_dependencies %}
            </body>
        </html>
    """

    class View:
        def get(self, request):
            return self.component.render_to_response(request=request)

2. Define fragment HTMLยค

The fragment to be inserted into the document.

IMPORTANT: Don't forget to set deps_strategy="fragment"

[root]/components/demo.py
class Frag(Component):
    # NOTE: We wrap the actual fragment in a template tag with x-if="false" to prevent it
    #       from being rendered until we have registered the component with AlpineJS.
    template = """
        <template x-if="false" data-name="frag">
            <div class="frag">
                123
                <span x-data="frag" x-text="fragVal">
                </span>
            </div>
        </template>
    """

    js = """
        Alpine.data('frag', () => ({
            fragVal: 'xxx',
        }));

        // Now that the component has been defined in AlpineJS, we can "activate"
        // all instances where we use the `x-data="frag"` directive.
        document.querySelectorAll('[data-name="frag"]').forEach((el) => {
            el.setAttribute('x-if', 'true');
        });
    """

    css = """
        .frag {
            background: blue;
        }
    """

    class View:
        def get(self, request):
            return self.component.render_to_response(
                request=request,
                # IMPORTANT: Don't forget `deps_strategy="fragment"`
                deps_strategy="fragment",
            )

3. Create view and URLsยค

[app]/urls.py
from django.urls import path

from components.demo import MyPage, Frag

urlpatterns = [
    path("mypage/", MyPage.as_view())
    path("mypage/frag", Frag.as_view()),
]

Example - Vanilla JSยค

1. Define document HTMLยค

This is the HTML into which a fragment will be loaded using vanilla JS.

[root]/components/demo.py
from django_components import Component, types

class MyPage(Component):
    template = """
        {% load component_tags %}
        <!DOCTYPE html>
        <html>
            <head>
                {% component_css_dependencies %}
            </head>
            <body>
                <div id="target">OLD</div>

                <button>
                  Click me!
                </button>
                <script>
                    const url = `/mypage/frag`;
                    document.querySelector('#loader').addEventListener('click', function () {
                        fetch(url)
                            .then(response => response.text())
                            .then(html => {
                                document.querySelector('#target').outerHTML = html;
                            });
                    });
                </script>

                {% component_js_dependencies %}
            </body>
        </html>
    """

    class View:
        def get(self, request):
            return self.component.render_to_response(request=request)

2. Define fragment HTMLยค

The fragment to be inserted into the document.

IMPORTANT: Don't forget to set deps_strategy="fragment"

[root]/components/demo.py
class Frag(Component):
    template = """
        <div class="frag">
            123
            <span id="frag-text"></span>
        </div>
    """

    js = """
        document.querySelector('#frag-text').textContent = 'xxx';
    """

    css = """
        .frag {
            background: blue;
        }
    """

    class View:
        def get(self, request):
            return self.component.render_to_response(
                request=request,
                # IMPORTANT: Don't forget `deps_strategy="fragment"`
                deps_strategy="fragment",
            )

3. Create view and URLsยค

[app]/urls.py
from django.urls import path

from components.demo import MyPage, Frag

urlpatterns = [
    path("mypage/", MyPage.as_view())
    path("mypage/frag", Frag.as_view()),
]