diff --git a/README.md b/README.md
index a3cb588..cdec1a5 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,7 @@ Provides HTMX integration for ASP.NET Core applications.
* [HtmxRequestAttribute](#htmxrequestattribute)
* [HttpResponse](#httpresponse)
* [The declarative way of setting response headers](#the-declarative-way-of-setting-response-headers)
+ * [Polling](#polling)
* [Tag Helpers](#tag-helpers)
* [HtmxUrlTagHelper](#htmxurltaghelper)
* [HtmxHeaderTagHelper](#htmxheadertaghelper)
@@ -360,8 +361,7 @@ Its callback receives an `HtmxResponse`, allowing you to configure response head
Response.Htmx(h => h
.TriggerEvent(
eventName: "process",
- detail: new { Value = ... })
- .StopPolling(ShouldStopPolling));
+ detail: new { Value = ... }));
```
`TriggerEvent` and `TriggerEvents` accept an optional `HtmxTriggerTiming` value.
@@ -377,12 +377,11 @@ the upstream timing change.
```csharp
Response.Htmx(
- static (h, stop) => h
+ static (h, value) => h
.TriggerEvent(
eventName: "process",
- detail: new { Value = ... })
- .StopPolling(stop),
- ShouldStopPolling);
+ detail: new { Value = value }),
+ ProcessValue);
```
:bulb: The same API works in Minimal API handlers by binding `HttpResponse`:
@@ -457,6 +456,44 @@ public HtmxSwap Reswap { get; set; }
Use `ReswapExpression` when the strongly typed `Reswap` property is not flexible enough.
+## Polling
+
+For server-controlled polling that works in every supported HTMX version, return
+the polling element itself and replace it with `outerHTML`:
+
+```html
+
+ Polling...
+
+```
+
+While polling should continue, return the same element with its request
+attributes. To stop, return the element without `hx-get` and `hx-trigger`:
+
+```html
+
+ Polling stopped!
+
+```
+
+This load-polling pattern gives the server control over every next request and
+works with HTMX 1.9.x, 2.x, and 4.x. For an indefinitely updated status, use
+`hx-trigger="every 1s"` instead.
+
+HTMX 1.9.x and 2.x also recognize HTTP status code `286` as a fixed-rate polling
+stop signal. Applications that target only those versions can opt into that legacy
+behavior directly:
+
+```csharp
+Response.StatusCode = 286;
+```
+
+HTMX 4.x treats `286` as a regular successful response, so it is not exposed as a
+toolkit API.
+
## Tag Helpers
The library provides five tag helpers:
diff --git a/samples/Ramstack.HtmxToolkit.Demo/Pages/Examples/Polling.cshtml b/samples/Ramstack.HtmxToolkit.Demo/Pages/Examples/Polling.cshtml
index 94efabc..8ec7251 100644
--- a/samples/Ramstack.HtmxToolkit.Demo/Pages/Examples/Polling.cshtml
+++ b/samples/Ramstack.HtmxToolkit.Demo/Pages/Examples/Polling.cshtml
@@ -8,25 +8,15 @@
Example 05
Polling
- Stop a repeating HTMX request from the server when a condition is reached
+ Let the server decide whether to schedule the next HTMX request
-
-
Polling is active
+
Server-controlled polling
+
The server returns the next polling element every second until it randomly stops.
-
- Waiting for the first response.
-
+
diff --git a/samples/Ramstack.HtmxToolkit.Demo/Pages/Examples/Polling.cshtml.cs b/samples/Ramstack.HtmxToolkit.Demo/Pages/Examples/Polling.cshtml.cs
index 0d9adc9..1d2b6bd 100644
--- a/samples/Ramstack.HtmxToolkit.Demo/Pages/Examples/Polling.cshtml.cs
+++ b/samples/Ramstack.HtmxToolkit.Demo/Pages/Examples/Polling.cshtml.cs
@@ -5,15 +5,17 @@ namespace Ramstack.HtmxToolkit.Demo.Pages.Examples;
public class PollingModel : PageModel
{
- public IActionResult OnGetStopPolling()
- {
- var stop = Random.Shared.Next(0, 20) == 10;
- Response.Htmx((h, f) => h.StopPolling(f), stop);
+ public PollingState State { get; } = new(false, "Polling is active");
- var content = $"Polling... {DateTime.Now:HH:mm:ss}";
- if (stop)
- content += "Polling stopped!";
+ public IActionResult OnGetPoll()
+ {
+ var stopped = Random.Shared.Next(0, 20) == 10;
+ var message = stopped
+ ? "Polling stopped!"
+ : $"Polling... {DateTime.Now:HH:mm:ss}";
- return Content(content);
+ return Partial("_PollingStatus", new PollingState(stopped, message));
}
+
+ public sealed record PollingState(bool Stopped, string Message);
}
diff --git a/samples/Ramstack.HtmxToolkit.Demo/Pages/Examples/_PollingStatus.cshtml b/samples/Ramstack.HtmxToolkit.Demo/Pages/Examples/_PollingStatus.cshtml
new file mode 100644
index 0000000..bed0f74
--- /dev/null
+++ b/samples/Ramstack.HtmxToolkit.Demo/Pages/Examples/_PollingStatus.cshtml
@@ -0,0 +1,18 @@
+@model PollingModel.PollingState
+
+@if (Model.Stopped)
+{
+
+ @Model.Message
+
+}
+else
+{
+
+ @Model.Message
+
+}
diff --git a/samples/Ramstack.HtmxToolkit.Demo/Pages/Index.cshtml b/samples/Ramstack.HtmxToolkit.Demo/Pages/Index.cshtml
index 691dc25..7157d0a 100644
--- a/samples/Ramstack.HtmxToolkit.Demo/Pages/Index.cshtml
+++ b/samples/Ramstack.HtmxToolkit.Demo/Pages/Index.cshtml
@@ -13,7 +13,7 @@
02. Route data
Pass route values without query-string assembly.
03. Request headers
Attach custom HTMX headers in Razor.
04. Fluent responses
Configure response directives on the server.
- 05. Polling
Stop repeated requests from a handler.
+ 05. Polling
Let the server control repeated requests.
06. HTMX requests
Detect asynchronous requests.
07. Response headers
Override the requested swap strategy.
08. Boosted navigation
Detect links enhanced by HTMX.
diff --git a/samples/Ramstack.HtmxToolkit.Demo/wwwroot/css/demo.css b/samples/Ramstack.HtmxToolkit.Demo/wwwroot/css/demo.css
index 4d76be2..0313687 100644
--- a/samples/Ramstack.HtmxToolkit.Demo/wwwroot/css/demo.css
+++ b/samples/Ramstack.HtmxToolkit.Demo/wwwroot/css/demo.css
@@ -274,6 +274,11 @@ code {
font-size: 0.85rem;
}
+.poll-status--stopped {
+ background: rgb(255 165 0 / 0.15);
+ color: orange;
+}
+
.demo-form {
display: grid;
gap: 1rem;
diff --git a/src/Ramstack.HtmxToolkit/HtmxResponse.cs b/src/Ramstack.HtmxToolkit/HtmxResponse.cs
index fe965d3..5f8f8d6 100644
--- a/src/Ramstack.HtmxToolkit/HtmxResponse.cs
+++ b/src/Ramstack.HtmxToolkit/HtmxResponse.cs
@@ -21,11 +21,6 @@ public readonly struct HtmxResponse
{
private readonly HttpResponse _response;
- ///
- /// The HTTP status code used by HTMX to stop polling.
- ///
- public const int StopPollingStatusCode = 286;
-
///
/// Gets the strongly typed HTMX response headers.
///
@@ -252,31 +247,6 @@ static HtmxResponse TriggerEventImpl(HtmxResponse response, string eventName, ob
public HtmxResponse TriggerEvents(IReadOnlyDictionary events, HtmxTriggerTiming timing = HtmxTriggerTiming.Receive) =>
AddEvents(this, events, timing);
- ///
- /// Sets HTTP status code 286 to stop polling.
- ///
- ///
- /// The current instance.
- ///
- public HtmxResponse StopPolling() =>
- StopPolling(true);
-
- ///
- /// Sets HTTP status code 286 when is .
- ///
- /// to stop polling;
- /// otherwise, .
- ///
- /// The current instance.
- ///
- public HtmxResponse StopPolling(bool condition)
- {
- if (condition)
- _response.StatusCode = StopPollingStatusCode;
-
- return this;
- }
-
///
/// Sets a response header and returns the response wrapper for fluent chaining.
///
diff --git a/tests/Ramstack.HtmxToolkit.Tests/HtmxResponseTests.cs b/tests/Ramstack.HtmxToolkit.Tests/HtmxResponseTests.cs
index 8148ff6..4cc1511 100644
--- a/tests/Ramstack.HtmxToolkit.Tests/HtmxResponseTests.cs
+++ b/tests/Ramstack.HtmxToolkit.Tests/HtmxResponseTests.cs
@@ -184,33 +184,6 @@ public void Reselect_SetsHeader()
Assert.That(context.Response.Headers[HtmxResponseHeaderNames.Reselect], Is.EqualTo("#list"));
}
- [Test]
- public void StopPolling_SetsStatusCode286()
- {
- var context = TestHelper.CreateHtmxRequestContext();
- context.Response.Htmx(r => r.StopPolling());
-
- Assert.That(context.Response.StatusCode, Is.EqualTo(HtmxResponse.StopPollingStatusCode));
- }
-
- [Test]
- public void StopPolling_WithFalseCondition_DoesNotChangeStatusCode()
- {
- var context = TestHelper.CreateHtmxRequestContext();
- context.Response.Htmx(r => r.StopPolling(false));
-
- Assert.That(context.Response.StatusCode, Is.Not.EqualTo(HtmxResponse.StopPollingStatusCode));
- }
-
- [Test]
- public void StopPolling_WithTrueCondition_SetsStatusCode286()
- {
- var context = TestHelper.CreateHtmxRequestContext();
- context.Response.Htmx(r => r.StopPolling(true));
-
- Assert.That(context.Response.StatusCode, Is.EqualTo(HtmxResponse.StopPollingStatusCode));
- }
-
[Test]
public void TriggerEvent_SetsReceiveTrigger()
{