From 9efac866ed1ef8d072bee7a91586a4ba3861befa Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Thu, 14 Jul 2022 08:52:47 -0400 Subject: [PATCH] Implement HX-Reswap header (#5) - Rework project structure to add common project --- .../Giraffe.Htmx.Common.Tests.fsproj | 30 +++++++++++ src/Common.Tests/Program.fs | 1 + src/Common.Tests/Tests.fs | 35 +++++++++++++ src/Common/Common.fs | 28 ++++++++++ src/Common/Giraffe.Htmx.Common.fsproj | 11 ++++ src/Giraffe.Htmx.sln | 52 +++++++++++++++++++ src/Htmx.Tests/Tests.fs | 11 ++++ src/Htmx/Giraffe.Htmx.fsproj | 4 ++ src/Htmx/Htmx.fs | 8 ++- src/ViewEngine.Htmx.Tests/Tests.fs | 32 ------------ .../Giraffe.ViewEngine.Htmx.fsproj | 4 ++ src/ViewEngine.Htmx/Htmx.fs | 26 ---------- 12 files changed, 182 insertions(+), 60 deletions(-) create mode 100644 src/Common.Tests/Giraffe.Htmx.Common.Tests.fsproj create mode 100644 src/Common.Tests/Program.fs create mode 100644 src/Common.Tests/Tests.fs create mode 100644 src/Common/Common.fs create mode 100644 src/Common/Giraffe.Htmx.Common.fsproj create mode 100644 src/Giraffe.Htmx.sln diff --git a/src/Common.Tests/Giraffe.Htmx.Common.Tests.fsproj b/src/Common.Tests/Giraffe.Htmx.Common.Tests.fsproj new file mode 100644 index 0000000..58aa294 --- /dev/null +++ b/src/Common.Tests/Giraffe.Htmx.Common.Tests.fsproj @@ -0,0 +1,30 @@ + + + + false + false + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/src/Common.Tests/Program.fs b/src/Common.Tests/Program.fs new file mode 100644 index 0000000..fdc31cd --- /dev/null +++ b/src/Common.Tests/Program.fs @@ -0,0 +1 @@ +module Program = let [] main _ = 0 diff --git a/src/Common.Tests/Tests.fs b/src/Common.Tests/Tests.fs new file mode 100644 index 0000000..4172698 --- /dev/null +++ b/src/Common.Tests/Tests.fs @@ -0,0 +1,35 @@ +module Tests + +open Giraffe.Htmx +open Xunit + +/// Tests for the HxSwap module +module Swap = + + [] + let ``InnerHtml is correct`` () = + Assert.Equal ("innerHTML", HxSwap.InnerHtml) + + [] + let ``OuterHtml is correct`` () = + Assert.Equal ("outerHTML", HxSwap.OuterHtml) + + [] + let ``BeforeBegin is correct`` () = + Assert.Equal ("beforebegin", HxSwap.BeforeBegin) + + [] + let ``BeforeEnd is correct`` () = + Assert.Equal ("beforeend", HxSwap.BeforeEnd) + + [] + let ``AfterBegin is correct`` () = + Assert.Equal ("afterbegin", HxSwap.AfterBegin) + + [] + let ``AfterEnd is correct`` () = + Assert.Equal ("afterend", HxSwap.AfterEnd) + + [] + let ``None is correct`` () = + Assert.Equal ("none", HxSwap.None) diff --git a/src/Common/Common.fs b/src/Common/Common.fs new file mode 100644 index 0000000..9a75b8c --- /dev/null +++ b/src/Common/Common.fs @@ -0,0 +1,28 @@ +/// Common definitions shared between attribute values and response headers +[] +module Giraffe.Htmx.Common + +/// Valid values for the `hx-swap` attribute / `HX-Reswap` header (may be combined with swap/settle/scroll/show config) +[] +module HxSwap = + + /// The default, replace the inner html of the target element + let InnerHtml = "innerHTML" + + /// Replace the entire target element with the response + let OuterHtml = "outerHTML" + + /// Insert the response before the target element + let BeforeBegin = "beforebegin" + + /// Insert the response before the first child of the target element + let AfterBegin = "afterbegin" + + /// Insert the response after the last child of the target element + let BeforeEnd = "beforeend" + + /// Insert the response after the target element + let AfterEnd = "afterend" + + /// Does not append content from response (out of band items will still be processed). + let None = "none" diff --git a/src/Common/Giraffe.Htmx.Common.fsproj b/src/Common/Giraffe.Htmx.Common.fsproj new file mode 100644 index 0000000..112a122 --- /dev/null +++ b/src/Common/Giraffe.Htmx.Common.fsproj @@ -0,0 +1,11 @@ + + + + true + + + + + + + diff --git a/src/Giraffe.Htmx.sln b/src/Giraffe.Htmx.sln new file mode 100644 index 0000000..c1ebafb --- /dev/null +++ b/src/Giraffe.Htmx.sln @@ -0,0 +1,52 @@ + +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.Htmx.Tests", "Htmx.Tests\Giraffe.Htmx.Tests.fsproj", "{D7CDD578-7A6F-4EF6-846A-80A55037E049}" +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.ViewEngine.Htmx.Tests", "ViewEngine.Htmx.Tests\Giraffe.ViewEngine.Htmx.Tests.fsproj", "{F21C28CE-1F18-4CB0-B2F7-10DABE84FB78}" +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}") = "Giraffe.Htmx.Common.Tests", "Common.Tests\Giraffe.Htmx.Common.Tests.fsproj", "{E261A653-68D5-4D7B-99A4-F09282B50F8A}" +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 + {D7CDD578-7A6F-4EF6-846A-80A55037E049}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7CDD578-7A6F-4EF6-846A-80A55037E049}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7CDD578-7A6F-4EF6-846A-80A55037E049}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7CDD578-7A6F-4EF6-846A-80A55037E049}.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 + {F21C28CE-1F18-4CB0-B2F7-10DABE84FB78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F21C28CE-1F18-4CB0-B2F7-10DABE84FB78}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F21C28CE-1F18-4CB0-B2F7-10DABE84FB78}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F21C28CE-1F18-4CB0-B2F7-10DABE84FB78}.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 + {E261A653-68D5-4D7B-99A4-F09282B50F8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E261A653-68D5-4D7B-99A4-F09282B50F8A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E261A653-68D5-4D7B-99A4-F09282B50F8A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E261A653-68D5-4D7B-99A4-F09282B50F8A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/src/Htmx.Tests/Tests.fs b/src/Htmx.Tests/Tests.fs index d123f8b..213ac70 100644 --- a/src/Htmx.Tests/Tests.fs +++ b/src/Htmx.Tests/Tests.fs @@ -283,6 +283,17 @@ module HandlerTests = Assert.Equal ("false", dic["HX-Replace-Url"][0]) } + [] + let ``withHxReswap succeeds`` () = + let ctx = Substitute.For () + let dic = HeaderDictionary () + ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore + task { + let! _ = withHxReswap HxSwap.BeforeEnd next ctx + Assert.True (dic.ContainsKey "HX-Reswap") + Assert.Equal (HxSwap.BeforeEnd, dic["HX-Reswap"][0]) + } + [] let ``withHxRetarget succeeds`` () = let ctx = Substitute.For () diff --git a/src/Htmx/Giraffe.Htmx.fsproj b/src/Htmx/Giraffe.Htmx.fsproj index cf71072..09928ad 100644 --- a/src/Htmx/Giraffe.Htmx.fsproj +++ b/src/Htmx/Giraffe.Htmx.fsproj @@ -15,4 +15,8 @@ + + + + diff --git a/src/Htmx/Htmx.fs b/src/Htmx/Htmx.fs index e9324a2..ff7f1e0 100644 --- a/src/Htmx/Htmx.fs +++ b/src/Htmx/Htmx.fs @@ -6,7 +6,7 @@ open System /// Determine if the given header is present let private hdr (headers : IHeaderDictionary) hdr = - match headers.[hdr] with it when it = StringValues.Empty -> None | it -> Some it[0] + match headers[hdr] with it when it = StringValues.Empty -> None | it -> Some it[0] /// Extensions to the header dictionary type IHeaderDictionary with @@ -93,7 +93,11 @@ module Handlers = /// Explicitly do not replace the current URL in the history stack let withHxNoReplaceUrl : HttpHandler = toLowerBool false |> withHxReplaceUrl - + + /// Override the `hx-swap` attribute from the initiating element + let withHxReswap : string -> HttpHandler = + setHttpHeader "HX-Reswap" + /// Allows you to override the `hx-target` attribute let withHxRetarget : string -> HttpHandler = setHttpHeader "HX-Retarget" diff --git a/src/ViewEngine.Htmx.Tests/Tests.fs b/src/ViewEngine.Htmx.Tests/Tests.fs index 8d50cfe..c9a4a5b 100644 --- a/src/ViewEngine.Htmx.Tests/Tests.fs +++ b/src/ViewEngine.Htmx.Tests/Tests.fs @@ -101,38 +101,6 @@ module Request = Assert.Equal ("\"noHeaders\": false", HxRequest.NoHeaders false) -/// Tests for the HxSwap module -module Swap = - - [] - let ``InnerHtml is correct`` () = - Assert.Equal ("innerHTML", HxSwap.InnerHtml) - - [] - let ``OuterHtml is correct`` () = - Assert.Equal ("outerHTML", HxSwap.OuterHtml) - - [] - let ``BeforeBegin is correct`` () = - Assert.Equal ("beforebegin", HxSwap.BeforeBegin) - - [] - let ``BeforeEnd is correct`` () = - Assert.Equal ("beforeend", HxSwap.BeforeEnd) - - [] - let ``AfterBegin is correct`` () = - Assert.Equal ("afterbegin", HxSwap.AfterBegin) - - [] - let ``AfterEnd is correct`` () = - Assert.Equal ("afterend", HxSwap.AfterEnd) - - [] - let ``None is correct`` () = - Assert.Equal ("none", HxSwap.None) - - /// Tests for the HxTrigger module module Trigger = diff --git a/src/ViewEngine.Htmx/Giraffe.ViewEngine.Htmx.fsproj b/src/ViewEngine.Htmx/Giraffe.ViewEngine.Htmx.fsproj index 19f5cd9..665b85c 100644 --- a/src/ViewEngine.Htmx/Giraffe.ViewEngine.Htmx.fsproj +++ b/src/ViewEngine.Htmx/Giraffe.ViewEngine.Htmx.fsproj @@ -15,4 +15,8 @@ + + + + diff --git a/src/ViewEngine.Htmx/Htmx.fs b/src/ViewEngine.Htmx/Htmx.fs index 706ccc8..51a948d 100644 --- a/src/ViewEngine.Htmx/Htmx.fs +++ b/src/ViewEngine.Htmx/Htmx.fs @@ -68,32 +68,6 @@ module HxRequest = let NoHeaders = toLowerBool >> sprintf "\"noHeaders\": %s" -/// Valid values for the `hx-swap` attribute (may be combined with swap/settle/scroll/show config) -[] -module HxSwap = - - /// The default, replace the inner html of the target element - let InnerHtml = "innerHTML" - - /// Replace the entire target element with the response - let OuterHtml = "outerHTML" - - /// Insert the response before the target element - let BeforeBegin = "beforebegin" - - /// Insert the response before the first child of the target element - let AfterBegin = "afterbegin" - - /// Insert the response after the last child of the target element - let BeforeEnd = "beforeend" - - /// Insert the response after the target element - let AfterEnd = "afterend" - - /// Does not append content from response (out of band items will still be processed). - let None = "none" - - /// Helpers for the `hx-trigger` attribute [] module HxTrigger =