diff --git a/src/Common/Common.fs b/src/Common/Common.fs
index 4c523c1..85b1219 100644
--- a/src/Common/Common.fs
+++ b/src/Common/Common.fs
@@ -1,28 +1,53 @@
-/// Common definitions shared between attribute values and response headers
+/// Common definitions shared between attribute values and response headers
[]
module Giraffe.Htmx.Common
-/// Valid values for the `hx-swap` attribute / `HX-Reswap` header (may be combined with swap/settle/scroll/show config)
+/// Serialize a list of key/value pairs to JSON (very rudimentary)
+/// The key/value pairs to be serialized to JSON
+/// A string with the key/value pairs serialized to JSON
+let internal toJson (pairs: (string * string) list) =
+ pairs
+ |> List.map (fun pair -> sprintf "\"%s\": \"%s\"" (fst pair) ((snd pair).Replace ("\"", "\\\"")))
+ |> String.concat ", "
+ |> sprintf "{ %s }"
+
+/// Convert a boolean to lowercase "true" or "false"
+/// The boolean value to convert
+/// "true" for true, "false" for false
+let internal toLowerBool (boolValue: bool) =
+ (string boolValue).ToLowerInvariant()
+
+
+/// Valid values for the hx-swap attribute / HX-Reswap header
+/// May be combined with swap / settle / scroll / show config)
+/// Documentation
[]
module HxSwap =
- /// The default, replace the inner html of the target element
+ /// The default, replace the inner HTML of the target element
+ []
let InnerHtml = "innerHTML"
- /// Replace the entire target element with the response
+ /// Replace the entire target element with the response
+ []
let OuterHtml = "outerHTML"
- /// Insert the response before the target element
+ /// Insert the response before the target element
+ []
let BeforeBegin = "beforebegin"
- /// Insert the response before the first child of the target element
+ /// Insert the response before the first child of the target element
+ []
let AfterBegin = "afterbegin"
- /// Insert the response after the last child of the target element
+ /// Insert the response after the last child of the target element
+ []
let BeforeEnd = "beforeend"
- /// Insert the response after the target element
+ /// Insert the response after the target element
+ []
let AfterEnd = "afterend"
- /// Does not append content from response (out of band items will still be processed).
+ /// Does not append content from response (out of band items will still be processed).
+ []
let None = "none"
diff --git a/src/Common/Giraffe.Htmx.Common.fsproj b/src/Common/Giraffe.Htmx.Common.fsproj
index 1aa7bbd..465b707 100644
--- a/src/Common/Giraffe.Htmx.Common.fsproj
+++ b/src/Common/Giraffe.Htmx.Common.fsproj
@@ -4,6 +4,7 @@
trueCommon definitions for Giraffe.HtmxREADME.md
+ bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml
@@ -15,5 +16,9 @@
-
+
+
+
+
+
diff --git a/src/Common/README.md b/src/Common/README.md
index 6747422..1b9f74a 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: 2.0.4**
+**htmx version: 2.0.6**
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index c622ac0..58015d0 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -1,9 +1,16 @@
- net6.0;net8.0;net9.0
- 2.0.4
- Update script tags to pull htmx 2.0.4 (no header or attribute changes)
+ net8.0;net9.0
+ 2.0.6
+ true
+ - All packages now have full XML documentation
+- Adds HxSync module and attribute helper to view engine
+- Updates script tags to pull htmx 2.0.6 (no header or attribute changes)
+- Drops .NET 6 support
+
+NOTE: The CDN for htmx changed from unpkg.com to cdn.jsdelivr.net; sites with Content-Security-Policy headers will want to update their allowed domains accordingly
+ danieljsummersBit Badger Solutionshttps://git.bitbadger.solutions/bit-badger/Giraffe.Htmx
diff --git a/src/Htmx/Giraffe.Htmx.fsproj b/src/Htmx/Giraffe.Htmx.fsproj
index 4111b40..be3f9bf 100644
--- a/src/Htmx/Giraffe.Htmx.fsproj
+++ b/src/Htmx/Giraffe.Htmx.fsproj
@@ -4,6 +4,7 @@
truehtmx header extensions and helpers for GiraffeREADME.md
+ bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml
diff --git a/src/Htmx/Htmx.fs b/src/Htmx/Htmx.fs
index b21559a..affa2db 100644
--- a/src/Htmx/Htmx.fs
+++ b/src/Htmx/Htmx.fs
@@ -11,121 +11,162 @@ let private hdr (headers : IHeaderDictionary) hdr =
/// Extensions to the header dictionary
type IHeaderDictionary with
- /// Indicates that the request is via an element using `hx-boost`
- member this.HxBoosted with get () = hdr this "HX-Boosted" |> Option.map bool.Parse
+ /// Indicates that the request is via an element using hx-boost
+ member this.HxBoosted
+ with get () = hdr this "HX-Boosted" |> Option.map bool.Parse
- /// The current URL of the browser _(note that this does not update until after settle)_
- member this.HxCurrentUrl with get () = hdr this "HX-Current-URL" |> Option.map Uri
+ /// The current URL of the browser (note that this does not update until after settle)
+ member this.HxCurrentUrl
+ with get () = hdr this "HX-Current-URL" |> Option.map Uri
- /// `true` if the request is for history restoration after a miss in the local history cache
- member this.HxHistoryRestoreRequest with get () = hdr this "HX-History-Restore-Request" |> Option.map bool.Parse
+ /// true if the request is for history restoration after a miss in the local history cache
+ member this.HxHistoryRestoreRequest
+ with get () = hdr this "HX-History-Restore-Request" |> Option.map bool.Parse
- /// The user response to an `hx-prompt`
- member this.HxPrompt with get () = hdr this "HX-Prompt"
+ /// The user response to an hx-prompt
+ member this.HxPrompt
+ with get () = hdr this "HX-Prompt"
- /// `true` if the request came from HTMX
- member this.HxRequest with get () = hdr this "HX-Request" |> Option.map bool.Parse
+ /// true if the request came from htmx
+ member this.HxRequest
+ with get () = hdr this "HX-Request" |> Option.map bool.Parse
- /// The `id` of the target element if it exists
- member this.HxTarget with get () = hdr this "HX-Target"
+ /// The id attribute of the target element if it exists
+ member this.HxTarget
+ with get () = hdr this "HX-Target"
- /// The `id` of the triggered element if it exists
- member this.HxTrigger with get () = hdr this "HX-Trigger"
+ /// The id attribute of the triggered element if it exists
+ member this.HxTrigger
+ with get () = hdr this "HX-Trigger"
- /// The `name` of the triggered element if it exists
- member this.HxTriggerName with get () = hdr this "HX-Trigger-Name"
+ /// The name attribute of the triggered element if it exists
+ member this.HxTriggerName
+ with get () = hdr this "HX-Trigger-Name"
/// Extensions for the request object
type HttpRequest with
- /// Whether this request was initiated from htmx
- member this.IsHtmx with get () = this.Headers.HxRequest |> Option.defaultValue false
+ /// Whether this request was initiated from htmx
+ member this.IsHtmx
+ with get () = this.Headers.HxRequest |> Option.defaultValue false
- /// Whether this request is an htmx history-miss refresh request
- member this.IsHtmxRefresh with get () =
- this.IsHtmx && (this.Headers.HxHistoryRestoreRequest |> Option.defaultValue false)
+ /// Whether this request is an htmx history-miss refresh request
+ member this.IsHtmxRefresh
+ with get () = this.IsHtmx && (this.Headers.HxHistoryRestoreRequest |> Option.defaultValue false)
-/// HTTP handlers for setting output headers
+/// HTTP handlers for setting output headers
[]
module Handlers =
- /// Convert a boolean to lowercase `true` or `false`
- let private toLowerBool (trueOrFalse : bool) =
- (string trueOrFalse).ToLowerInvariant ()
+ open Giraffe.Htmx.Common
+
+ /// Instruct htmx to perform a client-side redirect for content
+ /// The path where the content should be found
+ /// An HTTP handler with the HX-Location header set
+ /// Documentation
+ let withHxLocation (path: string) : HttpHandler =
+ setHttpHeader "HX-Location" path
+
+ /// Pushes a new url into the history stack
+ /// The URL to be pushed
+ /// An HTTP handler with the HX-Push-Url header set
+ /// Use to explicitly not push a new URL
+ /// Documentation
+ let withHxPushUrl (url: string) : HttpHandler =
+ setHttpHeader "HX-Push-Url" url
- /// Serialize a list of key/value pairs to JSON (very rudimentary)
- let private toJson (evts : (string * string) list) =
- evts
- |> List.map (fun evt -> sprintf "\"%s\": \"%s\"" (fst evt) ((snd evt).Replace ("\"", "\\\"")))
- |> String.concat ", "
- |> sprintf "{ %s }"
-
- /// Pushes a new url into the history stack
- let withHxPushUrl : string -> HttpHandler =
- setHttpHeader "HX-Push-Url"
-
- /// Explicitly do not push a new URL into the history stack
+ /// Explicitly do not push a new URL into the history stack
+ /// An HTTP handler with the HX-Push-Url header set to false
+ /// Documentation
let withHxNoPushUrl : HttpHandler =
toLowerBool false |> withHxPushUrl
- /// Pushes a new url into the history stack
- []
- let withHxPush = withHxPushUrl
-
- /// Explicitly do not push a new URL into the history stack
- []
- let withHxNoPush = withHxNoPushUrl
-
- /// Can be used to do a client-side redirect to a new location
- let withHxRedirect : string -> HttpHandler =
- setHttpHeader "HX-Redirect"
+ /// Can be used to do a client-side redirect to a new location
+ /// The URL to which the client should be redirected
+ /// An HTTP handler with the HX-Redirect header set
+ /// Documentation
+ let withHxRedirect (url: string) : HttpHandler =
+ setHttpHeader "HX-Redirect" url
- /// If set to `true` the client side will do a a full refresh of the page
- let withHxRefresh : bool -> HttpHandler =
- toLowerBool >> setHttpHeader "HX-Refresh"
+ /// If set to true the client side will do a full refresh of the page
+ /// Whether the client should refresh their page
+ /// An HTTP handler with the HX-Refresh header set
+ let withHxRefresh shouldRefresh : HttpHandler =
+ (toLowerBool >> setHttpHeader "HX-Refresh") shouldRefresh
- /// Replaces the current URL in the history stack
- let withHxReplaceUrl : string -> HttpHandler =
- setHttpHeader "HX-Replace-Url"
+ /// Replaces the current URL in the history stack
+ /// The URL to place in the history stack in place of the current one
+ /// An HTTP handler with the HX-Replace-URL header set
+ /// Use to explicitly not replace the current URL
+ /// Documentation
+ let withHxReplaceUrl url : HttpHandler =
+ setHttpHeader "HX-Replace-Url" url
- /// Explicitly do not replace the current URL in the history stack
+ /// Explicitly do not replace the current URL in the history stack
+ /// An HTTP handler with the HX-Replace-URL header set to false
+ /// Documentation
let withHxNoReplaceUrl : HttpHandler =
toLowerBool false |> withHxReplaceUrl
- /// Override which portion of the response will be swapped into the target document
- let withHxReselect : string -> HttpHandler =
- setHttpHeader "HX-Reselect"
+ /// Override which portion of the response will be swapped into the target document
+ /// The selector for the new response target
+ /// An HTTP handler with the HX-Reselect header set
+ let withHxReselect (target: string) : HttpHandler =
+ setHttpHeader "HX-Reselect" target
- /// Override the `hx-swap` attribute from the initiating element
- let withHxReswap : string -> HttpHandler =
- setHttpHeader "HX-Reswap"
+ /// Override the hx-swap attribute from the initiating element
+ /// The swap value to override
+ /// An HTTP handler with the HX-Reswap header set
+ /// Use HxSwap constants for best results
+ let withHxReswap (swap: string) : HttpHandler =
+ setHttpHeader "HX-Reswap" swap
- /// Allows you to override the `hx-target` attribute
- let withHxRetarget : string -> HttpHandler =
- setHttpHeader "HX-Retarget"
+ /// Allows you to override the hx-target attribute
+ /// The new target for the response
+ /// An HTTP handler with the HX-Retarget header set
+ let withHxRetarget (target: string) : HttpHandler =
+ setHttpHeader "HX-Retarget" target
- /// Allows you to trigger a single client side event
- let withHxTrigger : string -> HttpHandler =
- setHttpHeader "HX-Trigger"
+ /// Allows you to trigger a single client side event
+ /// The call to the event that should be triggered
+ /// An HTTP handler with the HX-Trigger header set
+ /// Documentation
+ let withHxTrigger (evt: string) : HttpHandler =
+ setHttpHeader "HX-Trigger" evt
- /// Allows you to trigger multiple client side events
+ /// Allows you to trigger multiple client side events
+ /// The calls to events that should be triggered
+ /// An HTTP handler with the HX-Trigger header set for all given events
+ /// Documentation
let withHxTriggerMany evts : HttpHandler =
toJson evts |> setHttpHeader "HX-Trigger"
- /// Allows you to trigger a single client side event after changes have settled
- let withHxTriggerAfterSettle : string -> HttpHandler =
- setHttpHeader "HX-Trigger-After-Settle"
+ /// Allows you to trigger a single client side event after changes have settled
+ /// The call to the event that should be triggered
+ /// An HTTP handler with the HX-Trigger-After-Settle header set
+ /// Documentation
+ let withHxTriggerAfterSettle (evt: string) : HttpHandler =
+ setHttpHeader "HX-Trigger-After-Settle" evt
- /// Allows you to trigger multiple client side events after changes have settled
+ /// Allows you to trigger multiple client side events after changes have settled
+ /// The calls to events that should be triggered
+ /// An HTTP handler with the HX-Trigger-After-Settle header set for all given events
+ /// Documentation
let withHxTriggerManyAfterSettle evts : HttpHandler =
toJson evts |> setHttpHeader "HX-Trigger-After-Settle"
- /// Allows you to trigger a single client side event after DOM swapping occurs
- let withHxTriggerAfterSwap : string -> HttpHandler =
- setHttpHeader "HX-Trigger-After-Swap"
+ /// Allows you to trigger a single client side event after DOM swapping occurs
+ /// The call to the event that should be triggered
+ /// An HTTP handler with the HX-Trigger-After-Swap header set
+ /// Documentation
+ let withHxTriggerAfterSwap (evt: string) : HttpHandler =
+ setHttpHeader "HX-Trigger-After-Swap" evt
- /// Allows you to trigger multiple client side events after DOM swapping occurs
+ /// Allows you to trigger multiple client side events after DOM swapping occurs
+ /// The calls to events that should be triggered
+ /// An HTTP handler with the HX-Trigger-After-Swap header set for all given events
+ /// Documentation
let withHxTriggerManyAfterSwap evts : HttpHandler =
toJson evts |> setHttpHeader "HX-Trigger-After-Swap"
diff --git a/src/Htmx/README.md b/src/Htmx/README.md
index 29addf7..a041166 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: 2.0.4**
+**htmx version: 2.0.6**
_Upgrading from v1.x: the [migration guide](https://htmx.org/migration-guide-htmx-1/) does not currently specify any request or response header changes. This means that there are no required code changes in moving from v1.* to v2.*._
diff --git a/src/Tests/Htmx.fs b/src/Tests/Htmx.fs
index 5b1d162..76121e1 100644
--- a/src/Tests/Htmx.fs
+++ b/src/Tests/Htmx.fs
@@ -11,22 +11,22 @@ let dictExtensions =
testList "IHeaderDictionaryExtensions" [
testList "HxBoosted" [
test "succeeds when the header is not present" {
- let ctx = Substitute.For ()
- ctx.Request.Headers.ReturnsForAnyArgs (HeaderDictionary ()) |> ignore
+ let ctx = Substitute.For()
+ ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore
Expect.isNone ctx.Request.Headers.HxBoosted "There should not have been a header returned"
}
test "succeeds when the header is present and true" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
- dic.Add ("HX-Boosted", "true")
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
+ dic.Add("HX-Boosted", "true")
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
Expect.isSome ctx.Request.Headers.HxBoosted "There should be a header present"
Expect.isTrue ctx.Request.Headers.HxBoosted.Value "The header value should have been true"
}
test "succeeds when the header is present and false" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
- dic.Add ("HX-Boosted", "false")
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
+ dic.Add("HX-Boosted", "false")
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
Expect.isSome ctx.Request.Headers.HxBoosted "There should be a header present"
Expect.isFalse ctx.Request.Headers.HxBoosted.Value "The header value should have been false"
@@ -34,14 +34,14 @@ let dictExtensions =
]
testList "HxCurrentUrl" [
test "succeeds when the header is not present" {
- let ctx = Substitute.For ()
- ctx.Request.Headers.ReturnsForAnyArgs (HeaderDictionary ()) |> ignore
+ let ctx = Substitute.For()
+ ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore
Expect.isNone ctx.Request.Headers.HxCurrentUrl "There should not have been a header returned"
}
test "succeeds when the header is present" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
- dic.Add ("HX-Current-URL", "http://localhost/test.htm")
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
+ dic.Add("HX-Current-URL", "http://localhost/test.htm")
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
Expect.isSome ctx.Request.Headers.HxCurrentUrl "There should be a header present"
Expect.equal
@@ -51,22 +51,22 @@ let dictExtensions =
]
testList "HxHistoryRestoreRequest" [
test "succeeds when the header is not present" {
- let ctx = Substitute.For ()
- ctx.Request.Headers.ReturnsForAnyArgs (HeaderDictionary ()) |> ignore
+ let ctx = Substitute.For()
+ ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore
Expect.isNone ctx.Request.Headers.HxHistoryRestoreRequest "There should not have been a header returned"
}
test "succeeds when the header is present and true" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
- dic.Add ("HX-History-Restore-Request", "true")
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
+ dic.Add("HX-History-Restore-Request", "true")
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
Expect.isSome ctx.Request.Headers.HxHistoryRestoreRequest "There should be a header present"
Expect.isTrue ctx.Request.Headers.HxHistoryRestoreRequest.Value "The header value should have been true"
}
test "succeeds when the header is present and false" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
- dic.Add ("HX-History-Restore-Request", "false")
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
+ dic.Add("HX-History-Restore-Request", "false")
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
Expect.isSome ctx.Request.Headers.HxHistoryRestoreRequest "There should be a header present"
Expect.isFalse
@@ -75,14 +75,14 @@ let dictExtensions =
]
testList "HxPrompt" [
test "succeeds when the header is not present" {
- let ctx = Substitute.For ()
- ctx.Request.Headers.ReturnsForAnyArgs (HeaderDictionary ()) |> ignore
+ let ctx = Substitute.For()
+ ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore
Expect.isNone ctx.Request.Headers.HxPrompt "There should not have been a header returned"
}
test "succeeds when the header is present" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
- dic.Add ("HX-Prompt", "of course")
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
+ dic.Add("HX-Prompt", "of course")
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
Expect.isSome ctx.Request.Headers.HxPrompt "There should be a header present"
Expect.equal ctx.Request.Headers.HxPrompt.Value "of course" "The header value was incorrect"
@@ -90,22 +90,22 @@ let dictExtensions =
]
testList "HxRequest" [
test "succeeds when the header is not present" {
- let ctx = Substitute.For ()
- ctx.Request.Headers.ReturnsForAnyArgs (HeaderDictionary ()) |> ignore
+ let ctx = Substitute.For()
+ ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore
Expect.isNone ctx.Request.Headers.HxRequest "There should not have been a header returned"
}
test "succeeds when the header is present and true" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
- dic.Add ("HX-Request", "true")
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
+ dic.Add("HX-Request", "true")
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
Expect.isSome ctx.Request.Headers.HxRequest "There should be a header present"
Expect.isTrue ctx.Request.Headers.HxRequest.Value "The header should have been true"
}
test "succeeds when the header is present and false" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
- dic.Add ("HX-Request", "false")
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
+ dic.Add("HX-Request", "false")
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
Expect.isSome ctx.Request.Headers.HxRequest "There should be a header present"
Expect.isFalse ctx.Request.Headers.HxRequest.Value "The header should have been false"
@@ -113,14 +113,14 @@ let dictExtensions =
]
testList "HxTarget" [
test "succeeds when the header is not present" {
- let ctx = Substitute.For ()
- ctx.Request.Headers.ReturnsForAnyArgs (HeaderDictionary ()) |> ignore
+ let ctx = Substitute.For()
+ ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore
Expect.isNone ctx.Request.Headers.HxTarget "There should not have been a header returned"
}
test "succeeds when the header is present" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
- dic.Add ("HX-Target", "#leItem")
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
+ dic.Add("HX-Target", "#leItem")
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
Expect.isSome ctx.Request.Headers.HxTarget "There should be a header present"
Expect.equal ctx.Request.Headers.HxTarget.Value "#leItem" "The header value was incorrect"
@@ -128,14 +128,14 @@ let dictExtensions =
]
testList "HxTrigger" [
test "succeeds when the header is not present" {
- let ctx = Substitute.For ()
+ let ctx = Substitute.For()
ctx.Request.Headers.ReturnsForAnyArgs (HeaderDictionary ()) |> ignore
Expect.isNone ctx.Request.Headers.HxTrigger "There should not have been a header returned"
}
test "succeeds when the header is present" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
- dic.Add ("HX-Trigger", "#trig")
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
+ dic.Add("HX-Trigger", "#trig")
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
Expect.isSome ctx.Request.Headers.HxTrigger "There should be a header present"
Expect.equal ctx.Request.Headers.HxTrigger.Value "#trig" "The header value was incorrect"
@@ -143,14 +143,14 @@ let dictExtensions =
]
testList "HxTriggerName" [
test "succeeds when the header is not present" {
- let ctx = Substitute.For ()
- ctx.Request.Headers.ReturnsForAnyArgs (HeaderDictionary ()) |> ignore
+ let ctx = Substitute.For()
+ ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore
Expect.isNone ctx.Request.Headers.HxTriggerName "There should not have been a header returned"
}
test "HxTriggerName succeeds when the header is present" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
- dic.Add ("HX-Trigger-Name", "click")
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
+ dic.Add("HX-Trigger-Name", "click")
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
Expect.isSome ctx.Request.Headers.HxTriggerName "There should be a header present"
Expect.equal ctx.Request.Headers.HxTriggerName.Value "click" "The header value was incorrect"
@@ -163,36 +163,36 @@ let reqExtensions =
testList "HttpRequestExtensions" [
testList "IsHtmx" [
test "succeeds when request is not from htmx" {
- let ctx = Substitute.For ()
- ctx.Request.Headers.ReturnsForAnyArgs (HeaderDictionary ()) |> ignore
+ let ctx = Substitute.For()
+ ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore
Expect.isFalse ctx.Request.IsHtmx "The request should not be an htmx request"
}
test "succeeds when request is from htmx" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
- dic.Add ("HX-Request", "true")
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
+ dic.Add("HX-Request", "true")
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
Expect.isTrue ctx.Request.IsHtmx "The request should have been an htmx request"
}
]
testList "IsHtmxRefresh" [
test "succeeds when request is not from htmx" {
- let ctx = Substitute.For ()
- ctx.Request.Headers.ReturnsForAnyArgs (HeaderDictionary ()) |> ignore
+ let ctx = Substitute.For()
+ ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore
Expect.isFalse ctx.Request.IsHtmxRefresh "The request should not have been an htmx refresh"
}
test "succeeds when request is from htmx, but not a refresh" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
- dic.Add ("HX-Request", "true")
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
+ dic.Add("HX-Request", "true")
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
Expect.isFalse ctx.Request.IsHtmxRefresh "The request should not have been an htmx refresh"
}
test "IsHtmxRefresh succeeds when request is from htmx and is a refresh" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
- dic.Add ("HX-Request", "true")
- dic.Add ("HX-History-Restore-Request", "true")
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
+ dic.Add("HX-Request", "true")
+ dic.Add("HX-History-Restore-Request", "true")
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
Expect.isTrue ctx.Request.IsHtmxRefresh "The request should have been an htmx refresh"
}
@@ -202,30 +202,38 @@ let reqExtensions =
open System.Threading.Tasks
/// Dummy "next" parameter to get the pipeline to execute/terminate
-let next (ctx : HttpContext) = Task.FromResult (Some ctx)
+let next (ctx: HttpContext) = Task.FromResult(Some ctx)
/// Tests for the HttpHandler functions provided in the Handlers module
let handlers =
testList "HandlerTests" [
+ testTask "withHxLocation succeeds" {
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
+ ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
+ let! _ = withHxLocation "/pagina-otro.html" next ctx
+ Expect.isTrue (dic.ContainsKey "HX-Location") "The HX-Location header should be present"
+ Expect.equal dic["HX-Location"].[0] "/pagina-otro.html" "The HX-Location value was incorrect"
+ }
testTask "withHxPushUrl succeeds" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
let! _ = withHxPushUrl "/a-new-url" next ctx
Expect.isTrue (dic.ContainsKey "HX-Push-Url") "The HX-Push-Url header should be present"
Expect.equal dic["HX-Push-Url"].[0] "/a-new-url" "The HX-Push-Url value was incorrect"
}
testTask "withHxNoPushUrl succeeds" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
let! _ = withHxNoPushUrl next ctx
Expect.isTrue (dic.ContainsKey "HX-Push-Url") "The HX-Push-Url header should be present"
Expect.equal dic["HX-Push-Url"].[0] "false" "The HX-Push-Url value was incorrect"
}
testTask "withHxRedirect succeeds" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
let! _ = withHxRedirect "/somewhere-else" next ctx
Expect.isTrue (dic.ContainsKey "HX-Redirect") "The HX-Redirect header should be present"
@@ -233,16 +241,16 @@ let handlers =
}
testList "withHxRefresh" [
testTask "succeeds when set to true" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
let! _ = withHxRefresh true next ctx
Expect.isTrue (dic.ContainsKey "HX-Refresh") "The HX-Refresh header should be present"
Expect.equal dic["HX-Refresh"].[0] "true" "The HX-Refresh value was incorrect"
}
testTask "succeeds when set to false" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
let! _ = withHxRefresh false next ctx
Expect.isTrue (dic.ContainsKey "HX-Refresh") "The HX-Refresh header should be present"
@@ -250,56 +258,56 @@ let handlers =
}
]
testTask "withHxReplaceUrl succeeds" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
let! _ = withHxReplaceUrl "/a-substitute-url" next ctx
Expect.isTrue (dic.ContainsKey "HX-Replace-Url") "The HX-Replace-Url header should be present"
Expect.equal dic["HX-Replace-Url"].[0] "/a-substitute-url" "The HX-Replace-Url value was incorrect"
}
testTask "withHxNoReplaceUrl succeeds" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
let! _ = withHxNoReplaceUrl next ctx
Expect.isTrue (dic.ContainsKey "HX-Replace-Url") "The HX-Replace-Url header should be present"
Expect.equal dic["HX-Replace-Url"].[0] "false" "The HX-Replace-Url value was incorrect"
}
testTask "withHxReselect succeeds" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
let! _ = withHxReselect "#test" next ctx
Expect.isTrue (dic.ContainsKey "HX-Reselect") "The HX-Reselect header should be present"
Expect.equal dic["HX-Reselect"].[0] "#test" "The HX-Reselect value was incorrect"
}
testTask "withHxReswap succeeds" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
let! _ = withHxReswap HxSwap.BeforeEnd next ctx
Expect.isTrue (dic.ContainsKey "HX-Reswap") "The HX-Reswap header should be present"
Expect.equal dic["HX-Reswap"].[0] HxSwap.BeforeEnd "The HX-Reswap value was incorrect"
}
testTask "withHxRetarget succeeds" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
let! _ = withHxRetarget "#somewhereElse" next ctx
Expect.isTrue (dic.ContainsKey "HX-Retarget") "The HX-Retarget header should be present"
Expect.equal dic["HX-Retarget"].[0] "#somewhereElse" "The HX-Retarget value was incorrect"
}
testTask "withHxTrigger succeeds" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
let! _ = withHxTrigger "doSomething" next ctx
Expect.isTrue (dic.ContainsKey "HX-Trigger") "The HX-Trigger header should be present"
Expect.equal dic["HX-Trigger"].[0] "doSomething" "The HX-Trigger value was incorrect"
}
testTask "withHxTriggerMany succeeds" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
let! _ = withHxTriggerMany [ "blah", "foo"; "bleh", "bar" ] next ctx
Expect.isTrue (dic.ContainsKey "HX-Trigger") "The HX-Trigger header should be present"
@@ -307,8 +315,8 @@ let handlers =
dic["HX-Trigger"].[0] """{ "blah": "foo", "bleh": "bar" }""" "The HX-Trigger value was incorrect"
}
testTask "withHxTriggerAfterSettle succeeds" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
let! _ = withHxTriggerAfterSettle "byTheWay" next ctx
Expect.isTrue
@@ -316,8 +324,8 @@ let handlers =
Expect.equal dic["HX-Trigger-After-Settle"].[0] "byTheWay" "The HX-Trigger-After-Settle value was incorrect"
}
testTask "withHxTriggerManyAfterSettle succeeds" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
let! _ = withHxTriggerManyAfterSettle [ "oof", "ouch"; "hmm", "uh" ] next ctx
Expect.isTrue
@@ -327,16 +335,16 @@ let handlers =
"The HX-Trigger-After-Settle value was incorrect"
}
testTask "withHxTriggerAfterSwap succeeds" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
let! _ = withHxTriggerAfterSwap "justASec" next ctx
Expect.isTrue (dic.ContainsKey "HX-Trigger-After-Swap") "The HX-Trigger-After-Swap header should be present"
Expect.equal dic["HX-Trigger-After-Swap"].[0] "justASec" "The HX-Trigger-After-Swap value was incorrect"
}
testTask "withHxTriggerManyAfterSwap succeeds" {
- let ctx = Substitute.For ()
- let dic = HeaderDictionary ()
+ let ctx = Substitute.For()
+ let dic = HeaderDictionary()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
let! _ = withHxTriggerManyAfterSwap [ "this", "1"; "that", "2" ] next ctx
Expect.isTrue (dic.ContainsKey "HX-Trigger-After-Swap") "The HX-Trigger-After-Swap header should be present"
diff --git a/src/Tests/ViewEngine.fs b/src/Tests/ViewEngine.fs
index 4653bbe..f0c9cd7 100644
--- a/src/Tests/ViewEngine.fs
+++ b/src/Tests/ViewEngine.fs
@@ -406,8 +406,8 @@ let hxEvent =
Expect.equal (XhrProgress.ToHxOnString()) "xhr:progress" "XhrProgress hx-on event name not correct"
}
]
-
]
+
/// Tests for the HxHeaders module
let hxHeaders =
testList "HxHeaders" [
@@ -497,6 +497,32 @@ let hxRequest =
]
]
+/// Tests for the HxSync module
+let hxSync =
+ testList "HxSync" [
+ test "Drop is correct" {
+ Expect.equal HxSync.Drop "drop" "Drop is incorrect"
+ }
+ test "Abort is correct" {
+ Expect.equal HxSync.Abort "abort" "Abort is incorrect"
+ }
+ test "Replace is correct" {
+ Expect.equal HxSync.Replace "replace" "Replace is incorrect"
+ }
+ test "Queue is correct" {
+ Expect.equal HxSync.Queue "queue" "Queue is incorrect"
+ }
+ test "QueueFirst is correct" {
+ Expect.equal HxSync.QueueFirst "queue first" "QueueFirst is incorrect"
+ }
+ test "QueueLast is correct" {
+ Expect.equal HxSync.QueueLast "queue last" "QueueLast is incorrect"
+ }
+ test "QueueAll is correct" {
+ Expect.equal HxSync.QueueAll "queue all" "QueueAll is incorrect"
+ }
+ ]
+
/// Tests for the HxTrigger module
let hxTrigger =
testList "HxTrigger" [
@@ -636,6 +662,9 @@ let hxTrigger =
test "succeeds when it is not the first modifier" {
Expect.equal (HxTrigger.Queue "def" "click") "click queue:def" "Queue modifier incorrect"
}
+ test "succeeds when no type of queueing is given" {
+ Expect.equal (HxTrigger.Queue "" "blur") "blur queue" "Queue modifier incorrect"
+ }
]
testList "QueueFirst" [
test "succeeds when it is the first modifier" {
@@ -726,7 +755,7 @@ let attributes =
|> shouldRender """"""
}
test "_hxHistory succeeds" {
- span [ _hxHistory "false" ] [] |> shouldRender """"""
+ span [ _hxHistory false ] [] |> shouldRender """"""
}
test "_hxHistoryElt succeeds" {
table [ _hxHistoryElt ] [] |> shouldRender """
"""
@@ -757,7 +786,7 @@ let attributes =
hr [ _hxPost "/hear-ye-hear-ye" ] |> shouldRender """"""
}
test "_hxPreserve succeeds" {
- img [ _hxPreserve ] |> shouldRender """"""
+ img [ _hxPreserve ] |> shouldRender """"""
}
test "_hxPrompt succeeds" {
strong [ _hxPrompt "Who goes there?" ] []
@@ -792,7 +821,8 @@ let attributes =
li [ _hxSwapOob "true" ] [] |> shouldRender """"""
}
test "_hxSync succeeds" {
- nav [ _hxSync "closest form:abort" ] [] |> shouldRender """"""
+ nav [ _hxSync "closest form" HxSync.Abort ] []
+ |> shouldRender """"""
}
test "_hxTarget succeeds" {
header [ _hxTarget "#somewhereElse" ] [] |> shouldRender """"""
@@ -819,14 +849,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"
}
]
@@ -839,7 +869,7 @@ let renderFragment =
/// Validate that the two object references are the same object
let isSame obj1 obj2 message =
- Expect.isTrue (obj.ReferenceEquals (obj1, obj2)) message
+ Expect.isTrue (obj.ReferenceEquals(obj1, obj2)) message
testList "findIdNode" [
test "fails with a Text node" {
@@ -921,7 +951,7 @@ let renderFragment =
}
test "fails when an ID is not matched" {
Expect.equal
- (RenderFragment.AsBytes.htmlFromNodes "whiff" []) (utf8.GetBytes (nodeNotFound "whiff"))
+ (RenderFragment.AsBytes.htmlFromNodes "whiff" []) (utf8.GetBytes(nodeNotFound "whiff"))
"HTML bytes are incorrect"
}
]
@@ -938,7 +968,7 @@ let renderFragment =
}
test "fails when an ID is not matched" {
Expect.equal
- (RenderFragment.AsBytes.htmlFromNode "foo" (hr [])) (utf8.GetBytes (nodeNotFound "foo"))
+ (RenderFragment.AsBytes.htmlFromNode "foo" (hr [])) (utf8.GetBytes(nodeNotFound "foo"))
"HTML bytes are incorrect"
}
]
@@ -946,31 +976,31 @@ let renderFragment =
testList "IntoStringBuilder" [
testList "htmlFromNodes" [
test "succeeds when an ID is matched" {
- let sb = StringBuilder ()
+ let sb = StringBuilder()
RenderFragment.IntoStringBuilder.htmlFromNodes sb "find-me"
[ p [] []; p [ _id "peekaboo" ] [ str "bzz"; str "nope"; span [ _id "find-me" ] [ str ";)" ] ]]
Expect.equal (string sb) """;)""" "HTML is incorrect"
}
test "fails when an ID is not matched" {
- let sb = StringBuilder ()
+ let sb = StringBuilder()
RenderFragment.IntoStringBuilder.htmlFromNodes sb "missing" []
Expect.equal (string sb) (nodeNotFound "missing") "HTML is incorrect"
}
]
testList "htmlFromNode" [
test "succeeds when ID is matched at top level" {
- let sb = StringBuilder ()
+ let sb = StringBuilder()
RenderFragment.IntoStringBuilder.htmlFromNode sb "top" (p [ _id "top" ] [ str "pinnacle" ])
Expect.equal (string sb) """
pinnacle
""" "HTML is incorrect"
}
test "succeeds when ID is matched in child element" {
- let sb = StringBuilder ()
+ let sb = StringBuilder()
div [] [ p [] [ str "nada" ]; p [ _id "it" ] [ str "is here" ]]
|> RenderFragment.IntoStringBuilder.htmlFromNode sb "it"
Expect.equal (string sb) """
is here
""" "HTML is incorrect"
}
test "fails when an ID is not matched" {
- let sb = StringBuilder ()
+ let sb = StringBuilder()
RenderFragment.IntoStringBuilder.htmlFromNode sb "bar" (hr [])
Expect.equal (string sb) (nodeNotFound "bar") "HTML is incorrect"
}
@@ -986,6 +1016,7 @@ let allTests =
hxHeaders
hxParams
hxRequest
+ hxSync
hxTrigger
hxVals
attributes
diff --git a/src/ViewEngine.Htmx/Giraffe.ViewEngine.Htmx.fsproj b/src/ViewEngine.Htmx/Giraffe.ViewEngine.Htmx.fsproj
index d1d39b0..6157aad 100644
--- a/src/ViewEngine.Htmx/Giraffe.ViewEngine.Htmx.fsproj
+++ b/src/ViewEngine.Htmx/Giraffe.ViewEngine.Htmx.fsproj
@@ -4,6 +4,7 @@
trueExtensions to Giraffe View Engine to support htmx attributes and their valuesREADME.md
+ bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml
diff --git a/src/ViewEngine.Htmx/Htmx.fs b/src/ViewEngine.Htmx/Htmx.fs
index ba4d623..99cb93f 100644
--- a/src/ViewEngine.Htmx/Htmx.fs
+++ b/src/ViewEngine.Htmx/Htmx.fs
@@ -1,110 +1,151 @@
+/// Types and functions supporting htmx attributes in Giraffe View Engine
module Giraffe.ViewEngine.Htmx
-/// Serialize a list of key/value pairs to JSON (very rudimentary)
-let private toJson (kvps : (string * string) list) =
- kvps
- |> List.map (fun kvp -> sprintf "\"%s\": \"%s\"" (fst kvp) ((snd kvp).Replace ("\"", "\\\"")))
- |> String.concat ", "
- |> sprintf "{ %s }"
-
-
-/// Valid values for the `hx-encoding` attribute
+/// Valid values for the hx-encoding attribute
[]
module HxEncoding =
- /// A standard HTTP form
- let Form = "application/x-www-form-urlencoded"
+ /// A standard HTTP form
+ []
+ let Form = "application/x-www-form-urlencoded"
- /// A multipart form (used for file uploads)
+ /// A multipart form (used for file uploads)
+ []
let MultipartForm = "multipart/form-data"
-/// The events recognized by htmx
+/// The events recognized by htmx
[]
type HxEvent =
- /// Send this event to an element to abort a request
+
+ /// Send this event to an element to abort a request
| Abort
- /// Triggered after an AJAX request has completed processing a successful response
+
+ /// Triggered after an AJAX request has completed processing a successful response
| AfterOnLoad
- /// Triggered after htmx has initialized a node
+
+ /// Triggered after htmx has initialized a node
| AfterProcessNode
- /// Triggered after an AJAX request has completed
+
+ /// Triggered after an AJAX request has completed
| AfterRequest
- /// Triggered after the DOM has settled
+
+ /// Triggered after the DOM has settled
| AfterSettle
- /// Triggered after new content has been swapped in
+
+ /// Triggered after new content has been swapped in
| AfterSwap
- /// Triggered before htmx disables an element or removes it from the DOM
+
+ /// Triggered before htmx disables an element or removes it from the DOM
| BeforeCleanupElement
- /// Triggered before any response processing occurs
+
+ /// Triggered before any response processing occurs
| BeforeOnLoad
- /// Triggered before htmx initializes a node
+
+ /// Triggered before htmx initializes a node
| BeforeProcessNode
- /// Triggered before an AJAX request is made
+
+ /// Triggered before an AJAX request is made
| BeforeRequest
- /// Triggered before a swap is done, allows you to configure the swap
+
+ /// Triggered before a swap is done, allows you to configure the swap
| BeforeSwap
- /// Triggered just before an ajax request is sent
+
+ /// Triggered just before an ajax request is sent
| BeforeSend
- /// Triggered before the request, allows you to customize parameters, headers
+
+ /// 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
+
+ /// Triggered on an error during cache writing
| HistoryCacheError
- /// Triggered on a cache miss in the history subsystem
+
+ /// Triggered on a cache miss in the history subsystem
| HistoryCacheMiss
- /// Triggered on a unsuccessful remote retrieval
+
+ /// Triggered on a unsuccessful remote retrieval
| HistoryCacheMissError
- /// Triggered on a successful remote retrieval
+
+ /// Triggered on a successful remote retrieval
| HistoryCacheMissLoad
- /// Triggered when htmx handles a history restoration action
+
+ /// Triggered when htmx handles a history restoration action
| HistoryRestore
- /// Triggered before content is saved to the history cache
+
+ /// Triggered before content is saved to the history cache
| BeforeHistorySave
- /// Triggered when new content is added to the DOM
+
+ /// 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
+
+ /// Triggered when an exception occurs during the onLoad handling in htmx
| OnLoadError
- /// Triggered after an out of band element as been swapped in
+
+ /// 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
+
+ /// 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
+
+ /// Triggered when an out of band element does not have a matching ID in the current DOM
| OobErrorNoTarget
- /// Triggered after a prompt is shown
+
+ /// Triggered after a prompt is shown
| Prompt
- /// Triggered after an url is pushed into history
+
+ /// Triggered after an url is pushed into history
| PushedIntoHistory
- /// Triggered when an HTTP response error (non-200 or 300 response code) occurs
+
+ /// 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
+
+ /// Triggered when a network error prevents an HTTP request from happening
| SendError
- /// Triggered when an error occurs with a SSE source
+
+ /// Triggered when an error occurs with a SSE source
| SseError
- /// Triggered when a SSE source is opened
+
+ /// Triggered when a SSE source is opened
| SseOpen
- /// Triggered when an error occurs during the swap phase
+
+ /// Triggered when an error occurs during the swap phase
| SwapError
- /// Triggered when an invalid target is specified
+
+ /// Triggered when an invalid target is specified
| TargetError
- /// Triggered when a request timeout occurs
+
+ /// Triggered when a request timeout occurs
| Timeout
- /// Triggered before an element is validated
+
+ /// Triggered before an element is validated
| ValidationValidate
- /// Triggered when an element fails validation
+
+ /// Triggered when an element fails validation
| ValidationFailed
- /// Triggered when a request is halted due to validation errors
+
+ /// Triggered when a request is halted due to validation errors
| ValidationHalted
- /// Triggered when an ajax request aborts
+
+ /// Triggered when an ajax request aborts
| XhrAbort
- /// Triggered when an ajax request ends
+
+ /// Triggered when an ajax request ends
| XhrLoadEnd
- /// Triggered when an ajax request starts
+
+ /// Triggered when an ajax request starts
| XhrLoadStart
- /// Triggered periodically during an ajax request that supports progress events
+
+ /// 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)
@@ -153,63 +194,123 @@ type HxEvent =
XhrProgress, ("xhr:progress", "xhr:progress")
]
- /// The htmx event name
+ /// The htmx event name
override this.ToString() = fst HxEvent.Values[this]
- /// The hx-on variant of the htmx event name
+ /// The hx-on variant of the htmx event name
member this.ToHxOnString() = snd HxEvent.Values[this]
-/// Helper to create the `hx-headers` attribute
+/// Helper to create the hx-headers attribute
[]
module HxHeaders =
- /// Create headers from a list of key/value pairs
- let From = toJson
+ /// Create headers from a list of key/value pairs
+ let From = Giraffe.Htmx.Common.toJson
-/// Values / helpers for the `hx-params` attribute
+/// Values / helpers for the hx-params attribute
+/// Documentation
[]
module HxParams =
- /// Include all parameters
- let All = "*"
+ /// Include all parameters
+ []
+ let All = "*"
- /// Include no parameters
+ /// Include no parameters
+ []
let None = "none"
- /// Include the specified parameters
- let With fields = match fields with [] -> "" | _ -> fields |> List.reduce (fun acc it -> $"{acc},{it}")
+ /// Include the specified parameters
+ /// One or more fields to include in the request
+ /// The list of fields for the hx-params attribute value
+ let With fields =
+ match fields with [] -> "" | _ -> fields |> List.reduce (fun acc it -> $"{acc},{it}")
- /// Exclude the specified parameters
- let Except fields = With fields |> sprintf "not %s"
+ /// Exclude the specified parameters
+ /// One or more fields to exclude from the request
+ /// The list of fields for the hx-params attribute value prefixed with "not"
+ let Except fields =
+ With fields |> sprintf "not %s"
-/// Helpers to define `hx-request` attribute values
+/// Helpers to define hx-request attribute values
+/// Documentation
[]
module HxRequest =
- /// Convert a boolean to its lowercase string equivalent
- let private toLowerBool (it : bool) =
- (string it).ToLowerInvariant ()
+ open Giraffe.Htmx.Common
- /// Configure the request with various options
- let Configure (opts : string list) =
+ /// Configure the request with various options
+ /// The options to configure
+ /// A string with the configured options
+ let Configure (opts: string list) =
opts
|> String.concat ", "
|> sprintf "{ %s }"
- /// Set a timeout (in milliseconds)
- let Timeout (ms : int) = $"\"timeout\": {ms}"
+ /// Set a timeout (in milliseconds)
+ /// The milliseconds for the request timeout
+ /// A string with the configured request timeout
+ let Timeout (ms: int) =
+ $"\"timeout\": {ms}"
- /// Include or exclude credentials from the request
- let Credentials = toLowerBool >> sprintf "\"credentials\": %s"
+ /// Include or exclude credentials from the request
+ /// true if credentials should be sent, false if not
+ /// A string with the configured credential options
+ let Credentials send =
+ (toLowerBool >> sprintf "\"credentials\": %s") send
- /// Exclude or include headers from the request
- let NoHeaders = toLowerBool >> sprintf "\"noHeaders\": %s"
+ /// Exclude or include headers from the request
+ ///
+ /// true if no headers should be sent; false if headers should be sent
+ ///
+ /// A string with the configured header options
+ let NoHeaders exclude =
+ (toLowerBool >> sprintf "\"noHeaders\": %s") exclude
-/// Helpers for the `hx-trigger` attribute
+/// Helpers for the hx-sync attribute
+/// Documentation
+[]
+module HxSync =
+
+ /// Drop (ignore) this request if a request is already in flight
+ /// This is the default for hx-sync
+ []
+ let Drop = "drop"
+
+ ///
+ /// Drop (ignore) this request if a request is already in flight, and if another request occurs while this one is in
+ /// flight, abort this request
+ ///
+ []
+ let Abort = "abort"
+
+ /// Abort any current in-flight request and replace it with this one
+ []
+ let Replace = "replace"
+
+ /// Place this request in an element-associated queue
+ []
+ let Queue = "queue"
+
+ /// Queue only the first request received while another request is in flight
+ []
+ let QueueFirst = "queue first"
+
+ /// Queue only the last request received while another request is in flight
+ []
+ let QueueLast = "queue last"
+
+ /// Queue all requests received while another request is in flight
+ []
+ let QueueAll = "queue all"
+
+
+/// Helpers for the hx-trigger attribute
+/// Documentation
[]
module HxTrigger =
@@ -221,247 +322,491 @@ module HxTrigger =
$"{parts[0]}[{parts[1]}&&{filter}]"
| false -> $"{trigger}[{filter}]"
- /// Trigger the event on a click
+ /// Trigger the event on a click
+ []
let Click = "click"
- /// Trigger the event on page load
+ /// Trigger the event on page load
+ []
let Load = "load"
- /// Trigger the event when the item is visible
+ /// Trigger the event when the item is visible
+ []
let Revealed = "revealed"
- /// Trigger this event every [timing declaration]
- let Every (duration : string) = $"every {duration}"
+ /// Trigger this event every [timing declaration]
+ /// The duration on which this trigger should fire (e.g., "1s", "500ms")
+ /// A trigger timing specification
+ let Every duration =
+ $"every %s{duration}"
- /// Helpers for defining filters
+ /// Helpers for defining filters
module Filter =
- /// Only trigger the event if the `ALT` key is pressed
+ /// Only trigger the event if the ALT key is pressed
let Alt = appendFilter "altKey"
- /// Only trigger the event if the `CTRL` key is pressed
+ /// Only trigger the event if the CTRL key is pressed
let Ctrl = appendFilter "ctrlKey"
- /// Only trigger the event if the `SHIFT` key is pressed
+ /// Only trigger the event if the SHIFT key is pressed
let Shift = appendFilter "shiftKey"
- /// Only trigger the event if `CTRL+ALT` are pressed
+ /// Only trigger the event if CTRL and ALT are pressed
let CtrlAlt = Ctrl >> Alt
- /// Only trigger the event if `CTRL+SHIFT` are pressed
+ /// Only trigger the event if CTRL and SHIFT are pressed
let CtrlShift = Ctrl >> Shift
- /// Only trigger the event if `CTRL+ALT+SHIFT` are pressed
+ /// Only trigger the event if CTRL, ALT, and SHIFT are pressed
let CtrlAltShift = CtrlAlt >> Shift
- /// Only trigger the event if `ALT+SHIFT` are pressed
+ /// Only trigger the event if ALT and SHIFT are pressed
let AltShift = Alt >> Shift
/// Append a modifier to the current trigger
let private appendModifier modifier current =
if current = "" then modifier else $"{current} {modifier}"
- /// Only trigger once
- let Once = appendModifier "once"
+ /// Only trigger once
+ /// The action which should only be fired once
+ /// A trigger spec to fire the given action once
+ let Once action =
+ appendModifier "once" action
- /// Trigger when changed
- let Changed = appendModifier "changed"
+ /// Trigger when changed
+ /// The element from which the onchange event will be emitted
+ /// A trigger spec to fire when the given element changes
+ let Changed elt =
+ appendModifier "changed" elt
- /// Delay execution; resets every time the event is seen
- let Delay = sprintf "delay:%s" >> appendModifier
+ /// Delay execution; resets every time the event is seen
+ /// The duration for the delay (e.g., "1s", "500ms")
+ /// The action which should be fired after the given delay
+ /// A trigger spec to fire the given action after the specified delay
+ let Delay duration action =
+ appendModifier $"delay:%s{duration}" action
- /// Throttle execution; ignore other events, fire when duration passes
- let Throttle = sprintf "throttle:%s" >> appendModifier
+ /// Throttle execution; ignore other events, fire when duration passes
+ /// The duration for the throttling (e.g., "1s", "500ms")
+ /// The action which should be fired after the given duration
+ /// A trigger spec to fire the given action after the specified duration
+ let Throttle duration action =
+ appendModifier $"throttle:%s{duration}" action
- /// Trigger this event from a CSS selector
- let From = sprintf "from:%s" >> appendModifier
+ /// Trigger this event from a CSS selector
+ /// A CSS selector to identify elements which may fire this trigger
+ /// The action to be fired
+ /// A trigger spec to fire from the given element(s)
+ let From selector action =
+ appendModifier $"from:%s{selector}" action
- /// Trigger this event from the `document` object
- let FromDocument = From "document"
+ /// Trigger this event from the document object
+ /// The action to be fired
+ /// A trigger spec to fire from the document element
+ let FromDocument action =
+ From "document" action
- /// Trigger this event from the `window` object
- let FromWindow = From "window"
+ /// Trigger this event from the window object
+ /// The action to be fired
+ /// A trigger spec to fire from the window object
+ let FromWindow action =
+ From "window" action
- /// Trigger this event from the closest parent CSS selector
- let FromClosest = sprintf "closest %s" >> From
+ /// Trigger this event from the closest parent CSS selector
+ /// The CSS selector from which the action should be fired
+ /// The action to be fired
+ /// A trigger spec to fire from the closest element
+ let FromClosest selector action =
+ From $"closest %s{selector}" action
- /// Trigger this event from the closest child CSS selector
- let FromFind = sprintf "find %s" >> From
+ /// Trigger this event from the closest child CSS selector
+ /// The child CSS selector from which the action should be fired
+ /// The action to be fired
+ /// A trigger spec to fire from the closest child element
+ let FromFind selector action =
+ From $"find %s{selector}" action
- /// Target the given CSS selector with the results of this event
- let Target = sprintf "target:%s" >> appendModifier
+ /// Target the given CSS selector with the results of this event
+ /// The CSS selector to which the result of the action will be applied
+ /// The action to be fired
+ /// A trigger spec to target the given selector
+ let Target selector action =
+ appendModifier $"target:%s{selector}" action
- /// Prevent any further events from occurring after this one fires
- let Consume = appendModifier "consume"
+ /// Prevent any further events from occurring after this one fires
+ /// The action to be fired
+ /// A trigger spec to fire the given action and prevent further events
+ let Consume action =
+ appendModifier "consume" action
- /// Configure queueing when events fire when others are in flight; if unspecified, the default is "last"
- let Queue = sprintf "queue:%s" >> appendModifier
+ ///
+ /// Configure queueing when events fire when others are in flight; if unspecified, the default is last
+ ///
+ ///
+ /// How the request should be queued (consider , ,
+ /// , and )
+ ///
+ /// The action to be fired
+ /// A trigger spec to queue the given action
+ let Queue how action =
+ let qSpec = if how = "" then "" else $":{how}"
+ appendModifier $"queue{qSpec}" action
- /// Queue the first event, discard all others (i.e., a FIFO queue of 1)
- let QueueFirst = Queue "first"
+ /// Queue the first event, discard all others (i.e., a FIFO queue of 1)
+ /// The action to be fired
+ /// A trigger spec to queue the given action
+ let QueueFirst action =
+ Queue "first" action
- /// Queue the last event; discards current when another is received (i.e., a LIFO queue of 1)
- let QueueLast = Queue "last"
+ /// Queue the last event; discards current when another is received (i.e., a LIFO queue of 1)
+ /// The action to be fired
+ /// A trigger spec to queue the given action
+ let QueueLast action =
+ Queue "last" action
- /// Queue all events; discard none
- let QueueAll = Queue "all"
+ /// Queue all events; discard none
+ /// The action to be fired
+ /// A trigger spec to queue the given action
+ let QueueAll action =
+ Queue "all" action
- /// Queue no events; discard all
- let QueueNone = Queue "none"
+ /// Queue no events; discard all
+ /// The action to be fired
+ /// A trigger spec to queue the given action
+ let QueueNone action =
+ Queue "none" action
-/// Helper to create the `hx-vals` attribute
+/// Helper to create the hx-vals attribute
+/// Documentation
[]
module HxVals =
- /// Create values from a list of key/value pairs
- let From = toJson
+ /// Create values from a list of key/value pairs
+ let From = Giraffe.Htmx.Common.toJson
-/// Attributes and flags for htmx
+open Giraffe.Htmx
+
+/// Attributes and flags for htmx
[]
module HtmxAttrs =
- /// Progressively enhances anchors and forms to use AJAX requests (use `_hxNoBoost` to set to false)
- let _hxBoost = attr "hx-boost" "true"
+ /// Progressively enhances anchors and forms to use AJAX requests
+ /// Use _hxNoBoost to set to false
+ /// Documentation
+ let _hxBoost = attr "hx-boost" "true"
- /// Shows a confirm() dialog before issuing a request
- let _hxConfirm = attr "hx-confirm"
+ /// Shows a confirm() dialog before issuing a request
+ /// The prompt to present to the user when seeking their confirmation
+ /// A configured hx-confirm attribute
+ /// Documentation
+ let _hxConfirm prompt =
+ attr "hx-confirm" prompt
- /// Issues a DELETE to the specified URL
- let _hxDelete = attr "hx-delete"
+ /// Issues a DELETE to the specified URL
+ /// The URL to which the DELETE request should be sent
+ /// A configured hx-delete attribute
+ /// Documentation
+ let _hxDelete url =
+ attr "hx-delete" url
- /// Disables htmx processing for the given node and any children nodes
- let _hxDisable = flag "hx-disable"
+ /// Disables htmx processing for the given node and any children nodes
+ /// Documentation
+ let _hxDisable = flag "hx-disable"
- /// Specifies elements that should be disabled when an htmx request is in flight
- let _hxDisabledElt = attr "hx-disabled-elt"
+ /// Specifies elements that should be disabled when an htmx request is in flight
+ /// The element to disable when an htmx request is in flight
+ /// A configured hx-disabled-elt attribute
+ /// Documentation
+ let _hxDisabledElt elt =
+ attr "hx-disabled-elt" elt
- /// Disinherit all ("*") or specific htmx attributes
- let _hxDisinherit = attr "hx-disinherit"
+ /// Disinherit all ("*") or specific htmx attributes
+ /// The htmx attributes to disinherit (should start with "hx-")
+ /// A configured hx-disinherit attribute
+ /// Documentation
+ let _hxDisinherit hxAttrs =
+ attr "hx-disinherit" hxAttrs
- /// Changes the request encoding type
- let _hxEncoding = attr "hx-encoding"
+ /// Changes the request encoding type
+ /// The encoding type (use HxEncoding constants)
+ /// A configured hx-encoding attribute
+ ///
+ /// Documentation
+ let _hxEncoding enc =
+ attr "hx-encoding" enc
- /// Extensions to use for this element
- let _hxExt = attr "hx-ext"
+ /// Extensions to use for this element
+ /// A list of extensions to apply to this element
+ /// A configured hx-ext attribute
+ /// Documentation
+ let _hxExt exts =
+ attr "hx-ext" exts
- /// Issues a GET to the specified URL
- let _hxGet = attr "hx-get"
+ /// Issues a GET to the specified URL
+ /// The URL to which the GET request should be sent
+ /// A configured hx-get attribute
+ /// Documentation
+ let _hxGet url =
+ attr "hx-get" url
- /// Adds to the headers that will be submitted with the request
- let _hxHeaders = attr "hx-headers"
+ /// Adds to the headers that will be submitted with the request
+ /// The headers to include with the request
+ /// A configured hx-headers attribute
+ /// Documentation
+ let _hxHeaders hdrs =
+ attr "hx-headers" hdrs
+ ///
/// Set to "false" to prevent pages with sensitive information from being stored in the history cache
- let _hxHistory = attr "hx-history"
+ ///
+ /// Whether the page should be stored in the history cache
+ /// A configured hx-history attribute
+ /// Documentation
+ let _hxHistory shouldStore =
+ attr "hx-history" (toLowerBool shouldStore)
- /// The element to snapshot and restore during history navigation
- let _hxHistoryElt = flag "hx-history-elt"
+ /// The element to snapshot and restore during history navigation
+ /// Documentation
+ let _hxHistoryElt =
+ flag "hx-history-elt"
- /// Includes additional data in AJAX requests
- let _hxInclude = attr "hx-include"
+ /// Includes additional data in AJAX requests
+ /// The specification of what should be included in the request
+ /// A configured hx-include attribute
+ /// Documentation
+ let _hxInclude spec =
+ attr "hx-include" spec
- /// The element to put the htmx-request class on during the AJAX request
- let _hxIndicator = attr "hx-indicator"
+ /// The element to put the htmx-request class on during the AJAX request
+ /// The selector for the indicator element
+ /// A configured hx-indicator attribute
+ /// Documentation
+ let _hxIndicator selector =
+ attr "hx-indicator" selector
- /// Overrides a previous `hx-boost`
- let _hxNoBoost = attr "hx-boost" "false"
+ /// Overrides a previous hx-boost (hx-boost="false")
+ /// Documentation
+ let _hxNoBoost =
+ attr "hx-boost" "false"
- /// Attach an event handler for DOM events
- let _hxOnEvent evtName =
- attr $"hx-on:%s{evtName}"
+ /// Attach an event handler for DOM events
+ /// The name of the event
+ /// The script to be executed when the event occurs
+ /// A configured hx-on attribute
+ /// Documentation
+ let _hxOnEvent evtName handler =
+ attr $"hx-on:%s{evtName}" handler
- /// Attach an event handler for htmx events
- let _hxOnHxEvent (hxEvent: HxEvent) =
- _hxOnEvent $":{hxEvent.ToHxOnString()}"
+ /// Attach an event handler for htmx events
+ /// The HxEvent to be handled
+ /// The script to be executed when the event occurs
+ /// A configured hx-on:: attribute
+ ///
+ /// Documentation
+ let _hxOnHxEvent (hxEvent: HxEvent) handler =
+ _hxOnEvent $":{hxEvent.ToHxOnString()}" handler
- /// Filters the parameters that will be submitted with a request
- let _hxParams = attr "hx-params"
+ /// Filters the parameters that will be submitted with a request
+ /// The fields to include (use HxParams to generate this value)
+ /// A configured hx-params attribute
+ ///
+ /// Documentation
+ let _hxParams toInclude =
+ attr "hx-params" toInclude
- /// Issues a PATCH to the specified URL
- let _hxPatch = attr "hx-patch"
+ /// Issues a PATCH to the specified URL
+ /// The URL to which the request should be directed
+ /// A configured hx-patch attribute
+ /// Documentation
+ let _hxPatch url =
+ attr "hx-patch" url
- /// Issues a POST to the specified URL
- let _hxPost = attr "hx-post"
+ /// Issues a POST to the specified URL
+ /// The URL to which the request should be directed
+ /// A configured hx-post attribute
+ /// Documentation
+ let _hxPost url =
+ attr "hx-post" url
- /// Preserves an element between requests
- let _hxPreserve = attr "hx-preserve" "true"
+ /// Preserves an element between requests
+ /// Documentation
+ let _hxPreserve =
+ flag "hx-preserve"
- /// Shows a prompt before submitting a request
- let _hxPrompt = attr "hx-prompt"
+ /// Shows a prompt() dialog before submitting a request
+ /// The text for the prompt
+ /// A configured hx-prompt attribute
+ /// The value provided will be in the HX-Prompt request header
+ /// Documentation
+ let _hxPrompt text =
+ attr "hx-prompt" text
- /// Pushes the URL into the location bar, creating a new history entry
- let _hxPushUrl = attr "hx-push-url"
+ /// Pushes the URL into the location bar, creating a new history entry
+ ///
+ ///
+ ///
"true" to push the fetched URL
+ ///
"false" to explicitly not push the fetched URL
+ ///
A specific URL to push
+ ///
+ ///
+ /// A configured hx-push-url attribute
+ /// Documentation
+ let _hxPushUrl spec =
+ attr "hx-push-url" spec
- /// Issues a PUT to the specified URL
- let _hxPut = attr "hx-put"
+ /// Issues a PUT to the specified URL
+ /// The URL to which the request should be directed
+ /// A configured hx-put attribute
+ /// Documentation
+ let _hxPut url =
+ attr "hx-put" url
- /// Replaces the current URL in the browser's history stack
- let _hxReplaceUrl = attr "hx-replace-url"
+ /// Replaces the current URL in the browser's history stack
+ ///
+ ///
+ ///
"true" to replace the current URL with the fetched one
+ ///
"false" to explicitly replace nothing
+ ///
A specific URL to replace in the browser's history
+ ///
+ ///
+ /// A configured hx-replace-url attribute
+ /// Documentation
+ let _hxReplaceUrl spec =
+ attr "hx-replace-url" spec
- /// Configures various aspects of the request
- let _hxRequest = attr "hx-request"
+ /// Configures various aspects of the request
+ /// The configuration spec (use HxRequest.Configure to create value)
+ /// A configured hx-request attribute
+ ///
+ /// Documentation
+ let _hxRequest spec =
+ attr "hx-request" spec
- /// Selects a subset of the server response to process
- let _hxSelect = attr "hx-select"
+ /// Selects a subset of the server response to process
+ /// A CSS selector for the content to be selected
+ /// A configured hx-select attribute
+ /// Documentation
+ let _hxSelect selector =
+ attr "hx-select" selector
- /// Selects a subset of an out-of-band server response
- let _hxSelectOob = attr "hx-select-oob"
+ /// Selects a subset of an out-of-band server response
+ /// One or more comma-delimited CSS selectors for the content to be selected
+ /// A configured hx-select-oob attribute
+ /// Documentation
+ let _hxSelectOob selectors =
+ attr "hx-select-oob" selectors
- /// Controls how the response content is swapped into the DOM (e.g. 'outerHTML' or 'beforeEnd')
- let _hxSwap = attr "hx-swap"
+ ///
+ /// Controls how the response content is swapped into the DOM (e.g. outerHTML or beforeEnd)
+ ///
+ /// The type of swap to perform (use HxSwap values)
+ /// A configured hx-swap attribute
+ /// Documentation
+ let _hxSwap swap =
+ attr "hx-swap" swap
- /// Controls how the response content is swapped into the DOM (e.g. 'outerHTML' or 'beforeEnd'), enabling CSS
- /// transitions
- let _hxSwapWithTransition = sprintf "%s transition:true" >> _hxSwap
+ ///
+ /// Controls how the response content is swapped into the DOM (e.g. outerHTML or beforeEnd), enabling
+ /// CSS transitions
+ ///
+ /// The type of swap to perform (use HxSwap values)
+ /// A configured hx-swap attribute
+ /// Documentation
+ let _hxSwapWithTransition swap =
+ _hxSwap $"%s{swap} transition:true"
+ ///
/// Marks content in a response as being "Out of Band", i.e. swapped somewhere other than the target
- let _hxSwapOob = attr "hx-swap-oob"
+ ///
+ ///
+ ///
+ ///
"true" to mark this as an OOB swap
+ ///
Any HxSwap value
+ ///
Any HxSwap value, followed by a colon (:) and a CSS selector
+ ///
+ ///
+ /// A configured hx-swap-oob attribute
+ /// Documentation
+ let _hxSwapOob swap =
+ attr "hx-swap-oob" swap
- /// Synchronize events based on another element
- let _hxSync = attr "hx-sync"
+ /// Synchronize events based on another element
+ /// A CSS selector for the element with which this one should sync
+ /// The request synchronization action to perform (use HxSync values)
+ /// A configured hx-sync attribute
+ ///
+ /// Documentation
+ let _hxSync selector action =
+ attr "hx-sync" $"%s{selector}:%s{action}"
- /// Specifies the target element to be swapped
- let _hxTarget = attr "hx-target"
+ /// Specifies the target element to be swapped
+ /// A CSS selector or relative reference (or both) to identify the target
+ /// A configured hx-target attribute
+ /// Documentation
+ let _hxTarget selector =
+ attr "hx-target" selector
- /// Specifies the event that triggers the request
- let _hxTrigger = attr "hx-trigger"
+ /// Specifies the event that triggers the request
+ /// The trigger specification (use HxTrigger to create)
+ /// A configured hx-trigger attribute
+ ///
+ /// Documentation
+ let _hxTrigger spec =
+ attr "hx-trigger" spec
- /// Validate an input element (uses HTML5 validation API)
- let _hxValidate = flag "hx-validate"
+ /// Validate an input element (uses HTML5 validation API)
+ /// Documentation
+ let _hxValidate =
+ flag "hx-validate"
- /// Adds to the parameters that will be submitted with the request
- let _hxVals = attr "hx-vals"
+ /// Adds to the parameters that will be submitted with the request
+ /// The values for the parameters (use HxVals.From to create)
+ /// A configured hx-vals attribute
+ ///
+ /// Documentation
+ let _hxVals values =
+ attr "hx-vals" values
- /// The name of the message to swap into the DOM.
- let _sseSwap = attr "sse-swap"
+ /// The URL of the SSE server
+ /// The URL from which events will be received
+ /// A configured sse-connect attribute
+ /// Extension Docs
+ let _sseConnect url =
+ attr "sse-connect" url
- /// The URL of the SSE server.
- let _sseConnect = attr "sse-connect"
+ /// The name(s) of the message(s) to swap into the DOM
+ /// The message names (comma-delimited) to swap (use "message" for unnamed events)
+ /// A configured sse-swap attribute
+ /// Extension Docs
+ let _sseSwap messages =
+ attr "sse-swap" messages
-/// Script tags to pull htmx into a web page
+/// Script tags to pull htmx into a web page
module Script =
- /// Script tag to load the minified version from unpkg.com
+ /// Script tag to load the minified version from unpkg.com
let minified =
- script [ _src "https://unpkg.com/htmx.org@2.0.4"
- _integrity "sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+"
+ script [ _src "https://cdn.jsdelivr.net/npm/htmx.org@2.0.6/dist/htmx.min.js"
+ _integrity "sha384-Akqfrbj/HpNVo8k11SXBb6TlBWmXXlYQrCSqEWmyKJe+hDm3Z/B2WVG4smwBkRVm"
_crossorigin "anonymous" ] []
- /// Script tag to load the unminified version from unpkg.com
+ /// Script tag to load the unminified version from unpkg.com
let unminified =
- script [ _src "https://unpkg.com/htmx.org@2.0.4/dist/htmx.js"
- _integrity "sha384-oeUn82QNXPuVkGCkcrInrS1twIxKhkZiFfr2TdiuObZ3n3yIeMiqcRzkIcguaof1"
+ script [ _src "https://cdn.jsdelivr.net/npm/htmx.org@2.0.6/dist/htmx.js"
+ _integrity "sha384-ksKjJrwjL5VxqAkAZAVOPXvMkwAykMaNYegdixAESVr+KqLkKE8XBDoZuwyWVUDv"
_crossorigin "anonymous" ] []
-/// Functions to extract and render an HTML fragment from a document
+/// Functions to extract and render an HTML fragment from a document
[]
module RenderFragment =
/// Does this element have an ID matching the requested ID name?
- let private isIdElement nodeId (elt : XmlElement) =
+ let private isIdElement nodeId (elt: XmlElement) =
snd elt
|> Array.exists (fun attr ->
match attr with
@@ -469,62 +814,83 @@ module RenderFragment =
| Boolean _ -> false)
/// Generate a message if the requested ID node is not found
- let private nodeNotFound (nodeId : string) =
+ let private nodeNotFound (nodeId: string) =
$"– ID {nodeId} not found –"
- /// Find the node with the named ID
- let rec findIdNode nodeId (node : XmlNode) : XmlNode option =
+ /// Find the node with the named ID
+ /// The id attribute to find
+ /// The node tree to search
+ /// The node with the requested id attribute, or None if it was not found
+ let rec findIdNode nodeId (node: XmlNode) : XmlNode option =
match node with
| Text _ -> None
| VoidElement elt -> if isIdElement nodeId elt then Some node else None
| ParentNode (elt, children) ->
- if isIdElement nodeId elt then Some node else children |> List.tryPick (fun c -> findIdNode nodeId c)
+ if isIdElement nodeId elt then Some node else children |> List.tryPick (findIdNode nodeId)
- /// Functions to render a fragment as a string
+ /// Functions to render a fragment as a string
[]
module AsString =
- /// Render to HTML for the given ID
- let htmlFromNodes nodeId (nodes : XmlNode list) =
- match nodes |> List.tryPick(fun node -> findIdNode nodeId node) with
+ /// Render to HTML for the given ID
+ /// The id attribute for the node to be rendered
+ /// The node trees to search
+ /// The HTML for the given id node, or an error message if it was not found
+ let htmlFromNodes nodeId (nodes: XmlNode list) =
+ match nodes |> List.tryPick (findIdNode nodeId) with
| Some idNode -> RenderView.AsString.htmlNode idNode
| None -> nodeNotFound nodeId
- /// Render to HTML for the given ID
+ /// Render to HTML for the given ID
+ /// The id attribute for the node to be rendered
+ /// The node tree to search
+ /// The HTML for the given id node, or an error message if it was not found
let htmlFromNode nodeId node =
match findIdNode nodeId node with
| Some idNode -> RenderView.AsString.htmlNode idNode
| None -> nodeNotFound nodeId
- /// Functions to render a fragment as bytes
+ /// Functions to render a fragment as bytes
[]
module AsBytes =
let private utf8 = System.Text.Encoding.UTF8
- /// Render to HTML for the given ID
- let htmlFromNodes nodeId (nodes : XmlNode list) =
- match nodes |> List.tryPick(fun node -> findIdNode nodeId node) with
+ /// Render to bytes for the given ID
+ /// The id attribute for the node to be rendered
+ /// The node trees to search
+ /// The bytes for the given id node, or an error message if it was not found
+ let htmlFromNodes nodeId (nodes: XmlNode list) =
+ match nodes |> List.tryPick (findIdNode nodeId) with
| Some idNode -> RenderView.AsBytes.htmlNode idNode
| None -> nodeNotFound nodeId |> utf8.GetBytes
- /// Render to HTML for the given ID
+ /// Render to bytes for the given ID
+ /// The id attribute for the node to be rendered
+ /// The node tree to search
+ /// The bytes for the given id node, or an error message if it was not found
let htmlFromNode nodeId node =
match findIdNode nodeId node with
| Some idNode -> RenderView.AsBytes.htmlNode idNode
| None -> nodeNotFound nodeId |> utf8.GetBytes
- /// Functions to render a fragment into a StringBuilder
+ /// Functions to render a fragment into a StringBuilder
[]
module IntoStringBuilder =
- /// Render to HTML for the given ID
- let htmlFromNodes sb nodeId (nodes : XmlNode list) =
- match nodes |> List.tryPick(fun node -> findIdNode nodeId node) with
+ /// Render HTML into a StringBuilder for the given ID
+ /// The StringBuilder into which the bytes will be rendered
+ /// The id attribute for the node to be rendered
+ /// The node trees to search
+ let htmlFromNodes sb nodeId (nodes: XmlNode list) =
+ match nodes |> List.tryPick (findIdNode nodeId) with
| Some idNode -> RenderView.IntoStringBuilder.htmlNode sb idNode
| None -> nodeNotFound nodeId |> sb.Append |> ignore
- /// Render to HTML for the given ID
+ /// Render HTML into a StringBuilder for the given ID
+ /// The StringBuilder into which the bytes will be rendered
+ /// The id attribute for the node to be rendered
+ /// The node tree to search
let htmlFromNode sb nodeId node =
match findIdNode nodeId node with
| Some idNode -> RenderView.IntoStringBuilder.htmlNode sb idNode
diff --git a/src/ViewEngine.Htmx/README.md b/src/ViewEngine.Htmx/README.md
index 7f17761..10b2c77 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: 2.0.4**
+**htmx version: 2.0.6**
_Upgrading from v1.x: see [the migration guide](https://htmx.org/migration-guide-htmx-1/) for changes_
@@ -29,7 +29,7 @@ Support modules include:
- `HxTrigger`
- `HxVals`
-There are two `XmlNode`s that will load the htmx script from unpkg; `Htmx.Script.minified` loads the minified version, and `Htmx.Script.unminified` loads the unminified version (useful for debugging).
+There are two `XmlNode`s that will load the htmx script from jsdelivr; `Htmx.Script.minified` loads the minified version, and `Htmx.Script.unminified` loads the unminified version (useful for debugging).
This also supports [fragment rendering](https://bitbadger.solutions/blog/2022/fragment-rendering-in-giraffe-view-engine.html), providing the flexibility to render an entire template, or only a portion of it (based on the element's `id` attribute).