Skip to content

About

HTMX + Elementary: Hypertext web apps with Swift

Topics

Resources

Code of conduct

Contributing

Stars

112 stars

Watchers

4 watching

Forks

Repository files navigation

ElementaryHTMX: Hypertext web apps with Swift

Ergonomic htmx extensions for Elementary

Important

ElementaryHTMX currently targets the htmx 2 API. Use the versioned modules (e.g. ElementaryHTMX2) to pin a specific htmx version.

htmx 4 is supported by the ElementaryHTMX4 module, see htmx 4.

import Elementary
import ElementaryHTMX

// first-class support for most htmx attributes
form(.hx.post("/items"), .hx.target("#list"), .hx.swap(.outerHTML)) {
    input(.type(.text), .name("item"), .value("New Item"))
    input(.type(.submit), .value("Add Item"))
}

div {
    button(.hx.delete("items/\(item.id)")) { "❌" }
    item.text
}

MyFragment(items: items)
    .attributes(.hx.swapOOB(.outerHTML, "#list"))

// server-sent swap commands with <hx-partial> (requires htmx 2.0.11 or later)
hxPartial(.hx.target("#notifications")) {
    "3 new messages"
}
import Elementary
import ElementaryHTMXSSE

// htmx Server-Sent Events extension
div(.hx.ext(.sse), .sse.connect("/time"), .sse.swap("message")) {
    "\(Date())"
}
import Elementary
import ElementaryHTMXWS

// htmx WebSockets extension
div(.hx.ext(.ws), .ws.connect("/echo")) {
    form(.ws.send) {
        input(.type(.text), .name("message"))
        button { "Send" }
    }
    div(.id("echo")) {}
}
import Elementary
import ElementaryHyperscript

// Hyperscript extension
button(.hyperscript("on click send hello to <form />")) {
    "Send"
}

Play with it

The example apps use htmx 4 with the ElementaryHTMX4 modules.

Check out the Hummingbird example app.

Check out the Vapor example app.

Documentation

The package brings the .hx syntax to all HTMLElements - providing a rich API for most htmx 2 attributes, as well as the hxPartial element for <hx-partial> (requires htmx 2.0.11 or later).

For htmx 2, there is also an ElementaryHTMXSSE module that adds the .sse syntax for the Server-Sent Events extension, as well as ElementaryHTMXWS to add the .ws syntax for the WebSockets extension.

htmx versions

Each supported htmx major version has its own versioned modules:

htmx Core SSE WebSockets
v2 ElementaryHTMX2 ElementaryHTMX2SSE ElementaryHTMX2WS
v4 ElementaryHTMX4 ElementaryHTMX4SSE ElementaryHTMX4WS

The unversioned ElementaryHTMX, ElementaryHTMXSSE and ElementaryHTMXWS modules re-export the current default version (htmx 2), so existing imports keep working. Depend on a versioned module if you want to stay on a specific htmx version when the default changes.

// Package.swift
.product(name: "ElementaryHTMX2", package: "elementary-htmx")
import Elementary
import ElementaryHTMX2

htmx 4

  • ElementaryHTMX4 mirrors the htmx 4 attributes by name.
  • Modifiers and options are lists of entries, rendered and quoted as HCON for you. This covers hx-swap and hx-trigger modifiers, hx-status, hx-boost, hx-vals, hx-headers, hx-config and the htmx-config meta tag. Use .init(key, value) for anything without a helper.
  • The hx-sse and hx-ws extensions are in ElementaryHTMX4SSE and ElementaryHTMX4WS, as .hx.sse and .hx.ws (hx-sse:connect, hx-ws:send, ...).
// Package.swift
.product(name: "ElementaryHTMX4", package: "elementary-htmx")
import Elementary
import ElementaryHTMX4

button(.hx.post("/clicked"), .hx.target("#output"), .hx.swap(.outerHTML)) { "Click Me" }
input(.name("q"), .hx.query("/search"), .hx.trigger(.event(.input), [.changed, .delay("500ms")]), .hx.target("#results"))

// modifiers, quoted for you: hx-trigger="submit from:'closest form'"
div(.hx.get("/preview"), .hx.trigger(.event(.submit), [.from("closest form")])) {}
div(.hx.get("/messages"), .hx.swap(.beforeEnd, [.scroll(.bottom), .settle("100ms")])) {}

// per-status overrides: hx-status:422="target:#errors select:#validation-errors"
form(.hx.post("/items"), .hx.status("422", [.target("#errors"), .select("#validation-errors")])) { ... }

// built-in morph swaps
div(.hx.get("/list"), .hx.swap(.innerMorph)) { ... }

// explicit inheritance: pass .inherited, .append or .inheritedAppend as the first argument
div(.hx.confirm(.inherited, "Are you sure?")) {   // hx-confirm:inherited="Are you sure?"
    button(.hx.delete("/account")) { "Delete My Account" }
    button(.hx.put("/account")) { "Update My Account" }
}
div(.hx.vals(.inherited, [.init("tenant", "acme")])) {
    button(.hx.post("/save"), .hx.vals(.append, [.init("source", "save-btn")])) { "Save" }
}

// multi-target updates with <hx-partial>
hxPartial(.hx.target("#messages"), .hx.swap(.beforeEnd)) { div { "New message" } }
hxPartial(.id("notifications")) { span { "5" } }

// <meta name="htmx-config" content="defaultSwap:outerHTML noSwap:'["204","304","4xx","5xx"]' my.option:42">
meta(htmxConfig: [.defaultSwap(.outerHTML), .noSwap(["204", "304", "4xx", "5xx"]), .init("my.option", "42")])

// hx-config="timeout:5s cache:no-cache"
button(.hx.get("/report"), .hx.config([.timeout("5s"), .cache("no-cache")])) { "Report" }
import Elementary
import ElementaryHTMX4
import ElementaryHTMX4SSE
import ElementaryHTMX4WS

// SSE (load hx-sse.js) and WebSockets (load hx-ws.js)
div(.hx.sse.connect("/events"), .hx.sse.close("done")) {}
div(.hx.ws.connect("/chat"), .hx.target("#messages"), .hx.swap(.beforeEnd)) {
    div(.id("messages")) {}
    form(.hx.ws.send) {
        input(.name("message"))
        button { "Send" }
    }
}

// extension config entries, globally or per connection
meta(htmxConfig: [.sse.reconnectDelay("1s"), .ws.reconnectMaxAttempts(5)])
div(.hx.sse.connect("/progress"), .hx.config([.sse.reconnect(false)])) {}

Upgrading from v2? htmx changed a lot in v4 (explicit inheritance, error responses are swapped, history, event names, SSE/WS attributes). See What's new in htmx 4. On the Swift side, the v2-style modifier chains (.outerHTML.transition(true), .event(.click).once()) still work in ElementaryHTMX4 but are deprecated in favor of modifier entries.

Hyperscript

The package also supports the Hyperscript _ attribute as .hyperscript.

Development

swift test runs the unit tests. IntegrationTests/run.sh runs the integration tests: it builds a small Hummingbird server that renders pages with ElementaryHTMX4, starts it, and checks with Playwright in headless Chromium that a vendored copy of htmx 4.0.0 actually updates the DOM. It needs Swift and Node.js.

./IntegrationTests/run.sh

Future directions

  • Add module (or separate package?) for htmx Request and Response headers

PRs welcome.

About

HTMX + Elementary: Hypertext web apps with Swift

Topics

Resources

Code of conduct

Contributing

Stars

112 stars

Watchers

4 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages