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 serverhx-post
– issues a POST request to the serverhx-target
– specifies which element is replaced by the responsehx-select
– specifies which element from the response is replaceshx-swap
– controls how content is swappedhx-trigger
– specifies which event triggers the request
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-request
which is fine with htmx.
Setting up an HTMX-Enabled Page
#- 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"), )
- Use the
Page
component: Employ the Page component as the foundation for your HTML documents to ensure they load the necessary HTMX script.
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')")), )