Migrating all validation to use HTMX and endpoints instead of WASM/Websocket connections

This commit is contained in:
2026-04-13 18:40:17 +05:00
parent d1f0967a0c
commit b323862e03
24 changed files with 1203 additions and 188 deletions
@@ -26,6 +26,9 @@
<body class="min-h-svh antialiased bg-background text-foreground">
<Routes />
<script src="_framework/blazor.web.js"></script>
<script src="https://unpkg.com/htmx.org@2.0.4"
integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+"
crossorigin="anonymous"></script>
<script type="module">
import { init as initDarkMode } from '/_content/Enciphered.Blazor.UIComponents/js/darkmode.js';
import { init as initSidebar } from '/_content/Enciphered.Blazor.UIComponents/js/sidebar.js';
@@ -5,45 +5,46 @@
<div class="space-y-6 max-w-lg">
<div>
<h1 class="text-3xl font-bold tracking-tight">Forms Demo</h1>
<p class="text-muted-foreground">All input components — fully static SSR with JS interactivity.</p>
<p class="text-muted-foreground">All input components — fully static SSR with htmx validation.</p>
</div>
<form>
<HtmxForm Endpoint="/api/forms/contact">
<FormField Label="Full Name" For="name">
<TextInput Id="name" Name="name" Placeholder="Jane Doe" data-testid="input-name" />
</FormField>
<div class="space-y-4">
<FormField Label="Full Name" For="name">
<TextInput Id="name" Name="name" Placeholder="Jane Doe" data-testid="input-name" />
</FormField>
<FormField Label="Email" For="email">
<TextInput Id="email" Name="email" Type="email" Placeholder="jane@example.com" data-testid="input-email" />
</FormField>
<FormField Label="Email" For="email">
<TextInput Id="email" Name="email" Type="email" Placeholder="jane@example.com" data-testid="input-email" />
</FormField>
<FormField Label="Password" For="password">
<TextInput Id="password" Name="password" Type="password" Placeholder="••••••••" data-testid="input-password" />
</FormField>
<FormField Label="Password" For="password">
<TextInput Id="password" Name="password" Type="password" Placeholder="••••••••" data-testid="input-password" />
</FormField>
<FormField Label="Age" For="age">
<NumberInput Id="age" Name="age" Placeholder="25" Min="0" Max="150" data-testid="input-age" />
</FormField>
<FormField Label="Age" For="age">
<NumberInput Id="age" Name="age" Placeholder="25" Min="0" Max="150" data-testid="input-age" />
</FormField>
<FormField Label="Birth Date" For="birthdate">
<DateInput Id="birthdate" Name="birthdate" data-testid="input-birthdate" />
</FormField>
<FormField Label="Birth Date" For="birthdate">
<DateInput Id="birthdate" Name="birthdate" data-testid="input-birthdate" />
</FormField>
<FormField Label="Preferred Time" For="preferredtime">
<TimeInput Id="preferredtime" Name="preferredtime" data-testid="input-time" />
</FormField>
<FormField Label="Preferred Time" For="preferredtime">
<TimeInput Id="preferredtime" Name="preferredtime" data-testid="input-time" />
</FormField>
<FormField Label="Appointment" For="appointment">
<DateTimeInput Id="appointment" Name="appointment" data-testid="input-appointment" />
</FormField>
<FormField Label="Appointment" For="appointment">
<DateTimeInput Id="appointment" Name="appointment" data-testid="input-appointment" />
</FormField>
<FormField Label="Confirmation" For="confirmation">
<TextInput Id="confirmation" Name="confirmation" Placeholder='Type "CONFIRM"' data-testid="input-confirmation" />
</FormField>
<div class="flex gap-2 pt-2">
<Button Type="submit" data-testid="btn-submit">Submit</Button>
<Button Type="reset" Variant="@ButtonVariant.Outline" data-testid="btn-reset">Reset</Button>
<Button Variant="@ButtonVariant.Destructive" Disabled="true" data-testid="btn-disabled">Disabled</Button>
</div>
<div class="flex gap-2 pt-2">
<Button Type="submit" data-testid="btn-submit">Submit</Button>
<Button Type="reset" Variant="@ButtonVariant.Outline" data-testid="btn-reset">Reset</Button>
<Button Variant="@ButtonVariant.Destructive" Disabled="true" data-testid="btn-disabled">Disabled</Button>
</div>
</form>
</HtmxForm>
</div>
@@ -0,0 +1,13 @@
namespace Enciphered.Blazor.UIComponents.Demo;
public class ContactFormModel
{
public string Name { get; set; } = "";
public string Email { get; set; } = "";
public string Password { get; set; } = "";
public int? Age { get; set; }
public DateOnly? Birthdate { get; set; }
public TimeOnly? Preferredtime { get; set; }
public DateTime? Appointment { get; set; }
public string Confirmation { get; set; } = "";
}
@@ -0,0 +1,47 @@
using Enciphered.Blazor.UIComponents.Validation;
namespace Enciphered.Blazor.UIComponents.Demo;
public class ContactFormValidator : FormValidator
{
public ContactFormValidator()
{
RuleFor("name",
displayName: "Name",
required: true,
minLength: 2);
RuleFor("email",
displayName: "Email",
required: true,
pattern: @".+@.+\..+",
message: "Please enter a valid email address.");
RuleFor("password",
displayName: "Password",
required: true,
minLength: 6);
RuleFor("age",
displayName: "Age",
min: 0,
max: 150);
RuleFor("birthdate",
displayName: "Birth Date",
custom: value => !DateOnly.TryParse(value, out _) ? "Please enter a valid date." : null);
RuleFor("preferredtime",
displayName: "Preferred Time",
custom: value => !TimeOnly.TryParse(value, out _) ? "Please enter a valid time." : null);
RuleFor("appointment",
displayName: "Appointment",
custom: value => !DateTime.TryParse(value, out _) ? "Please enter a valid date and time." : null);
RuleFor("confirmation",
displayName: "Confirmation",
required: true,
custom: value => value != "CONFIRM" ? "You must type CONFIRM to proceed." : null);
}
}
+18 -4
View File
@@ -1,27 +1,41 @@
using Enciphered.Blazor.UIComponents.Demo;
using Enciphered.Blazor.UIComponents.Demo.Components;
using Enciphered.Blazor.UIComponents.Validation;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorComponents();
builder.Services.AddAntiforgery();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseAntiforgery();
app.MapStaticAssets();
app.MapRazorComponents<App>()
.AddAdditionalAssemblies(typeof(Enciphered.Blazor.UIComponents.SidebarProvider).Assembly);
app.MapFormValidation<ContactFormValidator, ContactFormModel>("/api/forms/contact",
onSuccess: async model =>
{
Console.WriteLine("── Form Submitted ──");
Console.WriteLine($" Name: {model.Name}");
Console.WriteLine($" Email: {model.Email}");
Console.WriteLine($" Password: {model.Password}");
Console.WriteLine($" Age: {model.Age}");
Console.WriteLine($" Birth Date: {model.Birthdate}");
Console.WriteLine($" Time: {model.Preferredtime}");
Console.WriteLine($" Appointment: {model.Appointment}");
Console.WriteLine($" Confirmation: {model.Confirmation}");
await Task.CompletedTask;
});
app.Run();