Skip to content

Commit 9cba39e

Browse files
committed
feat: adds missing reswap modifier and adds reswap builder along with StopPolling
This commit introduces several enhancements to the `HtmxResponse` class. It adds a new optional parameter `modifier` to the `Reswap` method, allowing for more flexible response swapping. A new overload of the `Reswap` method is also added that takes a `SwapStyleBuilder` object as an argument. A new method, `StopPolling`, has been introduced which sets the response code to stop polling. Two new classes, `HtmxStatusCodes` and `SwapStyleBuilder`, have been created. The former provides status codes specific to HTMX responses while the latter aids in constructing swap style command strings for HTMX responses. Additionally, a new enum called `ScrollDirection` has been added for specifying scroll direction values. refactor: Add Reswap method and refactor SwapStyleBuilder Added a new Reswap method to the HtmxResponse class, allowing for more flexible response swapping. Refactored the SwapStyleBuilder class to use an OrderedDictionary for storing modifiers instead of a List, improving performance and readability. Also added nullability support for the SwapStyle property in SwapStyleBuilder. Added comprehensive unit tests for these changes in SwapStyleBuilderTests.cs. feat: Add ShowNone method to SwapStyleBuilder A new method, ShowNone, has been added to the SwapStyleBuilder class. This method turns off scrolling after a swap and allows for chaining with other methods in the class. Corresponding unit test has also been added to ensure that it returns the correct value.
1 parent 494ecfa commit 9cba39e

File tree

5 files changed

+449
-2
lines changed

5 files changed

+449
-2
lines changed

src/Htmxor/Http/HtmxResponse.cs

+41-2
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,25 @@ public HtmxResponse ReplaceUrl(string url)
131131
return this;
132132
}
133133

134+
/// <summary>
135+
/// Allows you to specify how the response will be swapped.
136+
/// </summary>
137+
/// <param name="modifier">The hx-swap attributes supports modifiers for changing the behavior of the swap.</param>
138+
/// <returns>This <see cref="HtmxResponse"/> object instance.</returns>
139+
public HtmxResponse Reswap(string modifier)
140+
{
141+
headers[HtmxResponseHeaderNames.Reswap] = modifier;
142+
143+
return this;
144+
}
145+
134146
/// <summary>
135147
/// Allows you to specify how the response will be swapped.
136148
/// </summary>
137149
/// <param name="swapStyle"></param>
150+
/// <param name="modifier">The hx-swap attributes supports modifiers for changing the behavior of the swap.</param>
138151
/// <returns>This <see cref="HtmxResponse"/> object instance.</returns>
139-
public HtmxResponse Reswap(SwapStyle swapStyle)
152+
public HtmxResponse Reswap(SwapStyle swapStyle, string? modifier = null)
140153
{
141154
var style = swapStyle switch
142155
{
@@ -152,11 +165,26 @@ public HtmxResponse Reswap(SwapStyle swapStyle)
152165
_ => throw new SwitchExpressionException(swapStyle),
153166
};
154167

155-
headers[HtmxResponseHeaderNames.Reswap] = style;
168+
var value = modifier != null ? $"{style} {modifier}" : style;
169+
170+
headers[HtmxResponseHeaderNames.Reswap] = value;
156171

157172
return this;
158173
}
159174

175+
/// <summary>
176+
/// Allows you to specify how the response will be swapped.
177+
/// </summary>
178+
/// <param></param>
179+
/// <param name="swapStyle"></param>
180+
/// <returns></returns>
181+
public HtmxResponse Reswap(SwapStyleBuilder swapStyle)
182+
{
183+
var (style, modifier) = swapStyle.Build();
184+
185+
return style is null ? Reswap(modifier) : Reswap((SwapStyle)style, modifier);
186+
}
187+
160188
/// <summary>
161189
/// A CSS selector that updates the target of the content update to a different element on the page.
162190
/// </summary>
@@ -181,6 +209,17 @@ public HtmxResponse Reselect(string selector)
181209
return this;
182210
}
183211

212+
/// <summary>
213+
/// Sets response code to stop polling
214+
/// </summary>
215+
/// <returns></returns>
216+
public HtmxResponse StopPolling()
217+
{
218+
context.Response.StatusCode = HtmxStatusCodes.StopPolling;
219+
220+
return this;
221+
}
222+
184223
/// <summary>
185224
/// Allows you to trigger client-side events.
186225
/// </summary>

src/Htmxor/Http/HtmxStatusCodes.cs

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Htmxor.Http;
2+
3+
public static class HtmxStatusCodes
4+
{
5+
public static readonly int StopPolling = 286;
6+
}

src/Htmxor/Http/SwapStyleBuilder.cs

+192
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
using System.Collections;
2+
using System.Collections.Specialized;
3+
4+
namespace Htmxor.Http;
5+
6+
/// <summary>
7+
/// A builder class for constructing a swap style command string for HTMX responses.
8+
/// </summary>
9+
public class SwapStyleBuilder
10+
{
11+
private readonly SwapStyle? style;
12+
private readonly OrderedDictionary modifiers = new();
13+
14+
/// <summary>
15+
/// Initializes a new instance of the SwapStyleBuilder with a specified swap style.
16+
/// </summary>
17+
/// <param name="style">The initial swap style to be applied.</param>
18+
public SwapStyleBuilder(SwapStyle? style = null)
19+
{
20+
this.style = style;
21+
}
22+
23+
/// <summary>
24+
/// Adds a delay to the swap operation.
25+
/// </summary>
26+
/// <param name="span">The time span to delay the swap.</param>
27+
/// <returns>The SwapStyleBuilder instance for chaining.</returns>
28+
public SwapStyleBuilder After(TimeSpan span)
29+
{
30+
AddModifier("swap", span.TotalMilliseconds < 1000 ? $"{span.TotalMilliseconds}ms" : $"{span.TotalSeconds}s");
31+
32+
return this;
33+
}
34+
35+
/// <summary>
36+
/// Specifies the direction to scroll the page after the swap.
37+
/// </summary>
38+
/// <param name="direction">The scroll direction.</param>
39+
/// <returns>The SwapStyleBuilder instance for chaining.</returns>
40+
public SwapStyleBuilder Scroll(ScrollDirection direction)
41+
{
42+
switch (direction)
43+
{
44+
case ScrollDirection.Top:
45+
AddModifier("scroll", "top");
46+
break;
47+
case ScrollDirection.Bottom:
48+
AddModifier("scroll", "bottom");
49+
break;
50+
}
51+
52+
return this;
53+
}
54+
55+
/// <summary>
56+
/// Determines whether to ignore the document title in the swap response.
57+
/// </summary>
58+
/// <param name="ignore">Whether to ignore the title.</param>
59+
/// <returns>The SwapStyleBuilder instance for chaining.</returns>
60+
public SwapStyleBuilder IgnoreTitle(bool ignore)
61+
{
62+
AddModifier("ignoreTitle", ignore);
63+
64+
return this;
65+
}
66+
67+
/// <summary>
68+
/// Enables or disables transition effects for the swap.
69+
/// </summary>
70+
/// <param name="show">Whether to show transition effects.</param>
71+
/// <returns>The SwapStyleBuilder instance for chaining.</returns>
72+
public SwapStyleBuilder Transition(bool show)
73+
{
74+
AddModifier("transition", show);
75+
76+
return this;
77+
}
78+
79+
/// <summary>
80+
/// Sets whether to focus and scroll to the swapped content.
81+
/// </summary>
82+
/// <param name="scroll">Whether to scroll to the focus element.</param>
83+
/// <returns>The SwapStyleBuilder instance for chaining.</returns>
84+
public SwapStyleBuilder FocusScroll(bool scroll)
85+
{
86+
AddModifier("focus-scroll", scroll);
87+
88+
return this;
89+
}
90+
91+
/// <summary>
92+
/// Specifies a CSS selector to dynamically target for the swap operation.
93+
/// </summary>
94+
/// <param name="selector">The CSS selector of the target element.</param>
95+
/// <param name="direction">The scroll direction after swap.</param>
96+
/// <returns>The SwapStyleBuilder instance for chaining.</returns>
97+
public SwapStyleBuilder ShowOn(string selector, ScrollDirection direction)
98+
{
99+
switch (direction)
100+
{
101+
case ScrollDirection.Top:
102+
AddModifier("show", $"{selector}:top");
103+
break;
104+
case ScrollDirection.Bottom:
105+
AddModifier("show", $"{selector}:bottom");
106+
break;
107+
}
108+
109+
return this;
110+
}
111+
112+
/// <summary>
113+
/// Specifies that the swap should show in the window, with an optional scroll direction.
114+
/// </summary>
115+
/// <param name="direction">The direction to scroll the window after the swap.</param>
116+
/// <returns>The SwapStyleBuilder instance for chaining.</returns>
117+
public SwapStyleBuilder ShowWindow(ScrollDirection direction)
118+
{
119+
switch (direction)
120+
{
121+
case ScrollDirection.Top:
122+
AddModifier("show", $"window:top");
123+
break;
124+
case ScrollDirection.Bottom:
125+
AddModifier("show", $"window:bottom");
126+
break;
127+
}
128+
129+
return this;
130+
}
131+
132+
/// <summary>
133+
/// Turns off scrolling after swap
134+
/// </summary>
135+
/// <returns>The SwapStyleBuilder instance for chaining.</returns>
136+
public SwapStyleBuilder ShowNone()
137+
{
138+
AddModifier("show", "none");
139+
140+
return this;
141+
}
142+
143+
/// <summary>
144+
/// Builds the swap style command string with all specified modifiers.
145+
/// </summary>
146+
/// <returns>A tuple containing the SwapStyle and the constructed command string.</returns>
147+
internal (SwapStyle?, string) Build()
148+
{
149+
var value = string.Empty;
150+
151+
if (modifiers.Count > 0)
152+
{
153+
value = modifiers.Cast<DictionaryEntry>()
154+
.Select(entry => $"{entry.Key}:{entry.Value}")
155+
.Aggregate((current, next) => $"{current} {next}");
156+
}
157+
158+
return (style, value);
159+
}
160+
161+
/// <summary>
162+
/// Adds a modifier to modifiers, overriding existing values if present
163+
/// </summary>
164+
/// <param name="modifier"></param>
165+
/// <param name="options"></param>
166+
private void AddModifier(string modifier, string options)
167+
{
168+
if (modifiers.Contains(modifier))
169+
modifiers.Remove(modifier);
170+
171+
modifiers.Add(modifier, options);
172+
}
173+
174+
private void AddModifier(string modifier, bool option) => AddModifier(modifier, option.ToString().ToLowerInvariant());
175+
}
176+
177+
/// <summary>
178+
/// Extension methods for the SwapStyle enum to facilitate building swap style commands.
179+
/// </summary>
180+
public static class SwapStyleBuilderExtension
181+
{
182+
// Each method below returns a SwapStyleBuilder instance initialized with the respective SwapStyle
183+
// and applies the specified modifier to it. This allows for fluent configuration of swap style commands.
184+
185+
public static SwapStyleBuilder After(this SwapStyle style, TimeSpan span) => new SwapStyleBuilder(style).After(span);
186+
public static SwapStyleBuilder Scroll(this SwapStyle style, ScrollDirection direction) => new SwapStyleBuilder(style).Scroll(direction);
187+
public static SwapStyleBuilder IgnoreTitle(this SwapStyle style, bool ignore) => new SwapStyleBuilder(style).IgnoreTitle(ignore);
188+
public static SwapStyleBuilder Transition(this SwapStyle style, bool show) => new SwapStyleBuilder(style).Transition(show);
189+
public static SwapStyleBuilder FocusScroll(this SwapStyle style, bool scroll) => new SwapStyleBuilder(style).FocusScroll(scroll);
190+
public static SwapStyleBuilder ShowOn(this SwapStyle style, string selector, ScrollDirection direction) => new SwapStyleBuilder(style).ShowOn(selector, direction);
191+
public static SwapStyleBuilder ShowWindow(this SwapStyle style, ScrollDirection direction) => new SwapStyleBuilder(style).ShowWindow(direction);
192+
}

src/Htmxor/ScrollDirection.cs

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace Htmxor;
2+
3+
/// <summary>
4+
/// Direction values for scroll and show modifier methods
5+
/// </summary>
6+
public enum ScrollDirection
7+
{
8+
Top,
9+
Bottom
10+
}

0 commit comments

Comments
 (0)