Patterns
Implémentation des cas d'usage courants
Modal CRUD (create + edit)
Le pattern le plus courant. La modale est injectée dans #modal-container via htmx.
1. La vue _dt.php — bouton create
<?=$dt->create_btn('Créer')?>
Ce bouton fait un hx-post sur {road}create et cible #modal-container.
2. Le controller — dt_modal_create()
// Déjà fourni par Dt_controller
// Il appelle $this->partial('admin/articles/_modal_create', ['dt' => $this->dt])
3. La vue _modal_create.php
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">Créer un article</div>
<div class="modal-body">
<?=$dt->modal_form('admin/articles/store')?> <!-- <form hx-post="..." hx-target="#modal-container"> -->
<input type="text" name="title" placeholder="Titre">
<button type="submit">Enregistrer</button>
</form>
</div>
</div>
</div>
4. Le controller — store()
public function store(): Response
{
$v = Validator::make($this->request->all_post())
->required('title', 'Le titre est requis');
if ($v->fails()) {
$this->dt->refresh();
// Retourner la modale avec les erreurs
return $this->partial('admin/articles/_modal_create', [
'dt' => $this->dt,
'errors' => $v->errors(),
'error_fields' => $v->error_fields(),
'old' => $this->request->all_post(),
]);
}
// Insérer en DB...
$toast = Toast::success('Article créé');
return $this->with_toast($toast,
$this->oob('#dt_table', $this->dt_page()) // Rafraîchit la table
);
}
#modal-container).
En cas de succès, on vide la modale et on rafraîchit la table via OOB.
Édition inline (double-clic)
L'utilisateur double-clique sur une ligne → les cellules deviennent des inputs. Le module inline-edit.js gère le backup/restore et Escape.
Structure _row.php (ligne normale)
<?=$dt->row($entity->id)?> <!-- <tr data-inline-row hx-trigger="dblclick" hx-swap="outerHTML"> -->
<td data-col="name"><?=$entity->name?></td>
<td data-col="email"><?=$entity->email?></td>
</tr>
data-col="name" sur chaque <td> permet au module JS de détecter sur quelle colonne l'utilisateur a double-cliqué et de pré-sélectionner le bon input.
Structure _row_edit.php (ligne en édition)
use App\DataTable\Inline_input as Input;
use App\DataTable\Inline_select as Select;
use App\DataTable\Inline_cancel_btn;
<?=$dt->row_edit($entity->id)?> <!-- <tr id="row-42" class="editing"> -->
<td>
<?=Input::make($focus, $error_fields)->type('text')->name('name')->value($entity->name)?>
</td>
<td>
<?=Input::make($focus, $error_fields)->type('email')->name('email')->value($entity->email)?>
</td>
<td>
<?=$dt->inline_save_btn('✓', $entity->id)->title('Enregistrer')?>
<?=Inline_cancel_btn::make()->title('Annuler')?>
</td>
</tr>
Controller — edit_inline() et update_inline()
// edit_inline() : retourne _row_edit.php
// update_inline() : traite la soumission
// → Les deux sont fournis par Dt_controller
// Il suffit de configurer $table, $entity, $road, $view_dir
event.stopPropagation() dans les onclick
des boutons avec hx-post. Cela peut bloquer htmx qui écoute les événements via les ancêtres.
Page dédiée (hx-boost)
Pour les formulaires complexes qui ne tiennent pas dans une modale.
Bouton dans la table
<?=$dt->edit_btn('Modifier', $entity->id)?>
<!-- génère: <a hx-boost hx-target="body" hx-swap="innerHTML" href="..."> -->
Controller — dt_edit()
// edit.php est rendu avec render() (layout complet) — fourni par Dt_controller
// La page doit contenir un <form> avec hx-boost
Formulaire edit.php
<?=$dt->form('admin/articles/update/' . $article->id)?>
<!-- <form hx-post="..." hx-target="body" hx-swap="innerHTML"> -->
<input type="text" name="title" value="<?=$article->title?>">
<button type="submit">Enregistrer</button>
</form>
<?=$dt->back_btn('Retour à la liste')?>
Controller — update()
public function update(): Response
{
// ... validation ...
if ($v->fails()) {
$this->dt->refresh();
return $this->render('admin/articles/edit', [
'dt' => $this->dt,
'article' => $article,
'errors' => $v->errors(),
]);
}
// ... mise à jour DB ...
$toast = Toast::success('Article mis à jour');
return $this->with_toast($toast,
$this->redirect(URL . 'admin/articles')
);
}
Bulk actions
Permet d'appliquer une action (corbeille, restaurer, supprimer) sur plusieurs lignes sélectionnées.
Dans _dt.php
<?=$dt->bulk_bar()?>
<!-- affiche la barre bulk (visible seulement si des cases sont cochées) -->
Dans _row.php
<td><?=$dt->gen_row_checkbox($entity->id)?></td>
Le controller hérite de dt_bulk() dans Dt_controller — il détecte l'action choisie et les IDs sélectionnés, applique, puis retourne la table mise à jour.
Toasts et gestion des erreurs
Toast simple
return $this->with_toast(
Toast::success('Opération réussie', 'success'), // 2e param = son
$response
);
Toast après redirect (hx-boost)
htmx-boost ne recharge pas la page, donc les flash classiques ne fonctionnent pas. Pattern "server-side data injection" :
// 1. Controller — stocker en session
$this->flash_toast(Toast::success('Article créé'), $this->session);
return $this->redirect(URL . 'admin/articles');
// 2. Layout dashboard.php — injecter dans window
<script>window.__flash_toast = <?= $flash_toast ?>;</script>
// 3. toast.js — vérifier au chargement et après htmx:afterSwap
check_flash();
Toujours passer 'dt' dans les réponses d'erreur
return $this->partial('admin/articles/_modal_create', [
'dt' => $this->dt, // ← ne pas oublier
'errors' => $v->errors(),
]);
OOB (Out-of-Band swap)
Pour mettre à jour deux zones DOM en une seule réponse htmx :
// Vider la modale ET rafraîchir la table
return $this->with_toast($toast,
$this->oob('#dt_table', $this->dt_page())
);
// Ou sans toast
return $this->oob('#dt_table', $this->dt_page());
Oob_trait::oob() wrappe la réponse principale avec hx-swap-oob. Natif htmx, pas d'extension nécessaire.