Files

199 lines
8.2 KiB
Plaintext
Raw Permalink Normal View History

<map-view>
2026-04-02 14:15:26 +02:00
<!--
==========================================================================
COMPOSANT <map-view>
RÔLE : afficher une carte interactive Leaflet avec un marqueur par formation.
BIBLIOTHÈQUE : Leaflet.js (chargée dans index.html via CDN)
FOND DE CARTE : OpenStreetMap (open source, gratuit)
PROPS reçues depuis <app> :
- results : tableau de formations (celles avec latitude/longitude non null)
COMMUNICATION INTER-COMPOSANTS :
Ce composant expose window.mapFocus(id) pour permettre à <result-list>
de centrer la carte sur une formation via le bouton "Localiser".
(Les deux composants ne sont pas parent/enfant → on passe par window)
CYCLE DE VIE Riot utilisé :
- onMounted() → créer la carte et les marqueurs initiaux
- onUpdated() → rafraîchir les marqueurs quand les résultats changent
- onBeforeUnmount() → détruire la carte pour libérer la mémoire
==========================================================================
-->
<div class="map-box">
<h3>Carte des formations</h3>
2026-04-02 14:15:26 +02:00
<!-- Conteneur de la carte Leaflet (le ref="carte" permet d'y accéder via this.$()) -->
2026-03-21 13:47:09 +01:00
<div class="map" ref="carte"></div>
</div>
<script>
export default {
2026-03-18 13:44:30 +01:00
2026-04-02 14:15:26 +02:00
// ========================================================================
// onMounted() — Initialisation de la carte Leaflet
//
// Étapes :
// 1. Créer l'instance Leaflet attachée au div "ref=carte"
// 2. Créer un LayerGroup pour gérer les marqueurs en groupe
// 3. Ajouter le fond de carte OpenStreetMap
// 4. Afficher les marqueurs initiaux
// 5. Exposer window.mapFocus pour la communication avec result-list
// 6. Corriger le rendu de Leaflet (invalidateSize) après l'animation CSS
// ========================================================================
2026-03-21 13:47:09 +01:00
onMounted() {
2026-04-02 14:15:26 +02:00
var divCarte = this.$('div[ref="carte"]'); // sélectionne le div de la carte
2026-03-21 13:47:09 +01:00
2026-04-02 14:15:26 +02:00
// Création de la carte Leaflet centrée sur la France (lat 46.8, lon 2.5), zoom 6
2026-03-30 16:33:11 +02:00
this.carte = L.map(divCarte).setView([46.8, 2.5], 6);
2026-04-02 14:15:26 +02:00
// LayerGroup : conteneur de marqueurs qui permet de tous les supprimer d'un coup
2026-03-30 16:33:11 +02:00
this.groupeMarqueurs = L.layerGroup().addTo(this.carte);
2026-04-02 14:15:26 +02:00
// Index id → marqueur : permet à centrerSurFormation() de retrouver rapidement un marqueur
2026-03-30 16:33:11 +02:00
this.marqueursIndex = {};
2026-04-02 14:15:26 +02:00
// Ajout du fond de carte OpenStreetMap (tuiles PNG)
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; OpenStreetMap contributors'
2026-03-30 16:33:11 +02:00
}).addTo(this.carte);
2026-04-02 14:15:26 +02:00
this.afficherMarqueurs(); // premier affichage des marqueurs
2026-03-18 13:44:30 +01:00
2026-04-02 14:15:26 +02:00
var composant = this; // capture pour les callbacks setTimeout
2026-03-20 01:51:08 +01:00
2026-04-02 14:15:26 +02:00
// invalidateSize() corrige un bug fréquent : si la carte s'anime à l'apparition,
// Leaflet ne connaît pas encore sa taille exacte et n'affiche pas les tuiles correctement.
// On l'appelle deux fois avec des délais différents pour être sûr.
2026-03-20 01:51:08 +01:00
setTimeout(function() {
2026-04-02 14:15:26 +02:00
if (composant.carte) { composant.carte.invalidateSize(); }
2026-03-30 16:33:11 +02:00
}, 200);
2026-03-20 01:51:08 +01:00
setTimeout(function() {
2026-04-02 14:15:26 +02:00
if (composant.carte) { composant.carte.invalidateSize(); }
2026-03-30 16:33:11 +02:00
}, 500);
2026-03-20 01:51:08 +01:00
2026-04-02 14:15:26 +02:00
// Exposition de mapFocus sur window pour permettre à result-list
// de centrer la carte sur une formation en cliquant "Localiser"
2026-03-20 01:51:08 +01:00
window.mapFocus = function(id) {
2026-03-30 16:33:11 +02:00
composant.centrerSurFormation(id);
};
},
2026-04-02 14:15:26 +02:00
// ========================================================================
// onUpdated() — Rafraîchissement de la carte quand les props changent
//
// Appelé par Riot chaque fois que le parent met à jour les résultats.
// On recharge tous les marqueurs et on corrige la taille de la carte.
// ========================================================================
onUpdated() {
2026-03-30 16:33:11 +02:00
this.afficherMarqueurs();
2026-03-18 13:44:30 +01:00
2026-03-30 16:33:11 +02:00
var composant = this;
2026-03-20 01:51:08 +01:00
2026-03-21 13:47:09 +01:00
if (this.carte) {
2026-04-02 14:15:26 +02:00
setTimeout(function() { composant.carte.invalidateSize(); }, 100);
setTimeout(function() { composant.carte.invalidateSize(); }, 300);
2026-03-18 13:44:30 +01:00
}
},
2026-04-02 14:15:26 +02:00
// ========================================================================
// onBeforeUnmount() — Nettoyage avant la suppression du composant
//
// On détruit l'instance Leaflet pour libérer les event listeners
// et éviter les memory leaks. On nettoie aussi window.mapFocus.
// ========================================================================
2026-03-18 13:44:30 +01:00
onBeforeUnmount() {
2026-03-21 13:47:09 +01:00
if (this.carte) {
2026-04-02 14:15:26 +02:00
this.carte.remove(); // détruit la carte et libère la mémoire
2026-03-30 16:33:11 +02:00
this.carte = null;
2026-03-18 13:44:30 +01:00
}
2026-04-02 14:15:26 +02:00
window.mapFocus = null; // nettoie la référence globale
},
2026-04-02 14:15:26 +02:00
// ========================================================================
// afficherMarqueurs()
// RÔLE : supprimer les anciens marqueurs et en créer de nouveaux pour
// chaque formation ayant des coordonnées GPS.
//
// Si des formations ont des coordonnées, on ajuste automatiquement le
// zoom de la carte pour les afficher toutes (fitBounds).
// Sinon, on revient à la vue par défaut (France entière).
// ========================================================================
2026-03-21 13:47:09 +01:00
afficherMarqueurs() {
if (!this.carte || !this.groupeMarqueurs) {
2026-03-30 16:33:11 +02:00
return;
}
2026-04-02 14:15:26 +02:00
this.groupeMarqueurs.clearLayers(); // supprime tous les marqueurs existants
this.marqueursIndex = {}; // réinitialise l'index
2026-04-02 14:15:26 +02:00
var coordonnees = []; // liste des coordonnées pour fitBounds
2026-03-30 16:33:11 +02:00
var formations = this.props.results || [];
2026-03-21 13:47:09 +01:00
for (var i = 0; i < formations.length; i++) {
2026-03-30 16:33:11 +02:00
var f = formations[i];
2026-04-02 14:15:26 +02:00
// On n'ajoute un marqueur que si la formation a des coordonnées GPS
2026-03-18 13:44:30 +01:00
if (f.latitude != null && f.longitude != null) {
2026-03-30 16:33:11 +02:00
var marqueur = L.marker([f.latitude, f.longitude]);
2026-04-02 14:15:26 +02:00
// Popup : fenêtre qui s'affiche au clic sur le marqueur
2026-03-30 16:33:11 +02:00
marqueur.bindPopup('<b>' + f.nom + '</b><br>' + f.ville);
marqueur.addTo(this.groupeMarqueurs);
2026-04-02 14:15:26 +02:00
// On indexe le marqueur par l'ID de la formation pour centrerSurFormation()
2026-03-30 16:33:11 +02:00
this.marqueursIndex[f.id] = marqueur;
coordonnees.push([f.latitude, f.longitude]);
}
}
2026-04-02 14:15:26 +02:00
// Ajuste le zoom pour montrer tous les marqueurs (avec 20px de marge)
2026-03-21 13:47:09 +01:00
if (coordonnees.length > 0) {
2026-03-30 16:33:11 +02:00
this.carte.fitBounds(coordonnees, { padding: [20, 20] });
} else {
2026-04-02 14:15:26 +02:00
// Aucun marqueur → on recentre sur la France
2026-03-30 16:33:11 +02:00
this.carte.setView([46.8, 2.5], 6);
}
2026-03-20 01:51:08 +01:00
},
2026-04-02 14:15:26 +02:00
// ========================================================================
// centrerSurFormation(id)
// RÔLE : centrer et zoomer la carte sur une formation spécifique.
// Appelé par window.mapFocus (depuis result-list via bouton "Localiser").
//
// Étapes :
// 1. Récupérer le marqueur depuis l'index
// 2. Scroller jusqu'à la carte (scrollIntoView)
// 3. Après 400ms (fin du scroll) : corriger la taille, zoomer, ouvrir le popup
// ========================================================================
2026-03-21 13:47:09 +01:00
centrerSurFormation(id) {
2026-03-30 16:33:11 +02:00
var marqueur = this.marqueursIndex[id];
2026-03-20 01:51:08 +01:00
2026-03-21 13:47:09 +01:00
if (marqueur && this.carte) {
2026-03-30 16:33:11 +02:00
var divCarte = this.$('div[ref="carte"]');
2026-03-21 13:47:09 +01:00
2026-04-02 14:15:26 +02:00
// Scroll vers la carte pour que l'utilisateur la voie avant le zoom
2026-03-21 13:47:09 +01:00
if (divCarte) {
2026-03-30 16:33:11 +02:00
divCarte.scrollIntoView({ behavior: 'smooth', block: 'center' });
2026-03-20 01:51:08 +01:00
}
2026-03-30 16:33:11 +02:00
var composant = this;
2026-03-20 01:51:08 +01:00
2026-04-02 14:15:26 +02:00
// On attend la fin du scroll (~400ms) avant de zoomer sur le marqueur
2026-03-20 01:51:08 +01:00
setTimeout(function() {
2026-03-30 16:33:11 +02:00
composant.carte.invalidateSize();
2026-04-02 14:15:26 +02:00
// Zoom niveau 13 (ville) avec animation, puis ouverture du popup
2026-03-30 16:33:11 +02:00
composant.carte.setView(marqueur.getLatLng(), 13, { animate: true });
marqueur.openPopup();
}, 400);
2026-03-20 01:51:08 +01:00
}
}
2026-03-21 13:47:09 +01:00
2026-03-30 16:33:11 +02:00
};
</script>
2026-03-21 13:47:09 +01:00
2026-03-20 01:51:08 +01:00
</map-view>