Filtrer vos articles WordPress avec Ajax

Je vous partage aujourd’hui un mu-plugin qui peux vous aider à filtrer vos articles WordPress avec Ajax (jQuery).

Démo : http://mariecomet.fr/works/

Si vous ne connaissez pas du tout Ajax avec jQuery, je vous invite à lire la doc officielle ainsi que la doc WordPress à ce sujet.

La création de ce mu-plugin répond à un besoin client simple mais récurrent : filtrer des articles (ou tout autre type de post) par catégories, avec Ajax (car c’est dynamique et cool), dans des pages d’archives.

Il existe des extensions sur répertoire officiel, ou sur des plateformes payantes, mais je n’en ai pas trouvé qui me convienne tout à fait et qui soit propre.

Qu’allons-nous faire :

  • Afficher nos catégories
  • Afficher nos articles
  • Trier nos articles quand un utilisateur choisit une ou plusieurs catégories.

Pour commencer, on déclare notre mu-plugin, on enregistre nos actions, nos scripts et nos styles :

<?php
/*
Plugin Name: Ajax Filter Posts
Description: Filter posts by taxonomy with ajax, css based on bootsrap but do what you want.
Version:     1.1
Author:      Marie Comet
*/
class Ajax_Filter_Posts {

	public function __construct(){
		add_action('plugins_loaded', array($this, 'init'), 2);
	}
	public function init(){
		//Add Ajax Actions
		add_action('wp_enqueue_scripts', array( $this, 'enqueue_genre_ajax_scripts' ));
		add_action('wp_ajax_genre_filter', array( $this, 'ajax_genre_filter'));
		add_action('wp_ajax_nopriv_genre_filter', array( $this, 'ajax_genre_filter'));

	}

	//EnqueueScripts

	public function enqueue_genre_ajax_scripts() {
	    wp_register_script( 'genre-ajax-js', plugin_dir_url(__FILE__). 'genre.js', array( 'jquery' ), '', true );
	    wp_localize_script( 'genre-ajax-js', 'ajax_genre_params', array( 'ajax_url' => admin_url( 'admin-ajax.php' ) ) );
	    wp_enqueue_script( 'genre-ajax-js' );
		wp_enqueue_style( 'style-css', plugin_dir_url( __FILE__ ) . 'style.css'  );
	}

Ligne 11 : on ajoute notre fonction init (ouverte en ligne 13) sur l’action muplugins_loaded

Ligne 15 : on ajoute notre fonction enqueue_genre_ajax_scripts (ouverte en ligne 23) sur l’action wp_enqueue_scripts

Ligne 16 et 17 : on ajoute notre fontction ajax_genre_filter en préfixant avec wp_ajax_ , cela nous permet de créer notre propre gestionnaire de requêtes Ajax. Je l’ai également ajoutée à wp_ajax_nopriv_ pour les utilisateurs non connectés.

Ligne 24 : On enregistre notre fichier genre.js qui contiendra notre code JavaScript.

Ligne 25 : La fonction wp_localize_script sert à passer une valeur à un script, ici l’url de admin-ajax.php qui sera accessible via la variable ajax_url

Ligne 26 : On charge notre fichier genre.js

Ligne 27 : On charge notre fichier style.css

Afficher nos filtres par catégories :

Nous allons créer une fonction qui  affiche nos filtres de catégories. C’est cette fonction que l’on appellera dans un template pour afficher notre système de filtre :


	/* Public function get_genre_filters accepts two parameters 
	*	$post_type :  the post type slug to query
	* 	$taxo : the taxonomy slug to query
	*	used for display the filters in your template (for example archive.php) like that :
	*	$new_posts_filter = new Ajax_Filter_Posts();
	*	echo $new_posts_filter->get_genre_filters('post', 'category');
	*/
	public function get_genre_filters($post_type, $taxo) {

	    $terms = get_terms($taxo);
	    $filters_html = false;
	 
	    if( $terms ):
	    	$filters_html .= '<div id="genre-filter">';
	    	$filters_html .= '<input type="hidden" name="post_type" value="'. $post_type .'" id="post_type"/>';
	    	$filters_html .= '<input type="hidden" name="taxo" value="'. $taxo .'" id="taxo"/>';
	        foreach( $terms as $term )
	        {
	            $term_id = $term->term_id;
	            $term_name = $term->name;
	 
	            $filters_html .= '<a class="term_id_'.$term_id.' btn btn-large selected"><input type="checkbox" checked="checked" name="filter_genre[]" value="'.$term_id.'" class="input-filter-work">'.$term_name.'</a>';
	        }
	        $filters_html .= '<a class="clear-all btn btn-large selected">Tous</a>';
	        $filters_html .= '</div><div id="genre-results" class="row row-eq-height no-gutters"></div>';
	 
	        return $filters_html;
	    endif;
	}

Ligne 37 : On déclare notre fonction get_genre_filters qui accepte deux valeurs : notre type de post et la catégorie.

Ligne 39 : On récupère tous les termes de notre catégorie.

Ligne 42 : Petite vérification sur l’existence des termes.

Ligne 43, 44 et 45 : On ouvre notre conteneur qui à pour ID ‘genre-filter’. Ensuite on créé deux éléments ‘input’ cachés qui contiennent notre type post et notre catégorie (passés dans les arguments de notre fonction). Notez les attributs ‘name’ qui seront réutilisés dans notre jQuery et PHP.

Ligne 51 : Pour chaque terme on créé un lien et un input de type checkbox qui a pour valeur l’identifiant du terme (ligne 48) et son nom (ligne 49).

Ligne 53 : On créé un lien qui servira à réinitialiser les filtres.

Ligne 54 : On créé le markup html qui contiendra nos articles. Notez l’ID ‘genre-results’, il sera utilisé dans notre jQuery.

Ligne 56 : On retourne nos filtres.

A ce stade vous pouvez déjà placer ce bout de code dans un template (par exemple archive.php) :

$new_posts_filter = new Ajax_Filter_Posts(); // On appelle notre Class déclarée dans notre mu-plugin
echo $new_posts_filter->get_genre_filters('post', 'category'); // On appelle  la fonction que nous venons de créer avec comme argument post (articles) et category (catégories des articles)

Vous devriez normalement voir apparaître vos termes de catégorie.

Construire la fonction qui traitera les requêtes Ajax :

Il nous faut maintenant créer la fonction qui traitera les requêtes Ajax envoyées par nos chers utilisateurs :

	/* Public function ajax_genre_filters 
	*	Ajax call construct loop and results
	*/
	public function ajax_genre_filter()
	{
		$query_data = $_GET;

		$post_type = (isset($query_data['post_type'])) ? $query_data['post_type'] : false;
		$taxo = (isset($query_data['taxo'])) ? $query_data['taxo'] : false;

		$genre_terms = (isset($query_data['genres'])) ? explode(',',$query_data['genres']) : false;

		$tax_query = ($genre_terms) ? array( array(
			'taxonomy' => $taxo,
			'field' => 'id',
			'terms' => $genre_terms
		) ) : false;
		
		$search_value = (isset($query_data['search']) ) ? $query_data['search'] : false;
		
		$paged = (isset($query_data['paged']) ) ? intval($query_data['paged']) : 1;
		
		$book_args = array(
			'post_type' => $post_type,
			's' => $search_value,
			'posts_per_page' => 15,
			'tax_query' => $tax_query,
			'paged' => $paged
		);
		$book_loop = new WP_Query($book_args);

		// here place your own loader
		echo '<div class="ajax-post-loader"><img src="' . plugin_dir_url(__FILE__) . '/space-comet-dark.png" class="comet-dark"/></div>';
		
		if( $book_loop->have_posts() ):
			while( $book_loop->have_posts() ): $book_loop->the_post();
			global $post;
			setup_postdata($post);
				get_template_part( 'loop-templates/content', $post_type );
			endwhile; ?>
			<?php
			echo '<div class="genre-filter-navigation col-xs-12 col-sm-12 col-md-12">';
			        $big = 999999999;
			        echo paginate_links( array(
			            'base' => esc_url_raw( str_replace( 999999999, '%#%', remove_query_arg( 'add-to-cart', get_pagenum_link( 999999999, false ) ) ) ),
			            'format' => '?paged=%#%',
			            'current' => max( 1, $paged ),
			            'total' => $book_loop->max_num_pages
			        ) );

			        echo '</div>';
			?>
		<?php else:
			get_template_part('content-none');
		endif;
		wp_reset_postdata();
		
		die();
	}
}
new Ajax_Filter_Posts();

Cette fonction ajax_genre_filter est celle que nous avons ajoutée à notre propre gestionnaire Ajax en ligne 16 et 17.

Ligne 65 : on définit que notre variable $query_data sera de type ‘GET’ (on récupère des données depuis le front).

Ligne 67 et 68 : On récupère les valeurs de nos deux input cachés que nous avions créés précédemment, nous avons donc notre type de post et notre catégorie.

Ligne 70 : On récupère, si ils existent (si l’utilisateur a sélectionné un ou des termes) nos termes.

Ligne 72 : On stocke dans une variable $tax_query notre requête sur ces termes (ligne 73 à 76). Notez la vérification sur l’existence une fois de plus des termes, s’il elle nulle on défini notre variable à false.

Ligne 80 : Une variable dans laquelle on stocke l’existence ou non d’une pagination (nous y reviendrons plus tard).

Ligne 82 : On construit notre requête sur nos articles, on retrouve notre type de post ($post_type), notre requête sur les termes ($tax_query), la pagination ($paged)

Ligne 92 : Ici vous pouvez afficher un loader qui s’affichera au chargement/rechargement des articles.

Ligne 94 et 95 :  On vérifie que notre requête WP_Query (ligne 89) retourne des articles, si oui on construit notre boucle. Ensuite vous pouvez afficher ce dont vous avez besoin (titre, lien, image à la une…)

Ligne 106 : On affiche notre pagination, cf doc 

Ligne 122 : Important le die(); dans toute fonction traitant de l’Ajax. Il permet de stopper la fonction et de retourner une réponse.

Ligne 125 : On ferme notre Class et la lance.

Pour l’instant cette fonction de donnera rien en front, car nous n’avons pas encore créé le fichier jQuery qui l’appellera et lui enverra les informations.

Communiquer entre le front et le PHP : JQuery.

Créez donc votre fichier genre.js 

Le fonctionnement est simple : Nous avons un lien et un input de type checkbox pour chaque termes de notre catégorie. Nos filtres se déclencheront en fonction de ceux sélectionnés.

//Genre Ajax Filtering
jQuery(function($)
{
    // Get the post_type and taxo on load
    var post_type = $(this).find('#post_type').val();
    var taxo = $(this).find('#taxo').val();

    genre_get_posts();


    //If list item is clicked, trigger input change and add css class
    $('#genre-filter a').live('click', function(){
        var input = $(this).find('input');

        //Check if clear all was clicked
        if ( $(this).hasClass('clear-all') )
        {
            $('#genre-filter a').addClass('selected'); //Clear settings
            $('#genre-filter a input').prop('checked', true);
            genre_get_posts(); //Load Posts
        }
        else if (input.is(':checked'))
        {
            input.prop('checked', false);
            $(this).removeClass('selected');
        } else {
            input.prop('checked', true);
            $(this).addClass('selected');
        }
 
        input.trigger("change");
    });
 
    //If input is changed, load posts
    $('#genre-filter input').live('change', function(){
        genre_get_posts(); //Load Posts
    });
 
    //Find Selected Genres
    function getSelectedGenres()
    {
        var genres = []; //Setup empty array
 
        $("#genre-filter a input:checked").each(function() {
            var val = $(this).val();
            genres.push(val); //Push value onto array
        });     
 
        return genres; //Return all of the selected genres in an array
    }
 
    //If pagination is clicked, load correct posts
    $('.genre-filter-navigation a').live('click', function(e){
        e.preventDefault();
 
        var url = $(this).attr('href'); //Grab the URL destination as a string
        var paged = url.split('&paged='); //Split the string at the occurance of &paged=
 
        genre_get_posts(paged[1]); //Load Posts (feed in paged value)
    });
 
    //Main ajax function
    function genre_get_posts(paged)
    {
        var paged_value = paged; //Store the paged value if it's being sent through when the function is called
        var ajax_url = ajax_genre_params.ajax_url; //Get ajax url (added through wp_localize_script)
 
        $.ajax({
            type: 'GET',
            url: ajax_url,
            data: {
                action: 'genre_filter',
                post_type: post_type,
                taxo: taxo,
                genres: getSelectedGenres, //Get array of values from previous function
                paged: paged_value //If paged value is being sent through with function call, store here
            },
            beforeSend: function ()
            {
               $('.ajax-post-loader img').fadeIn("slow");
            },
            success: function(data)
            {
                $('#genre-results').show("slow").html(data).fadeIn("slow");
                $('.ajax-post-loader img').fadeOut("slow");
            },
            error: function()
            {
                                //If an ajax error has occured, do something here...
                $("#genre-results").html('<p>Rien ici!</p>');
            }
        });
    }
 

Ligne 5 et 6 : On récupère les valeurs de nos deux input cachés qui contiennent le type de post et la catégorie.

Ligne 9 : On appelle tout de suite (au chargement de la page) notre fonction qui affiche les posts – nous la verrons plus tard.

Ligne 12 : On déclenche un événement au click d’un ou plusieurs termes, contenus dans notre DIV qui à pour ID ‘genre-filter’ (créée à la ligne 43 de notre fichier PHP)

Ligne 13 : On cherche l’input qui se trouve directement dans le lien clické et le stocke dans une variable ‘input’.

Ligne 16 : On vérifie si le lien clické à pour classe CSS ‘clear-all’ (notre élément qui sert à réinitialiser nos filtres)

Ligne 18 : Si c’est le cas, on retire toutes classes ‘selected’ et on décoche nos input. On relance la fonction ‘genre_get_posts’.

Ligne 21 : Si au click de l’utilisateur notre input était déjà coché, on le décoche et on retire la classe CSS ‘selected’ du lien associé. Ensuite vice versa : Si l’input n’était pas coché, on le coche.

Ligne 30 : On lance le changement sur notre input.

Ligne 34 : Si un input change (click utilisateur), on lance de nouveau la fonction ‘genre_get_posts’

Ligne 39 : On créé notre fonction getSelectedGenre qui sert à stocker les termes sélectionnés dans le tableau genres à chaque fois qu’un terme est coché ou décoché.

Ligne 52 : Lorsqu’un utilisateur clicke sur un lien de notre pagination, à la ligne 55 et 56 on stocke l’URL de destination, et la page.

Ligne 58  : On relance notre fonction genre_get_posts avec comme paramètre la pagination, pour afficher les articles de la page sélectionnée.

Ligne 62 : On créé notre fonction genre_get_posts , c’est elle qui envoie tous les paramètres nécessaire au serveur via notre fichier PHP.

Ligne 64 : On déclare une variable paged_value qui contient notre pagination, déclarée en ligne 56.

Ligne 65  : On récupère l’url du fichier admin-ajax.php que nous avions fait passer grace à wp_localize_script à la ligne 25 de notre fichier PHP. On la stocke dans la variable ajax_url

Ligne 67 : On construit notre appel Ajax. On définit le type : GET, et l’url, récupérée à la ligne 65;

A partir de la ligne 70 : Un tableau de données, nommé data dans lequel on définit l’action à exécuter, ici genre_filter qui fait référence à notre fonction PHP ajax_genre_filter; on retrouve notre type de post, notre catégorie (taxo), les termes selectionnés (on appelle la fonction getSelectedGenres vue précédemment); et enfin la pagination s’il y a.

Ce tableau data contiendra au retour (lorsque le PHP sera exécuté) nos résultats (ici nos articles).

Ligne 77 : beforeSend de Ajax sert à exécuter une action avant que l’action soit exécutée, ici j’affiche mon loader.

Ligne 81 : La valeur de retour success (si la requête sur ces termes retourne des articles) : Ligne 83  : on affiche notre DIV qui a pour ID ‘genre-results’, et on y insère notre data.

Ligne 84 : Enfin on cache le loader.

Ligne 86 : On gère les erreurs.

 

 

Crédit photo : Skitterphoto / Pixabay

7 thoughts on “Filtrer vos articles WordPress avec Ajax

  1. Bonjour,
    tout d’abord super tuto bien expliqué.
    J’ai repris ton modèle pour gérer une de custom post type classé par des taxonomies à l’aide de checkbox.
    J’ai du modifier le .live par .on
    Tout fonctionne sauf que lorsque je clique sur une checkbox l’ajax se lance 3 fois, aurais tu rencontré ce problème ?

  2. Salut Thomas !
    Ravie que le tuto t’ai servi.
    Non je n’ai pas rencontré ce problème d’ajax, as-tu une URL en ligne de ton travail ?
    Tes identifiants de markup sont bien uniques ?

    Marie.

  3. J’ai résolu mon problème mais maintenant j’ai la pagination qui me joue des tours

    Quand je clic sur mon lien de pagination il me retourne une page http://localhost:8888/mysite/admin-ajax.php?action=hotesse_filter&dispos&langues&paged=2 en me retournant les bonnes infos

    il ne reste pas sur la page http://localhost:8888/mysite/hotesses

    Les checkbox fonctionnent bien

    J’ai posté ici mon code http://wordpress.stackexchange.com/questions/236769/filter-taxonomy-with-ajax-error-pagination

  4. Je suis plutôt perdu et débute en programmation et ce tuto correspond parfaitement à ce dont j’ai besoin !
    Cela dit j’ai (il me semble) suivi toutes les étapes et lorsque je place le code qui appelle ajax-filter-posts.php dans mon template, la page n’affiche plus rien. J’ai loupé une étape ?

  5. Bonjour,
    Votre tuto est super bien expliqué, bon boulot. Cependant, j’ai un petit soucis quand j’intègre le code sur mon wordpress.
    Je souhaite afficher les articles par custom taxonomy que j’appelle plateforme. J’arrive à afficher le menu de selection et à retrouver mes différents termes de taxo qui sont dans plateforme, jusque là ca fonctionne bien. En revanche, je n’ai aucun article d’affiché, je ne sais pas pour quelle raison (ca ne fonctionne pas si je laisse le filtrage par catégorie non plus). Le fichier genre.js est bien inclus dans le footer, et je vois qu’aucun changement ne se passe quand je coche ou décoche une checkbox (le checked reste affiché tout le temps).

    Avez vous une idée de ce que ca peut être ?
    Merci

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Nom *

b7d7a254aed60e9035de4d5247585ef8~~~~~~~~~~