andersch.dev

<2024-11-01 Fri>
[ web ]

HTMX

HTMX is a HTML-over-the-wire JavaScript library that extends HTML with attributes to facilitate AJAX requests, WebSocket connections, CSS Transitions, and server-sent events directly in HTML.

Core Attributes

hx-get issues a GET to the specified URL hx-post issues a POST to the specified URL hx-on* handle events with inline scripts on elements hx-push-url push a URL into the browser location bar to create history hx-select select content to swap in from a response hx-select-oob select content to swap in from a response, somewhere other than the target (out of band) hx-swap controls how content will swap in (outerHTML, beforeend, afterend, …) hx-swap-oob mark element to swap in from a response (out of band) hx-target specifies the target element to be swapped hx-trigger specifies the event that triggers the request hx-vals add values to submit with the request (JSON format)

Swapping with hx-swap=...

  • innerHTML: puts the content inside target element (default)
  • outerHTML: replaces entire target element with returned content
  • afterbegin: prepends content before first child inside the target
  • beforebegin: prepends content before target in target's parent element
  • beforeend: appends content after last child inside target
  • afterend: appends content after target in target's parent element
  • delete: deletes target element regardless of response
  • none: does nothing (oob-swaps and Response Headers are still processed)

Swap Options (e.g. hx-swap="outerHTML ignoreTitle:true"):

  • transition: true/false, whether to use view transition API for this swap
  • swap: swap delay (e.g. 100ms) between clearing old and inserting new
  • settle: settle delay (e.g. 100ms) between inserting new and settling
  • ignoreTitle: if true, titles in new content will be ignored
  • scroll: top/bottom, will scroll the target element to its top or bottom
  • show: top/bottom, will scroll target element's top or bottom into view

HTMX and Go

<!DOCTYPE html>
<html>
<head>
    <title>htmx example</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
    <script src="https://unpkg.com/htmx.org@1.9.2"></script>
</head>
<body data-bs-theme="dark">
    <ul class="list-group" id="film-list">
        {{ range . }}
            {{ block "film-list-element" . }}
                <li class="list-group-item"> {{ .Title }} - {{ .Director }} </li>
            {{ end }}
        {{ end }}
    </ul>

    <!-- form to add a film to existing list  -->
    <form hx-post="/add" hx-target="#film-list" hx-swap="beforeend">
        <div class="mb-2">
            <label for="title">Title</label>
            <input type="text" class="form-control" id="title" name="title">
        </div>
        <div class="mb-3">
            <label for="director">Director</label>
            <input type="text" class="form-control" id="director" name="director">
        </div>
        <button type="submit" class="btn btn-primary">Add Film</button>
    </form>
</body>
</html>
package main

import (
    "log"
    "net/http"
    "html/template"
)

type Film struct {
    Title    string
    Director string
}

func main() {
    log.Println("Server started on: http://localhost:8000")
    tmpl := template.Must(template.ParseFiles("index.html"))

    films := map[string] Film {
        "The Shawshank Redemption": {"The Shawshank Redemption", "Frank Darabont"},
        "The Godfather": {"The Godfather", "Francis Ford Coppola"},
    }

    http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) {
        tmpl.Execute(w, films)
    })

    http.HandleFunc("/add", func (w http.ResponseWriter, r *http.Request) {
        log.Println("htmx Add request received")
        log.Println(r.Header.Get("HX-Request"))
        if r.Method == "POST" {
            title    := r.PostFormValue("title")
            director := r.PostFormValue("director")
            tmpl.ExecuteTemplate(w, "film-list-element", Film{title, director})
        }
    })

    log.Fatal(http.ListenAndServe(":8000", nil))
}

Libraries:

  • elem-go - create/manipulate HTML elements with htmx helpers
  • templ - write components compiled to go template code

Web Security Guidelines

Only call routes you control

  • Uncontrolled routes could insert malicious <script> tags
  • Only use relative URLs: hx-get="/events"

Always use an auto-escaping template engine

  • Replaces &, <, >, ", … with "&amp;", "&lt", etc.
  • Prevents Cross-Site Scripting (XSS) attacks

Only serve user-generated content inside HTML tags

  • No user-defined data in scripts, CSS, or as HTML attributes

Set authentication cookies with Secure, HttpOnly, and SameSite=Lax

  • Secure: only send the cookie via HTTPS
  • HttpOnly: don't make the cookie available to JS via document.cookie
  • SameSite=Lax: mitigate Cross-Site Request Forgery (CSRF)

See Web Security Basics (with htmx).

Resources