RazorX.Framework
1.0.0-beta.133
See the version list below for details.
Requires NuGet 4.0 or higher.
dotnet add package RazorX.Framework --version 1.0.0-beta.133
NuGet\Install-Package RazorX.Framework -Version 1.0.0-beta.133
<PackageReference Include="RazorX.Framework" Version="1.0.0-beta.133" />
<PackageVersion Include="RazorX.Framework" Version="1.0.0-beta.133" />
<PackageReference Include="RazorX.Framework" />
paket add RazorX.Framework --version 1.0.0-beta.133
#r "nuget: RazorX.Framework, 1.0.0-beta.133"
#:package RazorX.Framework@1.0.0-beta.133
#addin nuget:?package=RazorX.Framework&version=1.0.0-beta.133&prerelease
#tool nuget:?package=RazorX.Framework&version=1.0.0-beta.133&prerelease
RazorX.Framework
RazorX.Framework is a Server-Driven UI (SDUI) hypermedia framework that rethinks where architectural decisions belong in web applications. Inspired by htmx's hypermedia approach, RazorX takes a fundamentally different path: it separates concerns based on their natural ownership - the server controls what happens to the UI, while the client controls when and how to request changes.
This separation represents a philosophical stance about hypermedia applications. In RazorX, determining where a new todo appears in the DOM and how it merges with existing content is business logic that belongs on the server. The server understands the complete UI structure, relationships between components, and the semantic intent of updates. Meanwhile, the client owns interaction concerns - when users click a button that issues a request to an endpoint, the client handles the mechanics for the request, like queueing, encoding form data as JSON, or including state data that was persisted in a previous response.
By choosing deep ASP.NET Core integration over server-agnostic design, RazorX can fully realize this vision. Response headers carry precise DOM manipulation instructions while HTML attributes define request behaviors. The server orchestrates complex multi-element updates atomically through a fluent API, while the client remains a thin hypermedia agent that follows server directives without embedding application logic. This achieves what REST always promised: the server drives application state through hypermedia controls.
The result is a framework where responsibilities live where they belong. Business logic resides entirely on the server, expressed through Razor components and strongly-typed handlers. Interaction patterns stay on the client, managed through declarative attributes. This creates a coherent mental model where changing application behavior requires only server-side changes, while client-side code remains stable and reusable. It's hypermedia as it was meant to be - with the server as the single source of truth for both state and state transitions.
Getting Started
Installation
Install the RazorX.Framework NuGet package:
dotnet add package RazorX.Framework
Basic Setup
Configure RazorX in your ASP.NET Core application's Program.cs
:
using RazorX.Framework;
var builder = WebApplication.CreateBuilder(args);
// Add RazorX services
builder.Services.AddRxDriver(); // Defaults to JSON form encoding
var app = builder.Build();
// Serve static files (for razorx.js and razorx.css)
app.UseStaticFiles();
// Map RazorX routes
app.MapGroup(string.Empty).MapRoutes();
app.Run();
Note: By default, RazorX encodes form data as JSON for optimal compatibility with ASP.NET Core minimal APIs, which provide better model binding for JSON payloads. If you're using traditional MVC controllers or prefer standard form encoding, you can disable JSON conversion:
builder.Services.AddRxDriver(options => {
options.AddJsonConverters = false; // Use traditional form encoding
});
Client Setup
The RazorX client files are automatically copied to your wwwroot
folder during build:
razorx.js
(~45KB) - The JavaScript client file (compiled from TypeScript source) that handles:- Event delegation and trigger management
- AJAX request processing
- DOM manipulation via fragment merging
- Memory management with automatic cleanup
razorx.css
(~6KB) - Essential styles for:- Toast notifications positioning and animations
- Loading indicator visibility states
Initialize the client in your layout or page:
<link rel="stylesheet" href="/css/razorx.css">
<script type="module">
import { razorx } from '/js/razorx.js';
razorx.init({
// encodeRequestFormDataAsJson: true is the default
// Set to false if using traditional form encoding
});
</script>
Note on script placement: JavaScript modules are deferred by default, executing after the DOM is fully parsed regardless of placement in <head>
or <body>
. This ensures razorx.init()
runs after all elements with data-rx-*
attributes exist. Script placement is therefore a matter of preference - use <head>
for organization or <body>
for traditional placement.
Example Project
For a complete working implementation, see the RazorX Framework Example project. The examples below are simplified to demonstrate the general patterns and do not include all necessary code.
Step 1: Create a Layout (IRootComponent)
First, create a layout component that implements IRootComponent
. This serves as the shell for full page renders:
@* Components/Layout/App.razor *@
@implements IRootComponent
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>@(Title ?? "RazorX App")</title>
<link rel="stylesheet" href="/css/razorx.css">
<script type="module">
import { razorx } from '/js/razorx.js';
razorx.init({
// Add CSRF token if using antiforgery
addCookieToRequestHeader: "RequestVerificationToken"
});
</script>
@if (HeadContent != null)
{
<DynamicComponent Type="@HeadContent" />
}
</head>
<body>
<DynamicComponent Type="@MainContent" Parameters="@MainContentParameters" />
</body>
</html>
@code {
[Parameter] public Type? HeadContent { get; set; }
[Parameter] public Type MainContent { get; set; } = null!;
[Parameter] public Dictionary<string, object?> MainContentParameters { get; set; } = [];
[Parameter] public string? Title { get; set; }
}
Note on DynamicComponent usage: RazorX uses ASP.NET Core's DynamicComponent
from Razor Components to enable runtime component selection. The RxDriver passes component types (not instances) to the layout:
MainContent
receives the Type of the page component to renderHeadContent
optionally receives a Type for additional head elementsMainContentParameters
passes the model data as a dictionary to the MainContent component
This pattern allows the server to dynamically control which components are rendered without the layout needing compile-time knowledge of specific page types. When RenderPage<App, HomePage, HomeModel>()
is called, the framework passes typeof(HomePage)
as MainContent and the model wrapped in MainContentParameters.
Step 2: Create Components
Create your page and fragment components. Page components are full views, while fragment components are reusable pieces:
@* Components/Todo/TodoListPage.razor *@
@implements IComponentModel<TodoListModel>
<div id="todo-container">
<h1>Todo List</h1>
<input id="search-todos"
type="search"
name="filter"
data-rx-action="/search-todos"
data-rx-trigger="input"
data-rx-debounce="400"
placeholder="Search todos...">
<div id="todo-list">
@foreach (var todo in Model.Todos)
{
<TodoItem Model="@todo" />
}
</div>
<button id="add-todo-btn"
data-rx-action="/todo/new"
data-rx-method="GET">
Add Todo
</button>
</div>
@code {
[Parameter] public TodoListModel Model { get; set; } = null!;
}
@* Components/Todo/TodoItem.razor *@
@implements IComponentModel<TodoModel>
<article id="todo-item-@Model.Id">
<div>@Model.Text</div>
<button data-rx-action="/todo/@Model.Id"
data-rx-method="DELETE"
data-rx-loading-indicator="delete-spinner-@Model.Id">
Delete
<span id="delete-spinner-@Model.Id"
class="rx-loading-hidden"
aria-busy="true"></span>
</button>
</article>
@code {
[Parameter] public TodoModel Model { get; set; } = null!;
}
Step 3: Create a Request Handler
Create a handler that serves both full page requests and fragment updates:
using RazorX.Framework;
// Model definitions
public record TodoModel(int Id, string Text, bool IsComplete);
public record TodoListModel(List<TodoModel> Todos, int TotalCount);
public class TodoHandler : RequestHandler
{
private static readonly List<TodoModel> _todos = [];
public override void MapRoutes(IEndpointRouteBuilder router)
{
router.MapGet("/", GetTodoPage);
router.MapGet("/search-todos", SearchTodos);
router.MapDelete("/todo/{id:int}", DeleteTodo);
router.MapGet("/todo/new", GetNewTodoForm);
router.MapPost("/todo", CreateTodo);
}
// Full page render (initial load)
public static async Task<IResult> GetTodoPage(
HttpContext context,
IRxDriver rxDriver)
{
var model = new TodoListModel(_todos, _todos.Count);
return await rxDriver.RenderPage<App, TodoListPage, TodoListModel>(
context,
model,
"Todo List"
);
}
// Search with fragment updates
public static async Task<IResult> SearchTodos(
HttpContext context,
IRxDriver rxDriver,
string filter = "")
{
var filtered = _todos
.Where(t => t.Text.Contains(filter, StringComparison.OrdinalIgnoreCase))
.ToList();
return await rxDriver
.With(context)
.AddFragment<TodoList, List<TodoModel>>(
filtered,
"todo-list",
FragmentMergeStrategyType.SwapInner)
.AddTriggerSetState("filter", filter, MetadataScope.Session, updateUrl: true)
.Render();
}
// Delete with element removal
public static async Task<IResult> DeleteTodo(
HttpContext context,
IRxDriver rxDriver,
int id)
{
var todo = _todos.FirstOrDefault(t => t.Id == id);
if (todo == null)
{
return Results.NotFound();
}
_todos.Remove(todo);
return await rxDriver
.With(context)
.RemoveElement($"todo-item-{id}")
.AddTriggerToast("Todo deleted", ToastType.Success)
.Render();
}
// Return a form fragment for creating new todo
public static async Task<IResult> GetNewTodoForm(
HttpContext context,
IRxDriver rxDriver)
{
return await rxDriver
.With(context)
.AddFragment<TodoForm>("todo-container", FragmentMergeStrategyType.AppendBeforeEnd)
.AddTriggerFocusElement("todo-text-input", positionCursorEnd: true)
.Render();
}
// Create todo and update multiple fragments
public static async Task<IResult> CreateTodo(
HttpContext context,
IRxDriver rxDriver,
[FromForm] string text)
{
if (string.IsNullOrWhiteSpace(text))
{
return await rxDriver
.With(context)
.AddTriggerToast("Text is required", ToastType.Error)
.Render();
}
var todo = new TodoModel(_todos.Count + 1, text, false);
_todos.Add(todo);
return await rxDriver
.With(context)
.AddFragment<TodoItem, TodoModel>(
todo,
"todo-list",
FragmentMergeStrategyType.AppendAfterBegin)
.RemoveElement("todo-form")
.AddTriggerToast("Todo created!", ToastType.Success)
.AddTriggerFocusElement("add-todo-btn")
.Render();
}
}
Antiforgery Support
To add CSRF protection to your application:
var builder = WebApplication.CreateBuilder(args);
// Services
builder.Services.AddRxDriver();
builder.Services.AddAntiforgery(); // ASP.NET Core antiforgery
builder.Services.AddRxAntiforgery(); // RazorX antiforgery integration
var app = builder.Build();
// Middleware pipeline
app.UseStaticFiles();
app.UseAntiforgery();
app.UseRxAntiforgeryCookie(); // Manages CSRF tokens for AJAX requests
// Routes
app.MapGroup(string.Empty).MapRoutes();
app.Run();
Then update your client initialization:
razorx.init({
addCookieToRequestHeader: "RequestVerificationToken"
});
Important Notes
Templating: RazorX uses Razor Components (.razor files) exclusively. Traditional Razor Pages (.cshtml) are not supported.
Routing: RazorX uses the
RequestHandler
pattern with minimal APIs by default. Traditional MVC controllers can coexist - simply addservices.AddControllers()
andapp.MapControllers()
. Controllers can return Razor Components using the sameIRxDriver
methods.Request Detection: RazorX automatically detects whether to return a full page or fragment based on the presence of the "rx-request" header. You can check this in your handlers using the
IsRxRequest()
extension method:// Useful for error handling or conditional logic if (context.Request.IsRxRequest()) { // This is an AJAX request from RazorX client // Return 202 Accepted with location header for redirects return TypedResults.Accepted("/error"); } else { // This is a regular page navigation return TypedResults.Redirect("/error"); }
Element IDs: Any element with a
data-rx-action
attribute must have a unique ID for proper request tracking. The framework will throw an error if an ID is missing.Special Triggers: The
initialized
,poll
, andrevealed
triggers must use GET method. They cannot be debounced and don't supportdata-rx-disable-queueing
.
Framework Mechanics Reference
RxDriver API
The IRxDriver
interface provides methods for rendering pages and building AJAX responses.
Page Rendering Methods
RenderPage<TLayout, TPage>(HttpContext context, string? title)
Renders a full HTML page without a model.
RenderPage<TLayout, TPage, TModel>(HttpContext context, TModel model, string? title)
Renders a full HTML page with a model.
RenderPage<TLayout, THead, TPage>(HttpContext context, string? title)
Renders a full HTML page with custom head content.
RenderPage<TLayout, THead, TPage, TModel>(HttpContext context, TModel model, string? title)
Renders a full HTML page with custom head content and a model.
Response Builder Methods
With(HttpContext context)
Starts building a response for the given HTTP context. Returns IRxResponseBuilder
.
IRxResponseBuilder Methods
Fragment Methods
AddFragment<TComponent>(string targetId, FragmentMergeStrategyType strategy)
Adds a component fragment without a model.AddFragment<TComponent, TModel>(TModel model, string targetId, FragmentMergeStrategyType strategy)
Adds a component fragment with a model.RemoveElement(string targetId)
Removes an element from the DOM.
Trigger Methods
AddTriggerToast(string message, ToastType type, ...)
Shows a toast notification. Optional parameters: duration, vertical position, horizontal position, click to dismiss.AddTriggerFocusElement(string elementId, bool positionCursorEnd = false)
Sets focus to an element, optionally positioning cursor at end.AddTriggerSetState(string key, string value, MetadataScope scope, bool updateUrl = false)
Persists a single state value to browser storage.AddTriggerSetStateBatch(Dictionary<string, string> states, MetadataScope scope, bool updateUrl = false)
Persists multiple state values to browser storage.AddTriggerCloseDialog(string dialogId, string? onCloseData = null, string? resetFormId = null)
Closes a dialog element, optionally resetting a form.
Render Method
Render(bool ignoreActiveElementValueOnMorph = false)
Executes the response, returning anIResult
.
Fragment Merge Strategies
Swap
- Replaces entire target element (default)SwapInner
- Replaces inner content onlyMorph
- Intelligent DOM diffing (preserves state)AppendAfterBegin
- Insert as first childAppendBeforeEnd
- Insert as last childAppendBeforeBegin
- Insert before targetAppendAfterEnd
- Insert after target
Enums
ToastType
: Success, Error, Warning, InfoToastVerticalPosition
: Top, Center, BottomToastHorizontalPosition
: Left, Middle, RightMetadataScope
: Session (sessionStorage), Persistent (localStorage)
Client Attributes Reference
Core Attributes
Attribute | Required | Description | Example |
---|---|---|---|
data-rx-action |
Yes | URL/path for the request | data-rx-action="/api/todos" |
data-rx-method |
No | HTTP method (GET, POST, PUT, PATCH, DELETE). Defaults: GET for most elements, POST for forms | data-rx-method="DELETE" |
data-rx-trigger |
No | Event(s) that trigger the request. Default: "click" for buttons, "submit" for forms, "change" for inputs | data-rx-trigger="input" or data-rx-trigger='["click", "blur"]' |
Request Modifiers
Attribute | Description | Example |
---|---|---|
data-rx-debounce |
Delay in milliseconds before sending request | data-rx-debounce="500" |
data-rx-disable-in-flight |
Disable element while request is in progress | data-rx-disable-in-flight or data-rx-disable-in-flight="true" |
data-rx-disable-queueing |
Skip request queue, allow parallel requests | data-rx-disable-queueing |
data-rx-allow-event-default |
Don't prevent default browser behavior | data-rx-allow-event-default="true" |
UI Feedback
Attribute | Description | Example |
---|---|---|
data-rx-loading-indicator |
ID of element to show/hide during request | data-rx-loading-indicator="spinner" |
State Management
Attribute | Description | Example |
---|---|---|
data-rx-include-state |
Browser storage keys to include in request. String for single key, JSON array for multiple | data-rx-include-state="theme" or data-rx-include-state='["theme", "locale", "userId"]' |
Delegation
Attribute | Description | Example |
---|---|---|
data-rx-delegate-action-to |
ID of element to transfer the action to | data-rx-delegate-action-to="confirm-dialog-ok" |
File Upload
Attribute | Description | Example |
---|---|---|
data-rx-file-upload-max-size |
Maximum file size in bytes | data-rx-file-upload-max-size="5242880" |
data-rx-file-upload-timeout |
Upload timeout in milliseconds | data-rx-file-upload-timeout="60000" |
data-rx-file-upload-progress-id |
ID of progress element to update | data-rx-file-upload-progress-id="upload-progress" |
Special Triggers
Special triggers use JSON syntax in the data-rx-trigger
attribute.
Initialized
Fires once when element enters DOM.
<div data-rx-action="/api/load" data-rx-trigger='{"type": "initialized"}'></div>
<div data-rx-action="/api/load" data-rx-trigger='{"type": "initialized", "delay": 1000}'></div>
Poll
Fires repeatedly at intervals.
<div data-rx-action="/api/status" data-rx-trigger='{"type": "poll", "interval": 5000}'></div>
Revealed
Fires when element enters viewport.
<div data-rx-action="/api/lazy" data-rx-trigger='{"type": "revealed"}'></div>
<div data-rx-action="/api/lazy" data-rx-trigger='{"type": "revealed", "margin": "200px"}'></div>
Combining Triggers
Mix regular and special triggers.
<button data-rx-action="/api/data" data-rx-trigger='["click", {"type": "poll", "interval": 30000}]'>Click or Auto-refresh</button>
<div data-rx-action="/api/update" data-rx-trigger='["input", "blur", {"type": "initialized"}]'>Content</div>
Note: Special triggers must use GET method and cannot be debounced.
Complete Element Examples
Common attribute combinations:
<input data-rx-action="/api/search" data-rx-trigger="input" data-rx-debounce="300" />
<form data-rx-action="/api/save" data-rx-method="POST" data-rx-disable-in-flight data-rx-loading-indicator="save-spinner">
</form>
<button data-rx-action="/api/filter" data-rx-include-state='["sort", "filter", "page"]'>Apply Filter</button>
<button data-rx-action="/api/item/1" data-rx-method="DELETE" data-rx-delegate-action-to="confirm-ok">Delete</button>
<input type="file" data-rx-action="/api/upload" data-rx-file-upload-progress-id="progress" data-rx-file-upload-max-size="10485760" />
<div data-rx-action="/api/content" data-rx-trigger='{"type": "revealed", "margin": "100px"}'>Loading...</div>
<div data-rx-action="/api/metrics" data-rx-trigger='[{"type": "initialized"}, {"type": "poll", "interval": 10000}]'>Dashboard</div>
Response Headers
Headers sent by the server to control client behavior.
Header | Description |
---|---|
rx-merge |
JSON array of fragment merge strategies |
rx-trigger-close-dialog |
JSON object with dialog close instructions |
rx-trigger-focus-element |
JSON object with focus instructions |
rx-trigger-set-state |
JSON array of state updates |
rx-trigger-toast |
JSON object with toast notification |
rx-morph-ignore-active |
Boolean flag to preserve active element value |
Request Headers
Headers sent by the client.
Header | Description |
---|---|
rx-request |
Present on all AJAX requests (empty value) |
CSS Classes
Classes used by the framework.
Class | Description |
---|---|
rx-loading-visible |
Applied to loading indicators when visible |
rx-loading-hidden |
Applied to loading indicators when hidden |
Extension Methods
HttpContext.Request.IsRxRequest()
Returns true
if the request is an AJAX request from the RazorX client.
Component Interfaces
IRootComponent
Implemented by layout components for full page rendering.
IComponentModel<TModel>
Implemented by components that accept a model parameter.
Request Handler Pattern
RequestHandler
Abstract base class for defining routes and handlers.
public abstract class RequestHandler
{
public abstract void MapRoutes(IEndpointRouteBuilder router);
}
Routes are automatically discovered and registered at startup.
Events and Callbacks
RazorX provides three ways to hook into the framework's lifecycle: DOM events, global callbacks, and element callbacks.
Client Events
The framework dispatches custom events on the document that you can listen to:
document.addEventListener('rx:before-fetch', (event) => {
console.log('Request starting:', event.detail.requestConfiguration.action);
});
Available Events
Event | Detail Properties | Cancelable | Description |
---|---|---|---|
rx:before-document-processed |
None | No | Fired before initial document processing |
rx:after-document-processed |
None | No | Fired after initial document processing |
rx:before-initialize-element |
{ element: HTMLElement } |
Yes | Fired before element initialization. Call preventDefault() to cancel |
rx:after-initialize-element |
{ element: HTMLElement } |
No | Fired after element initialization |
rx:before-fetch |
{ triggerElement: HTMLElement, requestConfiguration: RequestConfiguration } |
No | Fired before AJAX request |
rx:after-fetch |
{ triggerElement: HTMLElement, requestDetail: RequestDetail, response: Response } |
No | Fired after AJAX response |
rx:before-document-update |
{ triggerElement: HTMLElement, targetElement: HTMLElement, strategy: string } |
Yes | Fired before DOM update. Call preventDefault() to cancel |
rx:after-document-update |
{ triggerElement: HTMLElement } |
No | Fired after DOM update |
rx:element-added |
{ element: HTMLElement } |
No | Fired when element added to DOM |
rx:element-morphed |
{ element: HTMLElement } |
No | Fired when element morphed |
rx:element-removed |
{ element: HTMLElement } |
No | Fired when element removed from DOM |
rx:element-trigger-error |
{ triggerElement: HTMLElement, error: Error } |
No | Fired on request error |
rx:file-selected |
{ fileInput: HTMLInputElement, files: FileInfo[], error?: Error } |
No | Fired when files selected |
rx:file-upload-progress |
{ fileInput: HTMLInputElement, progressContext: object } |
No | Fired during upload progress |
Global Callbacks
Register callbacks that apply to all RazorX elements:
razorx.addCallbacks({
beforeFetch: (element, config) => {
// Add auth header to all requests
config.headers.set('Authorization', 'Bearer ' + token);
},
afterFetch: (element, request, response) => {
// Log all responses
console.log(`${request.method} ${request.action}: ${response.status}`);
},
onElementTriggerError: (element, error) => {
// Global error handler
console.error('Request failed:', error);
}
});
Available Global Callbacks
Callback | Parameters | Return Value | Description |
---|---|---|---|
beforeDocumentProcessed |
None | void | Called before initial processing |
afterDocumentProcessed |
None | void | Called after initial processing |
beforeInitializeElement |
(element: HTMLElement) |
boolean | Return false to prevent initialization |
afterInitializeElement |
(element: HTMLElement) |
void | Called after element initialized |
beforeFetch |
(element: HTMLElement, config: RequestConfiguration) |
void | Called before request. Can modify config or call config.abort() |
afterFetch |
(element: HTMLElement, request: RequestDetail, response: Response) |
void | Called after response received |
beforeDocumentUpdate |
(triggerElement: HTMLElement, targetElement: HTMLElement, strategy: string) |
boolean | Return false to prevent update |
afterDocumentUpdate |
(element: HTMLElement) |
void | Called after DOM update |
onElementTriggerError |
(element: HTMLElement, error: any) |
void | Called on request error |
onElementMorphed |
(element: HTMLElement) |
void | Called after element morphed |
onFileSelected |
(fileInput: HTMLInputElement, files: FileInfo[], error?: Error) |
void | Called when files selected |
onFileUploadProgress |
(fileInput: HTMLInputElement, context: FileUploadProgressContext) |
void | Called during upload |
Element Callbacks
Register callbacks for specific elements:
const searchInput = document.getElementById('search');
searchInput.addRxCallbacks({
beforeFetch: (config) => {
// Add search-specific header
config.headers.set('X-Search-Context', 'navbar');
},
afterDocumentUpdate: () => {
// Highlight search results
highlightMatches(searchInput.value);
}
});
Available Element Callbacks
Callback | Parameters | Return Value | Description |
---|---|---|---|
beforeFetch |
(config: RequestConfiguration) |
void | Called before this element's request. Can modify config or call config.abort() |
afterFetch |
(request: RequestDetail, response: Response) |
void | Called after this element's response |
beforeDocumentUpdate |
(targetElement: HTMLElement, strategy: string) |
boolean | Return false to prevent this element's update |
afterDocumentUpdate |
None | void | Called after this element triggers DOM update |
onElementTriggerError |
(error: any) |
void | Called on this element's request error |
onFileUploadProgress |
(context: FileUploadProgressContext) |
void | Called during this file input's upload |
onFileSelected |
(files: FileInfo[], error?: Error) |
void | Called when files selected on this input |
Special Patterns
Aborting Requests
In beforeFetch
callbacks, call config.abort()
to cancel the request:
razorx.addCallbacks({
beforeFetch: (element, config) => {
if (!userIsAuthenticated()) {
config.abort(); // Prevents the request
showLoginModal();
}
}
});
Preventing Updates
Return false
from beforeDocumentUpdate
to cancel DOM updates:
element.addRxCallbacks({
beforeDocumentUpdate: (targetElement, strategy) => {
if (targetElement.querySelector('.unsaved-changes')) {
return confirm('Discard unsaved changes?');
}
return true; // Allow update
}
});
Request Configuration
The RequestConfiguration
object in beforeFetch
:
{
trigger: Event, // The triggering event
action: string, // Request URL
method: string, // HTTP method
body?: FormData | string, // Request body
headers: Headers, // Request headers (modifiable)
abort: () => void // Function to cancel request
}
File Upload Context
The FileUploadProgressContext
object:
{
file: File, // The file being uploaded
loaded: number, // Bytes uploaded
total: number, // Total file size
percentage: number // Upload percentage (0-100)
}
Event vs Callback Precedence
When both events and callbacks are registered, they execute in this order:
- Element callback (if defined)
- Global callback (if defined)
- DOM event (always dispatched)
For cancelable operations (beforeInitializeElement
, beforeDocumentUpdate
):
- Element callback returning
false
cancels immediately - Global callback returning
false
cancels immediately - Event
preventDefault()
cancels if not already canceled
Common Use Cases
// Global auth header
razorx.addCallbacks({
beforeFetch: (element, config) => {
config.headers.set('Authorization', `Bearer ${getToken()}`);
}
});
// Loading overlay
document.addEventListener('rx:before-fetch', () => {
showLoadingOverlay();
});
document.addEventListener('rx:after-fetch', () => {
hideLoadingOverlay();
});
// Error toast
razorx.addCallbacks({
onElementTriggerError: (element, error) => {
showErrorToast(error.message);
}
});
// Analytics tracking
document.addEventListener('rx:after-fetch', (event) => {
analytics.track('ajax_request', {
url: event.detail.requestDetail.action,
status: event.detail.response.status
});
});
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net9.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last Updated |
---|---|---|
1.0.0-beta.136 | 22 | 9/21/2025 |
1.0.0-beta.133 | 23 | 9/21/2025 |
1.0.0-beta.129 | 26 | 9/18/2025 |
1.0.0-beta.120 | 25 | 9/12/2025 |
1.0.0-beta.114 | 24 | 9/12/2025 |
1.0.0-beta.109 | 29 | 9/12/2025 |
1.0.0-beta.105 | 25 | 9/12/2025 |
1.0.0-beta.101 | 25 | 9/12/2025 |
1.0.0-beta.100 | 28 | 9/12/2025 |
1.0.0-beta.99 | 25 | 9/12/2025 |
1.0.0-beta.98 | 29 | 9/12/2025 |
1.0.0-beta.97 | 25 | 9/12/2025 |
1.0.0-beta.96 | 25 | 9/12/2025 |
1.0.0-beta.84 | 32 | 9/7/2025 |
1.0.0-beta.77 | 25 | 9/6/2025 |
1.0.0-beta.73 | 29 | 9/6/2025 |
1.0.0-beta.69 | 24 | 9/6/2025 |
1.0.0-beta.68 | 27 | 9/6/2025 |
1.0.0-beta.67 | 27 | 9/6/2025 |
1.0.0-beta.62 | 28 | 8/31/2025 |
1.0.0-beta.58 | 22 | 8/30/2025 |
1.0.0-beta.56 | 21 | 8/30/2025 |
1.0.0-beta.55 | 25 | 8/30/2025 |
1.0.0-beta.54 | 26 | 8/30/2025 |
1.0.0-beta.53 | 23 | 8/30/2025 |
1.0.0-beta.52 | 22 | 8/30/2025 |
1.0.0-beta.32 | 29 | 8/24/2025 |
1.0.0-beta.22 | 25 | 8/23/2025 |
1.0.0-beta.17 | 24 | 8/23/2025 |
See CHANGELOG.md for detailed release notes