document.addEventListener(‘DOMContentLoaded’, () => {
// — 1. Données pour le comparateur —
// Cette section contient les données des villages de Djerba.
// Puisqu’aucune API gratuite spécifique à Djerba offrant ce niveau de détail n’est disponible
// publiquement, les données sont incluses directement dans le script. Cela garantit la
// conformité à la contrainte “100% gratuits” et simplifie la mise en œuvre.
//
// EXEMPLE D’APPEL API (si une API externe existait et était utilisée) :
// const apiUrl = ‘https://api.djerbainfo.com/villages-comparaison’; // URL imaginaire
// fetch(apiUrl)
// .then(response => response.json())
// .then(data => {
// djerbaVillagesData = data; // Stocker les données de l’API
// updateDisplay(); // Mettre à jour l’affichage après le chargement
// })
// .catch(error => console.error(‘Erreur lors du chargement des villages:’, error));
//
// EXEMPLE DE STRUCTURE DE RÉPONSE JSON d’une telle API (similaire à nos données ci-dessous) :
// [
// {
// “id”: “houmt_souk”,
// “nom”: “Houmt Souk”,
// “population”: 75000,
// “distance”: 0,
// “ambiance”: “Animée, commerçante, historique”,
// “plageProximite”: “Oui (proche)”,
// “idealPour”: [“Familles”, “Amis”, “Culture”],
// “interets”: [“Souks traditionnels”, “Port de pêche”, “Borj El Kbir”]
// },
// // … autres villages
// ]
const djerbaVillagesData = [
{
id: ‘houmt_souk’,
nom: ‘Houmt Souk’,
population: 75000, // Estimation
distance: 0, // Distance par rapport à Houmt Souk (km)
ambiance: ‘Animée, commerçante, historique’,
plageProximite: ‘Oui (plages à proximité)’,
idealPour: [‘Familles’, ‘Amis’, ‘Culture’],
interets: [‘Souks traditionnels’, ‘Port de pêche’, ‘Borj El Kbir’, ‘Musée des Arts et Traditions Populaires’]
},
{
id: ‘midoun’,
nom: ‘Midoun’,
population: 60000, // Estimation
distance: 15,
ambiance: ‘Touristique, dynamique, festive’,
plageProximite: ‘Oui (plages principales)’,
idealPour: [‘Familles’, ‘Amis’, ‘Détente’],
interets: [‘Marché hebdomadaire’, ‘Parcs à thème’, ‘Zone touristique’, ‘Plages aménagées’]
},
{
id: ‘guellala’,
nom: ‘Guellala’,
population: 10000, // Estimation
distance: 25,
ambiance: ‘Authentique, artisanale, calme’,
plageProximite: ‘Non (côte rocheuse)’,
idealPour: [‘Culture’, ‘Détente’],
interets: [‘Poteries traditionnelles’, ‘Musée du Patrimoine de Guellala’, ‘Vues panoramiques’]
},
{
id: ‘erriadh’,
nom: ‘Erriadh’,
population: 8000, // Estimation
distance: 7,
ambiance: ‘Artistique, calme, culturelle’,
plageProximite: ‘Non (village intérieur)’,
idealPour: [‘Couples’, ‘Culture’, ‘Détente’],
interets: [‘Djerbahood (Street Art)’, ‘Synagogue de la Ghriba’, ‘Galeries d’art’]
},
{
id: ‘ajim’,
nom: ‘Ajim’,
population: 20000, // Estimation
distance: 20,
ambiance: ‘Portuaire, locale, authentique’,
plageProximite: ‘Oui (port et petites plages)’,
idealPour: [‘Amis’, ‘Culture’, ‘Détente’],
interets: [‘Port de pêche’, ‘Lieux de tournage Star Wars’, ‘Marché aux poissons’]
},
{
id: ‘fatou’,
nom: ‘Fatou’,
population: 3000, // Estimation
distance: 12,
ambiance: ‘Très calme, rurale, paisible’,
plageProximite: ‘Non (village intérieur)’,
idealPour: [‘Détente’, ‘Couples’],
interets: [‘Olivers centenaires’, ‘Paysages agricoles’, ‘Atmosphère locale’]
},
{
id: ‘mezraya’,
nom: ‘Mezraya’,
population: 12000, // Estimation
distance: 18,
ambiance: ‘Mixte, résidentielle, calme’,
plageProximite: ‘Oui (proche des plages de la zone touristique)’,
idealPour: [‘Familles’, ‘Détente’],
interets: [‘Plages moins fréquentées’, ‘Restaurants locaux’, ‘Vie résidentielle’]
}
];
// — 2. Récupération des éléments du DOM —
const villagesTableBody = document.getElementById(‘villagesTableBody’);
const searchVillageInput = document.getElementById(‘searchVillage’);
const sortColumnSelect = document.getElementById(‘sortColumn’);
const sortOrderSelect = document.getElementById(‘sortOrder’);
const filterBeachSelect = document.getElementById(‘filterBeach’);
const filterIdealForSelect = document.getElementById(‘filterIdealFor’);
const tableHeaders = document.querySelectorAll(‘#comparateurVillagesDjerba thead th[data-column]’);
// Variables d’état pour le tri
let currentSortColumn = ‘nom’; // Colonne par défaut pour le tri
let currentSortOrder = ‘asc’; // Ordre par défaut (‘asc’ pour croissant, ‘desc’ pour décroissant)
// — 3. Fonctions de logique —
/**
* Trie et filtre les données des villages en fonction des critères sélectionnés par l’utilisateur.
* @returns {Array} Un nouveau tableau contenant les villages filtrés et triés.
*/
const filterAndSortData = () => {
let filteredData = […djerbaVillagesData]; // Créer une copie des données originales pour ne pas les modifier
// 1. Filtrage par recherche textuelle (sur nom, ambiance, points d’intérêt)
const searchTerm = searchVillageInput.value.toLowerCase().trim();
if (searchTerm) {
filteredData = filteredData.filter(village =>
village.nom.toLowerCase().includes(searchTerm) ||
village.ambiance.toLowerCase().includes(searchTerm) ||
village.interets.some(interest => interest.toLowerCase().includes(searchTerm)) ||
village.idealPour.some(ideal => ideal.toLowerCase().includes(searchTerm))
);
}
// 2. Filtrage par proximité plage
const beachFilter = filterBeachSelect.value;
if (beachFilter !== ‘all’) {
filteredData = filteredData.filter(village => {
const hasBeach = village.plageProximite.toLowerCase().includes(‘oui’);
return beachFilter === ‘oui’ ? hasBeach : !hasBeach;
});
}
// 3. Filtrage par “Idéal pour”
const idealForFilter = filterIdealForSelect.value;
if (idealForFilter !== ‘all’) {
filteredData = filteredData.filter(village =>
village.idealPour.includes(idealForFilter)
);
}
// 4. Tri des données
const column = currentSortColumn;
const order = currentSortOrder;
// Récupérer le type de la colonne pour un tri numérique ou alphabétique
const columnType = sortColumnSelect.querySelector(`option[value=”${column}”]`).dataset.type;
filteredData.sort((a, b) => {
let valA = a[column];
let valB = b[column];
// Convertir en nombre si la colonne est de type ‘number’
if (columnType === ‘number’) {
valA = parseFloat(valA);
valB = parseFloat(valB);
} else {
// Pour les chaînes de caractères, utiliser localeCompare pour un tri alphabétique correct
valA = String(valA).toLowerCase();
valB = String(valB).toLowerCase();
}
if (valA valB) return order === ‘asc’ ? 1 : -1;
return 0;
});
return filteredData;
};
/**
* Met à jour le corps du tableau HTML avec les données des villages à afficher.
* @param {Array} dataToRender – Le tableau des objets villages à insérer dans le tableau.
*/
const renderTable = (dataToRender) => {
villagesTableBody.innerHTML = ”; // Vider le contenu actuel du tableau
if (dataToRender.length === 0) {
// Afficher un message si aucun résultat n’est trouvé
villagesTableBody.innerHTML = `
Aucun village ne correspond à vos critères de recherche ou de filtrage.`;
return;
}
// Créer et ajouter une ligne pour chaque village
dataToRender.forEach(village => {
const row = document.createElement(‘tr’);
// Appliquer des classes Tailwind pour le style des lignes
row.classList.add(‘bg-white’, ‘border-b’, ‘hover:bg-gray-50’);
row.innerHTML = `
${village.nom}
${village.population.toLocaleString(‘fr-FR’)}
${village.distance} km
${village.ambiance}
${village.plageProximite}
${village.idealPour.join(‘, ‘)}
${village.interets.join(‘, ‘)}
`;
villagesTableBody.appendChild(row);
});
};
/**
* Met à jour les en-têtes de colonne du tableau pour indiquer visuellement la colonne triée et l’ordre.
*/
const updateSortHeaders = () => {
tableHeaders.forEach(header => {
header.classList.remove(‘sorted-asc’, ‘sorted-desc’); // Nettoyer les indicateurs précédents
if (header.dataset.column === currentSortColumn) {
// Ajouter l’indicateur de tri pour la colonne actuellement triée
header.classList.add(`sorted-${currentSortOrder}`);
}
});
};
/**
* Fonction principale qui orchestre le filtrage, le tri et le rendu du tableau.
*/
const updateDisplay = () => {
const data = filterAndSortData(); // Récupérer les données filtrées et triées
renderTable(data); // Rendre le tableau avec ces données
updateSortHeaders(); // Mettre à jour les indicateurs de tri des en-têtes
};
// — 4. Gestionnaires d’événements —
// Écouteurs d’événements pour les contrôles de filtrage et de tri
searchVillageInput.addEventListener(‘input’, updateDisplay); // Mise à jour à chaque frappe dans le champ de recherche
filterBeachSelect.addEventListener(‘change’, updateDisplay); // Mise à jour au changement du filtre plage
filterIdealForSelect.addEventListener(‘change’, updateDisplay); // Mise à jour au changement du filtre “Idéal pour”
// Synchroniser les sélecteurs de tri avec l’état interne et déclencher la mise à jour
sortColumnSelect.addEventListener(‘change’, (e) => {
currentSortColumn = e.target.value;
updateDisplay();
});
sortOrderSelect.addEventListener(‘change’, (e) => {
currentSortOrder = e.target.value;
updateDisplay();
});
// Gestion du tri par clic sur l’en-tête du tableau
tableHeaders.forEach(header => {
header.addEventListener(‘click’, () => {
const column = header.dataset.column; // Récupérer le nom de la colonne depuis l’attribut data-column
if (column) {
// Si on clique sur la même colonne, on inverse l’ordre de tri
if (currentSortColumn === column) {
currentSortOrder = currentSortOrder === ‘asc’ ? ‘desc’ : ‘asc’;
} else {
// Si c’est une nouvelle colonne, on la définit comme colonne de tri en ordre ascendant par défaut
currentSortColumn = column;
currentSortOrder = ‘asc’;
}
// Mettre à jour les sélecteurs de tri pour qu’ils reflètent le changement
sortColumnSelect.value = currentSortColumn;
sortOrderSelect.value = currentSortOrder;
updateDisplay(); // Rafraîchir l’affichage
}
});
});
// — 5. Initialisation —
// Définir les valeurs initiales des sélecteurs de tri pour correspondre à l’état par défaut
sortColumnSelect.value = currentSortColumn;
sortOrderSelect.value = currentSortOrder;
updateDisplay(); // Afficher le tableau initial au chargement de la page
});
{“@context”:”https://schema.org”,”@type”:”FAQPage”,”mainEntity”:[{“@type”:”Question”,”name”:”Quel est le climat typique u00e0 Djerba en mai ? “,”acceptedAnswer”:{“@type”:”Answer”,”text”:”En mai, Djerba bu00e9nu00e9ficie d’un climat idu00e9al, avec des tempu00e9ratures moyennes oscillant entre 20 et 25u00b0C. L’ensoleillement est gu00e9nu00e9reux (environ 10-11 heures par jour) et les pluies sont rares et de courte duru00e9e. Les soiru00e9es sont douces, nu00e9cessitant parfois une petite veste.”}},{“@type”:”Question”,”name”:”La mer est-elle assez chaude pour la baignade en mai u00e0 Djerba ? “,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Oui, la tempu00e9rature de la mer Mu00e9diterranu00e9e autour de Djerba commence u00e0 u00eatre tru00e8s agru00e9able en mai, avoisinant les 20u00b0C. C’est parfait pour la baignade, le snorkeling et les sports nautiques sans la frau00eecheur de l’hiver ni la chaleur excessive de l’u00e9tu00e9.”}},{“@type”:”Question”,”name”:”Peut-on faire des excursions culturelles u00e0 Djerba en mai ? “,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Absolument ! Le mois de mai est l’une des meilleures pu00e9riodes pour les explorations culturelles. Le climat doux permet de visiter confortablement les souks de Houmt Souk, Djerbahood u00e0 Erriadh, le village des potiers de Guellala et les sites historiques sans souffrir de la chaleur ou de la foule.”}},{“@type”:”Question”,”name”:”Faut-il louer une voiture pour du00e9couvrir Djerba en mai ? “,”acceptedAnswer”:{“@type”:”Answer”,”text”:”La location de voiture est fortement recommandu00e9e pour explorer Djerba en mai. Elle offre une libertu00e9 totale de mouvement pour du00e9couvrir les plages isolu00e9es, les villages intu00e9rieurs et les sites touristiques u00e0 votre rythme. Les routes sont moins encombru00e9es qu’en haute saison.”}}]}