Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ See [Static assets](https://rameel.github.io/ramstack.htmxtoolkit/articles/stati

```html
<button hx-get
hx-controller="Books"
hx-action="List"
hx-route-category="science"
asp-controller="Books"
asp-action="List"
asp-route-category="science"
hx-target="#results">
Show science books
</button>
Expand Down Expand Up @@ -183,15 +183,16 @@ The URL attributes from the example above work with Razor Pages too:

```html
<button hx-post
hx-page="/Orders"
hx-page-handler="Archive"
hx-route-id="@Model.Id">
asp-page="/Orders"
asp-page-handler="Archive"
asp-route-id="@Model.Id">
Archive
</button>
```

The request method comes from `hx-get`, `hx-post`, `hx-put`, `hx-patch`, or `hx-delete`.
If you leave it out, `hx-get` is used.
Write one of `hx-get`, `hx-post`, `hx-put`, `hx-patch`, or `hx-delete` without a value to choose
the request method; the Tag Helper fills in its URL from the `asp-*` attributes.
Without such an attribute, no URL is generated.

Other Tag Helpers build HTMX attributes that otherwise need hand-written JSON:

Expand Down
12 changes: 7 additions & 5 deletions docs/articles/antiforgery.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ For a Razor Page with a `Save` POST handler:

```razor
<form hx-post
hx-page="/Account/Profile"
hx-page-handler="Save"
asp-page="/Account/Profile"
asp-page-handler="Save"
hx-target="#save-result">
<label>
Display name
Expand All @@ -99,10 +99,12 @@ public IActionResult OnPostSave(string? displayName)
}
```

No token input is required in this HTMX form because the layout metadata and Toolkit script supply it.
The standard Form Tag Helper renders the form `action` and a hidden token input from the same `asp-*` attributes.
The Toolkit script detects that input and does not add a second token. For HTMX requests without a token input,
such as a button outside a form, the layout metadata and Toolkit script supply the token.
The validation message returns 200 so HTMX 2.x displays it with its default response handling.
For a form that also works without JavaScript, include a normal POST action and let the standard Form Tag Helper
render a token, as shown in the [forms tutorial](forms-and-validation.md).
To make the form also work without JavaScript, return a full page for normal requests, as shown in the
[forms tutorial](forms-and-validation.md).

## Token refresh after boosted navigation

Expand Down
12 changes: 6 additions & 6 deletions docs/articles/forms-and-validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,9 @@ Create `Pages/_ProfileForm.cshtml`:

<form id="profile-editor"
method="post"
hx-post
asp-page="/Profile"
asp-page-handler="Save"
hx-post
hx-page="/Profile"
hx-page-handler="Save"
hx-target="this"
hx-swap="outerHTML"
novalidate>
Expand All @@ -89,7 +87,8 @@ The form replaces itself with `outerHTML`. Every returned form keeps the same ID
so it can be submitted again. `novalidate` lets you see the server's validation response without the browser
blocking an empty submission first. You can remove it when you add browser validation.

The ordinary `method` and `asp-page-*` attributes provide a non-HTMX form action. The standard Form Tag Helper
The `asp-page` and `asp-page-handler` attributes produce both the `hx-post` URL and, through the standard
Form Tag Helper, the ordinary form `action`, so the form also works without HTMX. The Form Tag Helper
also creates a hidden antiforgery input. The Toolkit script detects that input and does not add a second token.
For forms without that input, the layout metadata and script supply the token instead.

Expand All @@ -114,8 +113,9 @@ Create `Pages/Profile.cshtml`:
@await Html.PartialAsync("_ProfileForm", Model)

<aside id="profile-summary"
hx-page="/Profile"
hx-page-handler="Summary"
hx-get
asp-page="/Profile"
asp-page-handler="Summary"
hx-trigger="profile-saved from:body"
aria-live="polite">
@await Html.PartialAsync("_ProfileSummary", Model.CurrentDisplayName)
Expand Down
15 changes: 9 additions & 6 deletions docs/articles/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Replace `Pages/_ViewImports.cshtml` with:
```

The standard ASP.NET Core helpers resolve `~/` paths and generate versioned script URLs.
The toolkit helpers turn attributes such as `hx-page` and `hx-route-id` into HTMX request URLs.
The toolkit helpers turn standard routing attributes such as `asp-page` and `asp-route-id` into HTMX request URLs.
You will add the `HtmxStarter.Models` namespace in step 5.

## 4. Load the browser scripts
Expand Down Expand Up @@ -132,9 +132,10 @@ Create `Pages/Products.cshtml`:

<h1>Products</h1>

<button hx-page="/Products"
hx-page-handler="Details"
hx-route-id="42"
<button hx-get
asp-page="/Products"
asp-page-handler="Details"
asp-route-id="42"
hx-target="#product-details">
View product
</button>
Expand Down Expand Up @@ -181,12 +182,14 @@ Open <http://localhost:5080/Products> and select **View product**. The product d
Inspect the rendered button. Its routing attributes have become a URL similar to:

```html
<button hx-get="/Products?id=42&amp;handler=Details" hx-target="#product-details">
<button hx-get="/Products?id=42&amp;handler=Details" hx-target="#product-details"
formaction="/Products?id=42&amp;handler=Details">
View product
</button>
```

Query parameter order may differ. The important parts are the page path, `id=42`, and `handler=Details`.
The standard Form Action Tag Helper renders `formaction` from the same attributes; it matters only
when the button submits a form. Query parameter order may differ. The important parts are the page path, `id=42`, and `handler=Details`.

In the browser Network panel, the request should have `HX-Request: true`, return HTTP 200,
and contain only the `<article>` in its response. HTMX's default swap replaces the target's contents,
Expand Down
4 changes: 1 addition & 3 deletions docs/articles/loading-and-errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@ In `Pages/_ProfileForm.cshtml`, replace the opening `<form>` tag with:
```razor
<form id="profile-editor"
method="post"
hx-post
asp-page="/Profile"
asp-page-handler="Save"
hx-post
hx-page="/Profile"
hx-page-handler="Save"
hx-target="this"
hx-swap="outerHTML"
hx-indicator="#profile-busy"
Expand Down
10 changes: 5 additions & 5 deletions docs/articles/mvc.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,10 @@ Create `Views/Products/Index.cshtml`:
```razor
<h1>Products</h1>

<button hx-controller="Products"
hx-action="Details"
hx-route-id="42"
<button hx-get
asp-controller="Products"
asp-action="Details"
asp-route-id="42"
hx-target="#product-details">
View product
</button>
Expand Down Expand Up @@ -202,8 +203,7 @@ Create `Views/Products/_ProductForm.cshtml`:
@model ProductInput

<form id="product-editor"
method="post" asp-controller="Products" asp-action="Save"
hx-post hx-controller="Products" hx-action="Save"
method="post" hx-post asp-controller="Products" asp-action="Save"
hx-target="this" hx-swap="outerHTML" novalidate>
<div asp-validation-summary="All" role="alert"></div>

Expand Down
15 changes: 9 additions & 6 deletions docs/articles/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,10 @@ or `hx-swap`. Add a supported HTMX release to the application separately.
For example, write this in a Razor view:

```razor
<button hx-page="/Products"
hx-page-handler="Details"
hx-route-id="42"
<button hx-get
asp-page="/Products"
asp-page-handler="Details"
asp-route-id="42"
hx-target="#product-details">
View product
</button>
Expand All @@ -63,15 +64,17 @@ For example, write this in a Razor view:
Razor processes the toolkit attributes on the server. The browser receives markup similar to:

```html
<button hx-get="/Products?id=42&amp;handler=Details" hx-target="#product-details">
<button hx-get="/Products?id=42&amp;handler=Details" hx-target="#product-details"
formaction="/Products?id=42&amp;handler=Details">
View product
</button>

<div id="product-details"></div>
```

The query parameter order may differ. `hx-page`, `hx-page-handler`, and `hx-route-*` are server-side inputs;
HTMX in the browser uses the generated `hx-get`. `hx-target` passes through unchanged.
The query parameter order may differ. `asp-page`, `asp-page-handler`, and `asp-route-*` are server-side inputs;
HTMX in the browser uses the generated `hx-get`. The standard Form Action Tag Helper renders `formaction`
from the same attributes. `hx-target` passes through unchanged.

The page handler returns a partial view:

Expand Down
7 changes: 4 additions & 3 deletions docs/articles/pages-and-fragments.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,10 @@ public IActionResult OnGetInventory(int productId) =>
```

```razor
<button hx-page="/Products/Details"
hx-page-handler="Inventory"
hx-route-productid="@Model.Product.Id"
<button hx-get
asp-page="/Products/Details"
asp-page-handler="Inventory"
asp-route-productid="@Model.Product.Id"
hx-target="#inventory">
Check inventory
</button>
Expand Down
35 changes: 19 additions & 16 deletions docs/articles/recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ Start with the `ProfileInput`, `ProfileStore`, and bound `Input` property from t
use this markup in `Pages/Profile.cshtml` instead of `_ProfileForm`:

```razor
<form method="post" asp-page-handler="Save"
hx-post hx-page="/Profile" hx-page-handler="Save"
<form method="post" hx-post asp-page="/Profile" asp-page-handler="Save"
hx-target="#save-result" novalidate>
<div id="validation-errors" role="alert">
<div asp-validation-summary="All"></div>
Expand Down Expand Up @@ -84,8 +83,9 @@ The [forms tutorial](forms-and-validation.md) includes a complete example: a sav
and an independent summary requests its latest HTML. Listen on `body` when the source is another component:

```razor
<aside hx-page="/Profile"
hx-page-handler="Summary"
<aside hx-get
asp-page="/Profile"
asp-page-handler="Summary"
hx-trigger="profile-saved from:body">
</aside>
```
Expand Down Expand Up @@ -139,9 +139,10 @@ Create `Pages/Jobs/_Progress.cshtml` (and import the model's namespace in `_View
else
{
<div id="job-progress"
hx-page="/Jobs/Status"
hx-page-handler="Progress"
hx-route-progress="@Model.Percent"
hx-get
asp-page="/Jobs/Status"
asp-page-handler="Progress"
asp-route-progress="@Model.Percent"
hx-trigger="load delay:1s"
hx-target="this"
hx-swap="outerHTML">
Expand Down Expand Up @@ -256,7 +257,7 @@ Create `Pages/Orders.cshtml`:
<h1>Orders</h1>
<ul id="orders"></ul>
<div id="load-more">
<button hx-page="/Orders" hx-page-handler="More" hx-route-after="0"
<button hx-get asp-page="/Orders" asp-page-handler="More" asp-route-after="0"
hx-target="#orders" hx-swap="beforeend">
Load more
</button>
Expand Down Expand Up @@ -300,7 +301,7 @@ Create `Pages/_OrderBatch.cshtml`:
<div id="load-more" hx-swap-oob="outerHTML">
@if (Model.NextCursor is { } cursor)
{
<button hx-page="/Orders" hx-page-handler="More" hx-route-after="@cursor"
<button hx-get asp-page="/Orders" asp-page-handler="More" asp-route-after="@cursor"
hx-target="#orders" hx-swap="beforeend">
Load more
</button>
Expand All @@ -326,8 +327,9 @@ Use an input event with a delay so the server receives a request after the user
<input id="catalog-search"
name="query"
type="search"
hx-page="/Catalog"
hx-page-handler="Search"
hx-get
asp-page="/Catalog"
asp-page-handler="Search"
hx-trigger="input changed delay:300ms, search"
hx-target="#search-results"
hx-request-timeout="5000" />
Expand Down Expand Up @@ -363,9 +365,10 @@ Create `Pages/_ProductRow.cshtml` for a display row:
<td>@Model.Name</td>
<td>@Model.Price</td>
<td>
<button hx-page="/Products"
hx-page-handler="Edit"
hx-route-id="@Model.Id"
<button hx-get
asp-page="/Products"
asp-page-handler="Edit"
asp-route-id="@Model.Id"
hx-target="closest tr"
hx-swap="outerHTML">
Edit
Expand Down Expand Up @@ -413,8 +416,8 @@ Create `Pages/_ProductEdit.cshtml`. It posts back to another handler with the sa
<tr id="product-@Model.Id">
<td colspan="3">
<form hx-post
hx-page="/Products"
hx-page-handler="Save"
asp-page="/Products"
asp-page-handler="Save"
hx-target="closest tr"
hx-swap="outerHTML">
<div asp-validation-summary="All" role="alert"></div>
Expand Down
Loading
Loading