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
    );
}
En cas d'erreur, on retourne la modale avec les erreurs (remplace le contenu de #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
htmx gotcha : Ne pas mettre 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.