From 728a58fa78e27a19bfac54baf1c83185f60ccd53 Mon Sep 17 00:00:00 2001 From: wakingrufus Date: Mon, 3 Nov 2025 17:14:57 -0600 Subject: [PATCH] implement hx-on add constants add docs --- .github/workflows/build.yml | 2 +- docs/dsl.md | 50 ++-- docs/spring.md | 9 +- .../github/wakingrufus/htmx/HtmlExtensions.kt | 130 ++++++++-- .../github/wakingrufus/htmx/HtmxConstants.kt | 138 +++++++++++ .../com/github/wakingrufus/htmx/HtmxEvent.kt | 233 ++++++++++++++++++ .../com/github/wakingrufus/htmx/HtmxEvents.kt | 50 ++++ .../github/wakingrufus/htmx/HtmxEventTest.kt | 28 +++ .../wakingrufus/htmx/HtmxExtensionsTest.kt | 41 +++ .../github/wakingrufus/htmx/exemplar/HxOn.kt | 61 +++++ 10 files changed, 695 insertions(+), 47 deletions(-) create mode 100644 khtmx-dsl/src/commonMain/kotlin/com/github/wakingrufus/htmx/HtmxConstants.kt create mode 100644 khtmx-dsl/src/commonMain/kotlin/com/github/wakingrufus/htmx/HtmxEvent.kt create mode 100644 khtmx-dsl/src/commonMain/kotlin/com/github/wakingrufus/htmx/HtmxEvents.kt create mode 100644 khtmx-dsl/src/commonTest/kotlin/com/github/wakingrufus/htmx/HtmxEventTest.kt create mode 100644 khtmx-dsl/src/commonTest/kotlin/com/github/wakingrufus/htmx/exemplar/HxOn.kt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b0ac823..6fba399 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,7 +39,7 @@ jobs: coverage_report_title: JaCoCo github_token: ${{ secrets.GITHUB_TOKEN }} skip_check_run: false - minimum_coverage: 55 + minimum_coverage: 60 fail_below_threshold: true publish_only_summary: false diff --git a/docs/dsl.md b/docs/dsl.md index ba9fab2..6baab48 100644 --- a/docs/dsl.md +++ b/docs/dsl.md @@ -72,7 +72,7 @@ val listTemplate = htmxTemplate> { |---------------------------------------------------------------|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------| | [`hx-get`](https://htmx.org/attributes/hx-get/) | ✅ | [Example](https://github.com/wakingrufus/khtmx/blob/main/khtmx-dsl/src/commonTest/kotlin/com/github/wakingrufus/htmx/exemplar/HxGet.kt) | | [`hx-post`](https://htmx.org/attributes/hx-post/) | ✅ | [Example](https://github.com/wakingrufus/khtmx/blob/main/khtmx-dsl/src/commonTest/kotlin/com/github/wakingrufus/htmx/exemplar/HxPost.kt) | -| [`hx-on*`](https://htmx.org/attributes/hx-on/) | ❌ | | +| [`hx-on*`](https://htmx.org/attributes/hx-on/) | ✅ | [Example](https://github.com/wakingrufus/khtmx/blob/main/khtmx-dsl/src/commonTest/kotlin/com/github/wakingrufus/htmx/exemplar/HxOn.kt) | | [`hx-push-url`](https://htmx.org/attributes/hx-push-url/) | ✅ | [Example](https://github.com/wakingrufus/khtmx/blob/main/khtmx-dsl/src/commonTest/kotlin/com/github/wakingrufus/htmx/exemplar/HxPushUrl.kt) | | [`hx-select`](https://htmx.org/attributes/hx-select/) | ✅ | [Example](https://github.com/wakingrufus/khtmx/blob/main/khtmx-dsl/src/commonTest/kotlin/com/github/wakingrufus/htmx/exemplar/HxSelect.kt) | | [`hx-select-oob`](https://htmx.org/attributes/hx-select-oob/) | ✅ | [Example](https://github.com/wakingrufus/khtmx/blob/main/khtmx-dsl/src/commonTest/kotlin/com/github/wakingrufus/htmx/exemplar/HxSelectOob.kt) | @@ -87,27 +87,33 @@ val listTemplate = htmxTemplate> { | [`hx-boost`](https://htmx.org/attributes/hx-boost/) | ❌ | | | [`hx-confirm`](https://htmx.org/attributes/hx-confirm/) | ❌ | | | [`hx-delete`](https://htmx.org/attributes/hx-delete/) | ✅ | [Example](https://github.com/wakingrufus/khtmx/blob/main/khtmx-dsl/src/commonTest/kotlin/com/github/wakingrufus/htmx/exemplar/HxDelete.kt) | -| [`hx-disable`](https://htmx.org/attributes/hx-disable/) | ❌ | -| [`hx-disabled-elt`](https://htmx.org/attributes/hx-disabled-elt/) | ❌ | -| [`hx-disinherit`](https://htmx.org/attributes/hx-disinherit/) | ❌ | -| [`hx-encoding`](https://htmx.org/attributes/hx-encoding/) | ❌ | -| [`hx-ext`](https://htmx.org/attributes/hx-ext/) | ❌ | -| [`hx-headers`](https://htmx.org/attributes/hx-headers/) | ❌ | -| [`hx-history`](https://htmx.org/attributes/hx-history/) | ❌ | -| [`hx-history-elt`](https://htmx.org/attributes/hx-history-elt/) | ❌ | -| [`hx-include`](https://htmx.org/attributes/hx-include/) | ❌ | -| [`hx-indicator`](https://htmx.org/attributes/hx-indicator/) | ❌ | -| [`hx-inherit`](https://htmx.org/attributes/hx-inherit/) | ❌ | -| [`hx-params`](https://htmx.org/attributes/hx-params/) | ✅ | -| [`hx-patch`](https://htmx.org/attributes/hx-patch/) | ✅ | -| [`hx-preserve`](https://htmx.org/attributes/hx-preserve/) | ❌ | -| [`hx-prompt`](https://htmx.org/attributes/hx-prompt/) | ❌ | -| [`hx-put`](https://htmx.org/attributes/hx-put/) | ✅ | -| [`hx-replace-url`](https://htmx.org/attributes/hx-replace-url/) | ❌ | -| [`hx-request`](https://htmx.org/attributes/hx-request/) | ❌ | -| [`hx-sync`](https://htmx.org/attributes/hx-sync/) | ❌ | -| [`hx-validate`](https://htmx.org/attributes/hx-validate/) | ❌ | -| [`hx-vars`](https://htmx.org/attributes/hx-vars/) | ❌ | +| [`hx-disable`](https://htmx.org/attributes/hx-disable/) | ❌ | | +| [`hx-disabled-elt`](https://htmx.org/attributes/hx-disabled-elt/) | ❌ | | +| [`hx-disinherit`](https://htmx.org/attributes/hx-disinherit/) | ❌ | | +| [`hx-encoding`](https://htmx.org/attributes/hx-encoding/) | ❌ | | +| [`hx-ext`](https://htmx.org/attributes/hx-ext/) | ❌ | | +| [`hx-headers`](https://htmx.org/attributes/hx-headers/) | ❌ | | +| [`hx-history`](https://htmx.org/attributes/hx-history/) | ❌ | | +| [`hx-history-elt`](https://htmx.org/attributes/hx-history-elt/) | ❌ | | +| [`hx-include`](https://htmx.org/attributes/hx-include/) | ❌ | | +| [`hx-indicator`](https://htmx.org/attributes/hx-indicator/) | ❌ | | +| [`hx-inherit`](https://htmx.org/attributes/hx-inherit/) | ❌ | | +| [`hx-params`](https://htmx.org/attributes/hx-params/) | ✅ | | +| [`hx-patch`](https://htmx.org/attributes/hx-patch/) | ✅ | | +| [`hx-preserve`](https://htmx.org/attributes/hx-preserve/) | ❌ | | +| [`hx-prompt`](https://htmx.org/attributes/hx-prompt/) | ❌ | | +| [`hx-put`](https://htmx.org/attributes/hx-put/) | ✅ | | +| [`hx-replace-url`](https://htmx.org/attributes/hx-replace-url/) | ❌ | | +| [`hx-request`](https://htmx.org/attributes/hx-request/) | ❌ | | +| [`hx-sync`](https://htmx.org/attributes/hx-sync/) | ❌ | | +| [`hx-validate`](https://htmx.org/attributes/hx-validate/) | ❌ | | +| [`hx-vars`](https://htmx.org/attributes/hx-vars/) | ❌ | | + +| Constants | Supported | +|-----------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------| +| [CSS Classes](https://htmx.org/reference/#classes) | [✅](https://github.com/wakingrufus/khtmx/blob/main/khtmx-dsl/src/commonMain/kotlin/com/github/wakingrufus/htmx/HtmxConstants.kt) | +| [HTTP Headers](https://htmx.org/reference/#headers) | [✅](https://github.com/wakingrufus/khtmx/blob/main/khtmx-dsl/src/commonMain/kotlin/com/github/wakingrufus/htmx/HtmxConstants.kt) | +| [Events](https://htmx.org/reference/#events) | [✅](https://github.com/wakingrufus/khtmx/blob/main/khtmx-dsl/src/commonMain/kotlin/com/github/wakingrufus/htmx/HtmxEvent.kt) | | HTMX Features | Supported | |-----------------------------------------------------|------------| diff --git a/docs/spring.md b/docs/spring.md index cea7e77..c7e086e 100644 --- a/docs/spring.md +++ b/docs/spring.md @@ -11,7 +11,7 @@ khtmx-spring provides a DSL for Spring Boot which allows a seamless integration ## Getting Started -This entry point for khtmx-spring is the `SpringHtmxDsl` class. +This entry point for khtmx-spring is the `SpringHtmxDsl` class. ### AutoConfiguration To use `SpringHtmxDsl` in an Spring application with AutoConfiguration, extend this class, similar to `BeanRegistrarDsl`: @@ -168,7 +168,10 @@ get("things", TestController::getAll) { ```kotlin route(HttpVerb.GET, "thing/1", TestController::get) { span { - +it.id.toString() + +it.id.toString() } } -``` \ No newline at end of file +``` + +### Response Headers +There is no support yet for [HTMX Response Headers](https://htmx.org/reference/#response_headers). diff --git a/khtmx-dsl/src/commonMain/kotlin/com/github/wakingrufus/htmx/HtmlExtensions.kt b/khtmx-dsl/src/commonMain/kotlin/com/github/wakingrufus/htmx/HtmlExtensions.kt index c299846..7e5071c 100644 --- a/khtmx-dsl/src/commonMain/kotlin/com/github/wakingrufus/htmx/HtmlExtensions.kt +++ b/khtmx-dsl/src/commonMain/kotlin/com/github/wakingrufus/htmx/HtmlExtensions.kt @@ -4,102 +4,190 @@ import com.github.wakingrufus.htmx.swap.HxSwapDsl import com.github.wakingrufus.htmx.swap.HxSwapType import kotlinx.html.HTMLTag +/** + * Entry point for the DSL which works with any [HttpVerb]. + * @param dsl high-level DSL that helps to author triggers in a way that coordinates the related attributes and enforce type safety + */ @HtmxDsl fun HTMLTag.hx(verb: HttpVerb, path: String, dsl: HxDsl.() -> Unit = {}) { HxDsl(verb, path).apply(dsl).invoke(this) } +/** + * Convenience method for using [hx] for a POST + * https://htmx.org/attributes/hx-post/ + * @param dsl high-level DSL that helps to author triggers in a way that coordinates the related attributes and enforce type safety + */ +@HtmxDsl +fun HTMLTag.hxPost(path: String, dsl: HxDsl.() -> Unit = {}) { + hx(HttpVerb.POST, path, dsl) +} + +/** + * Convenience method for using [hx] for a GET + * https://htmx.org/attributes/hx-get/ + * @param dsl high-level DSL that helps to author triggers in a way that coordinates the related attributes and enforce type safety + */ +@HtmxDsl +fun HTMLTag.hxGet(path: String, dsl: HxDsl.() -> Unit = {}) { + hx(HttpVerb.GET, path, dsl) +} + +/** + * Convenience method for using [hx] for a PUT + * https://htmx.org/attributes/hx-put/ + * @param dsl high-level DSL that helps to author triggers in a way that coordinates the related attributes and enforce type safety + */ +@HtmxDsl +fun HTMLTag.hxPut(path: String, dsl: HxDsl.() -> Unit = {}) { + hx(HttpVerb.PUT, path, dsl) +} + +/** + * Convenience method for using [hx] for a PATCH + * https://htmx.org/attributes/hx-patch/ + * @param dsl high-level DSL that helps to author triggers in a way that coordinates the related attributes and enforce type safety + */ +@HtmxDsl +fun HTMLTag.hxPatch(path: String, dsl: HxDsl.() -> Unit = {}) { + hx(HttpVerb.PATCH, path, dsl) +} + +/** + * Convenience method for using [hx] for a DELETE + * https://htmx.org/attributes/hx-delete/ + * @param dsl high-level DSL that helps to author triggers in a way that coordinates the related attributes and enforce type safety + */ +@HtmxDsl +fun HTMLTag.hxDelete(path: String, dsl: HxDsl.() -> Unit = {}) { + hx(HttpVerb.DELETE, path, dsl) +} + +/** + * https://htmx.org/attributes/hx-select/ + */ @HtmxDsl fun HTMLTag.hxSelect(selector: String) { attributes["hx-select"] = selector } +/** + * https://htmx.org/attributes/hx-select-oob/ + */ @HtmxDsl fun HTMLTag.hxSelectOob(selector: String) { attributes["hx-select-oob"] = selector } +/** + * https://htmx.org/attributes/hx-params/ + */ @HtmxDsl fun HTMLTag.hxParams(params: String) { attributes["hx-params"] = params } +/** + * https://htmx.org/attributes/hx-push-url/ + */ @HtmxDsl fun HTMLTag.hxPushUrl(value: Boolean) { attributes["hx-push-url"] = value.toString() } +/** + * https://htmx.org/attributes/hx-push-url/ + */ @HtmxDsl fun HTMLTag.hxPushUrl(value: String) { attributes["hx-push-url"] = value } +/** + * https://htmx.org/attributes/hx-swap/ + */ @HtmxDsl fun HTMLTag.hxSwap(value: String) { attributes["hx-swap"] = value } +/** + * https://htmx.org/attributes/hx-swap/ + */ @HtmxDsl fun HTMLTag.hxSwap(dsl: HxSwapDsl.() -> Unit = {}) { attributes["hx-swap"] = HxSwapDsl().apply(dsl).invoke().invoke() } +/** + * https://htmx.org/attributes/hx-select-oob/ + */ @HtmxDsl fun HTMLTag.hxSwapOob(value: Boolean) { attributes["hx-swap-oob"] = value.toString() } +/** + * https://htmx.org/attributes/hx-select-oob/ + */ @HtmxDsl fun HTMLTag.hxSwapOob(type: HxSwapType) { attributes["hx-swap-oob"] = type.stringValue } +/** + * https://htmx.org/attributes/hx-select-oob/ + */ @HtmxDsl fun HTMLTag.hxSwapOob(type: HxSwapType, selector: String) { attributes["hx-swap-oob"] = type.stringValue + ":" + selector } +/** + * https://htmx.org/attributes/hx-target/ + */ @HtmxDsl fun HTMLTag.hxTarget(selector: String) { attributes["hx-target"] = selector } +/** + * https://htmx.org/attributes/hx-trigger/ + */ @HtmxDsl fun HTMLTag.hxTrigger(value: String) { attributes["hx-trigger"] = value } +/** + * https://htmx.org/attributes/hx-vals/ + */ @HtmxDsl fun HTMLTag.hxVals(json: String) { attributes["hx-vals"] = json } +/** + * https://htmx.org/attributes/hx-on/ + */ @HtmxDsl -fun HTMLTag.hxPost(path: String, dsl: HxDsl.() -> Unit = {}) { - hx(HttpVerb.POST, path, dsl) -} - -@HtmxDsl -fun HTMLTag.hxGet(path: String, dsl: HxDsl.() -> Unit = {}) { - hx(HttpVerb.GET, path, dsl) -} - -@HtmxDsl -fun HTMLTag.hxPut(path: String, dsl: HxDsl.() -> Unit = {}) { - hx(HttpVerb.PUT, path, dsl) +fun HTMLTag.hxOn(event: String, script: String) { + attributes["hx-on:$event"] = script } +/** + * https://htmx.org/attributes/hx-on/ + */ @HtmxDsl -fun HTMLTag.hxPatch(path: String, dsl: HxDsl.() -> Unit = {}) { - hx(HttpVerb.PATCH, path, dsl) -} - -@HtmxDsl -fun HTMLTag.hxDelete(path: String, dsl: HxDsl.() -> Unit = {}) { - hx(HttpVerb.DELETE, path, dsl) +fun HTMLTag.hxOn(event: HtmxEvent, script: String) { + attributes["hx-on:" + event.htmlForm] = script } +/** + * https://htmx.org/attributes/hx-on/ + * @param event reference to an event via [HtmxEvents] method reference + */ @HtmxDsl -fun HTMLTag.htmxIndicator() { - attributes["class"] = "htmx-indicator" +fun HTMLTag.hxOn(event: () -> HtmxEvent, script: String) { + attributes["hx-on:" + event().htmlForm] = script } diff --git a/khtmx-dsl/src/commonMain/kotlin/com/github/wakingrufus/htmx/HtmxConstants.kt b/khtmx-dsl/src/commonMain/kotlin/com/github/wakingrufus/htmx/HtmxConstants.kt new file mode 100644 index 0000000..f5868bd --- /dev/null +++ b/khtmx-dsl/src/commonMain/kotlin/com/github/wakingrufus/htmx/HtmxConstants.kt @@ -0,0 +1,138 @@ +package com.github.wakingrufus.htmx + +/** + * Classes used by [HTMX](https://htmx.org/reference/#classes) + */ +object CssClass { + /** + * Applied to a new piece of content before it is swapped, removed after it is settled. + */ + val htmxAdded = "htmx-added" + + /** + * A dynamically generated class that will toggle visible (opacity:1) when a htmx-request class is present + */ + val htmxIndicator = "htmx-indicator" + + /** + * Applied to either the element or the element specified with hx-indicator while a request is ongoing + */ + val htmxRequest = "htmx-request" + + /** + * Applied to a target after content is swapped, removed after it is settled. The duration can be modified via hx-swap. + */ + val htmxSettling = "htmx-settling" + + /** + * Applied to a target before any content is swapped, removed after it is swapped. The duration can be modified via hx-swap. + */ + val htmxSwapping = "htmx-swapping" +} + +/** + * Headers used in requests sent by [HTMX](https://htmx.org/reference/#request_headers) + */ +object RequestHeader { + /** + * indicates that the request is via an element using hx-boost + */ + val hxBoosted = "HX-Boosted" + + /** + * the current URL of the browser + * + */ + val hxCurrentUrl = "HX-Current-URL" + + /** + * “true” if the request is for history restoration after a miss in the local history cache + */ + val hxHistoryRestoreRequest = "HX-History-Restore-Request" + + /** + * the user response to an hx-prompt + */ + val hxPrompt = "HX-Prompt" + + /** + * always “true” + */ + val hxRequest = "HX-Request" + + /** + * the id of the target element if it exists + */ + val hxTarget = "HX-Target" + + /** + * the name of the triggered element if it exists + */ + val hxTriggerName = "HX-Trigger-Name" + + /** + * the id of the triggered element if it exists + */ + val hxTrigger = "HX-Trigger" +} + +/** + * Headers interpreted by [HTMX](https://htmx.org/reference/#response_headers) when receiving responses from the server. + */ +object ResponseHeader { + /** + * allows you to do a client-side redirect that does not do a full page reload + */ + val hxLocation = "HX-Location" + + /** + * pushes a new url into the history stack + */ + val hxPushUrl = "HX-Push-Url" + + /** + * can be used to do a client-side redirect to a new location + */ + val hxRedirect = "HX-Redirect" + + /** + * if set to “true” the client-side will do a full refresh of the page + */ + val hxRefresh = "HX-Refresh" + + /** + * replaces the current URL in the location bar + */ + val hxReplaceUrl = "HX-Replace-Url" + + /** + * allows you to specify how the response will be swapped. See hx-swap for possible values + */ + val hxReswap = "HX-Reswap" + + /** + * a CSS selector that updates the target of the content update to a different element on the page + */ + val hxRetarget = "HX-Retarget" + + /** + * a CSS selector that allows you to choose which part of the response is used to be swapped in. + * Overrides an existing hx-select on the triggering element + */ + val hxReselect = "HX-Reselect" + + /** + * allows you to trigger client-side events + */ + val hxTrigger = "HX-Trigger" + + /** + * allows you to trigger client-side events after the settle step + */ + val hxTriggerAfterSettle = "HX-Trigger-After-Settle" + + /** + * allows you to trigger client-side events after the swap step + */ + val hxTriggerAfterSwap = "HX-Trigger-After-Swap" +} diff --git a/khtmx-dsl/src/commonMain/kotlin/com/github/wakingrufus/htmx/HtmxEvent.kt b/khtmx-dsl/src/commonMain/kotlin/com/github/wakingrufus/htmx/HtmxEvent.kt new file mode 100644 index 0000000..71eccf2 --- /dev/null +++ b/khtmx-dsl/src/commonMain/kotlin/com/github/wakingrufus/htmx/HtmxEvent.kt @@ -0,0 +1,233 @@ +package com.github.wakingrufus.htmx + +enum class HtmxEvent(val jsForm: String, val htmlForm: String = jsForm) { + /** + * send this event to an element to abort a request + */ + ABORT("htmx:abort"), + + /** + * triggered after an AJAX request has completed processing a successful response + */ + AFTER_ON_LOAD("htmx:afterOnLoad", "htmx:after-on-load"), + + /** + * triggered after htmx has initialized a node + */ + AFTER_PROCESS_NODE("htmx:afterProcessNode", "htmx:after-process-node"), + + /** + * triggered after an AJAX request has completed + */ + AFTER_REQUEST("htmx:afterRequest", "htmx:after-request"), + + /** + * triggered after the DOM has settled + */ + AFTER_SETTLE("htmx:afterSettle", "htmx:after-settle"), + + /** + * triggered after new content has been swapped in + */ + AFTER_SWAP("htmx:afterSwap", "htmx:after-swap"), + + /** + * triggered before htmx disables an element or removes it from the DOM + */ + BEFORE_CLEANUP_ELEMENT("htmx:beforeCleanupElement", "htmx:before-cleanup-element"), + + /** + * triggered before any response processing occurs + */ + BEFORE_ON_LOAD("htmx:beforeOnLoad", "htmx:before-on-load"), + + /** + * triggered before htmx initializes a node + */ + BEFORE_PROCESS_NODE("htmx:beforeProcessNode", "htmx:before-process-node"), + + /** + * triggered before an AJAX request is made + */ + BEFORE_REQUEST("htmx:beforeRequest", "htmx:before-request"), + + /** + * triggered before a swap is done, allows you to configure the swap + */ + BEFORE_SWAP("htmx:beforeSwap", "htmx:before-swap"), + + /** + * triggered just before an ajax request is sent + */ + BEFORE_SEND("htmx:beforeSend", "htmx:before-send"), + + /** + * triggered before the View Transition wrapped swap occurs + */ + BEFORE_TRANSITION("htmx:beforeTransition", "htmx:before-transition"), + + /** + * triggered before the request, allows you to customize parameters, headers + */ + CONFIG_REQUEST("htmx:configRequest", "htmx:config-request"), + + /** + * triggered after a trigger occurs on an element, allows you to cancel (or delay) issuing the AJAX request + */ + CONFIRM("htmx:confirm"), + + /** + * triggered on an error during cache writing + */ + HISTORY_CACHE_ERROR("htmx:historyCacheError", "htmx:history-cache-error"), + + /** + * triggered on a cache hit in the history subsystem + */ + HISTORY_CACHE_HIT("htmx:historyCacheHit", "htmx:history-cache-hit"), + + /** + * triggered on a cache miss in the history subsystem + */ + HISTORY_CACHE_MISS("htmx:historyCacheMiss", "htmx:history-cache-miss"), + + /** + * triggered on a unsuccessful remote retrieval + */ + HISTORY_CACHE_MISS_LOAD_ERROR("htmx:historyCacheMissLoadError", "htmx:history-cache-miss-load-error"), + + /** + * triggered on a successful remote retrieval + */ + HISTORY_CACHE_MISS_LOAD("htmx:historyCacheMissLoad", "htmx:history-cache-miss-load"), + + /** + * triggered when htmx handles a history restoration action + */ + HISTORY_RESTORE("htmx:historyRestore", "htmx:history-restore"), + + /** + * triggered before content is saved to the history cache + */ + BEFORE_HISTORY_SAVE("htmx:beforeHistorySave", "htmx:before-history-save"), + + /** + * triggered when new content is added to the DOM + */ + LOAD("htmx:load"), + + /** + * triggered when an element refers to a SSE event in its trigger, but no parent SSE source has been defined + */ + NO_SSE_SOURCE_ERROR("htmx:noSSESourceError", "htmx:no-sse-source-error"), + + /** + * triggered when an exception occurs during the onLoad handling in htmx + */ + ON_LOAD_ERROR("htmx:onLoadError", "htmx:on-load-error"), + + /** + * triggered after an out of band element as been swapped in + */ + OOB_AFTER_SWAP("htmx:oobAfterSwap", "htmx:oob-after-swap"), + + /** + * triggered before an out of band element swap is done, allows you to configure the swap + */ + OOB_BEFORE_SWAP("htmx:oobBeforeSwap", "htmx:oob-before-swap"), + + /** + * triggered when an out of band element does not have a matching ID in the current DOM + */ + OOB_ERROR_NO_TARGET("htmx:oobErrorNoTarget", "htmx:oob-error-no-target"), + + /** + * triggered after a prompt is shown + */ + PROMPT("htmx:prompt"), + + /** + * triggered after a url is pushed into history + */ + PUSHED_INTO_HISTORY("htmx:pushedIntoHistory", "htmx:pushed-into-history"), + + /** + * triggered after a url is replaced in history + */ + REPLACED_IN_HISTORY("htmx:replacedInHistory", "htmx:replaced-in-history"), + + /** + * triggered when an HTTP response error (non-200 or 300 response code) occurs + */ + RESPONSE_ERROR("htmx:responseError", "htmx:response-error"), + + /** + * triggered when a request is aborted + */ + SEND_ABORT("htmx:sendAbort", "htmx:send-abort"), + + /** + * triggered when a network error prevents an HTTP request from happening + */ + SEND_ERROR("htmx:sendError", "htmx:send-error"), + + /** + * triggered when an error occurs with a SSE source + */ + SSE_ERROR("htmx:sseError", "htmx:sse-error"), + + /** + * triggered when a SSE source is opened + */ + SSE_OPEN("htmx:sseOpen", "htmx:sse-open"), + + /** + * triggered when an error occurs during the swap phase + */ + SWAP_ERROR("htmx:swapError", "htmx:swap-error"), + + /** + * triggered when an invalid target is specified + */ + TARGET_ERROR("htmx:targetError", "htmx:target-error"), + + /** + * triggered when a request timeout occurs + */ + TIMEOUT("htmx:timeout"), + + /** + * triggered before an element is validated + */ + VALIDATION_VALIDATE("htmx:validation:validate"), + + /** + * triggered when an element fails validation + */ + VALIDATION_FAILED("htmx:validation:failed"), + + /** + * triggered when a request is halted due to validation errors + */ + VALIDATION_HALTED("htmx:validation:halted"), + + /** + * triggered when an ajax request aborts + */ + XHR_ABORT("htmx:xhr:abort"), + + /** + * triggered when an ajax request ends + */ + XHR_LOADEND("htmx:xhr:loadend"), + + /** + * triggered when an ajax request starts + */ + XHR_LOADSTART("htmx:xhr:loadstart"), + + /** + * triggered periodically during an ajax request that supports progress events + */ + PROGRESS("htmx:xhr:progress") +} diff --git a/khtmx-dsl/src/commonMain/kotlin/com/github/wakingrufus/htmx/HtmxEvents.kt b/khtmx-dsl/src/commonMain/kotlin/com/github/wakingrufus/htmx/HtmxEvents.kt new file mode 100644 index 0000000..36b2ee5 --- /dev/null +++ b/khtmx-dsl/src/commonMain/kotlin/com/github/wakingrufus/htmx/HtmxEvents.kt @@ -0,0 +1,50 @@ +package com.github.wakingrufus.htmx + +/** + * provide syntactic sugar for ideomatic htmx event references. + * Instead of + * `hxOn(HtmxEvent.LOAD, "alert('Clicked!')")` + * you can write + * `hxOn(::load, "alert('Clicked!')")` + */ +object HtmxEvents { + fun abort() = HtmxEvent.ABORT + fun afterOnLoad() = HtmxEvent.AFTER_ON_LOAD + fun afterProcessNode() = HtmxEvent.AFTER_PROCESS_NODE + fun afterRequest() = HtmxEvent.AFTER_REQUEST + fun afterSettle() = HtmxEvent.AFTER_SETTLE + fun afterSwap() = HtmxEvent.AFTER_SWAP + fun beforeCleanupElement() = HtmxEvent.BEFORE_CLEANUP_ELEMENT + fun beforeOnLoad() = HtmxEvent.BEFORE_ON_LOAD + fun beforeProcessNode() = HtmxEvent.BEFORE_PROCESS_NODE + fun beforeRequest() = HtmxEvent.BEFORE_REQUEST + fun beforeSwap() = HtmxEvent.BEFORE_SWAP + fun beforeSend() = HtmxEvent.BEFORE_SEND + fun beforeTransition() = HtmxEvent.BEFORE_TRANSITION + fun configRequest() = HtmxEvent.CONFIG_REQUEST + fun confirm() = HtmxEvent.CONFIRM + fun historyCacheError() = HtmxEvent.HISTORY_CACHE_ERROR + fun historyCacheHit() = HtmxEvent.HISTORY_CACHE_HIT + fun historyCacheMiss() = HtmxEvent.HISTORY_CACHE_MISS + fun historyCacheMissLoadError() = HtmxEvent.HISTORY_CACHE_MISS_LOAD_ERROR + fun historyCacheMissLoad() = HtmxEvent.HISTORY_CACHE_MISS_LOAD + fun historyRestore() = HtmxEvent.HISTORY_RESTORE + fun beforeHistorySave() = HtmxEvent.BEFORE_HISTORY_SAVE + fun load() = HtmxEvent.LOAD + fun noSSESourceError() = HtmxEvent.NO_SSE_SOURCE_ERROR + fun onLoadError() = HtmxEvent.ON_LOAD_ERROR + fun oobAfterSwap() = HtmxEvent.OOB_AFTER_SWAP + fun oobBeforeSwap() = HtmxEvent.OOB_BEFORE_SWAP + fun oobErrorNoTarget() = HtmxEvent.OOB_ERROR_NO_TARGET + fun prompt() = HtmxEvent.PROMPT + fun pushedIntoHistory() = HtmxEvent.PUSHED_INTO_HISTORY + fun replacedInHistory() = HtmxEvent.REPLACED_IN_HISTORY + fun responseError() = HtmxEvent.RESPONSE_ERROR + fun sendAbort() = HtmxEvent.SEND_ABORT + fun sendError() = HtmxEvent.SEND_ERROR + fun sseError() = HtmxEvent.SSE_ERROR + fun sseOpen() = HtmxEvent.SSE_OPEN + fun swapError() = HtmxEvent.SWAP_ERROR + fun targetError() = HtmxEvent.TARGET_ERROR + fun timeout() = HtmxEvent.TIMEOUT +} diff --git a/khtmx-dsl/src/commonTest/kotlin/com/github/wakingrufus/htmx/HtmxEventTest.kt b/khtmx-dsl/src/commonTest/kotlin/com/github/wakingrufus/htmx/HtmxEventTest.kt new file mode 100644 index 0000000..6fca3a8 --- /dev/null +++ b/khtmx-dsl/src/commonTest/kotlin/com/github/wakingrufus/htmx/HtmxEventTest.kt @@ -0,0 +1,28 @@ +package com.github.wakingrufus.htmx + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse + +internal class HtmxEventTest { + @Test + fun test_html_form() { + HtmxEvent.entries.forEach { + assertEquals( + it.htmlForm, + it.htmlForm.lowercase(), + "html form for $it does not contain upper case letters" + ) + } + } + + @Test + fun test_js_form() { + HtmxEvent.entries.forEach { + assertFalse( + it.jsForm.contains("-"), + "js form for $it does not contain dashes" + ) + } + } +} diff --git a/khtmx-dsl/src/commonTest/kotlin/com/github/wakingrufus/htmx/HtmxExtensionsTest.kt b/khtmx-dsl/src/commonTest/kotlin/com/github/wakingrufus/htmx/HtmxExtensionsTest.kt index 635f258..44c7fd5 100644 --- a/khtmx-dsl/src/commonTest/kotlin/com/github/wakingrufus/htmx/HtmxExtensionsTest.kt +++ b/khtmx-dsl/src/commonTest/kotlin/com/github/wakingrufus/htmx/HtmxExtensionsTest.kt @@ -1,5 +1,6 @@ package com.github.wakingrufus.htmx +import com.github.wakingrufus.htmx.HtmxEvents.abort import com.github.wakingrufus.htmx.exemplar.htmxExample import kotlinx.html.div import kotlinx.html.span @@ -66,4 +67,44 @@ internal class HtmxExtensionsTest { } assertEquals(actual, """
""") } + + @Test + fun test_hxOn_custom() { + val actual = htmxExample { + div { + hxOn("click", """print("alert")""") + } + } + assertEquals(actual, """
""") + } + + @Test + fun test_hxOn_htmx_event() { + val actual = htmxExample { + div { + hxOn(HtmxEvent.CONFIRM, """print("alert")""") + } + } + assertEquals(actual, """
""") + } + + @Test + fun test_hxOn_htmx_event_html_form() { + val actual = htmxExample { + div { + hxOn(HtmxEvent.ON_LOAD_ERROR, """print("alert")""") + } + } + assertEquals(actual, """
""") + } + + @Test + fun test_hxOn_htmx_event_reference() { + val actual = htmxExample { + div { + hxOn(::abort, """print("alert")""") + } + } + assertEquals(actual, """
""") + } } diff --git a/khtmx-dsl/src/commonTest/kotlin/com/github/wakingrufus/htmx/exemplar/HxOn.kt b/khtmx-dsl/src/commonTest/kotlin/com/github/wakingrufus/htmx/exemplar/HxOn.kt new file mode 100644 index 0000000..58b9ed8 --- /dev/null +++ b/khtmx-dsl/src/commonTest/kotlin/com/github/wakingrufus/htmx/exemplar/HxOn.kt @@ -0,0 +1,61 @@ +package com.github.wakingrufus.htmx.exemplar + +import com.github.wakingrufus.htmx.HtmxEvent +import com.github.wakingrufus.htmx.HtmxEvents.beforeRequest +import com.github.wakingrufus.htmx.hxGet +import com.github.wakingrufus.htmx.hxOn +import kotlinx.html.button +import kotlinx.html.div +import kotlin.test.Test +import kotlin.test.assertEquals + +/** + * Replicates the HTMX example for [hx-swap](https://htmx.org/attributes/hx-swap/) + */ +class HxOn { + @Test + fun hxOn_example() { + val actual = htmxExample { + div { + hxOn("click", """alert('Clicked!')""") + +"Click" + } + } + + assertEquals("""
Click
""", actual) + } + + @Test + fun hxOn_example_htmx() { + val actual = htmxExample { + button { + hxGet("/info") + hxOn(HtmxEvent.BEFORE_REQUEST, """alert('Making a request!')""") + +"Get Info!" + } + } + + assertEquals( + //language=html + """""", + actual + ) + } + + @Test + fun hxOn_example_htmx_double() { + val actual = htmxExample { + button { + hxGet("/info") + hxOn(::beforeRequest, """alert('Making a request!')""") + +"Get Info!" + } + } + + assertEquals( + //language=html + """""", + actual + ) + } +}