# Query Loop avancée : trier par champ personnalisé et exposer un filtre

> Livrer un annuaire filtrable sans plugin de recherche tiers : comment combiner meta_query et un bloc de filtre côté Interactivity API, étape par étape.

- Auteur : Clément Hadrot
- Publié le : 2025-10-28
- Mis à jour le : 2025-10-28
- Catégorie : FSE
- URL : https://wpmoderne.dev.wordpress-developpement.fr/fse/query-loop-tri-filtre-annuaire/

## L’essentiel

- Le tri par champ personnalisé passe par un filtre pre_get_posts ciblé
- Un bloc de filtre en Interactivity API met à jour l'URL sans rechargement
- La recherche plein texte reste hors périmètre de cette solution

Une chambre de commerce m'a confié la refonte de son annuaire d'entreprises adhérentes, avec une exigence précise : permettre de trier les fiches par date d'adhésion ou par nombre d'employés, deux champs personnalisés stockés en `post_meta`, et proposer un filtre par secteur d'activité, sans installer de plugin de recherche avancée jugé disproportionné pour un annuaire de deux cents entrées.

Voici la construction complète, en combinant un tri personnalisé côté serveur via `pre_get_posts` et un bloc de filtre construit avec l'Interactivity API pour une mise à jour fluide de la liste.

## Étape 1 : exposer le tri par champ personnalisé à la Query Loop

Par défaut, l'interface de la Query Loop propose un tri par date de publication, titre ou ordre menu, mais aucun champ personnalisé n'apparaît nativement dans le sélecteur. Le contournement le plus fiable consiste à intercepter la requête via `pre_get_posts`, en lisant un paramètre d'URL dédié plutôt que de tenter de modifier l'inspecteur de blocs lui-même.

```
add_action( 'pre_get_posts', function( \WP_Query $query ) {
    if ( is_admin() || ! $query->is_main_query() ) {
        return;
    }
    if ( 'entreprise' !== $query->get( 'post_type' ) ) {
        return;
    }
    $tri = sanitize_key( $_GET['tri'] ?? '' );
    if ( 'employes' === $tri ) {
        $query->set( 'meta_key', 'nombre_employes' );
        $query->set( 'orderby', 'meta_value_num' );
        $query->set( 'order', 'DESC' );
    } elseif ( 'adhesion' === $tri ) {
        $query->set( 'meta_key', 'date_adhesion' );
        $query->set( 'orderby', 'meta_value' );
        $query->set( 'order', 'ASC' );
    }
} );
```

Cette approche conserve la Query Loop native de l'éditeur intacte, sans bloc personnalisé de remplacement, tout en pilotant le tri depuis un simple paramètre d'URL modifiable par le visiteur.

## Étape 2 : filtrer par secteur d'activité avec meta_query

> L'essentiel à retenir : Le tri par champ personnalisé passe par un filtre pre_get_posts ciblé ; Un bloc de filtre en Interactivity API met à jour l'URL sans rechargement ; La recherche plein texte reste hors périmètre de cette solution

Le secteur d'activité, stocké lui aussi en `post_meta` sous forme de terme unique par fiche, se filtre via une extension du même filtre `pre_get_posts`, avec une `meta_query` construite dynamiquement selon les paramètres présents dans l'URL :

```
add_action( 'pre_get_posts', function( \WP_Query $query ) {
    if ( is_admin() || ! $query->is_main_query() || 'entreprise' !== $query->get( 'post_type' ) ) {
        return;
    }
    $secteur = sanitize_text_field( $_GET['secteur'] ?? '' );
    if ( $secteur ) {
        $query->set( 'meta_query', array(
            array(
                'key'     => 'secteur_activite',
                'value'   => $secteur,
                'compare' => '=',
            ),
        ) );
    }
} );
```

## Étape 3 : construire le bloc de filtre côté client

Plutôt qu'un simple formulaire HTML rechargeant toute la page, un petit bloc en Interactivity API permet de mettre à jour l'URL et de recharger uniquement la Query Loop, à condition que celle-ci ait bien son option `Activer les mises à jour côté client` cochée, disponible depuis la version 6.5.

```
<select
  data-wp-interactive="chambre-commerce/filtre"
  data-wp-on--change="actions.filtrerParSecteur"
>
  <option value="">Tous les secteurs</option>
  <option value="industrie">Industrie</option>
  <option value="services">Services</option>
  <option value="commerce">Commerce</option>
</select>
```

```
import { store } from '@wordpress/interactivity';

store( 'chambre-commerce/filtre', {
    actions: {
        filtrerParSecteur( evenement ) {
            const url = new URL( window.location.href );
            url.searchParams.set( 'secteur', evenement.target.value );
            window.history.pushState( {}, '', url );
            window.dispatchEvent( new PopStateEvent( 'popstate' ) );
        },
    },
} );
```

Le déclenchement d'un événement `popstate` synthétique permet à la Query Loop en mode interactif de détecter le changement d'URL et de rafraîchir son contenu côté client, sans rechargement complet, en réutilisant exactement le même mécanisme que la pagination native décrite dans un précédent article.

### Combiner tri et filtre sans conflit

Les deux filtres `pre_get_posts` coexistent sans conflit puisqu'ils agissent sur des paramètres d'URL distincts (`tri` et `secteur`), combinables librement par l'utilisateur, chaque combinaison produisant une URL propre et partageable, ce qui a été un critère important pour la chambre de commerce souhaitant que ses membres puissent partager un lien filtré par courriel.

- Un tri par champ personnalisé, sans bloc de remplacement de la Query Loop native.
- Un filtre par secteur combinable librement avec le tri.
- Des URL propres et partageables, contrairement à un filtrage purement JavaScript sans mise à jour de l'adresse.

> La recherche plein texte reste un sujet distinct, qui dépasse ce que `meta_query` peut raisonnablement couvrir : au-delà d'un simple tri ou filtre par champ exact, une solution de recherche dédiée redevient pertinente.

## En résumé

Un annuaire filtrable et triable par champ personnalisé ne nécessite ni plugin de recherche avancée ni bloc de remplacement de la Query Loop, à condition de combiner intelligemment `pre_get_posts` côté serveur et l'Interactivity API côté client pour une expérience fluide, sans jamais sacrifier la simplicité d'URL partageable qui reste précieuse pour ce type d'usage.
