Sur un site client sous FLEXIcontent, le besoin était d'afficher une liste de séjours en ordre aléatoire, avec un bouton "Voir plus de séjours" qui charge la suite sans rechargement de page. Voici comment j'ai remplacé la pagination native par un Load More AJAX pur — sans jQuery, sans plugin tiers.
Le problème de base
Sur un site client, la page catégorie FLEXIcontent affiche une liste de séjours. La pagination native fonctionne, mais c'est une expérience cassée : l'utilisateur clique sur "Page 2", la page recharge entièrement, il perd sa position, l'URL change. Pour une liste de voyages que l'on veut parcourir fluidement, c'est rédhibitoire.
La demande : afficher 9 items en ordre aléatoire, puis un bouton "Voir plus" qui charge la suite sans rechargement de page.
Simple en apparence. Moins simple à cause de la façon dont FLEXIcontent gère la pagination.
Pourquoi l'approche naïve ne marche pas
La première idée qui vient : charger tous les items d'un coup (limit = 0), masquer ceux au-delà du 9ème en CSS, révéler au clic en JS.
Ça marche si la catégorie contient 30 items. Ça ne marche plus à 300 — vous chargez tout le HTML, toutes les images (même avec lazy loading), toute la mémoire PHP. Sur un hébergement mutualisé, c'est la limite mémoire ou un timeout.
La vraie solution : laisser FLEXIcontent paginer normalement, et gérer le "charger plus" en AJAX en s'appuyant sur sa pagination existante.
La solution : AJAX + pagination FLEXIcontent
FLEXIcontent expose un objet $this->pagination dans ses templates. Il contient exactement ce qu'il faut :
pagination->total— nombre total d'itemspagination->limit— items par page (votre "9")pagination->limitstart— offset courant
Le principe : remplacer la section pagination dans category.php par un bouton qui connaît ces valeurs, et qui fetche la page suivante en AJAX pour en extraire les items et les injecter dans le DOM.
Côté template category.php, la section pagination devient :
<?php
$_pag_total = (int) $this->pagination->total;
$_pag_limit = (int) $this->pagination->limit;
$_pag_limitstart = (int) $this->pagination->limitstart;
$_pag_next = $_pag_limitstart + $_pag_limit;
$_pag_remaining = $_pag_total - $_pag_next;
if ($_pag_remaining > 0) :
?>
<div id="fc-loadmore-wrap">
<button id="fc-loadmore-btn" type="button"
data-next="<?php echo $_pag_next; ?>"
data-limit="<?php echo $_pag_limit; ?>"
data-total="<?php echo $_pag_total; ?>"
data-baseurl="<?php echo htmlspecialchars(\Joomla\CMS\Uri\Uri::current(), ENT_QUOTES, 'UTF-8'); ?>">
Voir les <?php echo $_pag_remaining; ?> autres séjours
</button>
</div>
<?php endif; ?>
Côté JS, le fetch cible simplement ?limitstart=9, puis ?limitstart=18, etc. On parse le HTML retourné, on extrait les blocs d'items et on les injecte :
fetch(url.toString())
.then(r => r.text())
.then(html => {
const doc = new DOMParser().parseFromString(html, 'text/html');
const newStd = doc.querySelector('.standard-block.fc-items-block');
const curStd = document.querySelector('.standard-block.fc-items-block');
if (newStd && curStd) {
Array.from(newStd.children).forEach(el => {
curStd.appendChild(document.adoptNode(el));
});
}
// ... idem pour .featured-block
});
Le bouton se met à jour avec le nombre d'items restants après chaque lot. Quand il n'y a plus rien, il disparaît.
Ce que j'ai amélioré par rapport à un "load more" classique
Un load more basique charge un lot fixe et affiche un bouton statique. Ici, quelques détails font la différence :
Le compteur dynamique. Le bouton indique toujours combien d'items restent : "Voir les 18 autres séjours", puis "Voir les 9 autres séjours", puis disparaît. L'utilisateur sait exactement où il en est.
Gestion des deux blocs FLEXIcontent. FLEXIcontent distingue les "featured items" et les "standard items" dans deux blocs séparés. Le script injecte dans les deux indépendamment, respectant ainsi la configuration du template (colonnes, layout, masonry…).
Gestion des erreurs réseau. Si le fetch échoue, le bouton se réactive et reprend son libellé. L'utilisateur peut réessayer sans recharger la page.
Zero dépendance. Pas de jQuery, pas de bibliothèque tierce. Fetch API natif, DOMParser natif. Ça marche sur tous les navigateurs modernes et n'alourdit pas la page d'une ligne.
Compatible avec l'ordre aléatoire. FLEXIcontent peut trier les items par ORDER BY RAND(). Ce réglage se fait dans le backend, le template n'a rien à forcer. Chaque lot est aléatoire indépendamment — c'est voulu dans ce cas d'usage, mais si vous avez besoin d'un aléatoire stable sur toute la session, il faudra une graine SQL ou une liste d'IDs en session.
Configuration côté backend
Dans FLEXIcontent, paramètres de la vue catégorie :
- Items per page → la taille de votre lot (9 dans ce cas)
- Ordering → Random si vous voulez de l'aléatoire
- La pagination native peut rester activée — elle ne s'affiche plus puisque le template ne l'appelle plus, mais ses données sont toujours disponibles dans
$this->pagination
Fichiers à modifier
Un seul fichier : components/com_flexicontent/templates/[votre_template]/category.php
La section <!-- BOF pagination --> est remplacée par le bouton et le script. C'est tout.
Pas touche à category_items.php — FLEXIcontent gère l'affichage de chaque lot exactement comme il le ferait pour une page normale.
En résumé
La clé était de comprendre que FLEXIcontent fait déjà tout le travail de pagination. On n'a pas besoin de le contourner ou de tout charger d'un coup. On se contente de lui demander la page suivante via fetch, de récupérer les blocs qui nous intéressent dans la réponse HTML, et de les greffer proprement dans le DOM existant.
Résultat : une expérience fluide, un code minimal, aucune régression sur le reste du template.
