Ludic Logo
Edit

Using HTMX with Ludic

HTMX is a powerful library that simplifies the creation of dynamic, interactive web pages. It lets you achieve the responsiveness of single-page applications without the complexity of writing extensive JavaScript code. HTMX works by extending standard HTML with special attributes that control how elements interact with the server.

The most typical example is when you want to fetch content from a server on a button click event and append or replace content on your page with the response. You can examine a similar example bellow.

Here is a couple of HTMX attributes that allow you to add dynamic functionality to your pages:

  • hx-get – issues a GET request to the server
  • hx-post – issues a POST request to the server
  • hx-target – specifies which element is replaced by the response
  • hx-select – specifies which element from the response is replaces
  • hx-swap – controls how content is swapped
  • hx-trigger – specifies which event triggers the request
HTMX Attributes Reference
Check all the available attributes in the HTMX documentation.

HTMX Integration in Ludic

#

The ludic.html module seamlessly supports HTMX attributes, making it easy to add dynamic functionality to your pages. Let's see a simple example:

from ludic.html import button

button("Click Me", hx_post="/clicked", hx_swap="innerHTML")

This code would generate the following HTML:

<button hx-post="/clicked" hx-swap="innerHTML">
    Click Me
</button>

Please note that HTMX uses a special attribute, hx-on*, which cannot currently be type-checked. However, it can still be used in the following way:

button(
    "Get Info!",
    hx_get="/info",
    hx_on__before_request="alert('Alert!')",  # type: ignore[call-arg]
)

This would render the attribute as hx-on--before-requestwhich is fine with htmx.

Setting up an HTMX-Enabled Page

#
  1. Include the HTMX library: Add the HTMX script to your base HTML component
    from typing import override
    
    from ludic.attrs import NoAttrs
    from ludic.catalog.pages import HtmlPage, Head, Body
    from ludic.components import Component
    from ludic.types import AnyChildren
    
    
    class Page(Component[AnyChildren, NoAttrs]):
        @override
        def render(self) -> HtmlPage:
            return HtmlPage(
                Head(title="My App"),
                Body(*self.children, htmx_version="1.9.10"),
            )
    
  2. Use the Page component: Employ the Page component as the foundation for your HTML documents to ensure they load the necessary HTMX script.
Default hx-swap Operation
The default HTMX swap operation is set to outerHTML in all pages based on the HtmlPage component. This is different than HTMX default which is innerHTML.

Sample Swap Operation

#

Let's illustrate how to create a dynamic page with HTMX and Ludic:

from ludic.html import b, div, p, button
from ludic.web import LudicApp, Request

from your_app.pages import Page

app = LudicApp()

@app.get("/")
def homepage(request: Request) -> Page:
    return Page(
        p(
            "This is a simple example with one button performing the "
            "hx-swap operation."
        ),
        button("Show Hidden Content", hx_get=request.url_for(content)),
    )

@app.get("/content")
def content() -> p:
    return p(b("This is the hidden content."))

You can test the performed action here:

This is a simple example with one button performing the hx-swap operation.

Explanation

#
  • Button Behavior: Clicking the button triggers an HTTP GET request to /content. The response replaces the original button with the content returned from the /content endpoint.
  • Web Framework: Ludic acts as a web framework (built on Starlette), empowering you to define endpoints and handle requests. Explore the Web Framework section of the documentation for in-depth information.

Headers

#

It is possible to append custom HTMX headers in responses, here is an example:

from ludic import types
from ludic.html import p

from your_app.server import app

@app.get("/")
def index() -> tuple[p, types.HXHeaders]:
    return p("Example"), {"HX-Location": {"path": "/", "target": "#test"}}

You can also change the return type of your endpoint with tuple[div, types.Headers] which allows arbitrary headers, not just HTMX-specific ones.

Rendering JavaScript

#

In some cases, HTMX components require some JavaScript code. For that purpose, there is the ludic.types.JavaScript class:

from ludic.html import button, div, h2, table
from ludic.types import JavaScript

from your_app.server import app

@app.get("/data")
def data() -> div:
    return div(
        h2("Data"),
        table(...),
        button("Click Here", on_click=JavaScript("alert('test')")),
    )
Made with Ludic and HTMX and 🐍 • DiscordGitHub