diff --git a/umap/static/umap/js/umap.controls.js b/umap/static/umap/js/umap.controls.js index 91be2abe..e305f780 100644 --- a/umap/static/umap/js/umap.controls.js +++ b/umap/static/umap/js/umap.controls.js @@ -1049,7 +1049,8 @@ L.U.IframeExporter = L.Evented.extend({ options: { includeFullScreenLink: true, currentView: false, - keepCurrentDatalayers: false + keepCurrentDatalayers: false, + viewCurrentFeature: false }, queryString: { @@ -1074,7 +1075,7 @@ L.U.IframeExporter = L.Evented.extend({ initialize: function (map) { this.map = map; - this.baseUrl = '//' + window.location.host + window.location.pathname; + this.baseUrl = L.Util.getBaseUrl(); // Use map default, not generic default this.queryString.onLoadPanel = this.map.options.onLoadPanel; }, @@ -1085,6 +1086,9 @@ L.U.IframeExporter = L.Evented.extend({ build: function () { var datalayers = []; + if (this.options.viewCurrentFeature && this.map.currentFeature) { + this.queryString.feature = this.map.currentFeature.getSlug(); + } if (this.options.keepCurrentDatalayers) { this.map.eachDataLayer(function (datalayer) { if (datalayer.isVisible() && datalayer.umap_id) { @@ -1096,7 +1100,7 @@ L.U.IframeExporter = L.Evented.extend({ delete this.queryString.datalayers; } var currentView = this.options.currentView ? window.location.hash : '', - iframeUrl = this.baseUrl + '?' + this.map.xhr.buildQueryString(this.queryString) + currentView, + iframeUrl = this.baseUrl + '?' + L.Util.buildQueryString(this.queryString) + currentView, code = ''; if (this.options.includeFullScreenLink) { code += '
'; diff --git a/umap/static/umap/js/umap.core.js b/umap/static/umap/js/umap.core.js index 9e8d6bf1..1f3f8cbf 100644 --- a/umap/static/umap/js/umap.core.js +++ b/umap/static/umap/js/umap.core.js @@ -175,6 +175,19 @@ L.Util.flattenCoordinates = function (coords) { return coords; }; + +L.Util.buildQueryString = function (params) { + var query_string = []; + for (var key in params) { + query_string.push(encodeURIComponent(key) + '=' + encodeURIComponent(params[key])); + } + return query_string.join('&'); +}; + +L.Util.getBaseUrl = function () { + return '//' + window.location.host + window.location.pathname; +}; + L.DomUtil.add = function (tagName, className, container, content) { var el = L.DomUtil.create(tagName, className, container); if (content) { @@ -428,6 +441,7 @@ L.U.Help = L.Class.extend({ shortCredit: L._('Will be displayed in the bottom right corner of the map'), longCredit: L._('Will be visible in the caption of the map'), sortKey: L._('Property to use for sorting features'), + slugKey: L._('The name of the property to use as feature unique identifier.'), filterKey: L._('Comma separated list of properties to use when filtering features'), interactive: L._('If false, the polygon will act as a part of the underlying map.'), outlink: L._('Define link to open in a new window on polygon click.'), diff --git a/umap/static/umap/js/umap.features.js b/umap/static/umap/js/umap.features.js index e7fb7e73..26aca0a6 100644 --- a/umap/static/umap/js/umap.features.js +++ b/umap/static/umap/js/umap.features.js @@ -45,6 +45,15 @@ L.U.FeatureMixin = { return this.datalayer && this.datalayer.isRemoteLayer(); }, + getSlug: function () { + return this.properties[this.map.options.slugKey || 'name'] || ''; + }, + + getPermalink: function () { + const slug = this.getSlug(); + if (slug) return L.Util.getBaseUrl() + "?" + L.Util.buildQueryString({feature: slug}) + window.location.hash; + }, + view: function(e) { if (this.map.editEnabled) return; var outlink = this.properties._umap_options.outlink, @@ -64,6 +73,7 @@ L.U.FeatureMixin = { } // TODO deal with an event instead? if (this.map.slideshow) this.map.slideshow.current = this; + this.map.currentFeature = this; this.attachPopup(); this.openPopup(e && e.latlng || this.getCenter()); }, @@ -351,7 +361,9 @@ L.U.FeatureMixin = { }, getContextMenuItems: function (e) { - var items = []; + var permalink = this.getPermalink(), + items = []; + if (permalink) items.push({text: L._('Permalink'), callback: function() {window.open(permalink);}}); if (this.map.editEnabled && !this.isReadOnly()) { items = items.concat(this.getContextMenuEditItems(e)); } diff --git a/umap/static/umap/js/umap.js b/umap/static/umap/js/umap.js index ed746886..96ce2ebc 100644 --- a/umap/static/umap/js/umap.js +++ b/umap/static/umap/js/umap.js @@ -122,10 +122,11 @@ L.U.Map.include({ this.initTileLayers(this.options.tilelayers); - // Global storage for retrieving datalayers + // Global storage for retrieving datalayers and features this.datalayers = {}; this.datalayers_index = []; this.dirty_datalayers = []; + this.features_index = {}; // Retrocompat if (this.options.slideshow && this.options.slideshow.delay && this.options.slideshow.active === undefined) this.options.slideshow.active = true; @@ -208,6 +209,10 @@ L.U.Map.include({ if (this.options.onLoadPanel === 'databrowser') this.openBrowser(); else if (this.options.onLoadPanel === 'caption') this.displayCaption(); }); + this.onceDataLoaded(function () { + const slug = L.Util.queryString('feature'); + if (slug && this.features_index[slug]) this.features_index[slug].view(); + }); window.onbeforeunload = function (e) { @@ -292,9 +297,9 @@ L.U.Map.include({ }, initDatalayers: function () { - var toload = this.options.datalayers.length, - datalayer, seen = this.options.datalayers.length, - self = this; + var toload = dataToload = seen = this.options.datalayers.length, + self = this, + datalayer; var loaded = function () { self.datalayersLoaded = true; self.fire('datalayersloaded'); @@ -303,12 +308,22 @@ L.U.Map.include({ toload--; if (toload === 0) loaded(); }; + var dataLoaded = function () { + self.dataLoaded = true; + self.fire('dataloaded'); + }; + var decrementDataToLoad = function () { + dataToload--; + if (dataToload === 0) dataLoaded(); + }; for (var j = 0; j < this.options.datalayers.length; j++) { datalayer = this.createDataLayer(this.options.datalayers[j]); if (datalayer.displayedOnLoad()) datalayer.onceLoaded(decrementToLoad); else decrementToLoad(); + if (datalayer.displayedOnLoad()) datalayer.onceDataLoaded(decrementDataToLoad); + else decrementDataToLoad(); } - if (seen === 0) loaded(); // no datalayer + if (seen === 0) loaded() && dataLoaded(); // no datalayer }, indexDatalayers: function () { @@ -330,6 +345,7 @@ L.U.Map.include({ }, onceDatalayersLoaded: function (callback, context) { + // Once datalayers **metadata** have been loaded if (this.datalayersLoaded) { callback.call(context || this, this); } else { @@ -338,6 +354,16 @@ L.U.Map.include({ return this; }, + onceDataLoaded: function (callback, context) { + // Once datalayers **data** have been loaded + if (this.dataLoaded) { + callback.call(context || this, this); + } else { + this.once('dataloaded', callback, context); + } + return this; + }, + updateDatalayersControl: function () { if (this._controls.datalayers) this._controls.datalayers.update(); }, @@ -579,6 +605,7 @@ L.U.Map.include({ ['options.includeFullScreenLink', {handler: 'Switch', label: L._('Include full screen link?')}], ['options.currentView', {handler: 'Switch', label: L._('Current view instead of default map view?')}], ['options.keepCurrentDatalayers', {handler: 'Switch', label: L._('Keep current visible layers')}], + ['options.viewCurrentFeature', {handler: 'Switch', label: L._('Open current item')}], 'queryString.moreControl', 'queryString.scrollWheelZoom', 'queryString.miniMap', @@ -1016,6 +1043,7 @@ L.U.Map.include({ 'sortKey', 'labelKey', 'filterKey', + 'slugKey', 'showLabel', 'labelDirection', 'labelInteractive', @@ -1209,7 +1237,8 @@ L.U.Map.include({ ['options.easing', {handler: 'Switch', label: L._('Advanced transition')}], 'options.labelKey', ['options.sortKey', {handler: 'BlurInput', helpEntries: 'sortKey', placeholder: L._('Default: name'), label: L._('Sort key'), inheritable: true}], - ['options.filterKey', {handler: 'Input', helpEntries: 'filterKey', placeholder: L._('Default: name'), label: L._('Filter keys'), inheritable: true}] + ['options.filterKey', {handler: 'Input', helpEntries: 'filterKey', placeholder: L._('Default: name'), label: L._('Filter keys'), inheritable: true}], + ['options.slugKey', {handler: 'BlurInput', helpEntries: 'slugKey', placeholder: L._('Default: name'), label: L._('Feature identifier key')}] ]; builder = new L.U.FormBuilder(this, optionsFields, { diff --git a/umap/static/umap/js/umap.layer.js b/umap/static/umap/js/umap.layer.js index 3661d816..dd9be047 100644 --- a/umap/static/umap/js/umap.layer.js +++ b/umap/static/umap/js/umap.layer.js @@ -421,6 +421,7 @@ L.U.DataLayer = L.Evented.extend({ this.layer.addLayer(feature); this.indexProperties(feature); if (this.hasDataLoaded()) this.fire('datachanged'); + this.map.features_index[feature.getSlug()] = feature; }, removeLayer: function (feature) { @@ -429,6 +430,7 @@ L.U.DataLayer = L.Evented.extend({ this._index.splice(this._index.indexOf(id), 1); delete this._layers[id]; this.layer.removeLayer(feature); + delete this.map.features_index[feature.getSlug()]; if (this.hasDataLoaded()) this.fire('datachanged'); }, diff --git a/umap/static/umap/js/umap.slideshow.js b/umap/static/umap/js/umap.slideshow.js index b24f6055..35d158dd 100644 --- a/umap/static/umap/js/umap.slideshow.js +++ b/umap/static/umap/js/umap.slideshow.js @@ -46,7 +46,7 @@ L.U.Slideshow = L.Class.extend({ // Certainly IE8, which has a limited version of defineProperty } if (this.options.autoplay) { - this.map.onceDatalayersLoaded(function () { + this.map.onceDataLoaded(function () { this.play(); }, this); } diff --git a/umap/static/umap/js/umap.xhr.js b/umap/static/umap/js/umap.xhr.js index cd35ded0..126f61a9 100644 --- a/umap/static/umap/js/umap.xhr.js +++ b/umap/static/umap/js/umap.xhr.js @@ -280,14 +280,6 @@ L.U.Xhr = L.Evented.extend({ logout: function(url) { this.get(url); - }, - - buildQueryString: function (params) { - var query_string = []; - for (var key in params) { - query_string.push(encodeURIComponent(key) + '=' + encodeURIComponent(params[key])); - } - return query_string.join('&'); } });