From a7d99b67516c5f75fddd4b370729f9f2d6643939 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Tue, 2 Jan 2024 18:01:45 -0500 Subject: [PATCH 1/3] Update to version 1.9.10 - Target FSharp.Core 6.0.0 for all TFMs (#8) - Fix spelling of _hxDisabledElt - WIP on _hxOn improvements --- src/Common/README.md | 2 +- src/Directory.Build.props | 11 +- src/Htmx/Giraffe.Htmx.fsproj | 2 +- src/Htmx/README.md | 2 +- src/Tests/ViewEngine.fs | 439 ++++++++++++++++++++++++++++++++-- src/ViewEngine.Htmx/Htmx.fs | 160 ++++++++++++- src/ViewEngine.Htmx/README.md | 2 +- 7 files changed, 592 insertions(+), 26 deletions(-) diff --git a/src/Common/README.md b/src/Common/README.md index 28c0427..2dff994 100644 --- a/src/Common/README.md +++ b/src/Common/README.md @@ -2,4 +2,4 @@ This package contains common code shared between [`Giraffe.Htmx`](https://www.nuget.org/packages/Giraffe.Htmx) and [`Giraffe.ViewEngine.Htmx`](https://www.nuget.org/packages/Giraffe.ViewEngine.Htmx), and will be automatically installed when you install either one. -**htmx version: 1.9.8** +**htmx version: 1.9.10** diff --git a/src/Directory.Build.props b/src/Directory.Build.props index f189553..4f48f8d 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,8 +2,8 @@ net6.0;net7.0;net8.0 - 1.9.8 - Update script tags to pull htmx 1.9.8; add support for .NET 8 + 1.9.10 + Update script tags to pull htmx 1.9.10; add support for .NET 8 danieljsummers Bit Badger Solutions https://github.com/bit-badger/Giraffe.Htmx @@ -14,4 +14,9 @@ MIT Giraffe htmx - \ No newline at end of file + + + + + + diff --git a/src/Htmx/Giraffe.Htmx.fsproj b/src/Htmx/Giraffe.Htmx.fsproj index 09928ad..7c0b7c6 100644 --- a/src/Htmx/Giraffe.Htmx.fsproj +++ b/src/Htmx/Giraffe.Htmx.fsproj @@ -12,7 +12,7 @@ - + diff --git a/src/Htmx/README.md b/src/Htmx/README.md index 17b0539..8688751 100644 --- a/src/Htmx/README.md +++ b/src/Htmx/README.md @@ -2,7 +2,7 @@ This package enables server-side support for [htmx](https://htmx.org) within [Giraffe](https://giraffe.wiki) and ASP.NET's `HttpContext`. -**htmx version: 1.9.8** +**htmx version: 1.9.10** ### Setup diff --git a/src/Tests/ViewEngine.fs b/src/Tests/ViewEngine.fs index 8668302..69da20c 100644 --- a/src/Tests/ViewEngine.fs +++ b/src/Tests/ViewEngine.fs @@ -15,6 +15,399 @@ let hxEncoding = } ] +let hxEvent = + testList "HxEvent" [ + testList "Abort" [ + test "ToString succeeds" { + Expect.equal (string Abort) "abort" "Abort event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal (Abort.ToHxOnString()) "abort" "Abort hx-on event name not correct" + } + ] + testList "AfterOnLoad" [ + test "ToString succeeds" { + Expect.equal (string AfterOnLoad) "afterOnLoad" "AfterOnLoad event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal (AfterOnLoad.ToHxOnString()) "after-on-load" "AfterOnLoad hx-on event name not correct" + } + ] + testList "AfterProcessNode" [ + test "ToString succeeds" { + Expect.equal (string AfterProcessNode) "afterProcessNode" "AfterProcessNode event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal + (AfterProcessNode.ToHxOnString()) + "after-process-node" + "AfterProcessNode hx-on event name not correct" + } + ] + testList "AfterRequest" [ + test "ToString succeeds" { + Expect.equal (string AfterRequest) "afterRequest" "AfterRequest event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal (AfterRequest.ToHxOnString()) "after-request" "AfterRequest hx-on event name not correct" + } + ] + testList "AfterSettle" [ + test "ToString succeeds" { + Expect.equal (string AfterSettle) "afterSettle" "AfterSettle event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal (AfterSettle.ToHxOnString()) "after-settle" "AfterSettle hx-on event name not correct" + } + ] + testList "AfterSwap" [ + test "ToString succeeds" { + Expect.equal (string AfterSwap) "afterSwap" "AfterSwap event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal (AfterSwap.ToHxOnString()) "after-swap" "AfterSwap hx-on event name not correct" + } + ] + testList "BeforeCleanupElement" [ + test "ToString succeeds" { + Expect.equal + (string BeforeCleanupElement) "beforeCleanupElement" "BeforeCleanupElement event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal + (BeforeCleanupElement.ToHxOnString()) + "before-cleanup-element" + "BeforeCleanupElement hx-on event name not correct" + } + ] + testList "BeforeOnLoad" [ + test "ToString succeeds" { + Expect.equal (string BeforeOnLoad) "beforeOnLoad" "BeforeOnLoad event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal (BeforeOnLoad.ToHxOnString()) "before-on-load" "BeforeOnLoad hx-on event name not correct" + } + ] + testList "BeforeProcessNode" [ + test "ToString succeeds" { + Expect.equal (string BeforeProcessNode) "beforeProcessNode" "BeforeProcessNode event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal + (BeforeProcessNode.ToHxOnString()) + "before-process-node" + "BeforeProcessNode hx-on event name not correct" + } + ] + testList "BeforeRequest" [ + test "ToString succeeds" { + Expect.equal (string BeforeRequest) "beforeRequest" "BeforeRequest event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal + (BeforeRequest.ToHxOnString()) "before-request" "BeforeRequest hx-on event name not correct" + } + ] + testList "BeforeSwap" [ + test "ToString succeeds" { + Expect.equal (string BeforeSwap) "beforeSwap" "BeforeSwap event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal (BeforeSwap.ToHxOnString()) "before-swap" "BeforeSwap hx-on event name not correct" + } + ] + testList "BeforeSend" [ + test "ToString succeeds" { + Expect.equal (string BeforeSend) "beforeSend" "BeforeSend event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal (BeforeSend.ToHxOnString()) "before-send" "BeforeSend hx-on event name not correct" + } + ] + testList "ConfigRequest" [ + test "ToString succeeds" { + Expect.equal (string ConfigRequest) "configRequest" "ConfigRequest event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal + (ConfigRequest.ToHxOnString()) "config-request" "ConfigRequest hx-on event name not correct" + } + ] + testList "Confirm" [ + test "ToString succeeds" { + Expect.equal (string Confirm) "confirm" "Confirm event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal (Confirm.ToHxOnString()) "confirm" "Confirm hx-on event name not correct" + } + ] + testList "HistoryCacheError" [ + test "ToString succeeds" { + Expect.equal (string HistoryCacheError) "historyCacheError" "HistoryCacheError event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal + (HistoryCacheError.ToHxOnString()) + "history-cache-error" + "HistoryCacheError hx-on event name not correct" + } + ] + testList "HistoryCacheMiss" [ + test "ToString succeeds" { + Expect.equal (string HistoryCacheMiss) "historyCacheMiss" "HistoryCacheMiss event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal + (HistoryCacheMiss.ToHxOnString()) + "history-cache-miss" + "HistoryCacheMiss hx-on event name not correct" + } + ] + testList "HistoryCacheMissError" [ + test "ToString succeeds" { + Expect.equal + (string HistoryCacheMissError) + "historyCacheMissError" + "HistoryCacheMissError event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal + (HistoryCacheMissError.ToHxOnString()) + "history-cache-miss-error" + "HistoryCacheMissError hx-on event name not correct" + } + ] + testList "HistoryCacheMissLoad" [ + test "ToString succeeds" { + Expect.equal + (string HistoryCacheMissLoad) "historyCacheMissLoad" "HistoryCacheMissLoad event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal + (HistoryCacheMissLoad.ToHxOnString()) + "history-cache-miss-load" + "HistoryCacheMissLoad hx-on event name not correct" + } + ] + testList "HistoryRestore" [ + test "ToString succeeds" { + Expect.equal (string HistoryRestore) "historyRestore" "HistoryRestore event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal + (HistoryRestore.ToHxOnString()) "history-restore" "HistoryRestore hx-on event name not correct" + } + ] + testList "BeforeHistorySave" [ + test "ToString succeeds" { + Expect.equal (string BeforeHistorySave) "beforeHistorySave" "BeforeHistorySave event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal + (BeforeHistorySave.ToHxOnString()) + "before-history-save" + "BeforeHistorySave hx-on event name not correct" + } + ] + testList "Load" [ + test "ToString succeeds" { + Expect.equal (string Load) "load" "Load event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal (Load.ToHxOnString()) "load" "Load hx-on event name not correct" + } + ] + testList "NoSseSourceError" [ + test "ToString succeeds" { + Expect.equal (string NoSseSourceError) "noSSESourceError" "NoSseSourceError event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal + (NoSseSourceError.ToHxOnString()) + "no-sse-source-error" + "NoSseSourceError hx-on event name not correct" + } + ] + testList "OnLoadError" [ + test "ToString succeeds" { + Expect.equal (string OnLoadError) "onLoadError" "OnLoadError event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal (OnLoadError.ToHxOnString()) "on-load-error" "OnLoadError hx-on event name not correct" + } + ] + testList "OobAfterSwap" [ + test "ToString succeeds" { + Expect.equal (string OobAfterSwap) "oobAfterSwap" "OobAfterSwap event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal (OobAfterSwap.ToHxOnString()) "oob-after-swap" "OobAfterSwap hx-on event name not correct" + } + ] + testList "OobBeforeSwap" [ + test "ToString succeeds" { + Expect.equal (string OobBeforeSwap) "oobBeforeSwap" "OobBeforeSwap event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal + (OobBeforeSwap.ToHxOnString()) "oob-before-swap" "OobBeforeSwap hx-on event name not correct" + } + ] + testList "OobErrorNoTarget" [ + test "ToString succeeds" { + Expect.equal (string OobErrorNoTarget) "oobErrorNoTarget" "OobErrorNoTarget event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal + (OobErrorNoTarget.ToHxOnString()) + "oob-error-no-target" + "OobErrorNoTarget hx-on event name not correct" + } + ] + testList "Prompt" [ + test "ToString succeeds" { + Expect.equal (string Prompt) "prompt" "Prompt event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal (Prompt.ToHxOnString()) "prompt" "Prompt hx-on event name not correct" + } + ] + testList "PushedIntoHistory" [ + test "ToString succeeds" { + Expect.equal (string PushedIntoHistory) "pushedIntoHistory" "PushedIntoHistory event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal + (PushedIntoHistory.ToHxOnString()) + "pushed-into-history" + "PushedIntoHistory hx-on event name not correct" + } + ] + testList "ResponseError" [ + test "ToString succeeds" { + Expect.equal (string ResponseError) "responseError" "ResponseError event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal + (ResponseError.ToHxOnString()) "response-error" "ResponseError hx-on event name not correct" + } + ] + testList "SendError" [ + test "ToString succeeds" { + Expect.equal (string SendError) "sendError" "SendError event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal (SendError.ToHxOnString()) "send-error" "SendError hx-on event name not correct" + } + ] + testList "SseError" [ + test "ToString succeeds" { + Expect.equal (string SseError) "sseError" "SseError event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal (SseError.ToHxOnString()) "sse-error" "SseError hx-on event name not correct" + } + ] + testList "SseOpen" [ + test "ToString succeeds" { + Expect.equal (string SseOpen) "sseOpen" "SseOpen event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal (SseOpen.ToHxOnString()) "sse-open" "SseOpen hx-on event name not correct" + } + ] + testList "SwapError" [ + test "ToString succeeds" { + Expect.equal (string SwapError) "swapError" "SwapError event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal (SwapError.ToHxOnString()) "swap-error" "SwapError hx-on event name not correct" + } + ] + testList "TargetError" [ + test "ToString succeeds" { + Expect.equal (string TargetError) "targetError" "TargetError event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal (TargetError.ToHxOnString()) "target-error" "TargetError hx-on event name not correct" + } + ] + testList "Timeout" [ + test "ToString succeeds" { + Expect.equal (string Timeout) "timeout" "Timeout event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal (Timeout.ToHxOnString()) "timeout" "Timeout hx-on event name not correct" + } + ] + testList "ValidationValidate" [ + test "ToString succeeds" { + Expect.equal + (string ValidationValidate) "validation:validate" "ValidationValidate event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal + (ValidationValidate.ToHxOnString()) + "validation:validate" + "ValidationValidate hx-on event name not correct" + } + ] + testList "ValidationFailed" [ + test "ToString succeeds" { + Expect.equal (string ValidationFailed) "validation:failed" "ValidationFailed event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal + (ValidationFailed.ToHxOnString()) + "validation:failed" + "ValidationFailed hx-on event name not correct" + } + ] + testList "ValidationHalted" [ + test "ToString succeeds" { + Expect.equal (string ValidationHalted) "validation:halted" "ValidationHalted event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal + (ValidationHalted.ToHxOnString()) + "validation:halted" + "ValidationHalted hx-on event name not correct" + } + ] + testList "XhrAbort" [ + test "ToString succeeds" { + Expect.equal (string XhrAbort) "xhr:abort" "XhrAbort event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal (XhrAbort.ToHxOnString()) "xhr:abort" "XhrAbort hx-on event name not correct" + } + ] + testList "XhrLoadEnd" [ + test "ToString succeeds" { + Expect.equal (string XhrLoadEnd) "xhr:loadend" "XhrLoadEnd event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal (XhrLoadEnd.ToHxOnString()) "xhr:loadend" "XhrLoadEnd hx-on event name not correct" + } + ] + testList "XhrLoadStart" [ + test "ToString succeeds" { + Expect.equal (string XhrLoadStart) "xhr:loadstart" "XhrLoadStart event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal (XhrLoadStart.ToHxOnString()) "xhr:loadstart" "XhrLoadStart hx-on event name not correct" + } + ] + testList "XhrProgress" [ + test "ToString succeeds" { + Expect.equal (string XhrProgress) "xhr:progress" "XhrProgress event name not correct" + } + test "ToHxOnString succeeds" { + Expect.equal (XhrProgress.ToHxOnString()) "xhr:progress" "XhrProgress hx-on event name not correct" + } + ] + + ] /// Tests for the HxHeaders module let hxHeaders = testList "HxHeaders" [ @@ -293,14 +686,14 @@ let hxVals = ] ] +/// Pipe-able assertion for a rendered node +let shouldRender expected node = + Expect.equal (RenderView.AsString.htmlNode node) expected "Rendered HTML incorrect" + /// Tests for the HtmxAttrs module let attributes = testList "Attributes" [ - /// Pipe-able assertion for a rendered node - let shouldRender expected node = - Expect.equal (RenderView.AsString.htmlNode node) expected "Rendered HTML incorrect" - test "_hxBoost succeeds" { div [ _hxBoost ] [] |> shouldRender """
""" } @@ -314,7 +707,7 @@ let attributes = p [ _hxDisable ] [] |> shouldRender """

""" } test "_hxDisabledElt succeeds" { - button [ _hxDiabledElt "this" ] [] |> shouldRender """""" + button [ _hxDisabledElt "this" ] [] |> shouldRender """""" } test "_hxDisinherit succeeds" { strong [ _hxDisinherit "*" ] [] |> shouldRender """""" @@ -347,11 +740,6 @@ let attributes = test "_hxNoBoost succeeds" { td [ _hxNoBoost ] [] |> shouldRender """""" } - test "_hxOn succeeds" { - let newLine = "\n" - strong [ _hxOn "submit: alert('oops')\nclick: alert('howdy!')" ] [] - |> shouldRender $"""""" - } test "_hxParams succeeds" { br [ _hxParams "[p1,p2]" ] |> shouldRender """
""" } @@ -425,14 +813,14 @@ let script = let html = RenderView.AsString.htmlNode Script.minified Expect.equal html - """""" + """""" "Minified script tag is incorrect" } test "unminified succeeds" { let html = RenderView.AsString.htmlNode Script.unminified Expect.equal html - """""" + """""" "Unminified script tag is incorrect" } ] @@ -584,7 +972,30 @@ let renderFragment = ] ] +#nowarn "44" + +/// Tests for the HtmxAttrs module +let deprecatedAttributes = + testList "Deprecated Attributes" [ + test "_hxOn succeeds" { + let newLine = "\n" + strong [ _hxOn "submit: alert('oops')\nclick: alert('howdy!')" ] [] + |> shouldRender $"""""" + } + ] + /// All tests in this module let allTests = - testList "ViewEngine.Htmx" - [ hxEncoding; hxHeaders; hxParams; hxRequest; hxTrigger; hxVals; attributes; script; renderFragment ] + testList "ViewEngine.Htmx" [ + hxEncoding + hxEvent + hxHeaders + hxParams + hxRequest + hxTrigger + hxVals + attributes + script + renderFragment + deprecatedAttributes + ] diff --git a/src/ViewEngine.Htmx/Htmx.fs b/src/ViewEngine.Htmx/Htmx.fs index 4b8a10c..f29f5c8 100644 --- a/src/ViewEngine.Htmx/Htmx.fs +++ b/src/ViewEngine.Htmx/Htmx.fs @@ -19,6 +19,146 @@ module HxEncoding = let MultipartForm = "multipart/form-data" +/// The events recognized by htmx +type HxEvent = + /// Send this event to an element to abort a request + | Abort + /// Triggered after an AJAX request has completed processing a successful response + | AfterOnLoad + /// Triggered after htmx has initialized a node + | AfterProcessNode + /// Triggered after an AJAX request has completed + | AfterRequest + /// Triggered after the DOM has settled + | AfterSettle + /// Triggered after new content has been swapped in + | AfterSwap + /// Triggered before htmx disables an element or removes it from the DOM + | BeforeCleanupElement + /// Triggered before any response processing occurs + | BeforeOnLoad + /// Triggered before htmx initializes a node + | BeforeProcessNode + /// Triggered before an AJAX request is made + | BeforeRequest + /// Triggered before a swap is done, allows you to configure the swap + | BeforeSwap + /// Triggered just before an ajax request is sent + | BeforeSend + /// Triggered before the request, allows you to customize parameters, headers + | ConfigRequest + /// Triggered after a trigger occurs on an element, allows you to cancel (or delay) issuing the AJAX request + | Confirm + /// Triggered on an error during cache writing + | HistoryCacheError + /// Triggered on a cache miss in the history subsystem + | HistoryCacheMiss + /// Triggered on a unsuccessful remote retrieval + | HistoryCacheMissError + /// Triggered on a successful remote retrieval + | HistoryCacheMissLoad + /// Triggered when htmx handles a history restoration action + | HistoryRestore + /// Triggered before content is saved to the history cache + | BeforeHistorySave + /// Triggered when new content is added to the DOM + | Load + /// Triggered when an element refers to a SSE event in its trigger, but no parent SSE source has been defined + | NoSseSourceError + /// Triggered when an exception occurs during the onLoad handling in htmx + | OnLoadError + /// Triggered after an out of band element as been swapped in + | OobAfterSwap + /// Triggered before an out of band element swap is done, allows you to configure the swap + | OobBeforeSwap + /// Triggered when an out of band element does not have a matching ID in the current DOM + | OobErrorNoTarget + /// Triggered after a prompt is shown + | Prompt + /// Triggered after an url is pushed into history + | PushedIntoHistory + /// Triggered when an HTTP response error (non-200 or 300 response code) occurs + | ResponseError + /// Triggered when a network error prevents an HTTP request from happening + | SendError + /// Triggered when an error occurs with a SSE source + | SseError + /// Triggered when a SSE source is opened + | SseOpen + /// Triggered when an error occurs during the swap phase + | SwapError + /// Triggered when an invalid target is specified + | TargetError + /// Triggered when a request timeout occurs + | Timeout + /// Triggered before an element is validated + | ValidationValidate + /// Triggered when an element fails validation + | ValidationFailed + /// Triggered when a request is halted due to validation errors + | ValidationHalted + /// Triggered when an ajax request aborts + | XhrAbort + /// Triggered when an ajax request ends + | XhrLoadEnd + /// Triggered when an ajax request starts + | XhrLoadStart + /// Triggered periodically during an ajax request that supports progress events + | XhrProgress + + /// The htmx event name (fst) and kebab-case name (snd, for use with hx-on) + static member private Values = Map [ + Abort, ("abort", "abort") + AfterOnLoad, ("afterOnLoad", "after-on-load") + AfterProcessNode, ("afterProcessNode", "after-process-node") + AfterRequest, ("afterRequest", "after-request") + AfterSettle, ("afterSettle", "after-settle") + AfterSwap, ("afterSwap", "after-swap") + BeforeCleanupElement, ("beforeCleanupElement", "before-cleanup-element") + BeforeOnLoad, ("beforeOnLoad", "before-on-load") + BeforeProcessNode, ("beforeProcessNode", "before-process-node") + BeforeRequest, ("beforeRequest", "before-request") + BeforeSwap, ("beforeSwap", "before-swap") + BeforeSend, ("beforeSend", "before-send") + ConfigRequest, ("configRequest", "config-request") + Confirm, ("confirm", "confirm") + HistoryCacheError, ("historyCacheError", "history-cache-error") + HistoryCacheMiss, ("historyCacheMiss", "history-cache-miss") + HistoryCacheMissError, ("historyCacheMissError", "history-cache-miss-error") + HistoryCacheMissLoad, ("historyCacheMissLoad", "history-cache-miss-load") + HistoryRestore, ("historyRestore", "history-restore") + BeforeHistorySave, ("beforeHistorySave", "before-history-save") + Load, ("load", "load") + NoSseSourceError, ("noSSESourceError", "no-sse-source-error") + OnLoadError, ("onLoadError", "on-load-error") + OobAfterSwap, ("oobAfterSwap", "oob-after-swap") + OobBeforeSwap, ("oobBeforeSwap", "oob-before-swap") + OobErrorNoTarget, ("oobErrorNoTarget", "oob-error-no-target") + Prompt, ("prompt", "prompt") + PushedIntoHistory, ("pushedIntoHistory", "pushed-into-history") + ResponseError, ("responseError", "response-error") + SendError, ("sendError", "send-error") + SseError, ("sseError", "sse-error") + SseOpen, ("sseOpen", "sse-open") + SwapError, ("swapError", "swap-error") + TargetError, ("targetError", "target-error") + Timeout, ("timeout", "timeout") + ValidationValidate, ("validation:validate", "validation:validate") + ValidationFailed, ("validation:failed", "validation:failed") + ValidationHalted, ("validation:halted", "validation:halted") + XhrAbort, ("xhr:abort", "xhr:abort") + XhrLoadEnd, ("xhr:loadend", "xhr:loadend") + XhrLoadStart, ("xhr:loadstart", "xhr:loadstart") + XhrProgress, ("xhr:progress", "xhr:progress") + ] + + /// The htmx event name + override this.ToString() = fst HxEvent.Values[this] + + /// The hx-on variant of the htmx event name + member this.ToHxOnString() = snd HxEvent.Values[this] + + /// Helper to create the `hx-headers` attribute [] module HxHeaders = @@ -176,6 +316,7 @@ module HxVals = /// Create values from a list of key/value pairs let From = toJson +open System /// Attributes and flags for htmx [] @@ -194,7 +335,7 @@ module HtmxAttrs = let _hxDisable = flag "hx-disable" /// Specifies elements that should be disabled when an htmx request is in flight - let _hxDiabledElt = attr "hx-disabled-elt" + let _hxDisabledElt = attr "hx-disabled-elt" /// Disinherit all ("*") or specific htmx attributes let _hxDisinherit = attr "hx-disinherit" @@ -227,8 +368,17 @@ module HtmxAttrs = let _hxNoBoost = attr "hx-boost" "false" /// Attach an event handler for DOM or htmx events + [] let _hxOn = attr "hx-on" + /// Attach an event handler for DOM events + let _hxOnEvent evtName = + attr $"hx-on:%s{evtName}" + + /// Attach an event handler for htmx events + let _hxOnHxEvent (hxEvent: HxEvent) = + _hxOnEvent $":{hxEvent.ToHxOnString()}" + /// Filters the parameters that will be submitted with a request let _hxParams = attr "hx-params" @@ -299,14 +449,14 @@ module Script = /// Script tag to load the minified version from unpkg.com let minified = - script [ _src "https://unpkg.com/htmx.org@1.9.8" - _integrity "sha384-rgjA7mptc2ETQqXoYC3/zJvkU7K/aP44Y+z7xQuJiVnB/422P/Ak+F/AqFR7E4Wr" + script [ _src "https://unpkg.com/htmx.org@1.9.10" + _integrity "sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC" _crossorigin "anonymous" ] [] /// Script tag to load the unminified version from unpkg.com let unminified = - script [ _src "https://unpkg.com/htmx.org@1.9.8/dist/htmx.js" - _integrity "sha384-zOAIsdGekNHQVAjCjVrQ1xHoxyvnxgr63EH6IyXsCfvKZdRFRyG1u8GbWxO5oZ38" + script [ _src "https://unpkg.com/htmx.org@1.9.10/dist/htmx.js" + _integrity "sha384-j1TtLExqttdT7C3Z/rJy8UZcCGiuqwwN9++coZ6up+5O/l2FHdp3IGfuJOvst6d1" _crossorigin "anonymous" ] [] diff --git a/src/ViewEngine.Htmx/README.md b/src/ViewEngine.Htmx/README.md index f2a4778..ff9fa78 100644 --- a/src/ViewEngine.Htmx/README.md +++ b/src/ViewEngine.Htmx/README.md @@ -2,7 +2,7 @@ This package enables [htmx](https://htmx.org) support within the [Giraffe](https://giraffe.wiki) view engine. -**htmx version: 1.9.8** +**htmx version: 1.9.10** ### Setup -- 2.45.1 From 33fe90d7b51733d2cf5778b8bccf49066c752890 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Tue, 2 Jan 2024 18:35:49 -0500 Subject: [PATCH 2/3] Add tests for hx-on attrs Fix broken link in README --- README.md | 8 ++++---- src/Tests/ViewEngine.fs | 7 +++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a0d89ca..3490494 100644 --- a/README.md +++ b/README.md @@ -67,10 +67,10 @@ If you want to load htmx from unpkg, `Htmx.Script.minified` or `Htmx.Script.unmi ## Feedback / Help -The author hangs out in the #htmx-general channel of the [htmx Discord server](https://htmx.org/discord) and the #web channel of the [F# Software Foundation's Slack server](https://fsharp.org/guides/slack/). +The author hangs out in the #dotnet-htmx channel (and most others) of the [htmx Discord server](https://htmx.org/discord) and the #web channel of the [F# Software Foundation's Slack server](https://fsharp.org/guides/slack/). ## Thanks -|[Giraffe logo](https://giraffe.wiki)|[htmx logo](https://htmx.org)|[JetBrains Logo (Main)](https://jb.gg/OpenSource)| -| :---: | :---: | :---: | -|for making ASP.NET Core functional|for making HTML cool again|for licensing their tools to this project| +|[Giraffe logo](https://giraffe.wiki)| [htmx logo](https://htmx.org) |[JetBrains Logo (Main)](https://jb.gg/OpenSource)| +| :---: |:------------------------------------------------------------------------------------------------------------------------------------:| :---: | +|for making ASP.NET Core functional| for making HTML cool again |for licensing their tools to this project| diff --git a/src/Tests/ViewEngine.fs b/src/Tests/ViewEngine.fs index 69da20c..5f0de7f 100644 --- a/src/Tests/ViewEngine.fs +++ b/src/Tests/ViewEngine.fs @@ -740,6 +740,13 @@ let attributes = test "_hxNoBoost succeeds" { td [ _hxNoBoost ] [] |> shouldRender """""" } + test "_hxOnEvent succeeds" { + a [ _hxOnEvent "click" "doThis()" ] [] |> shouldRender """""" + } + test "_hxOnHxEvent succeeds" { + strong [ _hxOnHxEvent BeforeSwap "changeStuff()" ] [] + |> shouldRender """""" + } test "_hxParams succeeds" { br [ _hxParams "[p1,p2]" ] |> shouldRender """
""" } -- 2.45.1 From 10f8920ad51eaaa974dc361d664fa191782bc5ed Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Tue, 2 Jan 2024 18:52:05 -0500 Subject: [PATCH 3/3] Add NuPkg icon --- src/Common/Giraffe.Htmx.Common.fsproj | 1 + src/Htmx/Giraffe.Htmx.fsproj | 1 + .../Giraffe.ViewEngine.Htmx.fsproj | 1 + src/icon.png | Bin 0 -> 11496 bytes 4 files changed, 3 insertions(+) create mode 100644 src/icon.png diff --git a/src/Common/Giraffe.Htmx.Common.fsproj b/src/Common/Giraffe.Htmx.Common.fsproj index dc835b3..df698f0 100644 --- a/src/Common/Giraffe.Htmx.Common.fsproj +++ b/src/Common/Giraffe.Htmx.Common.fsproj @@ -9,6 +9,7 @@ + diff --git a/src/Htmx/Giraffe.Htmx.fsproj b/src/Htmx/Giraffe.Htmx.fsproj index 7c0b7c6..e195b98 100644 --- a/src/Htmx/Giraffe.Htmx.fsproj +++ b/src/Htmx/Giraffe.Htmx.fsproj @@ -9,6 +9,7 @@ + diff --git a/src/ViewEngine.Htmx/Giraffe.ViewEngine.Htmx.fsproj b/src/ViewEngine.Htmx/Giraffe.ViewEngine.Htmx.fsproj index 665b85c..3d120f3 100644 --- a/src/ViewEngine.Htmx/Giraffe.ViewEngine.Htmx.fsproj +++ b/src/ViewEngine.Htmx/Giraffe.ViewEngine.Htmx.fsproj @@ -9,6 +9,7 @@ + diff --git a/src/icon.png b/src/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9857f164c19fcc187be14e2ba81076b7363f1ffe GIT binary patch literal 11496 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 literal 0 HcmV?d00001 -- 2.45.1