# Créer une FAQ réordonnable à la main, sans bloc ni champ complexe

> Pas besoin d'un générateur de blocs pour une FAQ triable par l'éditeur. Une métabox avec une liste glisser-déposer suffit, stockée en une seule meta.

- Auteur : Clément Hadrot
- Publié le : 2024-03-05
- Mis à jour le : 2024-03-05
- Catégorie : Tips
- URL : https://wpmoderne.dev.wordpress-developpement.fr/tips/creer-faq-reordonnable-sans-bloc-ni-champ-complexe/

## L’essentiel

- jQuery UI Sortable est déjà chargé dans l'admin WordPress
- Stockage en tableau sérialisé dans une seule post meta
- Aucune dépendance à un constructeur de blocs

Pour une clinique vétérinaire qui voulait une page de FAQ triée par ordre d'importance perçue plutôt que par ordre alphabétique, la question du client était directe : « Comment je fais glisser une question plus haut dans la liste, sans devoir renuméroter chaque champ à la main ? » Les blocs réutilisables classiques ne permettaient pas ce réordonnancement simple depuis l'écran d'édition standard, et installer une extension entière pour une simple liste de questions-réponses semblait disproportionné.

La solution retenue tient en une métabox personnalisée, une liste HTML triable avec la bibliothèque déjà présente dans l'admin (`jquery-ui-sortable`, incluse nativement dans WordPress), et un unique champ de post meta contenant un tableau sérialisé de paires question/réponse.

## Structurer les données côté PHP

Plutôt que de créer un champ personnalisé par question (ce qui complique furieusement le réordonnancement), on stocke l'ensemble de la liste comme un seul tableau associatif dans une meta unique :

```
add_action( 'add_meta_boxes', function () {
    add_meta_box(
        'faq_reordonnable',
        'Questions fréquentes',
        'clinique_afficher_metabox_faq',
        'page',
        'normal',
        'high'
    );
} );

function clinique_afficher_metabox_faq( WP_Post $post ) {
    $questions = get_post_meta( $post->ID, '_faq_questions', true );
    $questions = is_array( $questions ) ? $questions : array();

    wp_nonce_field( 'clinique_faq_nonce', 'clinique_faq_nonce_champ' );
    ?>
    <ul id="clinique-faq-liste">
        <?php foreach ( $questions as $index => $q ) : ?>
        <li>
            <input type="text" name="faq[<?php echo (int) $index; ?>][question]" value="<?php echo esc_attr( $q['question'] ); ?>" />
            <textarea name="faq[<?php echo (int) $index; ?>][reponse]"><?php echo esc_textarea( $q['reponse'] ); ?></textarea>
        </li>
        <?php endforeach; ?>
    </ul>
    <button type="button" id="clinique-faq-ajouter">Ajouter une question</button>
    <?php
}
```

## Rendre la liste triable

> L'essentiel à retenir : jQuery UI Sortable est déjà chargé dans l'admin WordPress ; Stockage en tableau sérialisé dans une seule post meta ; Aucune dépendance à un constructeur de blocs

Puisque `jquery-ui-sortable` fait partie des scripts déjà enregistrés dans l'admin WordPress, il suffit de le déclarer comme dépendance et d'initialiser le tri sur l'élément `<ul>` :

```
add_action( 'admin_enqueue_scripts', function ( $ecran ) {
    if ( 'page' !== get_current_screen()->post_type ) {
        return;
    }

    wp_enqueue_script(
        'clinique-faq-admin',
        get_theme_file_uri( 'inc/js/faq-admin.js' ),
        array( 'jquery-ui-sortable' ),
        '1.0',
        true
    );
} );
```

```
// inc/js/faq-admin.js
jQuery( function ( $ ) {
    $( '#clinique-faq-liste' ).sortable( {
        handle: false,
        placeholder: 'clinique-faq-placeholder',
        update: function () {
            // L'ordre visuel suffit : c'est l'ordre du DOM au moment
            // de la sauvegarde qui déterminera l'ordre enregistré.
        }
    } );
} );
```

Le point important, souvent oublié : l'ordre final n'a pas besoin d'être recalculé côté JavaScript à chaque glisser-déposer. C'est l'ordre des champs dans le DOM au moment de la soumission du formulaire qui sera transmis en PHP, dans cet ordre précis, grâce à la structure en tableau indexé des attributs `name`.

### Sauvegarder en respectant l'ordre du DOM

```
add_action( 'save_post_page', function ( $post_id ) {
    if ( ! isset( $_POST['clinique_faq_nonce_champ'] )
        || ! wp_verify_nonce( $_POST['clinique_faq_nonce_champ'], 'clinique_faq_nonce' ) ) {
        return;
    }

    if ( ! current_user_can( 'edit_page', $post_id ) ) {
        return;
    }

    $brut = $_POST['faq'] ?? array();
    $propre = array();

    foreach ( $brut as $entree ) {
        if ( '' === trim( $entree['question'] ?? '' ) ) {
            continue;
        }

        $propre[] = array(
            'question' => sanitize_text_field( $entree['question'] ),
            'reponse'  => sanitize_textarea_field( $entree['reponse'] ),
        );
    }

    update_post_meta( $post_id, '_faq_questions', array_values( $propre ) );
} );
```

PHP reçoit le tableau `$_POST['faq']` déjà dans l'ordre du DOM, à condition que les index numériques utilisés dans les attributs `name` ne soient pas mélangés par du JavaScript intermédiaire. C'est pour cette raison que le plugin de tri n'a besoin de renuméroter aucun index : PHP itère les tableaux associatifs dans l'ordre où leurs clés ont été insérées, qui correspond ici à l'ordre de soumission du formulaire HTML.

## Afficher la FAQ côté public

```
<?php
$questions = get_post_meta( get_the_ID(), '_faq_questions', true );

if ( is_array( $questions ) ) :
?>
<div class="faq-liste">
    <?php foreach ( $questions as $q ) : ?>
    <details>
        <summary><?php echo esc_html( $q['question'] ); ?></summary>
        <p><?php echo esc_html( $q['reponse'] ); ?></p>
    </details>
    <?php endforeach; ?>
</div>
<?php endif; ?>
```

- Toujours filtrer les questions vides à la sauvegarde, pour éviter d'accumuler des entrées fantômes après plusieurs suppressions.
- Prévoyez un bouton de suppression par ligne, pas seulement un bouton d'ajout, sous peine de listes qui ne font que grandir.
- Vérifiez le nonce et les capacités avant toute écriture, comme pour n'importe quelle métabox.

> Sur ce genre de contenu structuré léger, je résiste toujours à la tentation d'aller vers un champ répétable façon générateur de champs personnalisés : une simple liste triable, sans dépendance externe, reste plus simple à maintenir et plus rapide à charger.

## En résumé

Une FAQ réordonnable ne nécessite ni bloc personnalisé complexe, ni extension de champs additionnels : une métabox classique, une bibliothèque de tri déjà présente dans l'admin, et un stockage en tableau sérialisé suffisent largement pour ce cas d'usage précis. C'est une solution qui reste lisible pour la prochaine personne qui reprendra le thème.
