Add _hxValidate, fragment rendering

Also bump version to 1.8.4
This commit is contained in:
Daniel J. Summers 2022-11-24 10:50:08 -05:00
parent e0c567098d
commit bb2df73175
6 changed files with 223 additions and 11 deletions

View File

@ -2,4 +2,4 @@
This package contains common code shared between [`Giraffe.Htmx`](https://www.nuget.org/packages/Giraffe.Htmx) and [`Giraffe.ViewEngine.Htmx`](https://www.nuget.org/packages/Giraffe.ViewEngine.Htmx), and will be automatically installed when you install either one. This package contains common code shared between [`Giraffe.Htmx`](https://www.nuget.org/packages/Giraffe.Htmx) and [`Giraffe.ViewEngine.Htmx`](https://www.nuget.org/packages/Giraffe.ViewEngine.Htmx), and will be automatically installed when you install either one.
**htmx version: 1.8.0** **htmx version: 1.8.4**

View File

@ -2,7 +2,7 @@
<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>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<VersionPrefix>1.8.0</VersionPrefix> <VersionPrefix>1.8.4</VersionPrefix>
<PackageReleaseNotes>Support new attributes/headers in htmx 1.8.0</PackageReleaseNotes> <PackageReleaseNotes>Support new attributes/headers in htmx 1.8.0</PackageReleaseNotes>
<Authors>danieljsummers</Authors> <Authors>danieljsummers</Authors>
<Company>Bit Badger Solutions</Company> <Company>Bit Badger Solutions</Company>

View File

@ -2,7 +2,7 @@
This package enables server-side support for [htmx](https://htmx.org) within [Giraffe](https://giraffe.wiki) and ASP.NET's `HttpContext`. This package enables server-side support for [htmx](https://htmx.org) within [Giraffe](https://giraffe.wiki) and ASP.NET's `HttpContext`.
**htmx version: 1.8.0** **htmx version: 1.8.4**
### Setup ### Setup

View File

@ -434,12 +434,146 @@ module Script =
let ``Script.minified succeeds`` () = let ``Script.minified succeeds`` () =
let html = RenderView.AsString.htmlNode Script.minified let html = RenderView.AsString.htmlNode Script.minified
Assert.Equal Assert.Equal
("""<script src="https://unpkg.com/htmx.org@1.8.0" integrity="sha384-cZuAZ+ZbwkNRnrKi05G/fjBX+azI9DNOkNYysZ0I/X5ZFgsmMiBXgDZof30F5ofc" crossorigin="anonymous"></script>""", ("""<script src="https://unpkg.com/htmx.org@1.8.4" integrity="sha384-wg5Y/JwF7VxGk4zLsJEcAojRtlVp1FKKdGy1qN+OMtdq72WRvX/EdRdqg/LOhYeV" crossorigin="anonymous"></script>""",
html) html)
[<Fact>] [<Fact>]
let ``Script.unminified succeeds`` () = let ``Script.unminified succeeds`` () =
let html = RenderView.AsString.htmlNode Script.unminified let html = RenderView.AsString.htmlNode Script.unminified
Assert.Equal Assert.Equal
("""<script src="https://unpkg.com/htmx.org@1.8.0/dist/htmx.js" integrity="sha384-mrsv860ohrJ5KkqRxwXXj6OIT6sONUxOd+1kvbqW351hQd7JlfFnM0tLetA76GU0" crossorigin="anonymous"></script>""", ("""<script src="https://unpkg.com/htmx.org@1.8.4/dist/htmx.js" integrity="sha384-sh63gh7zpjxu153RyKJ06Oy5HxIVl6cchze/dJOHulOI7u0sGZoC/CfQJHPODhFn" crossorigin="anonymous"></script>""",
html) html)
/// Tests for the RenderFragment module
module RenderFragment =
open System.Text
[<Fact>]
let ``RenderFragment.findIdNode fails with a Text node`` () =
Assert.False (Option.isSome (RenderFragment.findIdNode "blue" (Text "")))
[<Fact>]
let ``RenderFragment.findIdNode fails with a VoidElement without a matching ID`` () =
Assert.False (Option.isSome (RenderFragment.findIdNode "purple" (br [ _id "mauve" ])))
[<Fact>]
let ``RenderFragment.findIdNode fails with a ParentNode with no children with a matching ID`` () =
Assert.False (Option.isSome (RenderFragment.findIdNode "green" (p [] [ str "howdy"; span [] [ str "huh" ] ])))
[<Fact>]
let ``RenderFragment.findIdNode succeeds with a VoidElement with a matching ID`` () =
let leNode = hr [ _id "groovy" ]
let foundNode = RenderFragment.findIdNode "groovy" leNode
Assert.True (Option.isSome foundNode)
Assert.Same (leNode, foundNode.Value)
[<Fact>]
let ``RenderFragment.findIdNode succeeds with a ParentNode with a child with a matching ID`` () =
let leNode = span [ _id "its-me" ] [ str "Mario" ]
let foundNode = RenderFragment.findIdNode "its-me" (p [] [ str "test"; str "again"; leNode; str "un mas" ])
Assert.True (Option.isSome foundNode)
Assert.Same (leNode, foundNode.Value)
/// Generate a message if the requested ID node is not found
let private nodeNotFound (nodeId : string) =
$"<em>&ndash; ID {nodeId} not found &ndash;</em>"
/// Tests for the AsString module
module AsString =
[<Fact>]
let ``RenderFragment.AsString.htmlFromNodes succeeds when an ID is matched`` () =
let html =
RenderFragment.AsString.htmlFromNodes "needle"
[ p [] []; p [ _id "haystack" ] [ span [ _id "needle" ] [ str "ouch" ]; str "hay"; str "hay" ]]
Assert.Equal ("""<span id="needle">ouch</span>""", html)
[<Fact>]
let ``RenderFragment.AsString.htmlFromNodes fails when an ID is not matched`` () =
Assert.Equal (nodeNotFound "oops", RenderFragment.AsString.htmlFromNodes "oops" [])
[<Fact>]
let ``RenderFragment.AsString.htmlFromNode succeeds when ID is matched at top level`` () =
let html = RenderFragment.AsString.htmlFromNode "wow" (p [ _id "wow" ] [ str "found it" ])
Assert.Equal ("""<p id="wow">found it</p>""", html)
[<Fact>]
let ``RenderFragment.AsString.htmlFromNode succeeds when ID is matched in child element`` () =
let html =
div [] [ p [] [ str "not it" ]; p [ _id "hey" ] [ str "ta-da" ]]
|> RenderFragment.AsString.htmlFromNode "hey"
Assert.Equal ("""<p id="hey">ta-da</p>""", html)
[<Fact>]
let ``RenderFragment.AsString.htmlFromNode fails when an ID is not matched`` () =
Assert.Equal (nodeNotFound "me", RenderFragment.AsString.htmlFromNode "me" (hr []))
/// Tests for the AsBytes module
module AsBytes =
/// Alias for UTF-8 encoding
let private utf8 = Encoding.UTF8
[<Fact>]
let ``RenderFragment.AsBytes.htmlFromNodes succeeds when an ID is matched`` () =
let bytes =
RenderFragment.AsBytes.htmlFromNodes "found"
[ p [] []; p [ _id "not-it" ] [ str "nope"; span [ _id "found" ] [ str "boo" ]; str "nope" ]]
Assert.Equal<byte> (utf8.GetBytes """<span id="found">boo</span>""", bytes)
[<Fact>]
let ``RenderFragment.AsBytes.htmlFromNodes fails when an ID is not matched`` () =
Assert.Equal<byte> (utf8.GetBytes (nodeNotFound "whiff"), RenderFragment.AsBytes.htmlFromNodes "whiff" [])
[<Fact>]
let ``RenderFragment.AsBytes.htmlFromNode succeeds when ID is matched at top level`` () =
let bytes = RenderFragment.AsBytes.htmlFromNode "first" (p [ _id "first" ] [ str "!!!" ])
Assert.Equal<byte> (utf8.GetBytes """<p id="first">!!!</p>""", bytes)
[<Fact>]
let ``RenderFragment.AsBytes.htmlFromNode succeeds when ID is matched in child element`` () =
let bytes =
div [] [ p [] [ str "not me" ]; p [ _id "child" ] [ str "node" ]]
|> RenderFragment.AsBytes.htmlFromNode "child"
Assert.Equal<byte> (utf8.GetBytes """<p id="child">node</p>""", bytes)
[<Fact>]
let ``RenderFragment.AsBytes.htmlFromNode fails when an ID is not matched`` () =
Assert.Equal<byte> (utf8.GetBytes (nodeNotFound "foo"), RenderFragment.AsBytes.htmlFromNode "foo" (hr []))
/// Tests for the IntoStringBuilder module
module IntoStringBuilder =
[<Fact>]
let ``RenderFragment.IntoStringBuilder.htmlFromNodes succeeds when an ID is matched`` () =
let sb = StringBuilder ()
RenderFragment.IntoStringBuilder.htmlFromNodes sb "find-me"
[ p [] []; p [ _id "peekaboo" ] [ str "bzz"; str "nope"; span [ _id "find-me" ] [ str ";)" ] ]]
Assert.Equal ("""<span id="find-me">;)</span>""", string sb)
[<Fact>]
let ``RenderFragment.IntoStringBuilder.htmlFromNodes fails when an ID is not matched`` () =
let sb = StringBuilder ()
RenderFragment.IntoStringBuilder.htmlFromNodes sb "missing" []
Assert.Equal (nodeNotFound "missing", string sb)
[<Fact>]
let ``RenderFragment.IntoStringBuilder.htmlFromNode succeeds when ID is matched at top level`` () =
let sb = StringBuilder ()
RenderFragment.IntoStringBuilder.htmlFromNode sb "top" (p [ _id "top" ] [ str "pinnacle" ])
Assert.Equal ("""<p id="top">pinnacle</p>""", string sb)
[<Fact>]
let ``RenderFragment.IntoStringBuilder.htmlFromNode succeeds when ID is matched in child element`` () =
let sb = StringBuilder ()
div [] [ p [] [ str "nada" ]; p [ _id "it" ] [ str "is here" ]]
|> RenderFragment.IntoStringBuilder.htmlFromNode sb "it"
Assert.Equal ("""<p id="it">is here</p>""", string sb)
[<Fact>]
let ``RenderFragment.IntoStringBuilder.htmlFromNode fails when an ID is not matched`` () =
let sb = StringBuilder ()
RenderFragment.IntoStringBuilder.htmlFromNode sb "bar" (hr [])
Assert.Equal (nodeNotFound "bar", string sb)

View File

@ -1,4 +1,4 @@
module Giraffe.ViewEngine.Htmx module Giraffe.ViewEngine.Htmx
/// Serialize a list of key/value pairs to JSON (very rudimentary) /// Serialize a list of key/value pairs to JSON (very rudimentary)
let private toJson (kvps : (string * string) list) = let private toJson (kvps : (string * string) list) =
@ -271,6 +271,9 @@ module HtmxAttrs =
/// Specifies the event that triggers the request /// Specifies the event that triggers the request
let _hxTrigger = attr "hx-trigger" let _hxTrigger = attr "hx-trigger"
/// Validate an input element (uses HTML5 validation API)
let _hxValidate = flag "hx-validate"
/// Adds to the parameters that will be submitted with the request /// Adds to the parameters that will be submitted with the request
let _hxVals = attr "hx-vals" let _hxVals = attr "hx-vals"
@ -283,12 +286,87 @@ module Script =
/// Script tag to load the minified version from unpkg.com /// Script tag to load the minified version from unpkg.com
let minified = let minified =
script [ _src "https://unpkg.com/htmx.org@1.8.0" script [ _src "https://unpkg.com/htmx.org@1.8.4"
_integrity "sha384-cZuAZ+ZbwkNRnrKi05G/fjBX+azI9DNOkNYysZ0I/X5ZFgsmMiBXgDZof30F5ofc" _integrity "sha384-wg5Y/JwF7VxGk4zLsJEcAojRtlVp1FKKdGy1qN+OMtdq72WRvX/EdRdqg/LOhYeV"
_crossorigin "anonymous" ] [] _crossorigin "anonymous" ] []
/// Script tag to load the unminified version from unpkg.com /// Script tag to load the unminified version from unpkg.com
let unminified = let unminified =
script [ _src "https://unpkg.com/htmx.org@1.8.0/dist/htmx.js" script [ _src "https://unpkg.com/htmx.org@1.8.4/dist/htmx.js"
_integrity "sha384-mrsv860ohrJ5KkqRxwXXj6OIT6sONUxOd+1kvbqW351hQd7JlfFnM0tLetA76GU0" _integrity "sha384-sh63gh7zpjxu153RyKJ06Oy5HxIVl6cchze/dJOHulOI7u0sGZoC/CfQJHPODhFn"
_crossorigin "anonymous" ] [] _crossorigin "anonymous" ] []
/// Functions to extract and render an HTML fragment from a document
[<RequireQualifiedAccess>]
module RenderFragment =
/// Does this element have an ID matching the requested ID name?
let private isIdElement nodeId (elt : XmlElement) =
snd elt
|> Array.exists (fun attr ->
match attr with
| KeyValue (name, value) -> name = "id" && value = nodeId
| Boolean _ -> false)
/// Generate a message if the requested ID node is not found
let private nodeNotFound (nodeId : string) =
$"<em>&ndash; ID {nodeId} not found &ndash;</em>"
/// Find the node with the named ID
let rec findIdNode nodeId (node : XmlNode) : XmlNode option =
match node with
| Text _ -> None
| VoidElement elt -> if isIdElement nodeId elt then Some node else None
| ParentNode (elt, children) ->
if isIdElement nodeId elt then Some node else children |> List.tryPick (fun c -> findIdNode nodeId c)
/// Functions to render a fragment as a string
[<RequireQualifiedAccess>]
module AsString =
/// Render to HTML for the given ID
let htmlFromNodes nodeId (nodes : XmlNode list) =
match nodes |> List.tryPick(fun node -> findIdNode nodeId node) with
| Some idNode -> RenderView.AsString.htmlNode idNode
| None -> nodeNotFound nodeId
/// Render to HTML for the given ID
let htmlFromNode nodeId node =
match findIdNode nodeId node with
| Some idNode -> RenderView.AsString.htmlNode idNode
| None -> nodeNotFound nodeId
/// Functions to render a fragment as bytes
[<RequireQualifiedAccess>]
module AsBytes =
let private utf8 = System.Text.Encoding.UTF8
/// Render to HTML for the given ID
let htmlFromNodes nodeId (nodes : XmlNode list) =
match nodes |> List.tryPick(fun node -> findIdNode nodeId node) with
| Some idNode -> RenderView.AsBytes.htmlNode idNode
| None -> nodeNotFound nodeId |> utf8.GetBytes
/// Render to HTML for the given ID
let htmlFromNode nodeId node =
match findIdNode nodeId node with
| Some idNode -> RenderView.AsBytes.htmlNode idNode
| None -> nodeNotFound nodeId |> utf8.GetBytes
/// Functions to render a fragment into a StringBuilder
[<RequireQualifiedAccess>]
module IntoStringBuilder =
/// Render to HTML for the given ID
let htmlFromNodes sb nodeId (nodes : XmlNode list) =
match nodes |> List.tryPick(fun node -> findIdNode nodeId node) with
| Some idNode -> RenderView.IntoStringBuilder.htmlNode sb idNode
| None -> nodeNotFound nodeId |> sb.Append |> ignore
/// Render to HTML for the given ID
let htmlFromNode sb nodeId node =
match findIdNode nodeId node with
| Some idNode -> RenderView.IntoStringBuilder.htmlNode sb idNode
| None -> nodeNotFound nodeId |> sb.Append |> ignore

View File

@ -2,7 +2,7 @@
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.8.0** **htmx version: 1.8.4**
### Setup ### Setup