Compare commits
21 Commits
v2.0.0-bet
...
htmx-v4
| Author | SHA1 | Date | |
|---|---|---|---|
| de75672bb3 | |||
| 121eb95d87 | |||
| 6b7458070b | |||
| 10c31d77b5 | |||
| f4cd184a15 | |||
| 5626031593 | |||
| f0de18845f | |||
| 961307fd99 | |||
| a2960a79c6 | |||
| 541384a92f | |||
| 8cb5d6bfa7 | |||
| 1a11e3511a | |||
| 32e962416d | |||
| 29839fa795 | |||
| 7f9b3a6234 | |||
| a8d2b819dc | |||
| 1ea05b79ed | |||
| b3665a4b72 | |||
| 94b68f76c9 | |||
| 90de16529c | |||
| 59246ae7f5 |
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@@ -17,9 +17,9 @@ jobs:
|
||||
dotnet-version: [ "6.0", "7.0", "8.0" ]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup .NET ${{ matrix.dotnet-version }}.x
|
||||
uses: actions/setup-dotnet@v3
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: ${{ matrix.dotnet-version }}.x
|
||||
- name: Restore dependencies
|
||||
@@ -32,9 +32,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-and-test
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v3
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: "8.0"
|
||||
- name: Package Common library
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
- name: Move View Engine package
|
||||
run: cp src/ViewEngine.Htmx/bin/Release/Giraffe.ViewEngine.Htmx.*.nupkg .
|
||||
- name: Save Packages
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: packages
|
||||
path: |
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -6,3 +6,6 @@
|
||||
.idea
|
||||
*.user
|
||||
.vscode
|
||||
src/*.nupkg
|
||||
src/tests*.txt
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ htmx uses attributes and HTTP headers to attain its interactivity; the libraries
|
||||
|---|---|
|
||||
|[](https://www.nuget.org/packages/Giraffe.Htmx/)|[](https://www.nuget.org/packages/Giraffe.ViewEngine.Htmx/)|
|
||||
|
||||
Both of these packages will also install `Giraffe.Htmx.Common`, which has some common definitions and provides a local-to-your-project version of the htmx JavaScript _(as of v2.0.8)_.
|
||||
|
||||
## Server Side (`Giraffe.Htmx`)
|
||||
|
||||
In addition to the regular HTTP request payloads, htmx sets [one or more headers](https://htmx.org/docs/#request_headers) along with the request. Once `Giraffe.Htmx` is opened, these are available as properties on `HttpContext.Request.Headers`. These consist of the header name, translated to a .NET name (ex. `HX-Current-URL` becomes `HxCurrentUrl`), and a strongly-typed property based on the expected value of that header. Additionally, they are all exposed as `Option`s, as they may or may not be present for any given request.
|
||||
@@ -42,6 +44,8 @@ let theHandler : HttpHandler =
|
||||
|
||||
Of note is that the `HX-Trigger` headers can take either one or more events. For a single event with no parameters, use `withHxTrigger`; for a single event with parameters, or multiple events, use `withHxTriggerMany`. Both these have `AfterSettle` and `AfterSwap` versions as well.
|
||||
|
||||
`HtmxScript.local` provides an `HtmlString` with a script tag to load the package-provided htmx library. This can be used in code, Razor templates, etc. (If you're using Giraffe.ViewEngine, see below.)
|
||||
|
||||
## View Engine (`Giraffe.ViewEngine.Htmx`)
|
||||
|
||||
As htmx uses [attributes](https://htmx.org/docs/#attributes) to extend HTML, the primary part of this library defines attributes that can be used within Giraffe views. Simply open `Giraffe.ViewEngine.Htmx`, and these attributes, along with support modules, will be visible.
|
||||
@@ -66,7 +70,7 @@ let shiftClick =
|
||||
]
|
||||
```
|
||||
|
||||
If you want to load htmx from unpkg, `Htmx.Script.minified` or `Htmx.Script.unminified` can be used to load the script in your HTML trees.
|
||||
If you want to use the package-provided htmx library, `Htmx.Script.local` will create the `script` tag for you. To load htmx from jsDelivr, `Htmx.Script.cdnMinified` or `Htmx.Script.cdnUnminified` can be used to load the script in your HTML trees. In this case, if you are using a Content Security Policy (CSP) header, `cdn.jsdelivr.net` will need to be added to the `script-src` list.
|
||||
|
||||
## Feedback / Help
|
||||
|
||||
|
||||
@@ -1,28 +1,97 @@
|
||||
/// Common definitions shared between attribute values and response headers
|
||||
/// <summary>Common definitions shared between attribute values and response headers</summary>
|
||||
[<AutoOpen>]
|
||||
module Giraffe.Htmx.Common
|
||||
|
||||
/// Valid values for the `hx-swap` attribute / `HX-Reswap` header (may be combined with swap/settle/scroll/show config)
|
||||
/// <summary>The version of htmx embedded in the package</summary>
|
||||
let HtmxVersion = "4.0.0-alpha6"
|
||||
|
||||
/// <summary>The path for the provided htmx script</summary>
|
||||
let internal htmxLocalScript = $"/_content/Giraffe.Htmx.Common/htmx.min.js?ver={HtmxVersion}"
|
||||
|
||||
/// <summary>Serialize a list of key/value pairs to JSON (very rudimentary)</summary>
|
||||
/// <param name="pairs">The key/value pairs to be serialized to JSON</param>
|
||||
/// <returns>A string with the key/value pairs serialized to JSON</returns>
|
||||
let internal toJson (pairs: (string * string) list) =
|
||||
pairs
|
||||
|> List.map (fun pair -> sprintf "\"%s\": \"%s\"" (fst pair) ((snd pair).Replace ("\"", "\\\"")))
|
||||
|> String.concat ", "
|
||||
|> sprintf "{ %s }"
|
||||
|
||||
/// <summary>Convert a boolean to lowercase "true" or "false"</summary>
|
||||
/// <param name="boolValue">The boolean value to convert</param>
|
||||
/// <returns>"true" for <c>true</c>, "false" for <c>false</c></returns>
|
||||
let internal toLowerBool (boolValue: bool) =
|
||||
(string boolValue).ToLowerInvariant()
|
||||
|
||||
|
||||
/// <summary>Valid values for the <c>hx-swap</c> attribute / <c>HX-Reswap</c> header</summary>
|
||||
/// <remarks>May be combined with <c>swap</c> / <c>scroll</c> / <c>show</c> config)</remarks>
|
||||
/// <seealso href="https://four.htmx.org/attributes/hx-swap/">Documentation</seealso>
|
||||
[<RequireQualifiedAccess>]
|
||||
module HxSwap =
|
||||
|
||||
/// The default, replace the inner html of the target element
|
||||
/// <summary>The default, replace the inner HTML of the target element</summary>
|
||||
[<Literal>]
|
||||
let InnerHtml = "innerHTML"
|
||||
|
||||
/// Replace the entire target element with the response
|
||||
/// <summary>Replace the entire target element with the response</summary>
|
||||
[<Literal>]
|
||||
let OuterHtml = "outerHTML"
|
||||
|
||||
/// Insert the response before the target element
|
||||
let BeforeBegin = "beforebegin"
|
||||
/// <summary>Morph the inner HTML of the target to the new content</summary>
|
||||
[<Literal>]
|
||||
let InnerMorph = "innerMorph"
|
||||
|
||||
/// Insert the response before the first child of the target element
|
||||
let AfterBegin = "afterbegin"
|
||||
/// <summary>Morph the outer HTML of the target to the new content</summary>
|
||||
[<Literal>]
|
||||
let OuterMorph = "innerMorph"
|
||||
|
||||
/// Insert the response after the last child of the target element
|
||||
let BeforeEnd = "beforeend"
|
||||
/// <summary>Replace the text content of the target without parsing the response as HTML</summary>
|
||||
[<Literal>]
|
||||
let TextContent = "textContent"
|
||||
|
||||
/// Insert the response after the target element
|
||||
let AfterEnd = "afterend"
|
||||
/// <summary>Insert the response before the target element</summary>
|
||||
[<Literal>]
|
||||
let Before = "before"
|
||||
|
||||
/// Does not append content from response (out of band items will still be processed).
|
||||
/// <summary>Insert the response before the target element (pre-v4 name)</summary>
|
||||
[<Literal>]
|
||||
let BeforeBegin = Before
|
||||
|
||||
/// <summary>Insert the response before the first child of the target element</summary>
|
||||
[<Literal>]
|
||||
let Prepend = "prepend"
|
||||
|
||||
/// <summary>Insert the response before the first child of the target element (pre-v4 name)</summary>
|
||||
[<Literal>]
|
||||
let AfterBegin = Prepend
|
||||
|
||||
/// <summary>Insert the response after the last child of the target element</summary>
|
||||
[<Literal>]
|
||||
let Append = "append"
|
||||
|
||||
/// <summary>Insert the response after the last child of the target element (pre-v4 name)</summary>
|
||||
[<Literal>]
|
||||
let BeforeEnd = Append
|
||||
|
||||
/// <summary>Insert the response after the target element</summary>
|
||||
[<Literal>]
|
||||
let After = "after"
|
||||
|
||||
/// <summary>Insert the response after the target element (pre-v4 name)</summary>
|
||||
[<Literal>]
|
||||
let AfterEnd = After
|
||||
|
||||
/// <summary>Delete the target element regardless of response</summary>
|
||||
[<Literal>]
|
||||
let Delete = "delete"
|
||||
|
||||
/// <summary>Does not append content from response (out of band items will still be processed)</summary>
|
||||
[<Literal>]
|
||||
let None = "none"
|
||||
|
||||
/// <summary>Update existing elements by <c>id</c> and add new ones</summary>
|
||||
/// <remarks>This requires the <c>upsert</c> extension</remarks>
|
||||
/// <seealso href="https://four.htmx.org/extensions/upsert">Extension</seealso>
|
||||
[<Literal>]
|
||||
let Upsert = "upsert"
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
|
||||
<PropertyGroup>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<Description>Common definitions for Giraffe.Htmx</Description>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -15,5 +16,9 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Update="FSharp.Core" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="Giraffe.Htmx" />
|
||||
<InternalsVisibleTo Include="Giraffe.ViewEngine.Htmx" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
## Giraffe.Htmx.Common
|
||||
|
||||
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.
|
||||
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. It also contains htmx as a static web asset, allowing it to be loaded from your local (or published) project.
|
||||
|
||||
**htmx version: 2.0.0-beta1**
|
||||
**htmx version: 4.0.0-alpha6**
|
||||
|
||||
_**NOTE:** Pay special attention to breaking changes highlighted in the packages listed above._
|
||||
1
src/Common/wwwroot/htmx.min.js
vendored
Normal file
1
src/Common/wwwroot/htmx.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -1,18 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
<VersionPrefix>2.0.0</VersionPrefix>
|
||||
<VersionSuffix>beta1</VersionSuffix>
|
||||
<PackageReleaseNotes>Update script tags to pull htmx 2.0.0-beta1</PackageReleaseNotes>
|
||||
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
|
||||
<VersionPrefix>4.0.0</VersionPrefix>
|
||||
<VersionSuffix>alpha6a</VersionSuffix>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PackageReleaseNotes>First htmx 4 alpha release of these libraries
|
||||
- [Server] Marked removed headers as obsolete; added new HX-Source header
|
||||
- [View Engine] Marked removed attributes as obsolete
|
||||
- [View Engine] Added new attributes, modifiers, and support for new hx-partial tag
|
||||
- Updated script tags to pull htmx 4.0.0-alpha6
|
||||
|
||||
See package READMEs; this is not an update-and-forget-it release
|
||||
</PackageReleaseNotes>
|
||||
<Authors>danieljsummers</Authors>
|
||||
<Company>Bit Badger Solutions</Company>
|
||||
<PackageProjectUrl>https://github.com/bit-badger/Giraffe.Htmx</PackageProjectUrl>
|
||||
<PackageProjectUrl>https://git.bitbadger.solutions/bit-badger/Giraffe.Htmx</PackageProjectUrl>
|
||||
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
||||
<RepositoryUrl>https://github.com/bit-badger/Giraffe.Htmx</RepositoryUrl>
|
||||
<RepositoryUrl>https://git.bitbadger.solutions/bit-badger/Giraffe.Htmx</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<Copyright>MIT License</Copyright>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageTags>Giraffe htmx beta</PackageTags>
|
||||
<PackageTags>Giraffe htmx</PackageTags>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<Description>htmx header extensions and helpers for Giraffe</Description>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -13,7 +14,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Giraffe" Version="6.2.0" />
|
||||
<PackageReference Include="Giraffe" Version="6.4.0" />
|
||||
<PackageReference Update="FSharp.Core" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
220
src/Htmx/Htmx.fs
220
src/Htmx/Htmx.fs
@@ -11,121 +11,189 @@ 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
|
||||
/// <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
|
||||
|
||||
/// 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
|
||||
/// <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
|
||||
|
||||
/// `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
|
||||
/// <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
|
||||
|
||||
/// The user response to an `hx-prompt`
|
||||
member this.HxPrompt with get () = hdr this "HX-Prompt"
|
||||
/// <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"
|
||||
|
||||
/// `true` if the request came from HTMX
|
||||
member this.HxRequest with get () = hdr this "HX-Request" |> Option.map bool.Parse
|
||||
/// <summary><c>true</c> if the request came from htmx</summary>
|
||||
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"
|
||||
/// <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
|
||||
|
||||
/// The `id` of the triggered element if it exists
|
||||
member this.HxTrigger with get () = hdr this "HX-Trigger"
|
||||
/// <summary>The <c>id</c> attribute of the target element if it exists</summary>
|
||||
member this.HxTarget
|
||||
with get () = hdr this "HX-Target"
|
||||
|
||||
/// The `name` of the triggered element if it exists
|
||||
member this.HxTriggerName with get () = hdr this "HX-Trigger-Name"
|
||||
/// <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
|
||||
|
||||
/// Whether this request was initiated from htmx
|
||||
member this.IsHtmx with get () = this.Headers.HxRequest |> Option.defaultValue false
|
||||
/// <summary>Whether this request was initiated from htmx</summary>
|
||||
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)
|
||||
/// <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)
|
||||
|
||||
|
||||
/// HTTP handlers for setting output headers
|
||||
/// <summary>HTTP handlers for setting output headers</summary>
|
||||
[<AutoOpen>]
|
||||
module Handlers =
|
||||
|
||||
/// Convert a boolean to lowercase `true` or `false`
|
||||
let private toLowerBool (trueOrFalse : bool) =
|
||||
(string trueOrFalse).ToLowerInvariant ()
|
||||
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
|
||||
|
||||
/// 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
|
||||
/// <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
|
||||
|
||||
/// Pushes a new url into the history stack
|
||||
[<Obsolete "Use withHxPushUrl; HX-Push was replaced by HX-Push-Url in v1.8.0">]
|
||||
let withHxPush = withHxPushUrl
|
||||
|
||||
/// Explicitly do not push a new URL into the history stack
|
||||
[<Obsolete "Use withHxNoPushUrl; HX-Push was replaced by HX-Push-Url in v1.8.0">]
|
||||
let withHxNoPush = withHxNoPushUrl
|
||||
|
||||
/// Can be used to do a client-side redirect to a new location
|
||||
let withHxRedirect : string -> HttpHandler =
|
||||
setHttpHeader "HX-Redirect"
|
||||
/// <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
|
||||
|
||||
/// If set to `true` the client side will do a a full refresh of the page
|
||||
let withHxRefresh : bool -> HttpHandler =
|
||||
toLowerBool >> setHttpHeader "HX-Refresh"
|
||||
/// <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
|
||||
|
||||
/// Replaces the current URL in the history stack
|
||||
let withHxReplaceUrl : string -> HttpHandler =
|
||||
setHttpHeader "HX-Replace-Url"
|
||||
/// <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
|
||||
|
||||
/// Explicitly do not replace the current URL in the history stack
|
||||
/// <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
|
||||
|
||||
/// Override which portion of the response will be swapped into the target document
|
||||
let withHxReselect : string -> HttpHandler =
|
||||
setHttpHeader "HX-Reselect"
|
||||
/// <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
|
||||
|
||||
/// Override the `hx-swap` attribute from the initiating element
|
||||
let withHxReswap : string -> HttpHandler =
|
||||
setHttpHeader "HX-Reswap"
|
||||
/// <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
|
||||
|
||||
/// Allows you to override the `hx-target` attribute
|
||||
let withHxRetarget : string -> HttpHandler =
|
||||
setHttpHeader "HX-Retarget"
|
||||
/// <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
|
||||
|
||||
/// Allows you to trigger a single client side event
|
||||
let withHxTrigger : string -> HttpHandler =
|
||||
setHttpHeader "HX-Trigger"
|
||||
/// <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
|
||||
|
||||
/// Allows you to trigger multiple client side events
|
||||
/// <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"
|
||||
|
||||
/// Allows you to trigger a single client side event after changes have settled
|
||||
let withHxTriggerAfterSettle : string -> HttpHandler =
|
||||
setHttpHeader "HX-Trigger-After-Settle"
|
||||
/// <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
|
||||
|
||||
/// Allows you to trigger multiple client side events after changes have settled
|
||||
/// <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"
|
||||
|
||||
/// Allows you to trigger a single client side event after DOM swapping occurs
|
||||
let withHxTriggerAfterSwap : string -> HttpHandler =
|
||||
setHttpHeader "HX-Trigger-After-Swap"
|
||||
/// <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
|
||||
|
||||
/// Allows you to trigger multiple client side events after DOM swapping occurs
|
||||
/// <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>"""
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
|
||||
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.0-beta1**
|
||||
**htmx version: 4.0.0-alpha6**
|
||||
|
||||
_Note that htmx 2.0 is a BETA release. The [migration guide](https://v2-0v2-0.htmx.org/migration-guide-htmx-1/) does not currently specify any request or response header changes. This means that, as of this release, there are no required code changes in moving to this major version._
|
||||
_Upgrading from v2.x: the [migration guide](https://four.htmx.org/migration-guide-htmx-4/) lists changes for v4. For this package, the `HX-Trigger` and `HX-Trigger-Name` headers are marked obsolete. They are replaced by `HX-Source`, which provides the triggering tag name and `id` attribute. The `HX-Prompt` header has also been marked as obsolete, as the `hx-prompt` attribute which generated its content has been removed._
|
||||
|
||||
_Obsolete elements will be removed in the first production v4 release._
|
||||
|
||||
### Setup
|
||||
|
||||
1. Install the package (must use `--Prerelease` flag).
|
||||
1. Install the package.
|
||||
2. Prior to using the request header extension properties or the header-setting `HttpHandler`s, `open Giraffe.Htmx`.
|
||||
|
||||
### Use
|
||||
@@ -18,9 +20,9 @@ To obtain a request header, using the `IHeaderDictionary` extension properties:
|
||||
```fsharp
|
||||
let myHandler : HttpHander =
|
||||
fun next ctx ->
|
||||
match ctx.HxPrompt with
|
||||
| Some prompt -> ... // do something with the text the user provided
|
||||
| None -> ... // no text provided
|
||||
match ctx.Target with
|
||||
| Some elt -> ... // do something with id of the target element
|
||||
| None -> ... // no target element provided
|
||||
```
|
||||
|
||||
To set a response header:
|
||||
@@ -34,6 +36,8 @@ let myHandler : HttpHander =
|
||||
|
||||
The `HxSwap` module has constants to use for the `HX-Reswap` header. These may be extended with settle, show, and other qualifiers; see the htmx documentation for the `hx-swap` attribute for more information.
|
||||
|
||||
To load the package-provided htmx library without using Giraffe.ViewEngine, use `HtmxScript.local`.
|
||||
|
||||
### Learn
|
||||
|
||||
The naming conventions of this library were selected to mirror those provided by htmx. The header properties become `Hx*` on the `ctx.Request.Headers` object, and the response handlers are `withHx*` based on the header being set. The only part that does not line up is `withHxTrigger*` and `withHxTriggerMany`; the former set work with a single string (to trigger a single event with no arguments), while the latter set supports both arguments and multiple events.
|
||||
@@ -3,6 +3,12 @@ module Common
|
||||
open Expecto
|
||||
open Giraffe.Htmx
|
||||
|
||||
/// Test to ensure the version was updated
|
||||
let version =
|
||||
test "HtmxVersion is correct" {
|
||||
Expect.equal HtmxVersion "4.0.0-alpha6" "htmx version incorrect"
|
||||
}
|
||||
|
||||
/// Tests for the HxSwap module
|
||||
let swap =
|
||||
testList "HxSwap" [
|
||||
@@ -12,22 +18,49 @@ let swap =
|
||||
test "OuterHtml is correct" {
|
||||
Expect.equal HxSwap.OuterHtml "outerHTML" "Outer HTML swap value incorrect"
|
||||
}
|
||||
test "BeforeBegin is correct" {
|
||||
Expect.equal HxSwap.BeforeBegin "beforebegin" "Before Begin swap value incorrect"
|
||||
test "InnerMorph is correct" {
|
||||
Expect.equal HxSwap.InnerMorph "innerMorph" "Inner Morph swap value incorrect"
|
||||
}
|
||||
test "BeforeEnd is correct" {
|
||||
Expect.equal HxSwap.BeforeEnd "beforeend" "Before End swap value incorrect"
|
||||
test "OuterMorph is correct" {
|
||||
Expect.equal HxSwap.OuterMorph "innerMorph" "Outer Morph swap value incorrect"
|
||||
}
|
||||
test "TextContent is correct" {
|
||||
Expect.equal HxSwap.TextContent "textContent" "Text Content swap value incorrect"
|
||||
}
|
||||
test "Before is correct" {
|
||||
Expect.equal HxSwap.Before "before" "Before swap value incorrect"
|
||||
}
|
||||
test "BeforeBegin is correct" {
|
||||
Expect.equal HxSwap.BeforeBegin HxSwap.Before "Before Begin swap value incorrect"
|
||||
}
|
||||
test "Prepend is correct" {
|
||||
Expect.equal HxSwap.Prepend "prepend" "Prepend swap value incorrect"
|
||||
}
|
||||
test "AfterBegin is correct" {
|
||||
Expect.equal HxSwap.AfterBegin "afterbegin" "After Begin swap value incorrect"
|
||||
Expect.equal HxSwap.AfterBegin HxSwap.Prepend "Prepend swap value incorrect"
|
||||
}
|
||||
test "Append is correct" {
|
||||
Expect.equal HxSwap.Append "append" "Append swap value incorrect"
|
||||
}
|
||||
test "BeforeEnd is correct" {
|
||||
Expect.equal HxSwap.BeforeEnd HxSwap.Append "Before End swap value incorrect"
|
||||
}
|
||||
test "After is correct" {
|
||||
Expect.equal HxSwap.After "after" "After swap value incorrect"
|
||||
}
|
||||
test "AfterEnd is correct" {
|
||||
Expect.equal HxSwap.AfterEnd "afterend" "After End swap value incorrect"
|
||||
Expect.equal HxSwap.AfterEnd HxSwap.After "After End swap value incorrect"
|
||||
}
|
||||
test "Delete is correct" {
|
||||
Expect.equal HxSwap.Delete "delete" "Delete swap value incorrect"
|
||||
}
|
||||
test "None is correct" {
|
||||
Expect.equal HxSwap.None "none" "None swap value incorrect"
|
||||
}
|
||||
test "Upsert is correct" {
|
||||
Expect.equal HxSwap.Upsert "upsert" "Upsert swap value incorrect"
|
||||
}
|
||||
]
|
||||
|
||||
/// All tests for this module
|
||||
let allTests = testList "Htmx.Common" [ swap ]
|
||||
let allTests = testList "Htmx.Common" [ version; swap ]
|
||||
|
||||
@@ -11,22 +11,22 @@ let dictExtensions =
|
||||
testList "IHeaderDictionaryExtensions" [
|
||||
testList "HxBoosted" [
|
||||
test "succeeds when the header is not present" {
|
||||
let ctx = Substitute.For<HttpContext> ()
|
||||
ctx.Request.Headers.ReturnsForAnyArgs (HeaderDictionary ()) |> ignore
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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<HttpContext> ()
|
||||
let dic = HeaderDictionary ()
|
||||
dic.Add ("HX-Boosted", "true")
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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<HttpContext> ()
|
||||
let dic = HeaderDictionary ()
|
||||
dic.Add ("HX-Boosted", "false")
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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<HttpContext> ()
|
||||
ctx.Request.Headers.ReturnsForAnyArgs (HeaderDictionary ()) |> ignore
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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<HttpContext> ()
|
||||
let dic = HeaderDictionary ()
|
||||
dic.Add ("HX-Current-URL", "http://localhost/test.htm")
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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,111 +51,104 @@ let dictExtensions =
|
||||
]
|
||||
testList "HxHistoryRestoreRequest" [
|
||||
test "succeeds when the header is not present" {
|
||||
let ctx = Substitute.For<HttpContext> ()
|
||||
ctx.Request.Headers.ReturnsForAnyArgs (HeaderDictionary ()) |> ignore
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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<HttpContext> ()
|
||||
let dic = HeaderDictionary ()
|
||||
dic.Add ("HX-History-Restore-Request", "true")
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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<HttpContext> ()
|
||||
let dic = HeaderDictionary ()
|
||||
dic.Add ("HX-History-Restore-Request", "false")
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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
|
||||
ctx.Request.Headers.HxHistoryRestoreRequest.Value "The header should have been false"
|
||||
}
|
||||
]
|
||||
testList "HxPrompt" [
|
||||
test "succeeds when the header is not present" {
|
||||
let ctx = Substitute.For<HttpContext> ()
|
||||
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<HttpContext> ()
|
||||
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"
|
||||
}
|
||||
]
|
||||
testList "HxRequest" [
|
||||
test "succeeds when the header is not present" {
|
||||
let ctx = Substitute.For<HttpContext> ()
|
||||
ctx.Request.Headers.ReturnsForAnyArgs (HeaderDictionary ()) |> ignore
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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<HttpContext> ()
|
||||
let dic = HeaderDictionary ()
|
||||
dic.Add ("HX-Request", "true")
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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<HttpContext> ()
|
||||
let dic = HeaderDictionary ()
|
||||
dic.Add ("HX-Request", "false")
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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"
|
||||
}
|
||||
]
|
||||
testList "HxSource" [
|
||||
test "succeeds when the header is not present" {
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore
|
||||
Expect.isNone ctx.Request.Headers.HxSource "There should not have been a header returned"
|
||||
}
|
||||
test "succeeds when the header is present and both parts exist" {
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
let dic = HeaderDictionary()
|
||||
dic.Add("HX-Source", "button#theId")
|
||||
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
|
||||
let hdr = ctx.Request.Headers.HxSource
|
||||
Expect.isSome hdr "There should be a header present"
|
||||
Expect.equal (fst hdr.Value) "button" "The source tag was incorrect"
|
||||
Expect.isSome (snd hdr.Value) "There should be a source ID present"
|
||||
Expect.equal (snd hdr.Value).Value "theId" "The source ID was incorrect"
|
||||
}
|
||||
test "succeeds when the header is present and ID is blank" {
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
let dic = HeaderDictionary()
|
||||
dic.Add("HX-Source", "a#")
|
||||
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
|
||||
let hdr = ctx.Request.Headers.HxSource
|
||||
Expect.isSome hdr "There should be a header present"
|
||||
Expect.equal (fst hdr.Value) "a" "The source tag was incorrect"
|
||||
Expect.isNone (snd hdr.Value) "There should not be a source ID present"
|
||||
}
|
||||
test "succeeds when the header is present and ID is missing" {
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
let dic = HeaderDictionary()
|
||||
dic.Add("HX-Source", "form")
|
||||
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
|
||||
let hdr = ctx.Request.Headers.HxSource
|
||||
Expect.isSome hdr "There should be a header present"
|
||||
Expect.equal (fst hdr.Value) "form" "The source tag was incorrect"
|
||||
Expect.isNone (snd hdr.Value) "There should not be a source ID present"
|
||||
}
|
||||
]
|
||||
testList "HxTarget" [
|
||||
test "succeeds when the header is not present" {
|
||||
let ctx = Substitute.For<HttpContext> ()
|
||||
ctx.Request.Headers.ReturnsForAnyArgs (HeaderDictionary ()) |> ignore
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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<HttpContext> ()
|
||||
let dic = HeaderDictionary ()
|
||||
dic.Add ("HX-Target", "#leItem")
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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"
|
||||
}
|
||||
]
|
||||
testList "HxTrigger" [
|
||||
test "succeeds when the header is not present" {
|
||||
let ctx = Substitute.For<HttpContext> ()
|
||||
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<HttpContext> ()
|
||||
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"
|
||||
}
|
||||
]
|
||||
testList "HxTriggerName" [
|
||||
test "succeeds when the header is not present" {
|
||||
let ctx = Substitute.For<HttpContext> ()
|
||||
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<HttpContext> ()
|
||||
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"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
/// Tests for the HttpRequest extension properties
|
||||
@@ -163,36 +156,36 @@ let reqExtensions =
|
||||
testList "HttpRequestExtensions" [
|
||||
testList "IsHtmx" [
|
||||
test "succeeds when request is not from htmx" {
|
||||
let ctx = Substitute.For<HttpContext> ()
|
||||
ctx.Request.Headers.ReturnsForAnyArgs (HeaderDictionary ()) |> ignore
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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<HttpContext> ()
|
||||
let dic = HeaderDictionary ()
|
||||
dic.Add ("HX-Request", "true")
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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<HttpContext> ()
|
||||
ctx.Request.Headers.ReturnsForAnyArgs (HeaderDictionary ()) |> ignore
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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<HttpContext> ()
|
||||
let dic = HeaderDictionary ()
|
||||
dic.Add ("HX-Request", "true")
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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<HttpContext> ()
|
||||
let dic = HeaderDictionary ()
|
||||
dic.Add ("HX-Request", "true")
|
||||
dic.Add ("HX-History-Restore-Request", "true")
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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 +195,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<HttpContext>()
|
||||
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<HttpContext> ()
|
||||
let dic = HeaderDictionary ()
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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<HttpContext> ()
|
||||
let dic = HeaderDictionary ()
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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<HttpContext> ()
|
||||
let dic = HeaderDictionary ()
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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 +234,16 @@ let handlers =
|
||||
}
|
||||
testList "withHxRefresh" [
|
||||
testTask "succeeds when set to true" {
|
||||
let ctx = Substitute.For<HttpContext> ()
|
||||
let dic = HeaderDictionary ()
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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<HttpContext> ()
|
||||
let dic = HeaderDictionary ()
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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 +251,56 @@ let handlers =
|
||||
}
|
||||
]
|
||||
testTask "withHxReplaceUrl succeeds" {
|
||||
let ctx = Substitute.For<HttpContext> ()
|
||||
let dic = HeaderDictionary ()
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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<HttpContext> ()
|
||||
let dic = HeaderDictionary ()
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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<HttpContext> ()
|
||||
let dic = HeaderDictionary ()
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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<HttpContext> ()
|
||||
let dic = HeaderDictionary ()
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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<HttpContext> ()
|
||||
let dic = HeaderDictionary ()
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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<HttpContext> ()
|
||||
let dic = HeaderDictionary ()
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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<HttpContext> ()
|
||||
let dic = HeaderDictionary ()
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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 +308,8 @@ let handlers =
|
||||
dic["HX-Trigger"].[0] """{ "blah": "foo", "bleh": "bar" }""" "The HX-Trigger value was incorrect"
|
||||
}
|
||||
testTask "withHxTriggerAfterSettle succeeds" {
|
||||
let ctx = Substitute.For<HttpContext> ()
|
||||
let dic = HeaderDictionary ()
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
let dic = HeaderDictionary()
|
||||
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
|
||||
let! _ = withHxTriggerAfterSettle "byTheWay" next ctx
|
||||
Expect.isTrue
|
||||
@@ -316,8 +317,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<HttpContext> ()
|
||||
let dic = HeaderDictionary ()
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
let dic = HeaderDictionary()
|
||||
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
|
||||
let! _ = withHxTriggerManyAfterSettle [ "oof", "ouch"; "hmm", "uh" ] next ctx
|
||||
Expect.isTrue
|
||||
@@ -327,16 +328,16 @@ let handlers =
|
||||
"The HX-Trigger-After-Settle value was incorrect"
|
||||
}
|
||||
testTask "withHxTriggerAfterSwap succeeds" {
|
||||
let ctx = Substitute.For<HttpContext> ()
|
||||
let dic = HeaderDictionary ()
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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<HttpContext> ()
|
||||
let dic = HeaderDictionary ()
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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"
|
||||
@@ -346,5 +347,66 @@ let handlers =
|
||||
}
|
||||
]
|
||||
|
||||
/// Tests for the HtmxScript module
|
||||
let script =
|
||||
testList "HtmxScript" [
|
||||
test "local generates correct link" {
|
||||
Expect.equal
|
||||
(string HtmxScript.local)
|
||||
$"""<script src="/_content/Giraffe.Htmx.Common/htmx.min.js?ver={HtmxVersion}"></script>"""
|
||||
"htmx script link is incorrect"
|
||||
}
|
||||
]
|
||||
|
||||
#nowarn 44 // Obsolete items still have tests
|
||||
let dictExtensionsObs =
|
||||
testList "IHeaderDictionaryExtensions (Obsolete)" [
|
||||
testList "HxPrompt" [
|
||||
test "succeeds when the header is not present" {
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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<HttpContext>()
|
||||
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"
|
||||
}
|
||||
]
|
||||
testList "HxTrigger" [
|
||||
test "succeeds when the header is not present" {
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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<HttpContext>()
|
||||
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"
|
||||
}
|
||||
]
|
||||
testList "HxTriggerName" [
|
||||
test "succeeds when the header is not present" {
|
||||
let ctx = Substitute.For<HttpContext>()
|
||||
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<HttpContext>()
|
||||
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"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
/// All tests for this module
|
||||
let allTests = testList "Htmx" [ dictExtensions; reqExtensions; handlers ]
|
||||
let allTests = testList "Htmx" [ dictExtensions; reqExtensions; handlers; script; dictExtensionsObs ]
|
||||
|
||||
@@ -3,4 +3,4 @@ open Expecto
|
||||
let allTests = testList "Giraffe" [ Common.allTests; Htmx.allTests; ViewEngine.allTests ]
|
||||
|
||||
[<EntryPoint>]
|
||||
let main args = runTestsWithArgs defaultConfig args allTests
|
||||
let main args = runTestsWithCLIArgs [] args allTests
|
||||
|
||||
@@ -18,8 +18,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Expecto" Version="9.0.4" />
|
||||
<PackageReference Include="NSubstitute" Version="5.0.0" />
|
||||
<PackageReference Include="Expecto" Version="10.2.1" />
|
||||
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
||||
<PackageReference Update="FSharp.Core" Version="8.0.300" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<Description>Extensions to Giraffe View Engine to support htmx attributes and their values</Description>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,13 +2,17 @@
|
||||
|
||||
This package enables [htmx](https://htmx.org) support within the [Giraffe](https://giraffe.wiki) view engine.
|
||||
|
||||
**htmx version: 2.0.0-beta1**
|
||||
**htmx version: 4.0.0-alpha6**
|
||||
|
||||
_Note that this is a BETA release of htmx 2.0; see [the migration guide](https://v2-0v2-0.htmx.org/migration-guide-htmx-1/) for changes_
|
||||
_Upgrading from v2.x: see [the migration guide](https://four.htmx.org/migration-guide-htmx-4/) for changes, which are plentiful. htmx switches from `XMLHTTPRequest` to `fetch`, and many changes are related to the new event cycle._
|
||||
|
||||
_Inheritance is now explicit; to have an attribute's value inherited to its children, wrap the attribute in `hxInherited` (ex. `hxInherited (_hxTarget "#main")`). Values can be appended to inherited values as well using the `hxAppend` modifier._
|
||||
|
||||
_Several constructs have been marked obsolete in this release, and will be removed from the first production release of v4. With the exception of `_hxDisable`, though (which now functions as the deprecated `_hxDisabledElt` did), this should not introduce compile errors. Rather, this package will raise warnings for deprecated constructs, along with suggestions of what to use instead._
|
||||
|
||||
### Setup
|
||||
|
||||
1. Install the package (must use `--Prelease` flag).
|
||||
1. Install the package.
|
||||
2. Prior to using the attribute or support modules, `open Giraffe.ViewEngine.Htmx`.
|
||||
|
||||
### Use
|
||||
@@ -21,15 +25,16 @@ let autoload =
|
||||
```
|
||||
|
||||
Support modules include:
|
||||
- `HxConfig` _(new in v4)_
|
||||
- `HxEncoding`
|
||||
- `HxHeaders`
|
||||
- `HxParams`
|
||||
- `HxRequest`
|
||||
- ~~`HxParams`~~ _(removed in v4)_
|
||||
- ~~`HxRequest`~~ _(renamed to `HxConfig`)_
|
||||
- `HxSwap` (requires `open Giraffe.Htmx`)
|
||||
- `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).
|
||||
`Htmx.Script.local` creates an `XmlNode` to load the package-provided htmx library. There are also two `XmlNode`s that will load the htmx script from jsdelivr; `Htmx.Script.cdnMinified` loads the minified version, and `Htmx.Script.cdnUnminified` loads the unminified version (useful for debugging). When using the CDN nodes and a Content Security Policy (CSP) header, `cdn.jsdelivr.net` needs to be listed as an allowable `script-src`.
|
||||
|
||||
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).
|
||||
|
||||
|
||||
7
src/pack.sh
Executable file
7
src/pack.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
dotnet pack Common/Giraffe.Htmx.Common.fsproj -c Release
|
||||
dotnet pack Htmx/Giraffe.Htmx.fsproj -c Release
|
||||
dotnet pack ViewEngine.Htmx/Giraffe.ViewEngine.Htmx.fsproj -c Release
|
||||
cp Common/bin/Release/Giraffe.Htmx.Common.*.nupkg .
|
||||
cp Htmx/bin/Release/Giraffe.Htmx.*.nupkg .
|
||||
cp ViewEngine.Htmx/bin/Release/Giraffe.ViewEngine.Htmx.*.nupkg .
|
||||
Reference in New Issue
Block a user