Moteur de recherche PHP avec Ajax et scroll infini…

Mathieu Chartier Programmation 11 commentaires

Mes lecteurs assidus savent à quel point je porte dans mon coeur les aspects techniques du web. Certes, les livres que j'ai publiés traitent tous du webmarketing jusqu'à présent mais je reste attaché au code et à son intérêt pour les sites que nous créons à longueur de temps.

Mi-2013, j'avais créé un moteur de recherche PHP en programmation orientée objet (POO pour les intimes) que je fais évoluer régulièrement pour lui apporter sans cesse plus de possibilités. Désormais, ce moteur déjà puissant à l'origine s'est dotée de propriétés intéressantes telles que l'ajout d'une pagination personnalisable, une fonction d'autocomplétion (avec création automatique d'un index de mots ou non), la possibilité de faire des recherches d'expressions exactes comme sur Google (ex : "une super expression") ou encore le surlignage des mots recherchés en gras...

Tous les explications de base propres au moteur sont accessibles à cette adresse : moteur de recherche PHP Objet (POO) complet (pagination, surlignage, fulltext…).

Moteur de recherche PHP Ajax avec nouvelle pagination...

Je vais vous présenter deux scripts jQuery-Ajax que j'ai codés afin d'apporter des alternatives à la pagination de mon moteur de recherche PHP. Je précise que ces deux scripts peuvent être utilisés dans un autre contexte que mon moteur de recherche mais je l'ai fait essentiellement pour lui apporter un plus. Voici les deux alternatives adaptables au moteur :

  • Scroll infini (souvent appelé "infinite scroll") pour développer les résultats au fur et à mesure que l'on descend dans la fenêtre ;
  • Développement par cran ("trigger") en cliquant sur un lien du type "Afficher plus..." à la manière de Facebook.

Ces deux possibilités permettent d'obtenir de bons résultats visuels mais surtout d'éviter de cliquer sur les classiques liens "suivants" et "précédents". L'autre avantage de ces scripts est de générer des appels Ajax pour le moteur de recherche PHP.

Sans plus attendre, vous pouvez les télécharger individuellement ou récupérer le pack contenant plusieurs versions d'exemple du moteur avec tous les fichiers utiles (scripts JS, CSS, images de chargement, class du moteur de recherche PHP...).

Télécharger “Pack complet - moteur de recherche PHP 5.5 - PHP 7”moteurPHP5.5.zip – Téléchargé 69913 fois – 171,49 Ko

Télécharger “AjaxTrigger”ajaxTrigger.zip – Téléchargé 2184 fois – 1,64 Ko

Télécharger “AjaxInfiniteScroll”ajaxInfiniteScroll.zip – Téléchargé 2203 fois – 1,85 Ko

N.B. : le pack comprend désormais uniquement les versions compatibles PHP 5.5 du moteur de recherche et de chaque démo en langage PHP 5.5 !

Edit (03/10/2014)

La version 2.2 du moteur de recherche PHP Ajax a vu quelques modifications, je précise cela à ceux qui téléchargerait cette nouvelle mouture après l'avoir déjà utilisée auparavant. Désormais, deux paramètres sont à changer dans les fichiers du moteur au niveau du troisième paramètre de la méthode "moteurAffichage()".
Comparez avant et après au sein du fichier principal du moteur :

  • Avant la version 2.2 : $moteur->moteurAffichage('display', '', array(true, 1, $limit));
  • Depuis la version 2.2 : $moteur->moteurAffichage('display', '', array(true, 0, $limit, false));

Dans le tableau du 3e paramètre, il faut désormais mettre la limite de départ à 0 plutôt que 1 et ajouter une 4e valeur (un booléen true/false, false étant la valeur pour les paginations infinies ou par trigger, true pour la pagination classique).

Ensuite, l'autre modification à apporter est dans le fichier "queryAjax" (selon la version utilisée le nom diffère) au niveau de la variable $page, voici les lignes à modifier de la sorte :

if(isset($_GET['nb'])) {
	$page = htmlspecialchars($_GET['nb']);
} else {
	$page = 0;
}

N.B. : les exemples présentés dans cet article sont adaptés à cette nouvelle version !

Etapes de fonctionnement du moteur de recherche PHP-Ajax

Voici la liste des étapes à respecter pour que les scripts jQuery fonctionnent parfaitement :

  1. Installation des scripts dans le <head>...</head> en HTML (dans la page qui réceptionne les résultats tout du moins).
  2. Ajout d'un formulaire de recherche.
  3. Chargement d'une première page de résultats en PHP classique. Cette page reçoit la première "vague" de résultats avant que le script ne se mette en route pour dérouler le reste des résultats...
  4. Lancement du script Ajax vers un fichier PHP externe (connecté à la BDD indépendamment) qui ajoute les résultats suivants dans la liste.

N.B. : ces scripts étant codés en jQuery, il faut bien entendu charger la bibliothèque avant de les charger !

Exemple de première page du moteur de recherche PHP

Les codes suivants sont à placer dans le <body>...</body> de la page qui réceptionne les résultats. Seul le formulaire peut être placé ailleurs avec une "action" différenciée, mais pour l'exemple, tout se passe dans la même page...

Formulaire du moteur de recherche :

<form id="searchForm" name="moteurSubmit" method="GET" action="">
	<label for="q">Rechercher :</label>
	<input type="text" value="<?php if(isset($_GET['q'])) { echo htmlspecialchars($_GET['q']); } ?>" name="q" id="moteur" />
	<input type="submit" value="Envoyer" />
</form>

Usage de la class PHP 5 du moteur de recherche PHP objet :

Il faut impérativement ajouter une class HTML à chaque ligne de résultats (ici, c'est la class "results") ainsi qu'un compteur de résultats qui ajoutent dynamiquement des ID (ici, c'est $nb, mais vous n'êtes pas obligés d'ajouter le nombre comme dans l'exemple en revanche, c'est juste utile dans id="<?php echo $nb; ?>").

<?php
include_once("class.inc/BDD.class-inc.php");
include_once("class.inc/stopwords.php");
include_once("class.inc/moteur-php5.class-inc.php");

if(isset($_GET) && !empty($_GET['q'])) {
    $moteur = new moteurRecherche(stripslashes($_GET['q']), 'search', 'regexp', $stopwords);
    $colonnesWhere = array('title', 'description');
    $moteur->moteurRequetes($colonnesWhere);
}

if(isset($moteur)) {
    // Affichage de la requête avec $moteur->requete
    echo '<h3>Résultats de la recherche : <em>'.$moteur->requete.'</em></h3>';

	// Fonction d'affichage des résultats (callback appelé ensuite)	
	function display($requete, $nbResults, $mots) {

		if($nbResults == 0) { // Si aucun résultat n'est retourné
			echo "<p>Aucun résultat, veuillez effectuer une autre recherche !</p>";    
		} else { // Sinon on affiche les résultats en boucle

			// Affichage du nombre de résultats (optionnel)
			// N.B. : important pour l'affichage de résultats suivants (class "numR") !!!
			$affichageResultats = new affichageResultats();
			echo $affichageResultats->nbResultats(true);

			// Instanciation des ID (et du numéro de résultat si besoin)
			$nb = 0;
			while(($key = mysql_fetch_assoc($requete))) {
				$nb++; // Incrémentation de l'ID

				// On encode chaque clé/valeur de résultats en UTF-8
				foreach($key as $k => $v) {
					 $key[$k] = utf8_encode($v);
				}

				// Résultats de recherche à afficher (à personnaliser)
				$texte  = "<div class='results' id='".$nb."'>\n";
				$texte .= "\t<h3>".$nb." - ".$key['title']."</h3>\n";
				$texte .= "\t<p>".$key['description']."</p>\n";
				$texte .= "</div>\n";

				// Affichage du contenu après surlignage des mots recherchés
				// N.B. : optionnel --> possibilité de remplacer par echo $texte;
				$surlignage = new surlignageMot($mots, $texte);
				echo $surlignage->contenu;
			} // Fin de la boucle while
		}
	} // Fin de la fonction display (callback)

	// Nombre de résultats par "tranche d'affichage"
	$limit = 10;

	// Lancement de la fonction d'affichage avec paramètres
	$moteur->moteurAffichage('display', '', array(true, 0, $limit, false));

	// Affichage de la zone de chargement
	echo '<div id="loadMore">Afficher plus de résultats...</div>';
}
?>

Exemple de fichier de réception du script Ajax

Ce fichier reprend un peu le même principe que le code PHP situé juste au-dessus car il doit être en cohérence avec les résultats affichés précédemment. Seules quelques différences sont à noter :

  • suppression de la ligne d'affichage du nombre de résultats ;
  • suppression du lien "Afficher plus..." car il n'a pas besoin d'être rechargé ;
  • modification du "$nb = 0;" en "$nb=$_GET['nb'];" ;
  • modification du "$limit = 10;" (ou autre valeur) en "$limit = htmlspecialchars($_GET['limit']);".
<?php
include_once("class.inc/BDD.class-inc.php");
include_once("class.inc/stopwords.php");
include_once("class.inc/moteur-php5.class-inc.php");

$moteur = new moteurRecherche(stripslashes($_GET['q']), 'search', 'regexp', $stopwords);
$colonnesWhere = array('title', 'description');
$moteur->moteurRequetes($colonnesWhere);

if(isset($moteur)) {
	function affichage($requete, $nbResults, $mots) {
		// Récupération du numéro de résultats
		$nb = $_GET['nb'];

		while(($key = mysql_fetch_assoc($requete)) && $nb < $nbResults) {
			$nb++; // Incrémentation de l'ID

			// On encode chaque clé/valeur de résultats en UTF-8
			foreach($key as $k => $v) {
				 $key[$k] = utf8_encode($v);
			}

			// Résultats de recherche à afficher (à personnaliser)
			$texte  = "<div class='results' id='".$nb."'>\n";
			$texte .= "\t<h3>".$nb." - ".$key['title']."</h3>\n";
			$texte .= "\t<p>".$key['description']."</p>\n";
			$texte .= "</div>\n";

			// Affichage du contenu après surlignage des mots recherchés
			// N.B. : optionnel --> possibilité de remplacer par echo $texte;
			$surlignage = new surlignageMot($mots, $texte);
			echo $surlignage->contenu;
		} // Fin de la boucle while
	} // Fin de la fonction display (callback)

	// Nombre de résultats par "tranche d'affichage"
	$limit = htmlspecialchars($_GET['limit']);

	// Numéro de page récupéré dynamiquement
	if(isset($_GET['nb'])) {
		$page = htmlspecialchars($_GET['nb']);
	} else {
		$page = 0;
	}

	// Lancement de la fonction d'affichage avec paramètres
	$moteur->moteurAffichage('affichage', '', array(true, $page, $limit, false));
}
?>

Exemple de script - Ajax trigger avec jQuery

Voici comment faire fonctionner le trigger Ajax avec "Afficher plus de résultats" (à placer dans le head ou en fin de body dans le code HTML) :

  • ajout du CSS pour gérer l'affichage du "loader" (bouton "Afficher plus...") dans une feuille à part ou directement dans votre propre CSS ;
  • chargement de la bibliothèque jQuery (évitez les versions 2, optez plutôt pour des versions 1.7 ou 1.8 par exemple) ;
  • chargement du script ajaxTrigger.js ;
  • mise en place des paramètres utiles faire fonctionner le plugin jQuery.

N.B. : pour le moteur de recherche PHP, il convient absolument de mettre "nbResult: $('.numR').text()" pour que le nombre de résultats total soit récupéré dynamiquement. L'autre alternative en PHP 5 serait de charger les scripts en bas de page (fin de body) et d'utiliser à la place "<?php echo $moteur::$nbResultsChiffre; ?>"...

<link rel="stylesheet" href="css/click-trigger.css" />
<script type="application/javascript" src="js/jquery.js"></script>
<script type="application/javascript" src="js/ajaxTrigger.js"></script>
<script type="application/javascript">
$(document).ready(function () {
	// Tableau des arguments optionnels (ici les valeurs par défaut)
	var args = {
		target: 'queryAjax.php',					// Cible contenant le contenu à charger (boucle PHP/MySQL en général)
		limit: 5,									// Nombre de résultats à afficher par chargement
		nbResult: $('.numR').text(),				// Nombre total de résultats (récupéré dynamiquement)
		duration: 300,								// Durée d'affichage de l'image de chargement (en ms) --> 0 pour annuler !
		classLast: '.results',						// Class des résultats affichés (obligatoire pour fonctionner !)
		loadImg: 'img/loadingBlue.gif',				// Image de chargement ('' pour ne pas afficher d'image)
		idImg: 'imgLoading',						// ID du bloc contenant l'image de chargement
		attrID: 'id',								// Attribut contenant le numéro du résultat affiché ('id' conseillé !)
		evt: 'click',								// Type d'événement Javascript pour lancer la fonction
	};

	// Options complémentaires (requête de recherche par exemple ici --> Totalement personnalisable !)
	var options = {
		q: '<?php if(isset($_GET['q'])) { echo $_GET['q']; } ?>'
	};

	// Lancement de la fonction sur l'élément "Afficher plus"
	$('#loadMore').ajaxTrigger(args, options);
});
</script>

Trigger Ajax pour moteur de recherche PHP (poo)

Exemple de script - Scroll infini Ajax avec jQuery

Le scroll infini fonctionne globalement sur le même principe que le trigger Ajax, il convient donc de respecter la procédure expliquée précédemment avec le script suivant :

<link rel="stylesheet" href="css/infinite-scroll.css"/>
<script type="application/javascript" src="js/jquery.js"></script>
<script type="application/javascript" src="js/ajaxInfiniteScroll.js"></script>
<script type="application/javascript">
$(document).ready(function () {
	// Tableau des arguments optionnels (ici les valeurs par défaut)
	var args = {
		target: 'queryAjax.php',					// Cible contenant le contenu à charger (boucle PHP/MySQL en général)
		limit: 5,									// Nombre de résultats à afficher par chargement
		nbResult: $('.numR').text(),				// Nombre total de résultats (récupéré dynamiquement)
		duration: 500,								// Durée d'affichage de l'image de chargement (en ms) --> 0 pour annuler !
		classLast: '.results',						// Class des résultats affichés (obligatoire pour fonctionner !)
		loadMore: '#loadMore',						// Sélecteur de l'image de chargement
		attrID: 'id',								// Attribut contenant le numéro du résultat affiché ('id' conseillé !)
		evt: 'scroll',								// Type d'événement Javascript pour lancer la fonction
	};

	// Options complémentaires (requête de recherche par exemple ici --> Totalement personnalisable !)
	var options = {
		q: '<?php if(isset($_GET['q'])) { echo $_GET['q']; } ?>'
	};

	// Lancement de la fonction sur l'élément "Afficher plus"
	$(window).ajaxInfiniteScroll(args, options);
});
</script>

Evolutions éventuelles pour les scrips du moteur de recherche PHP-Ajax

Comme tous les scripts, ces deux fichiers codés en jQuery sont imparfaits et peuvent demander quelques ajustements, c'est pourquoi je vous conseille de repasser de temps en temps sur cette page pour vérifier la version en cours d'usage. En général, dès que quelqu'un me signale un gros problème ou que j'en détecte un de mon côté, je fais une petite mise à jour de correction, c'est la règle du jeu... :D

Actuellement, je n'ai décelé aucun problème particulier avec ajaxTrigger.js mais le scroll infini peut parfois être taquin quand nous allons trop vite avec le scroll et la souris (il charge deux fois les mêmes résultats puis se relance normalement). J'ai ajouté un système de "timer" pour limiter ce phénomène en ralentissant l'animation, ce qui ne m'a plus causé aucun problème après ça, mais je reste méfiant toutefois que les scroll infinis ne sont jamais parfaits...

En réalité, vous pouvez "feinter" pour simuler une sorte de scroll infini mais en utilisant ajaxTrigger.js, voici comment procéder :

  • mettre un événement "hover" plutôt que "click" dans le script de lancement du trigger Ajax ;
  • modifier le "echo '<div id="loadMore">Afficher plus de résultats...</div>';" en "echo '<div id="loadMore">&nbsp;</div>';".

Ainsi, quand vous arrivez en base de page et que vous "survolez" la div transparente, le script se lance et fait l'effet d'un scroll infini. C'est moins pratique mais c'est une solution alternative... :D

11 commentaires

  • Seb Menozei dit :

    Super classe, moi qui chercher comment faire un moteur de recherche PERFORMANT, je suis comblé ;) Le code est propre, clair et commenté. Il ne me reste plus qu'à l'améliorer.

  • Bonjour, super moteur de recherche, je souhaiterais modifier "ORDER BY", du plus récent au plus ancien et là je n'arrive pas à trouver l'endroit à modifier...."moteur-php5.5.class-inc".
    Merci
    Cordialement

    • Bonjour,
      Pas la peine de modifier directement le code, il y a un paramètre exprès pour ça dans la méthode moteurAffichage (voir commentaires dans le code présenté dans l'article, c'est le 4e ou 5e selon la version de la class utilisée).

  • Bonjour, j'utilise votre moteur de recherche depuis quelques semaines (très bien), mais je viens de découvrir un petit problème.
    La recherche ne veut pas se faire sur le mot Lot (département), sans doute trop court. Peut on remédier à cela ?
    Merci
    Cordialement

    • Bonjour,
      Quel type de recherche utilisez-vous ? La cause de l'exclusion du mot "lot" peut être multiple. Je vous donne toutes les cartes en main, vous devriez être dans un de ces cas :
      - vous utilisez le recherche FULLTEXT qui par défaut exclut les mots de moins de 4 lettres (c'est très fâcheux mais on ne peut pas y faire grand chose si nous avons un hébergement mutualisé).
      - vous avez mis une limite minimale de longueur de mots dans les options du moteur (je doute que ce soit ça mais vérifiez au cas où...)
      - le mot "lot" fait partie des stopwords français, il faut donc le supprimer (j'ai vérifié dans mon fichier et il n'y est pas...)

      Je pense que c'est le "fulltext" qui est le plus probable, mais je peux me tromper.
      Cordialement.

  • GAY Jean-Luc dit :

    Merci pour votre réponse.
    J'ai un hébergement sur un VPS, il y a t'il un remède ?
    Cordialement

    • Sur un VPS ça doit être jouable je pense... Si vous êtes sur un serveur Apache, il faut paramétrer l'option ft_min_word_len de MySQL (dans le fichier my.ini). Vous trouverez des informations à ce sujet sur le web normalement.

  • GAY Jean-Luc dit :

    Bonjour, votre moteur de recherche fonctionne parfaitement (merci), mais j'aimerais apporter une précision dans la recherche avec une sélection entre deux prix, peut être avec "BETWEEN". Ma base de données MySQL à une colonne prix.
    Est ce possible de l'intégrer à votre moteur ?
    Cordialement

    • Bonjour,
      Cela me semble "compliqué. Je vous explique pourquoi... Les requêtes sont construites ici dynamiquement en fonction du nombre de mots tapés, ce qui explique qu'une boucle récurrente s'effectue à chaque fois que l'on recherche un mot de plus. Par conséquent, une même partie de code est répétée, encore et toujours, selon ce nombre de mots. Dans le cas d'une requête avec BETWEEN, cela ne s'effectue pas ainsi donc me semble impossible sans réécriture de la requête, et donc du coeur du moteur.
      Toutefois, comme BETWEEN est utilisé dans une clause WHERE, j'ai prévu un paramètre additionnel pour personnaliser la requête ($orderLimitPerso) qui pourrait satisfaire ce besoin.
      Quoi qu'il en soit, il faudra tout de même mettre un peu les mains dans le cambouis, malheureusement... :-(

  • Djuimou Eddy Michel dit :

    Bonjour
    merci beaucoup pour votre aide. Je désire effectuer une recherche sur deux champs de recherche comme sur le site http://www.pagesjaunes.fr/ et je ne sais comment procéder merci d'avance pour votre aide.

    • Bonjour,
      Mon moteur ne permet pas encore de faire ça sans problème malheureusement, ou sinon il faut trouver la technique en codant par soi-même.
      Je vais certainement réécrire tout le moteur de recherche si l'année 2016 me le permet afin de faciliter tout ça mais actuellement je manque de temps pour m'y coller. :(

  • Déposer un commentaire

    L'adresse de messagerie ne sera pas publiée.* Champs obligatoires