#
Edit Row
#
Implementing Row Editing with htmx and ASP.NET Core
The Edit Row pattern allows users to edit individual rows in a table without leaving the page or opening a modal. By leveraging htmx, we can swap a static table row for an editable version and then swap it back once the update is complete.
#
1. The Table Structure
In Index.cshtml, we define a table where the <tbody> is configured to handle the swap target for any htmx request originating from its children.
Index.cshtml
<tbody hx-target="closest tr" hx-swap="outerHTML">
@foreach (var contact in Model.Contacts)
{
<partial name="_TableRow" model="@contact"/>
}
</tbody>
hx-target="closest tr": This tells htmx that the result of any request within this body should target the nearest table row.hx-swap="outerHTML": Replaces the entire<tr>element with the response from the server.
#
2. The Read-Only Row
Initially, each contact is rendered using a read-only partial view. The "Edit" button triggers a GET request to fetch the editable version of that specific row.
_TableRow.cshtml
@model Contact
<tr>
<td>@Model.Name</td>
<td>@Model.Email</td>
<td>
<button class="btn btn-primary"
hx-get="@Url.Page("Index", "Edit", new { Id = Model.Id })">
Edit
</button>
</td>
</tr>
#
3. The Editable Row
When the "Edit" button is clicked, the server returns the _EditRow.cshtml partial. This row contains input fields and action buttons.
_EditRow.cshtml
@model Contact
<tr>
<td>
<input type="hidden" asp-for="@Model.Id"/>
<input asp-for="@Model.Name" class="form-control">
@Html.AntiForgeryToken()
</td>
<td>
<input asp-for="@Model.Email" class="form-control">
</td>
<td>
<button class="btn btn-secondary" hx-get="@Url.Page("Index", "View", new { Id = Model.Id })">
Cancel
</button>
<button class="btn btn-success"
hx-put="@Url.Page("Index", "Update", new { Id = Model.Id })"
hx-include="closest tr">
Save
</button>
</td>
</tr>
hx-include="closest tr": Ensures that the values of the input fields in the current row are included in the PUT request.- The "Cancel" button simply fetches the read-only row again, discarding any unsaved changes.
#
4. The Backend: C# PageModel
The IndexModel handles the transitions between states. It provides handlers for entering edit mode, canceling/viewing, and performing the update.
Index.cshtml.cs
public class IndexModel(IContactService contactService) : PageModel
{
public IList<Contact>? Contacts { get; set; }
public void OnGet()
{
this.Contacts = contactService.Get().ToArray();
}
// Returns the editable row fragment
public PartialViewResult OnGetEdit(int Id)
{
var contact = contactService.Get(Id);
return Partial("_EditRow", contact);
}
// Returns the read-only row fragment (used for Cancel)
public PartialViewResult OnGetView(int Id)
{
var contact = contactService.Get(Id);
return Partial("_TableRow", contact);
}
// Handles the update and returns the updated read-only row
public PartialViewResult OnPutUpdate([FromForm] Contact contact)
{
contactService.Update(contact);
return Partial("_TableRow", contact);
}
}
#
Summary
This pattern keeps the user in the flow of their data. By targeting only the "closest tr", htmx makes it easy to manage row-level state without complex JavaScript selectors or managing a global state object. The transition between "view" and "edit" modes is seamless and extremely responsive.