Add _hxValidate, fragment rendering
Also bump version to 1.8.4
This commit is contained in:
parent
e0c567098d
commit
bb2df73175
|
@ -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**
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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>– ID {nodeId} not found –</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)
|
||||||
|
|
|
@ -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>– ID {nodeId} not found –</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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user