b530bb8c97
Co-authored-by: Copilot <copilot@github.com>
183 lines
6.2 KiB
Markdown
183 lines
6.2 KiB
Markdown
# Switch
|
|
|
|
An on/off toggle that looks like a physical light switch. Use it for settings where the effect is immediate or where a simple checked/unchecked checkbox would feel too plain — "Enable notifications", "Dark mode", "Maintenance mode".
|
|
|
|
---
|
|
|
|
## Quick example
|
|
|
|
```csharp
|
|
new Switch(
|
|
id: "notifications",
|
|
label: "Enable notifications",
|
|
name: "enableNotifications",
|
|
isChecked: true)
|
|
```
|
|
|
|
---
|
|
|
|
## All the options
|
|
|
|
```csharp
|
|
public Switch(
|
|
string id,
|
|
string label = "",
|
|
string name = "",
|
|
bool isChecked = false)
|
|
```
|
|
|
|
| Parameter | What it does |
|
|
|---|---|
|
|
| `id` | The element id for the hidden checkbox. |
|
|
| `label` | Optional text shown to the right of the toggle. |
|
|
| `name` | Form field name — required if you want the value submitted. |
|
|
| `isChecked` | Whether the switch is on by default. |
|
|
|
|
---
|
|
|
|
## Real-world examples
|
|
|
|
### Preferences form with multiple toggles
|
|
|
|
```csharp
|
|
new Switch(id: "email-alerts", label: "Email alerts", name: "emailAlerts", isChecked: prefs.EmailAlerts)
|
|
new Switch(id: "push-notifs", label: "Push notifications", name: "pushNotifs", isChecked: prefs.PushNotifs)
|
|
new Switch(id: "weekly-summary", label: "Weekly digest", name: "weeklySummary", isChecked: prefs.WeeklySummary)
|
|
```
|
|
|
|
Reading on the server:
|
|
|
|
```csharp
|
|
public record Command(
|
|
[property: FromForm] string? EmailAlerts = null, // null = off
|
|
[property: FromForm] string? PushNotifs = null,
|
|
[property: FromForm] string? WeeklySummary = null
|
|
);
|
|
|
|
bool emailAlerts = command.EmailAlerts != null;
|
|
```
|
|
|
|
> **Important:** Like a checkbox, an unchecked switch is not included in the form submission. Always use `string?` (nullable) with a default of `null`.
|
|
|
|
### Toggle without a label (e.g. in a table row)
|
|
|
|
```csharp
|
|
new Switch(id: $"active-{user.Id}", name: "isActive", isChecked: user.IsActive)
|
|
```
|
|
|
|
---
|
|
|
|
## How it works
|
|
|
|
Switch is a styled `<label>` wrapping a hidden `<input type="checkbox">`. JavaScript in `components.js` listens for clicks and animates the visible track and thumb. The hidden checkbox holds the actual state and is what gets submitted with a form. Because it is a real checkbox under the hood, the form submission behaviour is identical to a plain Checkbox component.
|
|
```
|
|
|
|
```html
|
|
<!-- Parent form with HTMX:
|
|
<form hx-post="/settings" hx-trigger="change from:#maintenance">
|
|
$$MaintenanceSwitch$$
|
|
</form>
|
|
-->
|
|
```
|
|
|
|
---
|
|
|
|
## Tips and tricks
|
|
|
|
- The hidden checkbox carries the value `"on"` when checked (standard checkbox default). If you need `"true"`, add `value="true"` by subclassing or via a wrapper form.
|
|
- Because the click is handled on the `<label>` element, the switch works correctly even when the hidden input is not directly clicked.
|
|
- For an HTMX auto-save switch, trigger on `change` from the hidden checkbox using `hx-trigger="change from:#myId"` on a parent element.
|
|
- Pairing `isChecked` with a server-read preference ensures the switch reflects the saved state on every page load.
|
|
- Pairing `isChecked` with a server-read preference ensures the switch reflects the saved state on every page load.
|
|
|
|
---
|
|
|
|
## Complete page example
|
|
|
|
**`Templates/NotificationsPage.htmx`**
|
|
```html
|
|
<div class="max-w-md mx-auto py-10">
|
|
<h1 class="text-2xl font-bold mb-6">Notifications</h1>
|
|
<form method="post" action="/notifications">
|
|
$$AntiforgeryToken$$
|
|
<div class="space-y-5 mb-8">
|
|
$$EmailSwitch$$
|
|
$$PushSwitch$$
|
|
$$SmsSwitch$$
|
|
</div>
|
|
$$SaveBtn$$
|
|
</form>
|
|
$$SuccessAlert$$
|
|
</div>
|
|
```
|
|
|
|
**`Templates/NotificationsPage.htmx.cs`**
|
|
```csharp
|
|
namespace Htmx.ApiDemo.Templates;
|
|
|
|
public sealed class NotificationsPage : NotificationsPageBase
|
|
{
|
|
private readonly IHtmxComponent _email;
|
|
private readonly IHtmxComponent _push;
|
|
private readonly IHtmxComponent _sms;
|
|
private readonly IHtmxComponent _save;
|
|
private readonly IHtmxComponent _success;
|
|
private readonly byte[] _afToken;
|
|
|
|
public NotificationsPage(
|
|
IAntiforgery af,
|
|
HttpContext ctx,
|
|
NotificationPrefs? prefs = null,
|
|
bool saved = false)
|
|
{
|
|
var tokens = af.GetAndStoreTokens(ctx);
|
|
_afToken = $"""<input type="hidden" name="{tokens.FormFieldName}" value="{tokens.RequestToken}">""".ToUtf8Bytes();
|
|
|
|
_email = new Components.Switch(id: "email-notif", label: "Email notifications", name: "emailNotif", isChecked: prefs?.Email ?? true);
|
|
_push = new Components.Switch(id: "push-notif", label: "Push notifications", name: "pushNotif", isChecked: prefs?.Push ?? false);
|
|
_sms = new Components.Switch(id: "sms-notif", label: "SMS notifications", name: "smsNotif", isChecked: prefs?.Sms ?? false);
|
|
_save = new Components.Button("Save", type: "submit");
|
|
_success = saved ? new Components.Alert(title: "Notification preferences saved.") : HtmxEmpty.Instance;
|
|
}
|
|
|
|
protected override void RenderAntiforgeryToken(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_afToken);
|
|
protected override void RenderEmailSwitch(HtmxRenderContext ctx) => _email.Render(ctx.Next());
|
|
protected override void RenderPushSwitch(HtmxRenderContext ctx) => _push.Render(ctx.Next());
|
|
protected override void RenderSmsSwitch(HtmxRenderContext ctx) => _sms.Render(ctx.Next());
|
|
protected override void RenderSaveBtn(HtmxRenderContext ctx) => _save.Render(ctx.Next());
|
|
protected override void RenderSuccessAlert(HtmxRenderContext ctx) => _success.Render(ctx.Next());
|
|
}
|
|
```
|
|
|
|
**POST handler**
|
|
```csharp
|
|
[Handler]
|
|
[MapPost("/notifications")]
|
|
public static partial class PostNotificationsHandler
|
|
{
|
|
public record Command(
|
|
[property: FromForm] string? EmailNotif = null,
|
|
[property: FromForm] string? PushNotif = null,
|
|
[property: FromForm] string? SmsNotif = null);
|
|
|
|
private static Task<IResult> HandleAsync(
|
|
[AsParameters] Command cmd, HttpContext ctx, IAntiforgery af, CancellationToken ct)
|
|
{
|
|
var prefs = new NotificationPrefs(
|
|
Email: cmd.EmailNotif != null,
|
|
Push: cmd.PushNotif != null,
|
|
Sms: cmd.SmsNotif != null);
|
|
// Persist prefs…
|
|
return ctx.WriteHtmxPage(
|
|
new NotificationsPage(af, ctx, prefs, saved: true), title: "Notifications");
|
|
}
|
|
}
|
|
|
|
public record NotificationPrefs(bool Email, bool Push, bool Sms);
|
|
```
|
|
|
|
**`AppJsonSerializerContext.cs`**
|
|
```csharp
|
|
[JsonSerializable(typeof(PostNotificationsHandler.Command), TypeInfoPropertyName = "NotificationsCommand")]
|
|
```
|