Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cfd8d7eb68 | |||
| 8f93aa4d35 | |||
| 121eb95d87 | |||
| 6b7458070b | |||
| 10c31d77b5 | |||
| f4cd184a15 | |||
| 5626031593 | |||
| f0de18845f | |||
| 961307fd99 | |||
| a2960a79c6 | |||
| 541384a92f | |||
| 8cb5d6bfa7 | |||
| 1a11e3511a | |||
| 32e962416d | |||
| 29839fa795 | |||
| 7f9b3a6234 | |||
| a8d2b819dc | |||
| 1ea05b79ed | |||
| 4f6bb8367a | |||
| b3665a4b72 | |||
| bdb7255a1c | |||
| 9276db7ffe | |||
| 94b68f76c9 | |||
| 90de16529c | |||
| 452f15b2d4 | |||
| 59246ae7f5 | |||
| 16355e8f58 | |||
| 71286b9064 | |||
| 85ac22877c | |||
| dea2323499 | |||
| fcaaa693bc | |||
| 8fd6af8c26 | |||
| 69a4034661 | |||
| 5aa9408e60 | |||
| 98f53a5e53 |
@@ -2,9 +2,10 @@ name: CI
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "main" ]
|
branches: [ "main", "htmx-version-2" ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ "main" ]
|
branches: [ "main" ]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-test:
|
build-and-test:
|
||||||
@@ -13,12 +14,12 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
dotnet-version: [ "6.0", "7.0" ]
|
dotnet-version: [ "6.0", "7.0", "8.0" ]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Setup .NET ${{ matrix.dotnet-version }}.x
|
- name: Setup .NET ${{ matrix.dotnet-version }}.x
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: ${{ matrix.dotnet-version }}.x
|
dotnet-version: ${{ matrix.dotnet-version }}.x
|
||||||
- name: Restore dependencies
|
- name: Restore dependencies
|
||||||
@@ -31,11 +32,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: build-and-test
|
needs: build-and-test
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: "7.0"
|
dotnet-version: "8.0"
|
||||||
- name: Package Common library
|
- name: Package Common library
|
||||||
run: dotnet pack src/Common/Giraffe.Htmx.Common.fsproj -c Release
|
run: dotnet pack src/Common/Giraffe.Htmx.Common.fsproj -c Release
|
||||||
- name: Move Common package
|
- name: Move Common package
|
||||||
@@ -49,7 +50,7 @@ jobs:
|
|||||||
- name: Move View Engine package
|
- name: Move View Engine package
|
||||||
run: cp src/ViewEngine.Htmx/bin/Release/Giraffe.ViewEngine.Htmx.*.nupkg .
|
run: cp src/ViewEngine.Htmx/bin/Release/Giraffe.ViewEngine.Htmx.*.nupkg .
|
||||||
- name: Save Packages
|
- name: Save Packages
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: packages
|
name: packages
|
||||||
path: |
|
path: |
|
||||||
|
|||||||
@@ -6,3 +6,6 @@
|
|||||||
.idea
|
.idea
|
||||||
*.user
|
*.user
|
||||||
.vscode
|
.vscode
|
||||||
|
src/*.nupkg
|
||||||
|
src/tests*.txt
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
htmx uses attributes and HTTP headers to attain its interactivity; the libraries here contain extensions to both Giraffe and Giraffe View Engine to enable strongly-typed development of htmx applications.
|
htmx uses attributes and HTTP headers to attain its interactivity; the libraries here contain extensions to both Giraffe and Giraffe View Engine to enable strongly-typed development of htmx applications.
|
||||||
|
|
||||||
|
> **NOTE** htmx v4 is curently in beta; this library is supporting it on the [`htmx-v4` branch](https://git.bitbadger.solutions/bit-badger/Giraffe.Htmx/src/branch/htmx-v4/). Beta versions of the v4 library include warnings about obsolete constructs. The htmx project intends to continue supporting the 2.x line (just as they still do the 1.x line, which we track on the [`htmx-v1` branch](https://git.bitbadger.solutions/bit-badger/Giraffe.Htmx/src/branch/htmx-v1/)); the upgrade is available, not forced. See the [htmx v4 migration docs](https://four.htmx.org/docs#migration) for more information.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
`Giraffe.Htmx` provides extensions that facilitate using htmx on the server side, primarily reading and setting headers. `Giraffe.ViewEngine.Htmx` provides attributes and helpers to produce views that utilize htmx. Both can be installed from NuGet via standard methods.
|
`Giraffe.Htmx` provides extensions that facilitate using htmx on the server side, primarily reading and setting headers. `Giraffe.ViewEngine.Htmx` provides attributes and helpers to produce views that utilize htmx. Both can be installed from NuGet via standard methods.
|
||||||
@@ -14,6 +16,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/)|
|
|[](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`)
|
## 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.
|
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.
|
||||||
@@ -25,9 +29,10 @@ A server may want to respond to a request that originated from htmx differently
|
|||||||
// "view" can be whatever your view engine needs for the body of the page
|
// "view" can be whatever your view engine needs for the body of the page
|
||||||
let result view : HttpHandler =
|
let result view : HttpHandler =
|
||||||
fun next ctx ->
|
fun next ctx ->
|
||||||
match ctx.Request.IsHtmx && not ctx.Request.IsHtmxRefresh with
|
if ctx.Request.IsHtmx && not ctx.Request.IsHtmxRefresh then
|
||||||
| true -> partial view
|
partial view
|
||||||
| false -> full view
|
else
|
||||||
|
full view
|
||||||
```
|
```
|
||||||
|
|
||||||
htmx also utilizes [response headers](https://htmx.org/docs/#response_headers) to affect client-side behavior. For each of these, this library provides `HttpHandler`s that can be chained along with the response. As an example, if the server returns a redirect response (301, 302, 303, 307), the `XMLHttpRequest` handler on the client will follow the redirection before htmx can do anything with it. To redirect to a new page, you would return an OK (200) response with an `HX-Redirect` header set in the response.
|
htmx also utilizes [response headers](https://htmx.org/docs/#response_headers) to affect client-side behavior. For each of these, this library provides `HttpHandler`s that can be chained along with the response. As an example, if the server returns a redirect response (301, 302, 303, 307), the `XMLHttpRequest` handler on the client will follow the redirection before htmx can do anything with it. To redirect to a new page, you would return an OK (200) response with an `HX-Redirect` header set in the response.
|
||||||
@@ -41,6 +46,8 @@ htmx also utilizes [response headers](https://htmx.org/docs/#response_headers) t
|
|||||||
|
|
||||||
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.
|
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`)
|
## 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.
|
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.
|
||||||
@@ -49,7 +56,9 @@ As an example, creating a `div` that loads data once the HTML is rendered:
|
|||||||
|
|
||||||
```fsharp
|
```fsharp
|
||||||
let autoload =
|
let autoload =
|
||||||
div [ _hxGet "/lazy-load-data"; _hxTrigger "load" ] [ str "Loading..." ]
|
div [ _hxGet "/lazy-load-data"; _hxTrigger HxTrigger.Load ] [
|
||||||
|
str "Loading..."
|
||||||
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
_(As `hx-boost="true"` is the usual desire for boosting, `_hxBoost` implies true. To disable it for an element, use `_hxNoBoost` instead.)_
|
_(As `hx-boost="true"` is the usual desire for boosting, `_hxBoost` implies true. To disable it for an element, use `_hxNoBoost` instead.)_
|
||||||
@@ -63,14 +72,14 @@ Some attributes have known values, such as `hx-trigger` and `hx-swap`; for these
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
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
|
## Feedback / Help
|
||||||
|
|
||||||
The author hangs out in the #htmx-general channel of the [htmx Discord server](https://htmx.org/discord) and the #web channel of the [F# Software Foundation's Slack server](https://fsharp.org/guides/slack/).
|
The author hangs out in the #dotnet-htmx channel (and most others) of the [htmx Discord server](https://htmx.org/discord) and the #web channel of the [F# Software Foundation's Slack server](https://fsharp.org/guides/slack/).
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
|[<img src="https://giraffe.wiki/giraffe.png" alt="Giraffe logo" width="200">](https://giraffe.wiki)|[<img src="https://bitbadger.solutions/htmx-black-transparent.svg" alt="htmx logo" width="200">](https://htmx.org)|[<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.png" alt="JetBrains Logo (Main)" width="200">](https://jb.gg/OpenSource)|
|
|[<img src="https://giraffe.wiki/giraffe.png" alt="Giraffe logo" width="200">](https://giraffe.wiki)| [<img src="https://bitbadger.solutions/upload/bit-badger/2024/01/htmx-black-transparent.svg" alt="htmx logo" width="200">](https://htmx.org) |[<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.png" alt="JetBrains Logo (Main)" width="200">](https://jb.gg/OpenSource)|
|
||||||
| :---: | :---: | :---: |
|
| :---: |:------------------------------------------------------------------------------------------------------------------------------------:| :---: |
|
||||||
|for making ASP.NET Core functional| for making HTML cool again |for licensing their tools to this project|
|
|for making ASP.NET Core functional| for making HTML cool again |for licensing their tools to this project|
|
||||||
|
|
||||||
|
|||||||
+40
-9
@@ -1,28 +1,59 @@
|
|||||||
/// Common definitions shared between attribute values and response headers
|
/// <summary>Common definitions shared between attribute values and response headers</summary>
|
||||||
[<AutoOpen>]
|
[<AutoOpen>]
|
||||||
module Giraffe.Htmx.Common
|
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 = "2.0.10"
|
||||||
|
|
||||||
|
/// <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>settle</c> / <c>scroll</c> / <c>show</c> config)</remarks>
|
||||||
|
/// <seealso href="https://htmx.org/attributes/hx-swap/">Documentation</seealso>
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module HxSwap =
|
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"
|
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"
|
let OuterHtml = "outerHTML"
|
||||||
|
|
||||||
/// Insert the response before the target element
|
/// <summary>Insert the response before the target element</summary>
|
||||||
|
[<Literal>]
|
||||||
let BeforeBegin = "beforebegin"
|
let BeforeBegin = "beforebegin"
|
||||||
|
|
||||||
/// Insert the response before the first child of the target element
|
/// <summary>Insert the response before the first child of the target element</summary>
|
||||||
|
[<Literal>]
|
||||||
let AfterBegin = "afterbegin"
|
let AfterBegin = "afterbegin"
|
||||||
|
|
||||||
/// Insert the response after the last child of the target element
|
/// <summary>Insert the response after the last child of the target element</summary>
|
||||||
|
[<Literal>]
|
||||||
let BeforeEnd = "beforeend"
|
let BeforeEnd = "beforeend"
|
||||||
|
|
||||||
/// Insert the response after the target element
|
/// <summary>Insert the response after the target element</summary>
|
||||||
|
[<Literal>]
|
||||||
let AfterEnd = "afterend"
|
let AfterEnd = "afterend"
|
||||||
|
|
||||||
/// Does not append content from response (out of band items will still be processed).
|
/// <summary>Does not append content from response (out of band items will still be processed).</summary>
|
||||||
|
[<Literal>]
|
||||||
let None = "none"
|
let None = "none"
|
||||||
|
|||||||
@@ -1,14 +1,24 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<Description>Common definitions for Giraffe.Htmx</Description>
|
<Description>Common definitions for Giraffe.Htmx</Description>
|
||||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
|
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Common.fs" />
|
<Compile Include="Common.fs" />
|
||||||
<None Include="README.md" Pack="true" PackagePath="\" />
|
<None Include="README.md" Pack="true" PackagePath="\" />
|
||||||
|
<None Include="..\icon.png" Pack="true" PackagePath="\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Update="FSharp.Core" Version="6.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<InternalsVisibleTo Include="Giraffe.Htmx" />
|
||||||
|
<InternalsVisibleTo Include="Giraffe.ViewEngine.Htmx" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
## Giraffe.Htmx.Common
|
## 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: 1.9.2**
|
**htmx version: 2.0.10**
|
||||||
|
|||||||
Vendored
+1
File diff suppressed because one or more lines are too long
@@ -1,14 +1,18 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
|
||||||
<VersionPrefix>1.9.2</VersionPrefix>
|
<VersionPrefix>2.0.10</VersionPrefix>
|
||||||
<PackageReleaseNotes>Update script tags to pull htmx 1.9.2</PackageReleaseNotes>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<PackageReleaseNotes>- Updates provided script and CDN links to serve htmx 2.0.10 (no header or attribute changes)
|
||||||
|
|
||||||
|
NOTE: As of 2.0.6, the CDN for htmx changed from unpkg.com to cdn.jsdelivr.net; sites with Content-Security-Policy headers will want to update their allowed script-src domains accordingly
|
||||||
|
</PackageReleaseNotes>
|
||||||
<Authors>danieljsummers</Authors>
|
<Authors>danieljsummers</Authors>
|
||||||
<Company>Bit Badger Solutions</Company>
|
<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>
|
<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>
|
<RepositoryType>Git</RepositoryType>
|
||||||
<Copyright>MIT License</Copyright>
|
<Copyright>MIT License</Copyright>
|
||||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
# Visual Studio Version 16
|
|
||||||
VisualStudioVersion = 16.0.30114.105
|
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
|
||||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Giraffe.Htmx", "Htmx\Giraffe.Htmx.fsproj", "{8AB3085C-5236-485A-8565-A09106E72E1E}"
|
|
||||||
EndProject
|
|
||||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Giraffe.ViewEngine.Htmx", "ViewEngine.Htmx\Giraffe.ViewEngine.Htmx.fsproj", "{F718B3C1-EE01-4F04-ABCE-BF2AE700FDA9}"
|
|
||||||
EndProject
|
|
||||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Giraffe.Htmx.Common", "Common\Giraffe.Htmx.Common.fsproj", "{75D66845-F93A-4463-AD29-A8B16E4D4BA9}"
|
|
||||||
EndProject
|
|
||||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Tests", "Tests\Tests.fsproj", "{39823773-4311-4E79-9CA0-F9DDC40CAF6A}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
|
||||||
HideSolutionNode = FALSE
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{8AB3085C-5236-485A-8565-A09106E72E1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{8AB3085C-5236-485A-8565-A09106E72E1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{8AB3085C-5236-485A-8565-A09106E72E1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{8AB3085C-5236-485A-8565-A09106E72E1E}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{F718B3C1-EE01-4F04-ABCE-BF2AE700FDA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{F718B3C1-EE01-4F04-ABCE-BF2AE700FDA9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{F718B3C1-EE01-4F04-ABCE-BF2AE700FDA9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{F718B3C1-EE01-4F04-ABCE-BF2AE700FDA9}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{75D66845-F93A-4463-AD29-A8B16E4D4BA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{75D66845-F93A-4463-AD29-A8B16E4D4BA9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{75D66845-F93A-4463-AD29-A8B16E4D4BA9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{75D66845-F93A-4463-AD29-A8B16E4D4BA9}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{39823773-4311-4E79-9CA0-F9DDC40CAF6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{39823773-4311-4E79-9CA0-F9DDC40CAF6A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{39823773-4311-4E79-9CA0-F9DDC40CAF6A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{39823773-4311-4E79-9CA0-F9DDC40CAF6A}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<Solution>
|
||||||
|
<Project Path="Common/Giraffe.Htmx.Common.fsproj" />
|
||||||
|
<Project Path="Htmx/Giraffe.Htmx.fsproj" />
|
||||||
|
<Project Path="Tests/Tests.fsproj" />
|
||||||
|
<Project Path="ViewEngine.Htmx/Giraffe.ViewEngine.Htmx.fsproj" />
|
||||||
|
</Solution>
|
||||||
@@ -4,15 +4,18 @@
|
|||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<Description>htmx header extensions and helpers for Giraffe</Description>
|
<Description>htmx header extensions and helpers for Giraffe</Description>
|
||||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
|
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Htmx.fs" />
|
<Compile Include="Htmx.fs" />
|
||||||
<None Include="README.md" Pack="true" PackagePath="\" />
|
<None Include="README.md" Pack="true" PackagePath="\" />
|
||||||
|
<None Include="..\icon.png" Pack="true" PackagePath="\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Giraffe" Version="5.0.0" />
|
<PackageReference Include="Giraffe" Version="6.4.0" />
|
||||||
|
<PackageReference Update="FSharp.Core" Version="6.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
+128
-71
@@ -11,117 +11,174 @@ let private hdr (headers : IHeaderDictionary) hdr =
|
|||||||
/// Extensions to the header dictionary
|
/// Extensions to the header dictionary
|
||||||
type IHeaderDictionary with
|
type IHeaderDictionary with
|
||||||
|
|
||||||
/// Indicates that the request is via an element using `hx-boost`
|
/// <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
|
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)_
|
/// <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
|
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
|
/// <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
|
member this.HxHistoryRestoreRequest
|
||||||
|
with get () = hdr this "HX-History-Restore-Request" |> Option.map bool.Parse
|
||||||
|
|
||||||
/// The user response to an `hx-prompt`
|
/// <summary>The user response to an <c>hx-prompt</c></summary>
|
||||||
member this.HxPrompt with get () = hdr this "HX-Prompt"
|
member this.HxPrompt
|
||||||
|
with get () = hdr this "HX-Prompt"
|
||||||
|
|
||||||
/// `true` if the request came from HTMX
|
/// <summary><c>true</c> if the request came from htmx</summary>
|
||||||
member this.HxRequest with get () = hdr this "HX-Request" |> Option.map bool.Parse
|
member this.HxRequest
|
||||||
|
with get () = hdr this "HX-Request" |> Option.map bool.Parse
|
||||||
|
|
||||||
/// The `id` of the target element if it exists
|
/// <summary>The <c>id</c> attribute of the target element if it exists</summary>
|
||||||
member this.HxTarget with get () = hdr this "HX-Target"
|
member this.HxTarget
|
||||||
|
with get () = hdr this "HX-Target"
|
||||||
|
|
||||||
/// The `id` of the triggered element if it exists
|
/// <summary>The <c>id</c> attribute of the triggered element if it exists</summary>
|
||||||
member this.HxTrigger with get () = hdr this "HX-Trigger"
|
member this.HxTrigger
|
||||||
|
with get () = hdr this "HX-Trigger"
|
||||||
|
|
||||||
/// The `name` of the triggered element if it exists
|
/// <summary>The <c>name</c> attribute of the triggered element if it exists</summary>
|
||||||
member this.HxTriggerName with get () = hdr this "HX-Trigger-Name"
|
member this.HxTriggerName
|
||||||
|
with get () = hdr this "HX-Trigger-Name"
|
||||||
|
|
||||||
|
|
||||||
/// Extensions for the request object
|
/// Extensions for the request object
|
||||||
type HttpRequest with
|
type HttpRequest with
|
||||||
|
|
||||||
/// Whether this request was initiated from htmx
|
/// <summary>Whether this request was initiated from htmx</summary>
|
||||||
member this.IsHtmx with get () = this.Headers.HxRequest |> Option.defaultValue false
|
member this.IsHtmx
|
||||||
|
with get () = this.Headers.HxRequest |> Option.defaultValue false
|
||||||
|
|
||||||
/// Whether this request is an htmx history-miss refresh request
|
/// <summary>Whether this request is an htmx history-miss refresh request</summary>
|
||||||
member this.IsHtmxRefresh with get () =
|
member this.IsHtmxRefresh
|
||||||
this.IsHtmx && (this.Headers.HxHistoryRestoreRequest |> Option.defaultValue false)
|
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>]
|
[<AutoOpen>]
|
||||||
module Handlers =
|
module Handlers =
|
||||||
|
|
||||||
/// Convert a boolean to lowercase `true` or `false`
|
open Giraffe.Htmx.Common
|
||||||
let private toLowerBool (trueOrFalse : bool) =
|
|
||||||
(string trueOrFalse).ToLowerInvariant ()
|
|
||||||
|
|
||||||
/// Serialize a list of key/value pairs to JSON (very rudimentary)
|
/// <summary>Instruct htmx to perform a client-side redirect for content</summary>
|
||||||
let private toJson (evts : (string * string) list) =
|
/// <param name="path">The path where the content should be found</param>
|
||||||
evts
|
/// <returns>An HTTP handler with the <c>HX-Location</c> header set</returns>
|
||||||
|> List.map (fun evt -> sprintf "\"%s\": \"%s\"" (fst evt) ((snd evt).Replace ("\"", "\\\"")))
|
/// <seealso href="https://htmx.org/headers/hx-location/">Documentation</seealso>
|
||||||
|> String.concat ", "
|
let withHxLocation (path: string) : HttpHandler =
|
||||||
|> sprintf "{ %s }"
|
setHttpHeader "HX-Location" path
|
||||||
|
|
||||||
/// Pushes a new url into the history stack
|
/// <summary>Pushes a new url into the history stack</summary>
|
||||||
let withHxPushUrl : string -> HttpHandler =
|
/// <param name="url">The URL to be pushed</param>
|
||||||
setHttpHeader "HX-Push-Url"
|
/// <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
|
||||||
|
|
||||||
/// 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 =
|
let withHxNoPushUrl : HttpHandler =
|
||||||
toLowerBool false |> withHxPushUrl
|
toLowerBool false |> withHxPushUrl
|
||||||
|
|
||||||
/// Pushes a new url into the history stack
|
/// <summary>Can be used to do a client-side redirect to a new location</summary>
|
||||||
[<Obsolete "Use withHxPushUrl; HX-Push was replaced by HX-Push-Url in v1.8.0">]
|
/// <param name="url">The URL to which the client should be redirected</param>
|
||||||
let withHxPush = withHxPushUrl
|
/// <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
|
||||||
|
|
||||||
/// Explicitly do not push a new URL into the history stack
|
/// <summary>If set to <c>true</c> the client side will do a full refresh of the page</summary>
|
||||||
[<Obsolete "Use withHxNoPushUrl; HX-Push was replaced by HX-Push-Url in v1.8.0">]
|
/// <param name="shouldRefresh">Whether the client should refresh their page</param>
|
||||||
let withHxNoPush = withHxNoPushUrl
|
/// <returns>An HTTP handler with the <c>HX-Refresh</c> header set</returns>
|
||||||
|
let withHxRefresh shouldRefresh : HttpHandler =
|
||||||
|
(toLowerBool >> setHttpHeader "HX-Refresh") shouldRefresh
|
||||||
|
|
||||||
/// Can be used to do a client-side redirect to a new location
|
/// <summary>Replaces the current URL in the history stack</summary>
|
||||||
let withHxRedirect : string -> HttpHandler =
|
/// <param name="url">The URL to place in the history stack in place of the current one</param>
|
||||||
setHttpHeader "HX-Redirect"
|
/// <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
|
||||||
|
|
||||||
/// If set to `true` the client side will do a a full refresh of the page
|
/// <summary>Explicitly do not replace the current URL in the history stack</summary>
|
||||||
let withHxRefresh : bool -> HttpHandler =
|
/// <returns>An HTTP handler with the <c>HX-Replace-URL</c> header set to <c>false</c></returns>
|
||||||
toLowerBool >> setHttpHeader "HX-Refresh"
|
/// <seealso href="https://htmx.org/headers/hx-replace-url/">Documentation</seealso>
|
||||||
|
|
||||||
/// Replaces the current URL in the history stack
|
|
||||||
let withHxReplaceUrl : string -> HttpHandler =
|
|
||||||
setHttpHeader "HX-Replace-Url"
|
|
||||||
|
|
||||||
/// Explicitly do not replace the current URL in the history stack
|
|
||||||
let withHxNoReplaceUrl : HttpHandler =
|
let withHxNoReplaceUrl : HttpHandler =
|
||||||
toLowerBool false |> withHxReplaceUrl
|
toLowerBool false |> withHxReplaceUrl
|
||||||
|
|
||||||
/// Override the `hx-swap` attribute from the initiating element
|
/// <summary>Override which portion of the response will be swapped into the target document</summary>
|
||||||
let withHxReswap : string -> HttpHandler =
|
/// <param name="target">The selector for the new response target</param>
|
||||||
setHttpHeader "HX-Reswap"
|
/// <returns>An HTTP handler with the <c>HX-Reselect</c> header set</returns>
|
||||||
|
let withHxReselect (target: string) : HttpHandler =
|
||||||
|
setHttpHeader "HX-Reselect" target
|
||||||
|
|
||||||
/// Allows you to override the `hx-target` attribute
|
/// <summary>Override the <c>hx-swap</c> attribute from the initiating element</summary>
|
||||||
let withHxRetarget : string -> HttpHandler =
|
/// <param name="swap">The swap value to override</param>
|
||||||
setHttpHeader "HX-Retarget"
|
/// <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 trigger a single client side event
|
/// <summary>Allows you to override the <c>hx-target</c> attribute</summary>
|
||||||
let withHxTrigger : string -> HttpHandler =
|
/// <param name="target">The new target for the response</param>
|
||||||
setHttpHeader "HX-Trigger"
|
/// <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 multiple client side events
|
/// <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 =
|
let withHxTriggerMany evts : HttpHandler =
|
||||||
toJson evts |> setHttpHeader "HX-Trigger"
|
toJson evts |> setHttpHeader "HX-Trigger"
|
||||||
|
|
||||||
/// Allows you to trigger a single client side event after changes have settled
|
/// <summary>Allows you to trigger a single client side event after changes have settled</summary>
|
||||||
let withHxTriggerAfterSettle : string -> HttpHandler =
|
/// <param name="evt">The call to the event that should be triggered</param>
|
||||||
setHttpHeader "HX-Trigger-After-Settle"
|
/// <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 =
|
let withHxTriggerManyAfterSettle evts : HttpHandler =
|
||||||
toJson evts |> setHttpHeader "HX-Trigger-After-Settle"
|
toJson evts |> setHttpHeader "HX-Trigger-After-Settle"
|
||||||
|
|
||||||
/// Allows you to trigger a single client side event after DOM swapping occurs
|
/// <summary>Allows you to trigger a single client side event after DOM swapping occurs</summary>
|
||||||
let withHxTriggerAfterSwap : string -> HttpHandler =
|
/// <param name="evt">The call to the event that should be triggered</param>
|
||||||
setHttpHeader "HX-Trigger-After-Swap"
|
/// <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 =
|
let withHxTriggerManyAfterSwap evts : HttpHandler =
|
||||||
toJson evts |> setHttpHeader "HX-Trigger-After-Swap"
|
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>"""
|
||||||
|
|
||||||
+5
-1
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
This package enables server-side support for [htmx](https://htmx.org) within [Giraffe](https://giraffe.wiki) and ASP.NET's `HttpContext`.
|
This package enables server-side support for [htmx](https://htmx.org) within [Giraffe](https://giraffe.wiki) and ASP.NET's `HttpContext`.
|
||||||
|
|
||||||
**htmx version: 1.9.2**
|
**htmx version: 2.0.10**
|
||||||
|
|
||||||
|
_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.*._
|
||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
|
|
||||||
@@ -32,6 +34,8 @@ To set a response header:
|
|||||||
|
|
||||||
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.
|
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
|
### 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.
|
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.
|
||||||
+7
-1
@@ -3,6 +3,12 @@ module Common
|
|||||||
open Expecto
|
open Expecto
|
||||||
open Giraffe.Htmx
|
open Giraffe.Htmx
|
||||||
|
|
||||||
|
/// Test to ensure the version was updated
|
||||||
|
let version =
|
||||||
|
test "HtmxVersion is correct" {
|
||||||
|
Expect.equal HtmxVersion "2.0.10" "htmx version incorrect"
|
||||||
|
}
|
||||||
|
|
||||||
/// Tests for the HxSwap module
|
/// Tests for the HxSwap module
|
||||||
let swap =
|
let swap =
|
||||||
testList "HxSwap" [
|
testList "HxSwap" [
|
||||||
@@ -30,4 +36,4 @@ let swap =
|
|||||||
]
|
]
|
||||||
|
|
||||||
/// All tests for this module
|
/// All tests for this module
|
||||||
let allTests = testList "Htmx.Common" [ swap ]
|
let allTests = testList "Htmx.Common" [ version; swap ]
|
||||||
|
|||||||
+29
-1
@@ -3,6 +3,7 @@ module Htmx
|
|||||||
open System
|
open System
|
||||||
open Expecto
|
open Expecto
|
||||||
open Giraffe.Htmx
|
open Giraffe.Htmx
|
||||||
|
open Microsoft.AspNetCore.Html
|
||||||
open Microsoft.AspNetCore.Http
|
open Microsoft.AspNetCore.Http
|
||||||
open NSubstitute
|
open NSubstitute
|
||||||
|
|
||||||
@@ -207,6 +208,14 @@ let next (ctx : HttpContext) = Task.FromResult (Some ctx)
|
|||||||
/// Tests for the HttpHandler functions provided in the Handlers module
|
/// Tests for the HttpHandler functions provided in the Handlers module
|
||||||
let handlers =
|
let handlers =
|
||||||
testList "HandlerTests" [
|
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" {
|
testTask "withHxPushUrl succeeds" {
|
||||||
let ctx = Substitute.For<HttpContext>()
|
let ctx = Substitute.For<HttpContext>()
|
||||||
let dic = HeaderDictionary()
|
let dic = HeaderDictionary()
|
||||||
@@ -265,6 +274,14 @@ let handlers =
|
|||||||
Expect.isTrue (dic.ContainsKey "HX-Replace-Url") "The HX-Replace-Url header should be present"
|
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"
|
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()
|
||||||
|
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" {
|
testTask "withHxReswap succeeds" {
|
||||||
let ctx = Substitute.For<HttpContext>()
|
let ctx = Substitute.For<HttpContext>()
|
||||||
let dic = HeaderDictionary()
|
let dic = HeaderDictionary()
|
||||||
@@ -338,5 +355,16 @@ 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
/// All tests for this module
|
/// All tests for this module
|
||||||
let allTests = testList "Htmx" [ dictExtensions; reqExtensions; handlers ]
|
let allTests = testList "Htmx" [ dictExtensions; reqExtensions; handlers; script ]
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ open Expecto
|
|||||||
let allTests = testList "Giraffe" [ Common.allTests; Htmx.allTests; ViewEngine.allTests ]
|
let allTests = testList "Giraffe" [ Common.allTests; Htmx.allTests; ViewEngine.allTests ]
|
||||||
|
|
||||||
[<EntryPoint>]
|
[<EntryPoint>]
|
||||||
let main args = runTestsWithArgs defaultConfig args allTests
|
let main args = runTestsWithCLIArgs [] args allTests
|
||||||
|
|||||||
@@ -18,8 +18,9 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Expecto" Version="9.0.4" />
|
<PackageReference Include="Expecto" Version="10.2.1" />
|
||||||
<PackageReference Include="NSubstitute" Version="5.0.0" />
|
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
||||||
|
<PackageReference Update="FSharp.Core" Version="8.0.300" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
+474
-27
@@ -15,6 +15,399 @@ let hxEncoding =
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
let hxEvent =
|
||||||
|
testList "HxEvent" [
|
||||||
|
testList "Abort" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string Abort) "abort" "Abort event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal (Abort.ToHxOnString()) "abort" "Abort hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "AfterOnLoad" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string AfterOnLoad) "afterOnLoad" "AfterOnLoad event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal (AfterOnLoad.ToHxOnString()) "after-on-load" "AfterOnLoad hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "AfterProcessNode" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string AfterProcessNode) "afterProcessNode" "AfterProcessNode event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal
|
||||||
|
(AfterProcessNode.ToHxOnString())
|
||||||
|
"after-process-node"
|
||||||
|
"AfterProcessNode hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "AfterRequest" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string AfterRequest) "afterRequest" "AfterRequest event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal (AfterRequest.ToHxOnString()) "after-request" "AfterRequest hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "AfterSettle" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string AfterSettle) "afterSettle" "AfterSettle event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal (AfterSettle.ToHxOnString()) "after-settle" "AfterSettle hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "AfterSwap" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string AfterSwap) "afterSwap" "AfterSwap event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal (AfterSwap.ToHxOnString()) "after-swap" "AfterSwap hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "BeforeCleanupElement" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal
|
||||||
|
(string BeforeCleanupElement) "beforeCleanupElement" "BeforeCleanupElement event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal
|
||||||
|
(BeforeCleanupElement.ToHxOnString())
|
||||||
|
"before-cleanup-element"
|
||||||
|
"BeforeCleanupElement hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "BeforeOnLoad" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string BeforeOnLoad) "beforeOnLoad" "BeforeOnLoad event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal (BeforeOnLoad.ToHxOnString()) "before-on-load" "BeforeOnLoad hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "BeforeProcessNode" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string BeforeProcessNode) "beforeProcessNode" "BeforeProcessNode event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal
|
||||||
|
(BeforeProcessNode.ToHxOnString())
|
||||||
|
"before-process-node"
|
||||||
|
"BeforeProcessNode hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "BeforeRequest" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string BeforeRequest) "beforeRequest" "BeforeRequest event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal
|
||||||
|
(BeforeRequest.ToHxOnString()) "before-request" "BeforeRequest hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "BeforeSwap" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string BeforeSwap) "beforeSwap" "BeforeSwap event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal (BeforeSwap.ToHxOnString()) "before-swap" "BeforeSwap hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "BeforeSend" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string BeforeSend) "beforeSend" "BeforeSend event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal (BeforeSend.ToHxOnString()) "before-send" "BeforeSend hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "ConfigRequest" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string ConfigRequest) "configRequest" "ConfigRequest event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal
|
||||||
|
(ConfigRequest.ToHxOnString()) "config-request" "ConfigRequest hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "Confirm" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string Confirm) "confirm" "Confirm event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal (Confirm.ToHxOnString()) "confirm" "Confirm hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "HistoryCacheError" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string HistoryCacheError) "historyCacheError" "HistoryCacheError event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal
|
||||||
|
(HistoryCacheError.ToHxOnString())
|
||||||
|
"history-cache-error"
|
||||||
|
"HistoryCacheError hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "HistoryCacheMiss" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string HistoryCacheMiss) "historyCacheMiss" "HistoryCacheMiss event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal
|
||||||
|
(HistoryCacheMiss.ToHxOnString())
|
||||||
|
"history-cache-miss"
|
||||||
|
"HistoryCacheMiss hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "HistoryCacheMissError" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal
|
||||||
|
(string HistoryCacheMissError)
|
||||||
|
"historyCacheMissError"
|
||||||
|
"HistoryCacheMissError event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal
|
||||||
|
(HistoryCacheMissError.ToHxOnString())
|
||||||
|
"history-cache-miss-error"
|
||||||
|
"HistoryCacheMissError hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "HistoryCacheMissLoad" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal
|
||||||
|
(string HistoryCacheMissLoad) "historyCacheMissLoad" "HistoryCacheMissLoad event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal
|
||||||
|
(HistoryCacheMissLoad.ToHxOnString())
|
||||||
|
"history-cache-miss-load"
|
||||||
|
"HistoryCacheMissLoad hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "HistoryRestore" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string HistoryRestore) "historyRestore" "HistoryRestore event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal
|
||||||
|
(HistoryRestore.ToHxOnString()) "history-restore" "HistoryRestore hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "BeforeHistorySave" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string BeforeHistorySave) "beforeHistorySave" "BeforeHistorySave event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal
|
||||||
|
(BeforeHistorySave.ToHxOnString())
|
||||||
|
"before-history-save"
|
||||||
|
"BeforeHistorySave hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "Load" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string Load) "load" "Load event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal (Load.ToHxOnString()) "load" "Load hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "NoSseSourceError" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string NoSseSourceError) "noSSESourceError" "NoSseSourceError event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal
|
||||||
|
(NoSseSourceError.ToHxOnString())
|
||||||
|
"no-sse-source-error"
|
||||||
|
"NoSseSourceError hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "OnLoadError" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string OnLoadError) "onLoadError" "OnLoadError event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal (OnLoadError.ToHxOnString()) "on-load-error" "OnLoadError hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "OobAfterSwap" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string OobAfterSwap) "oobAfterSwap" "OobAfterSwap event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal (OobAfterSwap.ToHxOnString()) "oob-after-swap" "OobAfterSwap hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "OobBeforeSwap" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string OobBeforeSwap) "oobBeforeSwap" "OobBeforeSwap event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal
|
||||||
|
(OobBeforeSwap.ToHxOnString()) "oob-before-swap" "OobBeforeSwap hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "OobErrorNoTarget" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string OobErrorNoTarget) "oobErrorNoTarget" "OobErrorNoTarget event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal
|
||||||
|
(OobErrorNoTarget.ToHxOnString())
|
||||||
|
"oob-error-no-target"
|
||||||
|
"OobErrorNoTarget hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "Prompt" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string Prompt) "prompt" "Prompt event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal (Prompt.ToHxOnString()) "prompt" "Prompt hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "PushedIntoHistory" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string PushedIntoHistory) "pushedIntoHistory" "PushedIntoHistory event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal
|
||||||
|
(PushedIntoHistory.ToHxOnString())
|
||||||
|
"pushed-into-history"
|
||||||
|
"PushedIntoHistory hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "ResponseError" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string ResponseError) "responseError" "ResponseError event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal
|
||||||
|
(ResponseError.ToHxOnString()) "response-error" "ResponseError hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "SendError" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string SendError) "sendError" "SendError event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal (SendError.ToHxOnString()) "send-error" "SendError hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "SseError" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string SseError) "sseError" "SseError event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal (SseError.ToHxOnString()) "sse-error" "SseError hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "SseOpen" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string SseOpen) "sseOpen" "SseOpen event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal (SseOpen.ToHxOnString()) "sse-open" "SseOpen hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "SwapError" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string SwapError) "swapError" "SwapError event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal (SwapError.ToHxOnString()) "swap-error" "SwapError hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "TargetError" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string TargetError) "targetError" "TargetError event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal (TargetError.ToHxOnString()) "target-error" "TargetError hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "Timeout" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string Timeout) "timeout" "Timeout event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal (Timeout.ToHxOnString()) "timeout" "Timeout hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "ValidationValidate" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal
|
||||||
|
(string ValidationValidate) "validation:validate" "ValidationValidate event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal
|
||||||
|
(ValidationValidate.ToHxOnString())
|
||||||
|
"validation:validate"
|
||||||
|
"ValidationValidate hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "ValidationFailed" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string ValidationFailed) "validation:failed" "ValidationFailed event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal
|
||||||
|
(ValidationFailed.ToHxOnString())
|
||||||
|
"validation:failed"
|
||||||
|
"ValidationFailed hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "ValidationHalted" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string ValidationHalted) "validation:halted" "ValidationHalted event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal
|
||||||
|
(ValidationHalted.ToHxOnString())
|
||||||
|
"validation:halted"
|
||||||
|
"ValidationHalted hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "XhrAbort" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string XhrAbort) "xhr:abort" "XhrAbort event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal (XhrAbort.ToHxOnString()) "xhr:abort" "XhrAbort hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "XhrLoadEnd" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string XhrLoadEnd) "xhr:loadend" "XhrLoadEnd event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal (XhrLoadEnd.ToHxOnString()) "xhr:loadend" "XhrLoadEnd hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "XhrLoadStart" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string XhrLoadStart) "xhr:loadstart" "XhrLoadStart event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal (XhrLoadStart.ToHxOnString()) "xhr:loadstart" "XhrLoadStart hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "XhrProgress" [
|
||||||
|
test "ToString succeeds" {
|
||||||
|
Expect.equal (string XhrProgress) "xhr:progress" "XhrProgress event name not correct"
|
||||||
|
}
|
||||||
|
test "ToHxOnString succeeds" {
|
||||||
|
Expect.equal (XhrProgress.ToHxOnString()) "xhr:progress" "XhrProgress hx-on event name not correct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
/// Tests for the HxHeaders module
|
/// Tests for the HxHeaders module
|
||||||
let hxHeaders =
|
let hxHeaders =
|
||||||
testList "HxHeaders" [
|
testList "HxHeaders" [
|
||||||
@@ -104,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
|
/// Tests for the HxTrigger module
|
||||||
let hxTrigger =
|
let hxTrigger =
|
||||||
testList "HxTrigger" [
|
testList "HxTrigger" [
|
||||||
@@ -243,6 +662,9 @@ let hxTrigger =
|
|||||||
test "succeeds when it is not the first modifier" {
|
test "succeeds when it is not the first modifier" {
|
||||||
Expect.equal (HxTrigger.Queue "def" "click") "click queue:def" "Queue modifier incorrect"
|
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" [
|
testList "QueueFirst" [
|
||||||
test "succeeds when it is the first modifier" {
|
test "succeeds when it is the first modifier" {
|
||||||
@@ -293,14 +715,14 @@ let hxVals =
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
/// Tests for the HtmxAttrs module
|
|
||||||
let attributes =
|
|
||||||
testList "Attributes" [
|
|
||||||
|
|
||||||
/// Pipe-able assertion for a rendered node
|
/// Pipe-able assertion for a rendered node
|
||||||
let shouldRender expected node =
|
let shouldRender expected node =
|
||||||
Expect.equal (RenderView.AsString.htmlNode node) expected "Rendered HTML incorrect"
|
Expect.equal (RenderView.AsString.htmlNode node) expected "Rendered HTML incorrect"
|
||||||
|
|
||||||
|
/// Tests for the HtmxAttrs module
|
||||||
|
let attributes =
|
||||||
|
testList "Attributes" [
|
||||||
|
|
||||||
test "_hxBoost succeeds" {
|
test "_hxBoost succeeds" {
|
||||||
div [ _hxBoost ] [] |> shouldRender """<div hx-boost="true"></div>"""
|
div [ _hxBoost ] [] |> shouldRender """<div hx-boost="true"></div>"""
|
||||||
}
|
}
|
||||||
@@ -313,6 +735,9 @@ let attributes =
|
|||||||
test "_hxDisable succeeds" {
|
test "_hxDisable succeeds" {
|
||||||
p [ _hxDisable ] [] |> shouldRender """<p hx-disable></p>"""
|
p [ _hxDisable ] [] |> shouldRender """<p hx-disable></p>"""
|
||||||
}
|
}
|
||||||
|
test "_hxDisabledElt succeeds" {
|
||||||
|
button [ _hxDisabledElt "this" ] [] |> shouldRender """<button hx-disabled-elt="this"></button>"""
|
||||||
|
}
|
||||||
test "_hxDisinherit succeeds" {
|
test "_hxDisinherit succeeds" {
|
||||||
strong [ _hxDisinherit "*" ] [] |> shouldRender """<strong hx-disinherit="*"></strong>"""
|
strong [ _hxDisinherit "*" ] [] |> shouldRender """<strong hx-disinherit="*"></strong>"""
|
||||||
}
|
}
|
||||||
@@ -330,7 +755,7 @@ let attributes =
|
|||||||
|> shouldRender """<figure hx-headers="{ "X-Special-Header": "some-header" }"></figure>"""
|
|> shouldRender """<figure hx-headers="{ "X-Special-Header": "some-header" }"></figure>"""
|
||||||
}
|
}
|
||||||
test "_hxHistory succeeds" {
|
test "_hxHistory succeeds" {
|
||||||
span [ _hxHistory "false" ] [] |> shouldRender """<span hx-history="false"></span>"""
|
span [ _hxHistory false ] [] |> shouldRender """<span hx-history="false"></span>"""
|
||||||
}
|
}
|
||||||
test "_hxHistoryElt succeeds" {
|
test "_hxHistoryElt succeeds" {
|
||||||
table [ _hxHistoryElt ] [] |> shouldRender """<table hx-history-elt></table>"""
|
table [ _hxHistoryElt ] [] |> shouldRender """<table hx-history-elt></table>"""
|
||||||
@@ -344,10 +769,12 @@ let attributes =
|
|||||||
test "_hxNoBoost succeeds" {
|
test "_hxNoBoost succeeds" {
|
||||||
td [ _hxNoBoost ] [] |> shouldRender """<td hx-boost="false"></td>"""
|
td [ _hxNoBoost ] [] |> shouldRender """<td hx-boost="false"></td>"""
|
||||||
}
|
}
|
||||||
test "_hxOn succeeds" {
|
test "_hxOnEvent succeeds" {
|
||||||
let newLine = "\n"
|
a [ _hxOnEvent "click" "doThis()" ] [] |> shouldRender """<a hx-on:click="doThis()"></a>"""
|
||||||
strong [ _hxOn "submit: alert('oops')\nclick: alert('howdy!')" ] []
|
}
|
||||||
|> shouldRender $"""<strong hx-on="submit: alert('oops'){newLine}click: alert('howdy!')"></strong>"""
|
test "_hxOnHxEvent succeeds" {
|
||||||
|
strong [ _hxOnHxEvent BeforeSwap "changeStuff()" ] []
|
||||||
|
|> shouldRender """<strong hx-on::before-swap="changeStuff()"></strong>"""
|
||||||
}
|
}
|
||||||
test "_hxParams succeeds" {
|
test "_hxParams succeeds" {
|
||||||
br [ _hxParams "[p1,p2]" ] |> shouldRender """<br hx-params="[p1,p2]">"""
|
br [ _hxParams "[p1,p2]" ] |> shouldRender """<br hx-params="[p1,p2]">"""
|
||||||
@@ -359,7 +786,7 @@ let attributes =
|
|||||||
hr [ _hxPost "/hear-ye-hear-ye" ] |> shouldRender """<hr hx-post="/hear-ye-hear-ye">"""
|
hr [ _hxPost "/hear-ye-hear-ye" ] |> shouldRender """<hr hx-post="/hear-ye-hear-ye">"""
|
||||||
}
|
}
|
||||||
test "_hxPreserve succeeds" {
|
test "_hxPreserve succeeds" {
|
||||||
img [ _hxPreserve ] |> shouldRender """<img hx-preserve="true">"""
|
img [ _hxPreserve ] |> shouldRender """<img hx-preserve>"""
|
||||||
}
|
}
|
||||||
test "_hxPrompt succeeds" {
|
test "_hxPrompt succeeds" {
|
||||||
strong [ _hxPrompt "Who goes there?" ] []
|
strong [ _hxPrompt "Who goes there?" ] []
|
||||||
@@ -383,10 +810,6 @@ let attributes =
|
|||||||
test "_hxSelectOob succeeds" {
|
test "_hxSelectOob succeeds" {
|
||||||
section [ _hxSelectOob "#oob" ] [] |> shouldRender """<section hx-select-oob="#oob"></section>"""
|
section [ _hxSelectOob "#oob" ] [] |> shouldRender """<section hx-select-oob="#oob"></section>"""
|
||||||
}
|
}
|
||||||
test "_hxSse succeeds" {
|
|
||||||
footer [ _hxSse "connect:/my-events" ] []
|
|
||||||
|> shouldRender """<footer hx-sse="connect:/my-events"></footer>"""
|
|
||||||
}
|
|
||||||
test "_hxSwap succeeds" {
|
test "_hxSwap succeeds" {
|
||||||
del [ _hxSwap "innerHTML" ] [] |> shouldRender """<del hx-swap="innerHTML"></del>"""
|
del [ _hxSwap "innerHTML" ] [] |> shouldRender """<del hx-swap="innerHTML"></del>"""
|
||||||
}
|
}
|
||||||
@@ -398,7 +821,8 @@ let attributes =
|
|||||||
li [ _hxSwapOob "true" ] [] |> shouldRender """<li hx-swap-oob="true"></li>"""
|
li [ _hxSwapOob "true" ] [] |> shouldRender """<li hx-swap-oob="true"></li>"""
|
||||||
}
|
}
|
||||||
test "_hxSync succeeds" {
|
test "_hxSync succeeds" {
|
||||||
nav [ _hxSync "closest form:abort" ] [] |> shouldRender """<nav hx-sync="closest form:abort"></nav>"""
|
nav [ _hxSync "closest form" HxSync.Abort ] []
|
||||||
|
|> shouldRender """<nav hx-sync="closest form:abort"></nav>"""
|
||||||
}
|
}
|
||||||
test "_hxTarget succeeds" {
|
test "_hxTarget succeeds" {
|
||||||
header [ _hxTarget "#somewhereElse" ] [] |> shouldRender """<header hx-target="#somewhereElse"></header>"""
|
header [ _hxTarget "#somewhereElse" ] [] |> shouldRender """<header hx-target="#somewhereElse"></header>"""
|
||||||
@@ -410,27 +834,39 @@ let attributes =
|
|||||||
dt [ _hxVals """{ "extra": "values" }""" ] []
|
dt [ _hxVals """{ "extra": "values" }""" ] []
|
||||||
|> shouldRender """<dt hx-vals="{ "extra": "values" }"></dt>"""
|
|> shouldRender """<dt hx-vals="{ "extra": "values" }"></dt>"""
|
||||||
}
|
}
|
||||||
test "_hxWs succeeds" {
|
test "_sseSwap succeeds" {
|
||||||
ul [ _hxWs "connect:/web-socket" ] [] |> shouldRender """<ul hx-ws="connect:/web-socket"></ul>"""
|
ul [ _sseSwap "sseMessageName" ] [] |> shouldRender """<ul sse-swap="sseMessageName"></ul>"""
|
||||||
|
}
|
||||||
|
test "_sseConnect succeeds" {
|
||||||
|
div [ _sseConnect "/gps/sse" ] [] |> shouldRender """<div sse-connect="/gps/sse"></div>"""
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
open Giraffe.Htmx.Common
|
||||||
|
|
||||||
/// Tests for the Script module
|
/// Tests for the Script module
|
||||||
let script =
|
let script =
|
||||||
testList "Script" [
|
testList "Script" [
|
||||||
test "minified succeeds" {
|
test "local succeeds" {
|
||||||
let html = RenderView.AsString.htmlNode Script.minified
|
let html = RenderView.AsString.htmlNode Script.local
|
||||||
Expect.equal
|
Expect.equal
|
||||||
html
|
html
|
||||||
"""<script src="https://unpkg.com/htmx.org@1.9.2" integrity="sha384-L6OqL9pRWyyFU3+/bjdSri+iIphTN/bvYyM37tICVyOJkWZLpP2vGn6VUEXgzg6h" crossorigin="anonymous"></script>"""
|
$"""<script src="/_content/Giraffe.Htmx.Common/htmx.min.js?ver={HtmxVersion}"></script>"""
|
||||||
"Minified script tag is incorrect"
|
"Local script tag is incorrect"
|
||||||
}
|
}
|
||||||
test "unminified succeeds" {
|
test "cdnMinified succeeds" {
|
||||||
let html = RenderView.AsString.htmlNode Script.unminified
|
let html = RenderView.AsString.htmlNode Script.cdnMinified
|
||||||
Expect.equal
|
Expect.equal
|
||||||
html
|
html
|
||||||
"""<script src="https://unpkg.com/htmx.org@1.9.2/dist/htmx.js" integrity="sha384-8ADa8Z9mz9MtKHmL1vCmqgEM5Q7eIiradzjEpmeELJ4kex3ibvmnctBNHlpLr/Zl" crossorigin="anonymous"></script>"""
|
$"""<script src="https://cdn.jsdelivr.net/npm/htmx.org@{HtmxVersion}/dist/htmx.min.js" integrity="sha384-H5SrcfygHmAuTDZphMHqBJLc3FhssKjG7w/CeCpFReSfwBWDTKpkzPP8c+cLsK+V" crossorigin="anonymous"></script>"""
|
||||||
"Unminified script tag is incorrect"
|
"CDN minified script tag is incorrect"
|
||||||
|
}
|
||||||
|
test "cdnUnminified succeeds" {
|
||||||
|
let html = RenderView.AsString.htmlNode Script.cdnUnminified
|
||||||
|
Expect.equal
|
||||||
|
html
|
||||||
|
$"""<script src="https://cdn.jsdelivr.net/npm/htmx.org@{HtmxVersion}/dist/htmx.js" integrity="sha384-Q+Dky3iHVJOr6wUjQ4ulh6uQ76an/t+ak1+PjMVaxRjbZamFLAG+u9InkfjbsEQf" crossorigin="anonymous"></script>"""
|
||||||
|
"CDN unminified script tag is incorrect"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -583,5 +1019,16 @@ let renderFragment =
|
|||||||
|
|
||||||
/// All tests in this module
|
/// All tests in this module
|
||||||
let allTests =
|
let allTests =
|
||||||
testList "ViewEngine.Htmx"
|
testList "ViewEngine.Htmx" [
|
||||||
[ hxEncoding; hxHeaders; hxParams; hxRequest; hxTrigger; hxVals; attributes; script; renderFragment ]
|
hxEncoding
|
||||||
|
hxEvent
|
||||||
|
hxHeaders
|
||||||
|
hxParams
|
||||||
|
hxRequest
|
||||||
|
hxSync
|
||||||
|
hxTrigger
|
||||||
|
hxVals
|
||||||
|
attributes
|
||||||
|
script
|
||||||
|
renderFragment
|
||||||
|
]
|
||||||
|
|||||||
@@ -4,15 +4,18 @@
|
|||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<Description>Extensions to Giraffe View Engine to support htmx attributes and their values</Description>
|
<Description>Extensions to Giraffe View Engine to support htmx attributes and their values</Description>
|
||||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
|
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Htmx.fs" />
|
<Compile Include="Htmx.fs" />
|
||||||
<None Include="README.md" Pack="true" PackagePath="\" />
|
<None Include="README.md" Pack="true" PackagePath="\" />
|
||||||
|
<None Include="..\icon.png" Pack="true" PackagePath="\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Giraffe.ViewEngine" Version="1.4.0" />
|
<PackageReference Include="Giraffe.ViewEngine" Version="1.4.0" />
|
||||||
|
<PackageReference Update="FSharp.Core" Version="6.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
+705
-175
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
This package enables [htmx](https://htmx.org) support within the [Giraffe](https://giraffe.wiki) view engine.
|
This package enables [htmx](https://htmx.org) support within the [Giraffe](https://giraffe.wiki) view engine.
|
||||||
|
|
||||||
**htmx version: 1.9.2**
|
**htmx version: 2.0.10**
|
||||||
|
|
||||||
|
_Upgrading from v1.x: see [the migration guide](https://htmx.org/migration-guide-htmx-1/) for changes_
|
||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
|
|
||||||
@@ -27,7 +29,7 @@ Support modules include:
|
|||||||
- `HxTrigger`
|
- `HxTrigger`
|
||||||
- `HxVals`
|
- `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).
|
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).
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Executable
+7
@@ -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