Le filtrage en temps réel : indispensable pour toute liste
Un système de filtrage permet aux utilisateurs de trouver rapidement ce qu’ils cherchent dans une liste de produits, articles, contacts ou tout autre type de données. Dans ce tutoriel, vous construisez un système complet avec recherche textuelle, filtres par catégorie, tri, et mise à jour en temps réel — le tout en JavaScript pur.
Les données
const produits = [
{ id: 1, nom: 'MacBook Pro', catégorie: 'laptop', prix: 850000, stock: true },
{ id: 2, nom: 'iPhone 15', catégorie: 'téléphone', prix: 650000, stock: true },
{ id: 3, nom: 'AirPods Pro', catégorie: 'accessoire', prix: 150000, stock: false },
{ id: 4, nom: 'Galaxy S24', catégorie: 'téléphone', prix: 550000, stock: true },
{ id: 5, nom: 'Dell XPS 15', catégorie: 'laptop', prix: 700000, stock: true },
{ id: 6, nom: 'Clavier Logitech', catégorie: 'accessoire', prix: 35000, stock: true },
{ id: 7, nom: 'ThinkPad X1', catégorie: 'laptop', prix: 900000, stock: false },
{ id: 8, nom: 'Souris MX Master', catégorie: 'accessoire', prix: 45000, stock: true },
{ id: 9, nom: 'iPad Pro', catégorie: 'tablette', prix: 600000, stock: true },
{ id: 10, nom: 'Pixel 8', catégorie: 'téléphone', prix: 450000, stock: true }
];
Le HTML
<div class="filter-app">
<!-- Barre de recherche -->
<input type="text" id="searchInput" placeholder="Rechercher un produit...">
<!-- Filtres par catégorie -->
<div class="filter-buttons" id="categoryFilters">
<button class="filter-btn activé" data-category="all">Tous</button>
<button class="filter-btn" data-category="laptop">Laptops</button>
<button class="filter-btn" data-category="téléphone">Téléphones</button>
<button class="filter-btn" data-category="tablette">Tablettes</button>
<button class="filter-btn" data-category="accessoire">Accessoires</button>
</div>
<!-- Options supplémentaires -->
<div class="filter-options">
<label><input type="checkbox" id="stockOnly"> En stock uniquement</label>
<select id="sortSelect">
<option value="nom">Trier par nom</option>
<option value="prix-asc">Prix croissant</option>
<option value="prix-desc">Prix décroissant</option>
</select>
</div>
<!-- Résultats -->
<p id="resultCount"></p>
<div id="productGrid" class="product-grid"></div>
</div>
Le JavaScript : moteur de filtrage
// État des filtres
let filters = {
search: '',
category: 'all',
stockOnly: false,
sort: 'nom'
};
// Éléments DOM
const searchInput = document.getElementById('searchInput');
const categoryBtns = document.querySelectorAll('.filter-btn');
const stockCheckbox = document.getElementById('stockOnly');
const sortSelect = document.getElementById('sortSelect');
const grid = document.getElementById('productGrid');
const countEl = document.getElementById('resultCount');
// Appliquer tous les filtres
function getFilteredProducts() {
let result = [...produits];
// 1. Filtre texte
if (filters.search) {
const query = filters.search.toLowerCase();
result = result.filter(p =>
p.nom.toLowerCase().includes(query)
);
}
// 2. Filtre catégorie
if (filters.category !== 'all') {
result = result.filter(p => p.catégorie === filters.category);
}
// 3. Filtre stock
if (filters.stockOnly) {
result = result.filter(p => p.stock);
}
// 4. Tri
result.sort((a, b) => {
switch (filters.sort) {
case 'prix-asc': return a.prix - b.prix;
case 'prix-desc': return b.prix - a.prix;
default: return a.nom.localeCompare(b.nom);
}
});
return result;
}
// Afficher les produits
function render() {
const filtered = getFilteredProducts();
countEl.textContent = filtered.length + ' produit(s) trouvé(s)';
if (filtered.length === 0) {
grid.innerHTML = '<p class="no-results">Aucun produit ne correspond à vos critères.</p>';
return;
}
grid.innerHTML = filtered.map(p =>
'<div class="product-card ' + (!p.stock ? 'out-of-stock' : '') + '">' +
'<h3>' + p.nom + '</h3>' +
'<span class="category">' + p.catégorie + '</span>' +
'<p class="price">' + p.prix.toLocaleString('fr-FR') + ' FCFA</p>' +
'<span class="stock ' + (p.stock ? 'in-stock' : '') + '">' +
(p.stock ? 'En stock' : 'Rupture') +
'</span>' +
'</div>'
).join('');
}
// Écouteurs d'événements
searchInput.addEventListener('input', (e) => {
filters.search = e.target.value;
render();
});
categoryBtns.forEach(btn => {
btn.addEventListener('click', () => {
categoryBtns.forEach(b => b.classList.remove('activé'));
btn.classList.add('activé');
filters.category = btn.dataset.category;
render();
});
});
stockCheckbox.addEventListener('change', (e) => {
filters.stockOnly = e.target.checked;
render();
});
sortSelect.addEventListener('change', (e) => {
filters.sort = e.target.value;
render();
});
// Affichage initial
render();
Comment ça fonctionne
Le système suit un pattern simple :
- L’utilisateur interagit avec un contrôle (recherche, filtré, tri)
- L’état
filtersest mis à jour - La fonction
render()est appelée getFilteredProducts()applique TOUS les filtres dans l’ordre- Le DOM est mis à jour avec les résultats
Tous les filtres sont cumulatifs : si vous cherchez « Mac » dans la catégorie « laptop » avec « en stock uniquement », les 3 filtres s’appliquent ensemble.
Exercice
- Ajoutez un filtré de prix avec un double slider (min/max)
- Ajoutez un compteur de filtres actifs sur chaque catégorie
- Sauvegardez les filtres dans l’URL (query params) pour que l’utilisateur puisse partager une recherche filtrée
- Ajoutez une animation de transition quand les cartes apparaissent/disparaissent