HTMX Patterns Reference
HTMX helper functions and interaction patterns for MC-ORG.
HTMX Helper Functions (hx.kt)
kotlin1// HTTP methods 2fun HTMLTag.hxGet(value: String) 3fun HTMLTag.hxPost(value: String) 4fun HTMLTag.hxPut(value: String) 5fun HTMLTag.hxPatch(value: String) 6fun HTMLTag.hxDelete(value: String) 7 8// Delete with confirmation modal (custom MC-ORG component) 9fun HTMLTag.hxDeleteWithConfirm( 10 url: String, 11 title: String, 12 description: String, 13 warning: String, 14 confirmText: String? = null // if set, user must type this to confirm 15) 16 17// Targeting and swapping 18fun HTMLTag.hxTarget(value: String) // CSS selector for swap target 19fun HTMLTag.hxSwap(value: String) // swap strategy (see table below) 20 21// Additional attributes 22fun HTMLTag.hxConfirm(value: String) // simple confirm dialog 23fun HTMLTag.hxTrigger(value: String) // e.g., "load", "click", "change" 24fun HTMLTag.hxPushUrl(value: String) // update browser URL 25 26// Include additional form data 27fun HTMLTag.hxInclude(value: String) // CSS selector for extra inputs to include 28 29// Out-of-band swaps (update multiple elements in one response) 30fun HTMLTag.hxOutOfBands(locator: String) // "true" or element spec 31 32// Error targeting 33fun HTMLTag.hxErrorTarget(target: String) 34fun HTMLTag.hxErrorTarget(target: String, errorCode: String) 35 36// Extensions 37fun HTMLTag.hxExtension(value: String)
HTMX Swap Strategies
| Value | Effect |
|---|---|
innerHTML | Replace inner content (default) |
outerHTML | Replace entire element |
beforeend | Append to end of target |
afterbegin | Prepend to beginning of target |
delete | Remove target element |
Response Helpers (htmxResponseUtils.kt)
kotlin1// HTMX redirect — sets HX-Redirect header (no full page reload) 2suspend fun ApplicationCall.clientRedirect(path: String) 3 4// Error responses — sets HX-ReTarget and HX-ReSwap for error display 5suspend fun ApplicationCall.respondBadRequest( 6 errorHtml: String = "An error occurred", 7 target: String = "#error-message", 8 swap: String = "innerHTML" 9) 10suspend fun ApplicationCall.respondNotFound(errorHtml: String = "Not found", ...)
Common Patterns
Form submission
kotlin1// Template 2form { 3 id = "create-project-form" 4 hxPost("/app/worlds/${worldId}/projects") 5 hxTarget("#create-project-form") 6 7 input(classes = "form-control") { name = "name"; placeholder = "Project name" } 8 button(classes = "btn btn--action") { type = ButtonType.submit; +"Create" } 9} 10 11// Handler response (replaces the form) 12respondHtml(createHTML().div { 13 id = "create-project-form" 14 div("notice notice--success") { +"Project created successfully!" } 15})
Update action (button trigger)
kotlin1// Template 2button(classes = "btn btn--action") { 3 hxPut("/app/worlds/${worldId}/projects/${projectId}/stage") 4 hxTarget("#project-stage") 5 +"Advance Stage" 6} 7 8// Handler response 9respondHtml(createHTML().div { 10 id = "project-stage" 11 span("badge badge--success") { +"Building" } 12})
Delete with confirmation
kotlin1// Template 2button(classes = "btn btn--danger") { 3 hxDeleteWithConfirm( 4 url = "/app/worlds/${worldId}/projects/${projectId}", 5 title = "Delete Project", 6 description = "Are you sure you want to delete this project?", 7 warning = "This will also delete all tasks and cannot be undone.", 8 confirmText = "DELETE" // user must type this 9 ) 10 +"Delete Project" 11} 12 13// Handler response (after deletion) 14respondHtml(createHTML().div { 15 id = "project-card" // replaced/removed element 16 div("notice notice--success") { +"Project deleted." } 17})
Dynamic content load on page load
kotlin1div { 2 id = "task-list" 3 hxGet("/app/worlds/${worldId}/projects/${projectId}/tasks") 4 hxTrigger("load") 5 +"Loading tasks..." 6}
Out-of-band swap (update multiple elements)
kotlin1// Response updates both #project-list (main) and #notification-count (OOB) 2respondHtml(createHTML().div { 3 id = "project-list" 4 projectListContent(projects) 5 6 span { 7 id = "notification-count" 8 hxOutOfBands("true") 9 +"${notificationCount}" 10 } 11})
Include extra inputs in request
kotlin1button(classes = "btn btn--action") { 2 hxPost("/app/worlds/${worldId}/tasks") 3 hxTarget("#task-list") 4 hxInclude("#task-form-inputs") // include inputs from another element 5 +"Add Task" 6}
Inline editing
kotlin1// View mode 2div { 3 id = "project-description" 4 span { +project.description } 5 button(classes = "btn btn--ghost btn--sm") { 6 hxGet("/app/worlds/${worldId}/projects/${projectId}/edit/description") 7 hxTarget("#project-description") 8 +"Edit" 9 } 10} 11 12// Handler returns edit form (replaces #project-description) 13respondHtml(createHTML().div { 14 id = "project-description" 15 form { 16 hxPut("/app/worlds/${worldId}/projects/${projectId}/description") 17 hxTarget("#project-description") 18 textarea(classes = "form-control") { name = "description"; +project.description } 19 div("cluster cluster--sm") { 20 button(classes = "btn btn--action") { +"Save" } 21 button(classes = "btn btn--neutral") { 22 hxGet("/app/worlds/${worldId}/projects/${projectId}") 23 hxTarget("#project-description") 24 +"Cancel" 25 } 26 } 27 } 28})
Rules
- GET endpoints return full pages via
createPage(user, "Title") { ... } - PUT/PATCH/POST/DELETE endpoints return HTML fragments (NOT full pages)
- Response element
idmust matchhxTargetselector - Always specify
hxTarget— never rely on defaults - Use semantic HTTP: GET=read, POST=create, PUT=replace, PATCH=partial, DELETE=remove