diff --git a/docs/articles/tag-helpers.md b/docs/articles/tag-helpers.md index 8ec994d..04fc13d 100644 --- a/docs/articles/tag-helpers.md +++ b/docs/articles/tag-helpers.md @@ -245,7 +245,7 @@ Use `hx-request-*` attributes rather than hand-writing version-specific JSON: asp-page="/Reports" asp-page-handler="Preview" hx-request-timeout="2000" - hx-request-credentials="@HtmxRequestCredentials.SameOrigin" + hx-request-credentials="SameOrigin" hx-target="#preview"> Generate preview @@ -275,6 +275,11 @@ The output depends on the configured HTMX target: Unsupported properties are omitted from the generated JSON. In particular, `HtmxRequestCredentials.Omit` cannot be represented by HTMX 1.9.x or 2.x and is omitted for those targets. +Enum attributes take a member name, such as `hx-request-credentials="Include"`. A C# expression starts with `@`, +for example `hx-request-credentials="@credentials"`; `HtmxRequestCredentials.Include` without `@` does not compile. +`hx-request-no-headers` and `hx-request-validate` can be written without a value, which means `true`. +To leave an option unset, omit its attribute; these attributes do not accept `null`. + An element with `hx-request-*` attributes cannot also have a hand-written `hx-request` for HTMX 1.9.x and 2.x or `hx-config` for HTMX 4.x, with or without modifiers or the `data-hx-` prefix. The Tag Helper throws `InvalidOperationException` rather than lose one of the values. @@ -283,8 +288,8 @@ Use `HtmxRequestCache` and `HtmxRequestRedirect` for the HTMX 4.x Fetch options: ```razor ``` diff --git a/samples/Ramstack.HtmxToolkit.Demo/Pages/Examples/RequestOptions.cshtml b/samples/Ramstack.HtmxToolkit.Demo/Pages/Examples/RequestOptions.cshtml index 0617195..8d019af 100644 --- a/samples/Ramstack.HtmxToolkit.Demo/Pages/Examples/RequestOptions.cshtml +++ b/samples/Ramstack.HtmxToolkit.Demo/Pages/Examples/RequestOptions.cshtml @@ -22,8 +22,8 @@ asp-page="/Examples/RequestOptions" asp-page-handler="Preview" hx-request-timeout="2000" - hx-request-cache="@HtmxRequestCache.NoCache" - hx-request-redirect="@HtmxRequestRedirect.Error" + hx-request-cache="NoCache" + hx-request-redirect="Error" hx-target="#preview-result"> Generate report preview diff --git a/src/Ramstack.HtmxToolkit/TagHelpers/HtmxRequestTagHelper.cs b/src/Ramstack.HtmxToolkit/TagHelpers/HtmxRequestTagHelper.cs index d6c46bf..5d24703 100644 --- a/src/Ramstack.HtmxToolkit/TagHelpers/HtmxRequestTagHelper.cs +++ b/src/Ramstack.HtmxToolkit/TagHelpers/HtmxRequestTagHelper.cs @@ -111,11 +111,15 @@ public int? Timeout /// In HTMX 4.x, this property maps to the string credentials option /// of hx-config. /// + /// + /// If the property has not been set, the option is not rendered, HTMX uses its default value, + /// and the property returns . + /// /// [HtmlAttributeName(RequestCredentialsAttributeName)] - public HtmxRequestCredentials? Credentials + public HtmxRequestCredentials Credentials { - get => _request.Credentials; + get => _request.Credentials.GetValueOrDefault(); set => _request.Credentials = value; } @@ -127,33 +131,49 @@ public HtmxRequestCredentials? Credentials /// Supported only in HTMX 1.9.x and 2.x. /// Headers added by the browser are unaffected. /// The Toolkit script also omits the HX-Request-Type header. + /// + /// If the property has not been set, the option is not rendered, HTMX uses its default value, + /// and the property returns . + /// /// [HtmlAttributeName(RequestNoHeadersAttributeName)] - public bool? NoHeaders + public bool NoHeaders { - get => _request.NoHeaders; + get => _request.NoHeaders.GetValueOrDefault(); set => _request.NoHeaders = value; } /// /// Gets or sets the Fetch cache mode for the request. /// - /// Supported only in HTMX 4.x. + /// + /// Supported only in HTMX 4.x. + /// + /// If the property has not been set, the option is not rendered, HTMX uses its default value, + /// and the property returns . + /// + /// [HtmlAttributeName(RequestCacheAttributeName)] - public HtmxRequestCache? Cache + public HtmxRequestCache Cache { - get => _request.Cache; + get => _request.Cache.GetValueOrDefault(); set => _request.Cache = value; } /// /// Gets or sets the Fetch redirect mode for the request. /// - /// Supported only in HTMX 4.x. + /// + /// Supported only in HTMX 4.x. + /// + /// If the property has not been set, the option is not rendered, HTMX uses its default value, + /// and the property returns . + /// + /// [HtmlAttributeName(RequestRedirectAttributeName)] - public HtmxRequestRedirect? Redirect + public HtmxRequestRedirect Redirect { - get => _request.Redirect; + get => _request.Redirect.GetValueOrDefault(); set => _request.Redirect = value; } @@ -194,11 +214,17 @@ public string? Integrity /// /// Gets or sets a value indicating whether the form is validated before submission. /// - /// Supported only in HTMX 4.x. + /// + /// Supported only in HTMX 4.x. + /// + /// If the property has not been set, the option is not rendered, HTMX uses its default value, + /// and the property returns . + /// + /// [HtmlAttributeName(RequestValidateAttributeName)] - public bool? Validate + public bool Validate { - get => _request.Validate; + get => _request.Validate.GetValueOrDefault(); set => _request.Validate = value; } @@ -240,16 +266,16 @@ internal sealed class HtmxRequestData /// public int? Timeout { get; set; } - /// + /// public HtmxRequestCredentials? Credentials { get; set; } - /// + /// public bool? NoHeaders { get; set; } - /// + /// public HtmxRequestCache? Cache { get; set; } - /// + /// public HtmxRequestRedirect? Redirect { get; set; } /// @@ -258,7 +284,7 @@ internal sealed class HtmxRequestData /// public string? Integrity { get; set; } - /// + /// public bool? Validate { get; set; } } diff --git a/tests/Ramstack.HtmxToolkit.Tests/TagHelpers/HtmxRequestTagHelperTests.cs b/tests/Ramstack.HtmxToolkit.Tests/TagHelpers/HtmxRequestTagHelperTests.cs index 1c2b8dd..f25921f 100644 --- a/tests/Ramstack.HtmxToolkit.Tests/TagHelpers/HtmxRequestTagHelperTests.cs +++ b/tests/Ramstack.HtmxToolkit.Tests/TagHelpers/HtmxRequestTagHelperTests.cs @@ -169,20 +169,30 @@ public async Task ProcessAsync_SerializesHtmx4RedirectMode(HtmxRequestRedirect r Assert.That(output.Attributes["hx-request"], Is.Null); } + [TestCase(HtmxTargetVersion.V1, "hx-request")] + [TestCase(HtmxTargetVersion.V2, "hx-request")] + [TestCase(HtmxTargetVersion.V4, "hx-config")] + public async Task ProcessAsync_OmitsUnsetOptions(HtmxTargetVersion version, string attributeName) + { + var output = TestHelper.CreateTagHelperOutput(); + var helper = CreateHelper(version); + helper.Timeout = 500; + + await helper.ProcessAsync(TestHelper.CreateTagHelperContext(), output); + + Assert.That(output.Attributes[attributeName]!.Value.ToString(), Is.EqualTo("{\"timeout\":500}")); + } + [Test] - public async Task ProcessAsync_Htmx4_OmitsClearedCacheAndRedirectModes() + public async Task ProcessAsync_Htmx4_SerializesValidateWhenFalse() { var output = TestHelper.CreateTagHelperOutput(); var helper = CreateHelper(HtmxTargetVersion.V4); - helper.Timeout = 500; - helper.Cache = HtmxRequestCache.Default; - helper.Redirect = HtmxRequestRedirect.Follow; - helper.Cache = null; - helper.Redirect = null; + helper.Validate = false; await helper.ProcessAsync(TestHelper.CreateTagHelperContext(), output); - Assert.That(output.Attributes["hx-config"]!.Value.ToString(), Is.EqualTo("{\"timeout\":500}")); + Assert.That(output.Attributes["hx-config"]!.Value.ToString(), Is.EqualTo("{\"validate\":false}")); } [Test]