199 lines
10 KiB
Forth
199 lines
10 KiB
Forth
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
|
|
|
|
/// <summary>Indicates that the request is via an element using <c>hx-boost</c></summary>
|
|
member this.HxBoosted
|
|
with get () = hdr this "HX-Boosted" |> Option.map bool.Parse
|
|
|
|
/// <summary>The current URL of the browser <em>(note that this does not update until after settle)</em></summary>
|
|
member this.HxCurrentUrl
|
|
with get () = hdr this "HX-Current-URL" |> Option.map Uri
|
|
|
|
/// <summary><c>true</c> if the request is for history restoration after a miss in the local history cache</summary>
|
|
member this.HxHistoryRestoreRequest
|
|
with get () = hdr this "HX-History-Restore-Request" |> Option.map bool.Parse
|
|
|
|
/// <summary>The user response to an <c>hx-prompt</c></summary>
|
|
[<Obsolete "hx-prompt is removed in v4">]
|
|
member this.HxPrompt
|
|
with get () = hdr this "HX-Prompt"
|
|
|
|
/// <summary><c>true</c> if the request came from htmx</summary>
|
|
member this.HxRequest
|
|
with get () = hdr this "HX-Request" |> Option.map bool.Parse
|
|
|
|
/// <summary>The tag name (fst) and <c>id</c> attribute (snd) of the element triggering this request</summary>
|
|
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
|
|
|
|
/// <summary>The <c>id</c> attribute of the target element if it exists</summary>
|
|
member this.HxTarget
|
|
with get () = hdr this "HX-Target"
|
|
|
|
/// <summary>The <c>id</c> attribute of the triggered element if it exists</summary>
|
|
[<Obsolete "HX-Trigger is removed in v4; use the second item of HX-Source">]
|
|
member this.HxTrigger
|
|
with get () = hdr this "HX-Trigger"
|
|
|
|
/// <summary>The <c>name</c> attribute of the triggered element if it exists</summary>
|
|
[<Obsolete "HX-Trigger-Name is removed in v4; may be available via extension, but will be removed from this library">]
|
|
member this.HxTriggerName
|
|
with get () = hdr this "HX-Trigger-Name"
|
|
|
|
|
|
/// Extensions for the request object
|
|
type HttpRequest with
|
|
|
|
/// <summary>Whether this request was initiated from htmx</summary>
|
|
member this.IsHtmx
|
|
with get () = this.Headers.HxRequest |> Option.defaultValue false
|
|
|
|
/// <summary>Whether this request is an htmx history-miss refresh request</summary>
|
|
member this.IsHtmxRefresh
|
|
with get () = this.IsHtmx && (this.Headers.HxHistoryRestoreRequest |> Option.defaultValue false)
|
|
|
|
|
|
/// <summary>HTTP handlers for setting output headers</summary>
|
|
[<AutoOpen>]
|
|
module Handlers =
|
|
|
|
open Giraffe.Htmx.Common
|
|
|
|
/// <summary>Instruct htmx to perform a client-side redirect for content</summary>
|
|
/// <param name="path">The path where the content should be found</param>
|
|
/// <returns>An HTTP handler with the <c>HX-Location</c> header set</returns>
|
|
/// <seealso href="https://htmx.org/headers/hx-location/">Documentation</seealso>
|
|
let withHxLocation (path: string) : HttpHandler =
|
|
setHttpHeader "HX-Location" path
|
|
|
|
/// <summary>Pushes a new url into the history stack</summary>
|
|
/// <param name="url">The URL to be pushed</param>
|
|
/// <returns>An HTTP handler with the <c>HX-Push-Url</c> header set</returns>
|
|
/// <remarks>Use <see cref="withHxNoPushUrl" /> to explicitly not push a new URL</remarks>
|
|
/// <seealso href="https://htmx.org/headers/hx-push-url/">Documentation</seealso>
|
|
let withHxPushUrl (url: string) : HttpHandler =
|
|
setHttpHeader "HX-Push-Url" url
|
|
|
|
/// <summary>Explicitly do not push a new URL into the history stack</summary>
|
|
/// <returns>An HTTP handler with the <c>HX-Push-Url</c> header set to <c>false</c></returns>
|
|
/// <seealso href="https://htmx.org/headers/hx-push-url/">Documentation</seealso>
|
|
let withHxNoPushUrl : HttpHandler =
|
|
toLowerBool false |> withHxPushUrl
|
|
|
|
/// <summary>Can be used to do a client-side redirect to a new location</summary>
|
|
/// <param name="url">The URL to which the client should be redirected</param>
|
|
/// <returns>An HTTP handler with the <c>HX-Redirect</c> header set</returns>
|
|
/// <seealso href="https://htmx.org/headers/hx-redirect/">Documentation</seealso>
|
|
let withHxRedirect (url: string) : HttpHandler =
|
|
setHttpHeader "HX-Redirect" url
|
|
|
|
/// <summary>If set to <c>true</c> the client side will do a full refresh of the page</summary>
|
|
/// <param name="shouldRefresh">Whether the client should refresh their page</param>
|
|
/// <returns>An HTTP handler with the <c>HX-Refresh</c> header set</returns>
|
|
let withHxRefresh shouldRefresh : HttpHandler =
|
|
(toLowerBool >> setHttpHeader "HX-Refresh") shouldRefresh
|
|
|
|
/// <summary>Replaces the current URL in the history stack</summary>
|
|
/// <param name="url">The URL to place in the history stack in place of the current one</param>
|
|
/// <returns>An HTTP handler with the <c>HX-Replace-URL</c> header set</returns>
|
|
/// <remarks>Use <see cref="withHxNoRelaceUrl" /> to explicitly not replace the current URL</remarks>
|
|
/// <seealso href="https://htmx.org/headers/hx-replace-url/">Documentation</seealso>
|
|
let withHxReplaceUrl url : HttpHandler =
|
|
setHttpHeader "HX-Replace-Url" url
|
|
|
|
/// <summary>Explicitly do not replace the current URL in the history stack</summary>
|
|
/// <returns>An HTTP handler with the <c>HX-Replace-URL</c> header set to <c>false</c></returns>
|
|
/// <seealso href="https://htmx.org/headers/hx-replace-url/">Documentation</seealso>
|
|
let withHxNoReplaceUrl : HttpHandler =
|
|
toLowerBool false |> withHxReplaceUrl
|
|
|
|
/// <summary>Override which portion of the response will be swapped into the target document</summary>
|
|
/// <param name="target">The selector for the new response target</param>
|
|
/// <returns>An HTTP handler with the <c>HX-Reselect</c> header set</returns>
|
|
let withHxReselect (target: string) : HttpHandler =
|
|
setHttpHeader "HX-Reselect" target
|
|
|
|
/// <summary>Override the <c>hx-swap</c> attribute from the initiating element</summary>
|
|
/// <param name="swap">The swap value to override</param>
|
|
/// <returns>An HTTP handler with the <c>HX-Reswap</c> header set</returns>
|
|
/// <remarks>Use <see cref="T:Giraffe.Htmx.Common.HxSwap">HxSwap</see> constants for best results</remarks>
|
|
let withHxReswap (swap: string) : HttpHandler =
|
|
setHttpHeader "HX-Reswap" swap
|
|
|
|
/// <summary>Allows you to override the <c>hx-target</c> attribute</summary>
|
|
/// <param name="target">The new target for the response</param>
|
|
/// <returns>An HTTP handler with the <c>HX-Retarget</c> header set</returns>
|
|
let withHxRetarget (target: string) : HttpHandler =
|
|
setHttpHeader "HX-Retarget" target
|
|
|
|
/// <summary>Allows you to trigger a single client side event</summary>
|
|
/// <param name="evt">The call to the event that should be triggered</param>
|
|
/// <returns>An HTTP handler with the <c>HX-Trigger</c> header set</returns>
|
|
/// <seealso href="https://htmx.org/headers/hx-trigger/">Documentation</seealso>
|
|
let withHxTrigger (evt: string) : HttpHandler =
|
|
setHttpHeader "HX-Trigger" evt
|
|
|
|
/// <summary>Allows you to trigger multiple client side events</summary>
|
|
/// <param name="evts">The calls to events that should be triggered</param>
|
|
/// <returns>An HTTP handler with the <c>HX-Trigger</c> header set for all given events</returns>
|
|
/// <seealso href="https://htmx.org/headers/hx-trigger/">Documentation</seealso>
|
|
let withHxTriggerMany evts : HttpHandler =
|
|
toJson evts |> setHttpHeader "HX-Trigger"
|
|
|
|
/// <summary>Allows you to trigger a single client side event after changes have settled</summary>
|
|
/// <param name="evt">The call to the event that should be triggered</param>
|
|
/// <returns>An HTTP handler with the <c>HX-Trigger-After-Settle</c> header set</returns>
|
|
/// <seealso href="https://htmx.org/headers/hx-trigger/">Documentation</seealso>
|
|
let withHxTriggerAfterSettle (evt: string) : HttpHandler =
|
|
setHttpHeader "HX-Trigger-After-Settle" evt
|
|
|
|
/// <summary>Allows you to trigger multiple client side events after changes have settled</summary>
|
|
/// <param name="evts">The calls to events that should be triggered</param>
|
|
/// <returns>An HTTP handler with the <c>HX-Trigger-After-Settle</c> header set for all given events</returns>
|
|
/// <seealso href="https://htmx.org/headers/hx-trigger/">Documentation</seealso>
|
|
let withHxTriggerManyAfterSettle evts : HttpHandler =
|
|
toJson evts |> setHttpHeader "HX-Trigger-After-Settle"
|
|
|
|
/// <summary>Allows you to trigger a single client side event after DOM swapping occurs</summary>
|
|
/// <param name="evt">The call to the event that should be triggered</param>
|
|
/// <returns>An HTTP handler with the <c>HX-Trigger-After-Swap</c> header set</returns>
|
|
/// <seealso href="https://htmx.org/headers/hx-trigger/">Documentation</seealso>
|
|
let withHxTriggerAfterSwap (evt: string) : HttpHandler =
|
|
setHttpHeader "HX-Trigger-After-Swap" evt
|
|
|
|
/// <summary>Allows you to trigger multiple client side events after DOM swapping occurs</summary>
|
|
/// <param name="evts">The calls to events that should be triggered</param>
|
|
/// <returns>An HTTP handler with the <c>HX-Trigger-After-Swap</c> header set for all given events</returns>
|
|
/// <seealso href="https://htmx.org/headers/hx-trigger/">Documentation</seealso>
|
|
let withHxTriggerManyAfterSwap evts : HttpHandler =
|
|
toJson evts |> setHttpHeader "HX-Trigger-After-Swap"
|
|
|
|
|
|
/// <summary>Load the package-provided version of the htmx script</summary>
|
|
[<RequireQualifiedAccess>]
|
|
module HtmxScript =
|
|
|
|
open Giraffe.Htmx.Common
|
|
open Microsoft.AspNetCore.Html
|
|
|
|
/// <summary><c>script</c> tag to load the package-provided version of the htmx script</summary>
|
|
let local = HtmlString $"""<script src="{htmxLocalScript}"></script>"""
|
|
|