From ac081591f971864671408fc08de95a64f01fffeb Mon Sep 17 00:00:00 2001 From: xaralis <filip.varecha@fragaria.cz> Date: Mon, 23 May 2022 10:27:34 +0100 Subject: [PATCH] feat(district,maps_utils): improve map back&foward navigation --- .../district_geo_feature_collection_page.html | 2 +- .../maps_utils/geo-feature-collection.js | 85 +++++++++---------- 2 files changed, 42 insertions(+), 45 deletions(-) diff --git a/district/templates/district/district_geo_feature_collection_page.html b/district/templates/district/district_geo_feature_collection_page.html index ba9f8dd0..fc4db176 100644 --- a/district/templates/district/district_geo_feature_collection_page.html +++ b/district/templates/district/district_geo_feature_collection_page.html @@ -68,7 +68,7 @@ </div> <section> - <h2 class="head-heavy-base mt-8 mb-4">{{ page.category_list_title|default:"Přehled dle kategorií" }}</h2> + <h2 id="kategorie" class="head-heavy-base mt-8 mb-4">{{ page.category_list_title|default:"Přehled dle kategorií" }}</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-8"> {% for category, features in features_by_category %} diff --git a/maps_utils/static/maps_utils/geo-feature-collection.js b/maps_utils/static/maps_utils/geo-feature-collection.js index f1471f3b..800ff933 100644 --- a/maps_utils/static/maps_utils/geo-feature-collection.js +++ b/maps_utils/static/maps_utils/geo-feature-collection.js @@ -327,7 +327,7 @@ const GeoFeatureCollection = Vue.component("GeoFeatureCollection", { */ const onEachFeature = (feature, layer) => { const markerPosLatLng = []; - const onClick = (evt) => this.zoomToLayer(layer); + const onClick = (evt) => this.zoomToLayer(layer, true); const markerForPolyCoords = (coords) => { // Find pole of inaccessibility (not centroid) for the polygon @@ -382,7 +382,7 @@ const GeoFeatureCollection = Vue.component("GeoFeatureCollection", { addMarker(feature, pos, onClick); // Bind click event on the layer. layer.on({ - click: (evt) => this.zoomToLayer(evt.target), + click: (evt) => this.zoomToLayer(evt.target, true), }); }); } @@ -409,48 +409,56 @@ const GeoFeatureCollection = Vue.component("GeoFeatureCollection", { this.map.panTo(this.layer.getBounds().getCenter()); } - // If hash is present when starting, locate the item and zoom to it. - if (window.location.hash) { - this.zoomToItemBySlugUrl(window.location.hash.substring(1)); - } + // Initial check - open item detail if in URL. + this.onUserNavigation(); + + // Listen to URL changes when user triggers browser navigation. + window.addEventListener("popstate", this.onUserNavigation); - // Listen to hashbang changes when user users browser history navigation. - window.addEventListener("hashchange", this.onHashChange, false); }, /** - * Stores item's url in the hashbang. + * Stores item's slug in URL `item` query param. * * @param {Object} item */ - setUrlHash(item) { + pushToUrl(item) { + const url = new URL(window.location); + if (item === null) { - history.replaceState({}, null, "#"); + url.searchParams.delete("item"); + history.pushState({}, "", url); } else { - history.pushState({}, item.title, "#" + item.slug); + url.searchParams.set("item", item.slug); + history.pushState({}, "", url); } }, /** - * Called when URL hash changes. Will display detail + * Called when user navigates. Will display detail * of corresponding item if such exist. * * @param {Event} evt */ - onHashChange(evt) { - const urlBits = evt.newURL.split("#"); - - if (urlBits.length == 2 && urlBits[1]) { - this.zoomToItemBySlugUrl(urlBits[1]); - } else { - this.closeItemInfo(); + onUserNavigation(evt) { + const urlParams = new URLSearchParams(window.location.search); + + // If query is present when starting, locate the item and zoom to it. + if (urlParams.has("item")) { + this.zoomToFeature(urlParams.get("item"), false); + } else if (this.currentItem) { + this.closeItemInfo(false); } }, /** * Hide current item detail, drop it from URL. + * @param {Boolean} updateUrl whether to push new state to history. */ - closeItemInfo() { + closeItemInfo(updateUrl = true) { if (this.currentItem) { this.currentItem = null; - this.setUrlHash(null); + + if (updateUrl) { + this.pushToUrl(null); + } } }, /** @@ -464,55 +472,44 @@ const GeoFeatureCollection = Vue.component("GeoFeatureCollection", { * Zoom to a point. * * @param {L.Latlng} Latlng - * @param {Boolean} pushState whether to push new state to history. + * @param {Object} feature + * @param {Boolean} updateUrl whether to push new state to history. */ zoomToPoint(latlng, feature, updateUrl = true) { this.map.panTo(latlng); this.currentItem = feature.properties; if (updateUrl) { - this.setUrlHash(this.currentItem); + this.pushToUrl(this.currentItem); } }, /** * Zoom to a detail of a layer. * * @param {L.Layer} layer - * @param {Boolean} pushState whether to push new state to history. + * @param {Boolean} updateUrl whether to push new state to history. */ zoomToLayer(layer, updateUrl = true) { this.map.fitBounds(layer.getBounds()); this.currentItem = layer.feature.properties; if (updateUrl) { - this.setUrlHash(this.currentItem); - } - }, - /** - * Zoom to a cateogry item. Wraps `zoomTo`. - * - * @param {Object} category - * @param {Object} item - */ - zoomToCategoryItem(category, item) { - const layer = Object.values(this.layer._layers).find( - (l) => l.feature.properties.id == item.properties.id - ); - if (layer) { - this.zoomToLayer(layer); + this.pushToUrl(this.currentItem); } }, /** - * Zoom to a item with corresponding slugified URL. + * Zoom to a feature with corresponding slugified URL. + * Only extracted to make things DRY, will not update the URL! * * @param {String} slugUrl + * @param {Boolean} updateUrl whether to push new state to history. */ - zoomToItemBySlugUrl(slugUrl) { + zoomToFeature(slugUrl, updateUrl = true) { const layer = Object.values(this.layer._layers).find( (l) => l.feature.properties.slug == slugUrl ); if (layer) { - this.zoomToLayer(layer, false); + this.zoomToLayer(layer, updateUrl); } }, /** @@ -546,7 +543,7 @@ const GeoFeatureCollection = Vue.component("GeoFeatureCollection", { </div> <ul v-show="category.expanded" :class="{'mb-2': index != categoryCount - 1}"> <li v-for="item in category.items" :key="item.properties.title"> - <button @click="zoomToCategoryItem(category, item)" class="text-left leading-tight text-xs" style="max-width: 17em;"> + <button @click="zoomToFeature(item.properties.slug)" class="text-left leading-tight text-xs" style="max-width: 17em;"> <span v-if="item.properties.index" class="rounded-full inline-flex items-center justify-center bg-grey-125 font-bold text-center text-2xs w-4 h-4 mr-1">{{ item.properties.index }}</span> <span>{{ item.properties.title }}</span> </button> -- GitLab