199 lines
8.2 KiB
Plaintext
199 lines
8.2 KiB
Plaintext
<map-view>
|
|
<!--
|
|
==========================================================================
|
|
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>
|
|
<!-- Conteneur de la carte Leaflet (le ref="carte" permet d'y accéder via this.$()) -->
|
|
<div class="map" ref="carte"></div>
|
|
</div>
|
|
|
|
<script>
|
|
export default {
|
|
|
|
// ========================================================================
|
|
// 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
|
|
// ========================================================================
|
|
onMounted() {
|
|
var divCarte = this.$('div[ref="carte"]'); // sélectionne le div de la carte
|
|
|
|
// Création de la carte Leaflet centrée sur la France (lat 46.8, lon 2.5), zoom 6
|
|
this.carte = L.map(divCarte).setView([46.8, 2.5], 6);
|
|
|
|
// LayerGroup : conteneur de marqueurs qui permet de tous les supprimer d'un coup
|
|
this.groupeMarqueurs = L.layerGroup().addTo(this.carte);
|
|
|
|
// Index id → marqueur : permet à centrerSurFormation() de retrouver rapidement un marqueur
|
|
this.marqueursIndex = {};
|
|
|
|
// Ajout du fond de carte OpenStreetMap (tuiles PNG)
|
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
attribution: '© OpenStreetMap contributors'
|
|
}).addTo(this.carte);
|
|
|
|
this.afficherMarqueurs(); // premier affichage des marqueurs
|
|
|
|
var composant = this; // capture pour les callbacks setTimeout
|
|
|
|
// 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.
|
|
setTimeout(function() {
|
|
if (composant.carte) { composant.carte.invalidateSize(); }
|
|
}, 200);
|
|
|
|
setTimeout(function() {
|
|
if (composant.carte) { composant.carte.invalidateSize(); }
|
|
}, 500);
|
|
|
|
// Exposition de mapFocus sur window pour permettre à result-list
|
|
// de centrer la carte sur une formation en cliquant "Localiser"
|
|
window.mapFocus = function(id) {
|
|
composant.centrerSurFormation(id);
|
|
};
|
|
},
|
|
|
|
// ========================================================================
|
|
// 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() {
|
|
this.afficherMarqueurs();
|
|
|
|
var composant = this;
|
|
|
|
if (this.carte) {
|
|
setTimeout(function() { composant.carte.invalidateSize(); }, 100);
|
|
setTimeout(function() { composant.carte.invalidateSize(); }, 300);
|
|
}
|
|
},
|
|
|
|
// ========================================================================
|
|
// 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.
|
|
// ========================================================================
|
|
onBeforeUnmount() {
|
|
if (this.carte) {
|
|
this.carte.remove(); // détruit la carte et libère la mémoire
|
|
this.carte = null;
|
|
}
|
|
window.mapFocus = null; // nettoie la référence globale
|
|
},
|
|
|
|
// ========================================================================
|
|
// 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).
|
|
// ========================================================================
|
|
afficherMarqueurs() {
|
|
if (!this.carte || !this.groupeMarqueurs) {
|
|
return;
|
|
}
|
|
|
|
this.groupeMarqueurs.clearLayers(); // supprime tous les marqueurs existants
|
|
this.marqueursIndex = {}; // réinitialise l'index
|
|
|
|
var coordonnees = []; // liste des coordonnées pour fitBounds
|
|
var formations = this.props.results || [];
|
|
|
|
for (var i = 0; i < formations.length; i++) {
|
|
var f = formations[i];
|
|
|
|
// On n'ajoute un marqueur que si la formation a des coordonnées GPS
|
|
if (f.latitude != null && f.longitude != null) {
|
|
var marqueur = L.marker([f.latitude, f.longitude]);
|
|
|
|
// Popup : fenêtre qui s'affiche au clic sur le marqueur
|
|
marqueur.bindPopup('<b>' + f.nom + '</b><br>' + f.ville);
|
|
marqueur.addTo(this.groupeMarqueurs);
|
|
|
|
// On indexe le marqueur par l'ID de la formation pour centrerSurFormation()
|
|
this.marqueursIndex[f.id] = marqueur;
|
|
coordonnees.push([f.latitude, f.longitude]);
|
|
}
|
|
}
|
|
|
|
// Ajuste le zoom pour montrer tous les marqueurs (avec 20px de marge)
|
|
if (coordonnees.length > 0) {
|
|
this.carte.fitBounds(coordonnees, { padding: [20, 20] });
|
|
} else {
|
|
// Aucun marqueur → on recentre sur la France
|
|
this.carte.setView([46.8, 2.5], 6);
|
|
}
|
|
},
|
|
|
|
// ========================================================================
|
|
// 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
|
|
// ========================================================================
|
|
centrerSurFormation(id) {
|
|
var marqueur = this.marqueursIndex[id];
|
|
|
|
if (marqueur && this.carte) {
|
|
var divCarte = this.$('div[ref="carte"]');
|
|
|
|
// Scroll vers la carte pour que l'utilisateur la voie avant le zoom
|
|
if (divCarte) {
|
|
divCarte.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
}
|
|
|
|
var composant = this;
|
|
|
|
// On attend la fin du scroll (~400ms) avant de zoomer sur le marqueur
|
|
setTimeout(function() {
|
|
composant.carte.invalidateSize();
|
|
// Zoom niveau 13 (ville) avec animation, puis ouverture du popup
|
|
composant.carte.setView(marqueur.getLatLng(), 13, { animate: true });
|
|
marqueur.openPopup();
|
|
}, 400);
|
|
}
|
|
}
|
|
|
|
};
|
|
</script>
|
|
|
|
</map-view>
|