diff --git a/src/Ramstack.HtmxToolkit/HtmxBinaryTypeJsonConverter.cs b/src/Ramstack.HtmxToolkit/HtmxBinaryTypeJsonConverter.cs index 5aa23c5..b1fbd5c 100644 --- a/src/Ramstack.HtmxToolkit/HtmxBinaryTypeJsonConverter.cs +++ b/src/Ramstack.HtmxToolkit/HtmxBinaryTypeJsonConverter.cs @@ -17,13 +17,8 @@ internal sealed class HtmxBinaryTypeJsonConverter : JsonConverter public override void Write(Utf8JsonWriter writer, HtmxBinaryType? value, JsonSerializerOptions options) { - if (value is null) - { - writer.WriteNullValue(); - } - else - { - writer.WriteStringValue(value.GetValueOrDefault().GetWsBinaryTypeValue()); - } + // NOTE: value is never null here: null-valued properties are omitted + // by JsonIgnoreCondition.WhenWritingNull before this converter is invoked. + writer.WriteStringValue(value.GetValueOrDefault().GetWsBinaryTypeValue()); } } diff --git a/src/Ramstack.HtmxToolkit/HtmxConfig.cs b/src/Ramstack.HtmxToolkit/HtmxConfig.cs index db12174..e467dc1 100644 --- a/src/Ramstack.HtmxToolkit/HtmxConfig.cs +++ b/src/Ramstack.HtmxToolkit/HtmxConfig.cs @@ -1,5 +1,7 @@ using System.Text.Json.Serialization; +using Microsoft.AspNetCore.Html; + namespace Ramstack.HtmxToolkit; /// @@ -7,6 +9,8 @@ namespace Ramstack.HtmxToolkit; /// public abstract class HtmxConfig { + private HtmlString? _json; + /// /// Gets the configured HTMX major version. /// @@ -23,10 +27,33 @@ internal HtmxConfig(HtmxTargetVersion version) => TargetVersion = version; /// - /// Serializes this configuration to JSON. + /// Returns this configuration serialized as JSON, + /// cached and reused until the configuration changes. + /// + /// + /// An containing the configuration serialized as JSON. + /// + internal HtmlString ToJson() => + _json ??= new HtmlString(Serialize()); + + /// + /// Serializes this configuration to a JSON string. /// /// - /// A string containing this configuration serialized as JSON. + /// A JSON string representing this configuration. /// - internal abstract string ToJson(); + protected abstract string Serialize(); + + /// + /// Assigns to and invalidates + /// the cached JSON so it is regenerated on the next serialization. + /// + /// The type of the field. + /// The backing field to update. + /// The value to assign. + protected void SetField(ref T field, T value) + { + field = value; + _json = null; + } } diff --git a/src/Ramstack.HtmxToolkit/HtmxFetchModeJsonConverter.cs b/src/Ramstack.HtmxToolkit/HtmxFetchModeJsonConverter.cs index 8b02b38..7ce7875 100644 --- a/src/Ramstack.HtmxToolkit/HtmxFetchModeJsonConverter.cs +++ b/src/Ramstack.HtmxToolkit/HtmxFetchModeJsonConverter.cs @@ -17,13 +17,8 @@ internal sealed class HtmxFetchModeJsonConverter : JsonConverter /// public override void Write(Utf8JsonWriter writer, HtmxFetchMode? value, JsonSerializerOptions options) { - if (value is null) - { - writer.WriteNullValue(); - } - else - { - writer.WriteStringValue(value.GetValueOrDefault().GetFetchModeValue()); - } + // NOTE: value is never null here: null-valued properties are omitted + // by JsonIgnoreCondition.WhenWritingNull before this converter is invoked. + writer.WriteStringValue(value.GetValueOrDefault().GetFetchModeValue()); } } diff --git a/src/Ramstack.HtmxToolkit/HtmxHistoryModeJsonConverter.cs b/src/Ramstack.HtmxToolkit/HtmxHistoryModeJsonConverter.cs index 553b0c0..881e684 100644 --- a/src/Ramstack.HtmxToolkit/HtmxHistoryModeJsonConverter.cs +++ b/src/Ramstack.HtmxToolkit/HtmxHistoryModeJsonConverter.cs @@ -21,24 +21,19 @@ internal sealed class HtmxHistoryModeJsonConverter : JsonConverter public override void Write(Utf8JsonWriter writer, HtmxHistoryMode? value, JsonSerializerOptions options) { - if (value is null) + // NOTE: value is never null here: null-valued properties are omitted + // by JsonIgnoreCondition.WhenWritingNull before this converter is invoked. + switch (value.GetValueOrDefault()) { - writer.WriteNullValue(); - } - else - { - switch (value.GetValueOrDefault()) - { - case HtmxHistoryMode.Enabled: - writer.WriteBooleanValue(true); - break; - case HtmxHistoryMode.Disabled: - writer.WriteBooleanValue(false); - break; - default: - writer.WriteStringValue(s_reload); - break; - } + case HtmxHistoryMode.Enabled: + writer.WriteBooleanValue(true); + break; + case HtmxHistoryMode.Disabled: + writer.WriteBooleanValue(false); + break; + default: + writer.WriteStringValue(s_reload); + break; } } } diff --git a/src/Ramstack.HtmxToolkit/HtmxScrollBehaviorJsonConverter.cs b/src/Ramstack.HtmxToolkit/HtmxScrollBehaviorJsonConverter.cs index 775d159..aa4f63d 100644 --- a/src/Ramstack.HtmxToolkit/HtmxScrollBehaviorJsonConverter.cs +++ b/src/Ramstack.HtmxToolkit/HtmxScrollBehaviorJsonConverter.cs @@ -17,13 +17,8 @@ internal sealed class HtmxScrollBehaviorJsonConverter : JsonConverter public override void Write(Utf8JsonWriter writer, HtmxScrollBehavior? value, JsonSerializerOptions options) { - if (value is null) - { - writer.WriteNullValue(); - } - else - { - writer.WriteStringValue(value.GetValueOrDefault().GetScrollBehaviorValue()); - } + // NOTE: value is never null here: null-valued properties are omitted + // by JsonIgnoreCondition.WhenWritingNull before this converter is invoked. + writer.WriteStringValue(value.GetValueOrDefault().GetScrollBehaviorValue()); } } diff --git a/src/Ramstack.HtmxToolkit/HtmxSwapJsonConverter.cs b/src/Ramstack.HtmxToolkit/HtmxSwapJsonConverter.cs index 7babc0b..44878f2 100644 --- a/src/Ramstack.HtmxToolkit/HtmxSwapJsonConverter.cs +++ b/src/Ramstack.HtmxToolkit/HtmxSwapJsonConverter.cs @@ -17,13 +17,8 @@ internal sealed class HtmxSwapJsonConverter : JsonConverter /// public override void Write(Utf8JsonWriter writer, HtmxSwap? value, JsonSerializerOptions options) { - if (value is null) - { - writer.WriteNullValue(); - } - else - { - writer.WriteStringValue(value.GetValueOrDefault().GetSwapValue()); - } + // NOTE: value is never null here: null-valued properties are omitted + // by JsonIgnoreCondition.WhenWritingNull before this converter is invoked. + writer.WriteStringValue(value.GetValueOrDefault().GetSwapValue()); } } diff --git a/src/Ramstack.HtmxToolkit/HtmxV1Config.cs b/src/Ramstack.HtmxToolkit/HtmxV1Config.cs index 1dcf201..88753c3 100644 --- a/src/Ramstack.HtmxToolkit/HtmxV1Config.cs +++ b/src/Ramstack.HtmxToolkit/HtmxV1Config.cs @@ -12,186 +12,306 @@ public sealed class HtmxV1Config() : HtmxConfig(HtmxTargetVersion.V1) /// Gets or sets a value indicating whether HTMX history support is enabled. /// The HTMX default is . /// - public bool? HistoryEnabled { get; set; } + public bool? HistoryEnabled + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the size of the history cache. /// The HTMX default is 10. /// - public int? HistoryCacheSize { get; set; } + public int? HistoryCacheSize + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether a full-page refresh should be issued /// on history misses rather than using an AJAX request. /// The HTMX default is . /// - public bool? RefreshOnHistoryMiss { get; set; } + public bool? RefreshOnHistoryMiss + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the default swap style. /// The HTMX default is . /// [JsonConverter(typeof(HtmxSwapJsonConverter))] - public HtmxSwap? DefaultSwapStyle { get; set; } + public HtmxSwap? DefaultSwapStyle + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the default swap delay in milliseconds. /// The HTMX default is 0. /// - public int? DefaultSwapDelay { get; set; } + public int? DefaultSwapDelay + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the default settle delay in milliseconds. /// The HTMX default is 20. /// - public int? DefaultSettleDelay { get; set; } + public int? DefaultSettleDelay + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether the indicator styles are loaded. /// The HTMX default is . /// - public bool? IncludeIndicatorStyles { get; set; } + public bool? IncludeIndicatorStyles + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the indicator class. /// The HTMX default is htmx-indicator. /// - public string? IndicatorClass { get; set; } + public string? IndicatorClass + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the request class. /// The HTMX default is htmx-request. /// - public string? RequestClass { get; set; } + public string? RequestClass + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the added class. /// The HTMX default is htmx-added. /// - public string? AddedClass { get; set; } + public string? AddedClass + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the swapping class. /// The HTMX default is htmx-swapping. /// - public string? SwappingClass { get; set; } + public string? SwappingClass + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the settling class. /// The HTMX default is htmx-settling. /// - public string? SettlingClass { get; set; } + public string? SettlingClass + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether the use of eval is allowed. /// The HTMX default is . /// - public bool? AllowEval { get; set; } + public bool? AllowEval + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether script tags should be processed in new content. /// The HTMX default is . /// - public bool? AllowScriptTags { get; set; } + public bool? AllowScriptTags + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the nonce added to inline scripts. /// The HTMX default is an empty string. /// - public string? InlineScriptNonce { get; set; } + public string? InlineScriptNonce + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the attributes to settle during the settling phase. /// The HTMX default is ["class", "style", "width", "height"]. /// - public string[]? AttributesToSettle { get; set; } + public string[]? AttributesToSettle + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether HTML template tags are used to parse content. /// The HTMX default is . /// - public bool? UseTemplateFragments { get; set; } + public bool? UseTemplateFragments + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the WebSocket reconnection delay strategy. /// The HTMX default is full-jitter. /// - public string? WsReconnectDelay { get; set; } + public string? WsReconnectDelay + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the type of binary data received over WebSocket connections. /// The HTMX default is . /// [JsonConverter(typeof(HtmxBinaryTypeJsonConverter))] - public HtmxBinaryType? WsBinaryType { get; set; } + public HtmxBinaryType? WsBinaryType + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the selector for elements that HTMX must not process. /// The HTMX default is [disable-htmx], [data-disable-htmx]. /// - public string? DisableSelector { get; set; } + public string? DisableSelector + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether credentials are included in cross-origin requests. /// The HTMX default is . /// - public bool? WithCredentials { get; set; } + public bool? WithCredentials + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the request timeout, in milliseconds. /// The HTMX default is 0. /// - public int? Timeout { get; set; } + public int? Timeout + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether requests are restricted to the current origin. /// The HTMX default is . /// - public bool? SelfRequestsOnly { get; set; } + public bool? SelfRequestsOnly + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the scrolling behavior for boosted links. /// The HTMX default is . /// [JsonConverter(typeof(HtmxScrollBehaviorJsonConverter))] - public HtmxScrollBehavior? ScrollBehavior { get; set; } + public HtmxScrollBehavior? ScrollBehavior + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether the focused element should be scrolled into view. /// The HTMX default is . /// - public bool? DefaultFocusScroll { get; set; } + public bool? DefaultFocusScroll + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether GET requests use a cache-busting parameter. /// The HTMX default is . /// - public bool? GetCacheBusterParam { get; set; } + public bool? GetCacheBusterParam + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether the View Transition API should be used for swaps. /// The HTMX default is . /// - public bool? GlobalViewTransitions { get; set; } + public bool? GlobalViewTransitions + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the HTTP methods that use URL parameters. /// The HTMX default is ["get"]. /// [JsonConverter(typeof(HttpVerbArrayJsonConverter))] - public HttpVerb[]? MethodsThatUseUrlParams { get; set; } + public HttpVerb[]? MethodsThatUseUrlParams + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether document titles found in new content are ignored. /// The HTMX default is . /// - public bool? IgnoreTitle { get; set; } + public bool? IgnoreTitle + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether boosted targets are scrolled into the viewport. /// The HTMX default is . /// - public bool? ScrollIntoViewOnBoost { get; set; } + public bool? ScrollIntoViewOnBoost + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether parsed trigger specifications use @@ -199,9 +319,13 @@ public sealed class HtmxV1Config() : HtmxConfig(HtmxTargetVersion.V1) /// [JsonPropertyName("triggerSpecsCache")] [JsonConverter(typeof(HtmxTriggerSpecsCacheJsonConverter))] - public bool? TriggerSpecsCacheEnabled { get; set; } + public bool? TriggerSpecsCacheEnabled + { + get; + set => SetField(ref field, value); + } /// - internal override string ToJson() => + protected override string Serialize() => JsonSerializer.Serialize(this, HtmxConfigJsonSerializerContext.Default.HtmxV1Config); } diff --git a/src/Ramstack.HtmxToolkit/HtmxV2Config.cs b/src/Ramstack.HtmxToolkit/HtmxV2Config.cs index bdfab03..2f76af9 100644 --- a/src/Ramstack.HtmxToolkit/HtmxV2Config.cs +++ b/src/Ramstack.HtmxToolkit/HtmxV2Config.cs @@ -14,192 +14,316 @@ public sealed class HtmxV2Config() : HtmxConfig(HtmxTargetVersion.V2) /// Gets or sets a value indicating whether HTMX history support is enabled. /// The HTMX default is . /// - public bool? HistoryEnabled { get; set; } + public bool? HistoryEnabled + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the size of the history cache. /// The HTMX default is 10. /// - public int? HistoryCacheSize { get; set; } + public int? HistoryCacheSize + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether a history miss causes a full-page refresh /// instead of an AJAX request. /// The HTMX default is . /// - public bool? RefreshOnHistoryMiss { get; set; } + public bool? RefreshOnHistoryMiss + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the default swap style. /// The HTMX default is . /// [JsonConverter(typeof(HtmxSwapJsonConverter))] - public HtmxSwap? DefaultSwapStyle { get; set; } + public HtmxSwap? DefaultSwapStyle + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the default swap delay in milliseconds. /// The HTMX default is 0. /// - public int? DefaultSwapDelay { get; set; } + public int? DefaultSwapDelay + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the default settle delay in milliseconds. /// The HTMX default is 20. /// - public int? DefaultSettleDelay { get; set; } + public int? DefaultSettleDelay + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether the indicator styles are loaded. /// The HTMX default is . /// - public bool? IncludeIndicatorStyles { get; set; } + public bool? IncludeIndicatorStyles + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the indicator class. /// The HTMX default is htmx-indicator. /// - public string? IndicatorClass { get; set; } + public string? IndicatorClass + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the request class. /// The HTMX default is htmx-request. /// - public string? RequestClass { get; set; } + public string? RequestClass + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the added class. /// The HTMX default is htmx-added. /// - public string? AddedClass { get; set; } + public string? AddedClass + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the swapping class. /// The HTMX default is htmx-swapping. /// - public string? SwappingClass { get; set; } + public string? SwappingClass + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the settling class. /// The HTMX default is htmx-settling. /// - public string? SettlingClass { get; set; } + public string? SettlingClass + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether the use of eval is allowed. /// The HTMX default is . /// - public bool? AllowEval { get; set; } + public bool? AllowEval + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether script tags should be processed in new content. /// The HTMX default is . /// - public bool? AllowScriptTags { get; set; } + public bool? AllowScriptTags + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the nonce added to inline scripts. /// The HTMX default is an empty string. /// - public string? InlineScriptNonce { get; set; } + public string? InlineScriptNonce + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the nonce added to inline styles. /// The HTMX default is an empty string. /// - public string? InlineStyleNonce { get; set; } + public string? InlineStyleNonce + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the attributes to settle during the settling phase. /// The HTMX default is ["class", "style", "width", "height"]. /// - public string[]? AttributesToSettle { get; set; } + public string[]? AttributesToSettle + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the WebSocket reconnection delay strategy. /// The HTMX default is full-jitter. /// - public string? WsReconnectDelay { get; set; } + public string? WsReconnectDelay + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the type of binary data received over WebSocket connections. /// The HTMX default is . /// [JsonConverter(typeof(HtmxBinaryTypeJsonConverter))] - public HtmxBinaryType? WsBinaryType { get; set; } + public HtmxBinaryType? WsBinaryType + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the selector for elements that HTMX must not process. /// The HTMX default is [disable-htmx], [data-disable-htmx]. /// - public string? DisableSelector { get; set; } + public string? DisableSelector + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether credentials are included in cross-origin requests. /// The HTMX default is . /// - public bool? WithCredentials { get; set; } + public bool? WithCredentials + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether attribute inheritance is disabled. /// The HTMX default is . /// - public bool? DisableInheritance { get; set; } + public bool? DisableInheritance + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the request timeout, in milliseconds. /// The HTMX default is 0. /// - public int? Timeout { get; set; } + public int? Timeout + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether requests are restricted to the current origin. /// The HTMX default is . /// - public bool? SelfRequestsOnly { get; set; } + public bool? SelfRequestsOnly + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the scrolling behavior for boosted links. /// The HTMX default is . /// [JsonConverter(typeof(HtmxScrollBehaviorJsonConverter))] - public HtmxScrollBehavior? ScrollBehavior { get; set; } + public HtmxScrollBehavior? ScrollBehavior + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether the focused element should be scrolled into view. /// The HTMX default is . /// - public bool? DefaultFocusScroll { get; set; } + public bool? DefaultFocusScroll + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether GET requests use a cache-busting parameter. /// The HTMX default is . /// - public bool? GetCacheBusterParam { get; set; } + public bool? GetCacheBusterParam + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether the View Transition API should be used for swaps. /// The HTMX default is . /// - public bool? GlobalViewTransitions { get; set; } + public bool? GlobalViewTransitions + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the HTTP methods that use URL parameters. /// The HTMX default is ["get", "delete"]. /// [JsonConverter(typeof(HttpVerbArrayJsonConverter))] - public HttpVerb[]? MethodsThatUseUrlParams { get; set; } + public HttpVerb[]? MethodsThatUseUrlParams + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether document titles found in new content are ignored. /// The HTMX default is . /// - public bool? IgnoreTitle { get; set; } + public bool? IgnoreTitle + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether boosted targets are scrolled into the viewport. /// The HTMX default is . /// - public bool? ScrollIntoViewOnBoost { get; set; } + public bool? ScrollIntoViewOnBoost + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether parsed trigger specifications use @@ -207,35 +331,55 @@ public sealed class HtmxV2Config() : HtmxConfig(HtmxTargetVersion.V2) /// [JsonConverter(typeof(HtmxTriggerSpecsCacheJsonConverter))] [JsonPropertyName("triggerSpecsCache")] - public bool? TriggerSpecsCacheEnabled { get; set; } + public bool? TriggerSpecsCacheEnabled + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the rules that determine how HTTP response status codes are handled. /// - public IList? ResponseHandling { get; set; } + public IList? ResponseHandling + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether out-of-band swaps nested in the main /// response are processed. /// The HTMX default is . /// - public bool? AllowNestedOobSwaps { get; set; } + public bool? AllowNestedOobSwaps + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether history cache-miss requests are marked /// as HTMX requests. /// The HTMX default is . /// - public bool? HistoryRestoreAsHxRequest { get; set; } + public bool? HistoryRestoreAsHxRequest + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether form validity is reported before /// a request is issued. /// The HTMX default is . /// - public bool? ReportValidityOfForms { get; set; } + public bool? ReportValidityOfForms + { + get; + set => SetField(ref field, value); + } /// - internal override string ToJson() => + protected override string Serialize() => JsonSerializer.Serialize(this, HtmxConfigJsonSerializerContext.Default.HtmxV2Config); } diff --git a/src/Ramstack.HtmxToolkit/HtmxV4Config.cs b/src/Ramstack.HtmxToolkit/HtmxV4Config.cs index fd2ef2e..84653aa 100644 --- a/src/Ramstack.HtmxToolkit/HtmxV4Config.cs +++ b/src/Ramstack.HtmxToolkit/HtmxV4Config.cs @@ -12,102 +12,166 @@ public sealed class HtmxV4Config() : HtmxConfig(HtmxTargetVersion.V4) /// Gets or sets a value indicating whether all HTMX events are logged to the console. /// The HTMX default is . /// - public bool? LogAll { get; set; } + public bool? LogAll + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the secondary attribute prefix recognized alongside hx-*. /// The HTMX default is data-hx-. /// - public string? Prefix { get; set; } + public string? Prefix + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the character used instead of : in attribute names. /// The HTMX default is undefined. /// - public string? MetaCharacter { get; set; } + public string? MetaCharacter + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets how HTMX history restoration is handled. /// The HTMX default is . /// [JsonConverter(typeof(HtmxHistoryModeJsonConverter))] - public HtmxHistoryMode? History { get; set; } + public HtmxHistoryMode? History + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the default swap style. /// The HTMX default is . /// [JsonConverter(typeof(HtmxSwapJsonConverter))] - public HtmxSwap? DefaultSwap { get; set; } + public HtmxSwap? DefaultSwap + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether an empty response body replaces the main swap target. /// The HTMX default is undefined. /// - public bool? DefaultSwapEmpty { get; set; } + public bool? DefaultSwapEmpty + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the default settle delay in milliseconds. /// The HTMX default is 1. /// - public int? DefaultSettleDelay { get; set; } + public int? DefaultSettleDelay + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether the indicator styles are loaded. /// The HTMX default is . /// [JsonPropertyName("includeIndicatorCSS")] - public bool? IncludeIndicatorCss { get; set; } + public bool? IncludeIndicatorCss + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the indicator class. /// The HTMX default is htmx-indicator. /// - public string? IndicatorClass { get; set; } + public string? IndicatorClass + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the request class. /// The HTMX default is htmx-request. /// - public string? RequestClass { get; set; } + public string? RequestClass + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the nonce added to inline scripts. /// The HTMX default is undefined, which means that no nonce is added. /// - public string? InlineScriptNonce { get; set; } + public string? InlineScriptNonce + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a comma-separated list of extensions that HTMX is allowed to load. /// The HTMX default is an empty string. /// - public string? Extensions { get; set; } + public string? Extensions + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether HTMX attributes are inherited implicitly. /// The HTMX default is . /// - public bool? ImplicitInheritance { get; set; } + public bool? ImplicitInheritance + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the default request timeout in milliseconds. /// The HTMX default is 60000. /// - public int? DefaultTimeout { get; set; } + public int? DefaultTimeout + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the request mode passed to the Fetch API. /// The HTMX default is same-origin. /// [JsonConverter(typeof(HtmxFetchModeJsonConverter))] - public HtmxFetchMode? Mode { get; set; } + public HtmxFetchMode? Mode + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether the focused element should be scrolled into view. /// The HTMX default is and can be overridden /// using the focus-scroll swap modifier. /// - public bool? DefaultFocusScroll { get; set; } + public bool? DefaultFocusScroll + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets a value indicating whether the @@ -115,31 +179,51 @@ public sealed class HtmxV4Config() : HtmxConfig(HtmxTargetVersion.V4) /// should be used when swapping in new content. /// The HTMX default is . /// - public bool? Transitions { get; set; } + public bool? Transitions + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the attribute name prefixes to preserve during morphing. /// The HTMX default is ["data-htmx-powered"]. /// - public string[]? MorphIgnore { get; set; } + public string[]? MorphIgnore + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the selector for elements to skip during morphing. /// The HTMX default is [hx-morph-skip]. /// - public string? MorphSkip { get; set; } + public string? MorphSkip + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the selector for elements whose children should not be morphed. /// The HTMX default is [hx-morph-skip-children]. /// - public string? MorphSkipChildren { get; set; } + public string? MorphSkipChildren + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the maximum number of siblings scanned while matching elements during morphing. /// The HTMX default is 10. /// - public int? MorphScanLimit { get; set; } + public int? MorphScanLimit + { + get; + set => SetField(ref field, value); + } /// /// Gets or sets the response status codes or patterns for which HTMX does not perform a swap. @@ -149,9 +233,13 @@ public sealed class HtmxV4Config() : HtmxConfig(HtmxTargetVersion.V4) /// Although HTMX declares this option as a number array, it converts each entry to a string /// at runtime and supports wildcard patterns such as "4xx" and "44x". /// - public string[]? NoSwap { get; set; } + public string[]? NoSwap + { + get; + set => SetField(ref field, value); + } /// - internal override string ToJson() => + protected override string Serialize() => JsonSerializer.Serialize(this, HtmxConfigJsonSerializerContext.Default.HtmxV4Config); } diff --git a/src/Ramstack.HtmxToolkit/HttpVerbArrayJsonConverter.cs b/src/Ramstack.HtmxToolkit/HttpVerbArrayJsonConverter.cs index bf93b71..fa00f8d 100644 --- a/src/Ramstack.HtmxToolkit/HttpVerbArrayJsonConverter.cs +++ b/src/Ramstack.HtmxToolkit/HttpVerbArrayJsonConverter.cs @@ -15,20 +15,15 @@ public override HttpVerb[] Read(ref Utf8JsonReader reader, Type typeToConvert, J throw new NotSupportedException(); /// - public override void Write(Utf8JsonWriter writer, HttpVerb[]? value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, HttpVerb[] value, JsonSerializerOptions options) { - if (value is null) - { - writer.WriteNullValue(); - } - else - { - writer.WriteStartArray(); + // NOTE: value is never null here: null-valued properties are omitted + // by JsonIgnoreCondition.WhenWritingNull before this converter is invoked. + writer.WriteStartArray(); - foreach (var verb in value) - writer.WriteStringValue(verb.GetHttpVerbValue()); + foreach (var verb in value) + writer.WriteStringValue(verb.GetHttpVerbValue()); - writer.WriteEndArray(); - } + writer.WriteEndArray(); } } diff --git a/src/Ramstack.HtmxToolkit/TagHelpers/HtmxConfigTagHelper.cs b/src/Ramstack.HtmxToolkit/TagHelpers/HtmxConfigTagHelper.cs index 18a8865..cf2b5cd 100644 --- a/src/Ramstack.HtmxToolkit/TagHelpers/HtmxConfigTagHelper.cs +++ b/src/Ramstack.HtmxToolkit/TagHelpers/HtmxConfigTagHelper.cs @@ -40,7 +40,7 @@ public override Task ProcessAsync(TagHelperContext context, TagHelperOutput outp var json = options.Value.HtmxConfig.ToJson(); output.Attributes.SetAttribute( - new TagHelperAttribute("content", new HtmlString(json), HtmlAttributeValueStyle.SingleQuotes)); + new TagHelperAttribute("content", json, HtmlAttributeValueStyle.SingleQuotes)); if (options.Value.IncludeAntiforgeryToken) RenderAntiforgeryAttributes(output);