Piloter la carte depuis des filtres externes et synchroniser une liste
Objectif
Pattern associé: synchronisation bidirectionnelle.
L’objectif est de piloter la carte depuis un formulaire métier externe, tout en gardant une liste alphanumérique synchronisée avec les mêmes données filtrées et les interactions réalisées sur la carte.
Objets API utilisés
Flux de données
- l’application conserve la source de vérité des filtres et des résultats
- elle filtre son dataset métier
- elle met à jour la liste métier externe
- elle remplace les features de la couche
GRAPHICSà partir du même sous-ensemble - un clic sur la carte met à jour l’état applicatif et la liste sélectionnée
Snippet minimal
<form id="filters"> <label> Mode <select name="travelMode"> <option value="">Tous</option> <option value="pedestrian">Piéton</option> <option value="bike">Vélo</option> </select> </label></form>
<ul id="result-list"></ul><geoviewer-esri-element id="viewer"></geoviewer-esri-element>
<script type="module"> const viewerElement = document.getElementById('viewer'); const filtersForm = document.getElementById('filters'); const resultList = document.getElementById('result-list');
const allItems = await fetch('/tutorials/json-point-demo/data/eco-vision-counters.json').then((response) => response.json(), );
const state = { filters: { travelMode: '', }, selectedId: null, };
function renderList(items) { resultList.innerHTML = items .map( (item) => `<li data-id="${item.id}" ${item.id === state.selectedId ? 'data-selected="true"' : ''}>${item.name}</li>`, ) .join(''); }
function applyFilters(items) { if (!state.filters.travelMode) { return items; }
return items.filter((item) => (item.travelModes ?? []).includes(state.filters.travelMode)); }
viewerElement.onViewerServiceReady((viewerService) => { viewerService.onReady(() => { const mapManager = viewerService.mapManager; const { events, featureFactory, zoom } = mapManager.tools;
const filterLayer = mapManager.addGraphicMapService({ id: 'external-filter-results', type: 'GRAPHICS', label: 'Résultats filtrés', visible: true, removable: true, identifiable: true, toc: { visible: true, open: false, }, objectIdProperty: 'id', });
function syncUiAndMap() { const filteredItems = applyFilters(allItems);
renderList(filteredItems); filterLayer.removeAll();
const features = filteredItems.map((item) => featureFactory.createPoint({ x: item.coords[1], y: item.coords[0], wkid: 4326, attributes: { id: item.id, name: item.name, raw: item, }, }), );
filterLayer.addFeatures(features); }
filtersForm.addEventListener('change', (event) => { const formData = new FormData(event.currentTarget); state.filters.travelMode = String(formData.get('travelMode') ?? ''); state.selectedId = null; syncUiAndMap(); });
events.listenGraphicMapServiceClick(filterLayer, async (features) => { const [feature] = features; if (!feature) { return; }
state.selectedId = String(feature.attributes.id); renderList(applyFilters(allItems)); await zoom.zoomToFeature(feature); });
syncUiAndMap(); }); });</script>Pièges fréquents
- Gardez la source de vérité côté application, pas dans la carte. Les filtres, les résultats et la sélection doivent pouvoir être recalculés sans dépendre du viewer.
- La liste métier et la couche
GRAPHICSdoivent être alimentées à partir du même tableau filtré, sinon elles dérivent rapidement. - Au clic carte, mettez à jour l’état applicatif puis re-rendez la liste. N’essayez pas de manipuler la liste “à la main” indépendamment du state.
- Si vous devez notifier d’autres briques applicatives, vous pouvez ensuite publier un événement métier via
topicManager.