module Giraffe.Htmx
open Microsoft.AspNetCore.Http
open Microsoft.Extensions.Primitives
open System
/// Determine if the given header is present
let private hdr (headers : IHeaderDictionary) hdr =
match headers[hdr] with it when it = StringValues.Empty -> None | it -> Some it[0]
/// 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
/// 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
/// 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
/// The tag name (fst) and id attribute (snd) of the element triggering this request
member this.HxSource
with get () =
match hdr this "HX-Source" with
| Some src ->
let parts = src.Split "#"
if parts.Length = 1 then
Some (parts[0], None)
else
Some (parts[0], if parts[1] <> "" then Some parts[1] else None)
| None -> None
/// The id attribute of the target element if it exists
member this.HxTarget
with get () = hdr this "HX-Target"
/// The id attribute of the triggered element if it exists
[]
member this.HxTrigger
with get () = hdr this "HX-Trigger"
/// 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 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
[]
module Handlers =
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
/// 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
/// 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 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
/// 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
/// 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
/// 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
/// 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
/// 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
/// 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
/// 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
/// 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
/// 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
/// 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
/// 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"
/// Load the package-provided version of the htmx script
[]
module HtmxScript =
open Giraffe.Htmx.Common
open Microsoft.AspNetCore.Html
/// script tag to load the package-provided version of the htmx script
let local = HtmlString $""""""