Inline editing
A row turns into a form and back. Each answer is one row of the same loop - the PHP narrows the rows to the one asked for, padValidate checks the post, the session keeps the edits.
padValidatepadSession&padFragment=row
hx-gethx-posthx-includehx-target="closest tr"
Live
| Name | City | ||
|---|---|---|---|
| Ada Fernández | ada@example.com | Amsterdam | |
| Bram Visser | bram@example.com | Utrecht | |
| Chen Wei | chen@example.com | Rotterdam | |
| Dalia Haddad | dalia@example.com | Eindhoven | |
| Emeka Obi | emeka@example.com | Groningen |
Your edits stay in your session.
Requests htmx made
Nothing yet - use the example above, and every request it makes shows here with the part PAD answered.
How it is built
PAD, on the server htmx, in the browser
<table class="table">
<thead><tr><th>Name</th><th>E-mail</th><th>City</th><th></th></tr></thead>
<tbody hx-target="closest tr" hx-swap="outerHTML">
{rows}
{fragment 'row'}
{if $editing}
<tr class="is-editing">
<td><input class="input{if $errName} is-invalid{/if}" name="name" value="{$name}" aria-label="Name">{if $errName}<span class="error">{$errName}</span>{/if}</td>
<td><input class="input{if $errEmail} is-invalid{/if}" name="email" value="{$email}" aria-label="E-mail">{if $errEmail}<span class="error">{$errEmail}</span>{/if}</td>
<td><input class="input{if $errCity} is-invalid{/if}" name="city" value="{$city}" aria-label="City">{if $errCity}<span class="error">{$errCity}</span>{/if}</td>
<td class="actions">
<button class="btn-sm" hx-post="?examples/edit&id={$id}&padFragment=row" hx-include="closest tr">Save</button>
<button class="btn-sm btn-ghost" hx-get="?examples/edit&id={$id}&padFragment=row">Cancel</button>
</td>
</tr>
{else}
<tr>
<td><strong>{$name}</strong></td>
<td>{$email}</td>
<td>{$city}</td>
<td class="actions"><button class="btn-sm btn-ghost" hx-get="?examples/edit&id={$id}&mode=edit&padFragment=row">Edit</button></td>
</tr>
{/if}
{/fragment}
{/rows}
</tbody>
</table>
<p class="small muted" style="margin-top: 14px">
Your edits stay in your session.
<button class="btn-sm btn-ghost" hx-post="?examples/edit&reset" hx-select="table" hx-target="previous table" hx-swap="outerHTML">Undo them all</button>
</p><?php
// The contacts are the session's copy of _data/contacts.json. htmx asks for one row at a
// time - &id= with &padFragment=row - and this file narrows the loop to that row, so the
// first {fragment 'row'} to render is the answer: the row to read, the row as a form
// (mode=edit), or after a post the row as stored - or the form again with padValidate's
// messages in it. The token of the post comes in the X-CSRF-Token header (_inits.pad).
$contacts = padSession ( 'htmxContacts', htmxData ( 'contacts' ) );
$id = (int) padRequest ( 'id', 0 );
$mode = padRequest ( 'mode', '' ) === 'edit' ? 'edit' : 'view';
$errors = [];
if ( padRequestIs ( 'POST' ) and padRequestHas ( 'reset' ) ) {
$contacts = htmxData ( 'contacts' );
padSessionPut ( 'htmxContacts', $contacts );
$id = 0;
}
elseif ( padRequestIs ( 'POST' ) and $id ) {
$errors = padValidate ( [ 'name' => 'required|max:40', 'email' => 'required|email', 'city' => 'required|max:30' ] );
foreach ( $contacts as $at => $contact )
if ( $contact ['id'] == $id and ! $errors ) {
$contacts [$at] = [ 'id' => $id, 'name' => trim ( padRequest ( 'name' ) ), 'email' => trim ( padRequest ( 'email' ) ), 'city' => trim ( padRequest ( 'city' ) ) ];
padSessionPut ( 'htmxContacts', $contacts );
}
$mode = $errors ? 'edit' : 'view';
}
// The rows the template walks: all of them, or the one asked for - a form of it showing
// the posted values and the messages when the post had errors.
$rows = [];
foreach ( $contacts as $contact ) {
if ( $id and $contact ['id'] != $id )
continue;
if ( $errors )
$contact = array_replace ( $contact, [ 'name' => htmxText ( 'name' ),
'email' => htmxText ( 'email' ),
'city' => htmxText ( 'city' ) ] );
$rows [] = $contact + [ 'editing' => ( $id and $mode == 'edit' ),
'errName' => $errors ['name'] ?? '',
'errEmail' => $errors ['email'] ?? '',
'errCity' => $errors ['city'] ?? '' ];
}
// An id the session does not hold - a stale or made-up request - has no row to answer: a
// 404, which htmx swaps nothing for, where the empty loop rendered no {fragment 'row'} and
// the answer was a 500, "there is no fragment named 'row' on this page".
if ( $id and ! $rows )
padAbort ( 404, 'There is no such contact' );
?>