Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "Zoom to here" link in popup #831

Merged
merged 10 commits into from
Apr 28, 2023
1 change: 1 addition & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ module.exports = function(grunt) {
'dist/DOMTokenList.js': ['src/mapml/utils/DOMTokenList.js'],
'dist/map-caption.js': ['src/map-caption.js'],
'dist/map-feature.js': ['src/map-feature.js'],
'dist/map-extent.js': ['src/map-extent.js'],
'dist/map-area.js': ['src/map-area.js'],
'dist/layer.js': ['src/layer.js'],
'dist/leaflet.js': ['dist/leaflet-src.js',
Expand Down
4 changes: 2 additions & 2 deletions src/layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,6 @@ export class MapLayer extends HTMLElement {
}

connectedCallback() {
//creates listener that waits for createmap event, this allows for delayed builds of maps
//this allows a safeguard for the case where loading a custom TCRS takes longer than loading mapml-viewer.js/web-map.js
if(this.hasAttribute("data-moving")) return;
this._onAdd();
}
Expand All @@ -97,6 +95,8 @@ export class MapLayer extends HTMLElement {
if(this.getAttribute('src') && !this.shadowRoot) {
this.attachShadow({mode: 'open'});
}
//creates listener that waits for createmap event, this allows for delayed builds of maps
//this allows a safeguard for the case where loading a custom TCRS takes longer than loading mapml-viewer.js/web-map.js
this.parentNode.addEventListener('createmap', ()=>{
this._ready();
// if the map has been attached, set this layer up wrt Leaflet map
Expand Down
22 changes: 16 additions & 6 deletions src/map-extent.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import { MapInput } from './map-input.js';
import { MapLink } from './map-link.js';

export class MapExtent extends HTMLElement {
static get observedAttributes() {
return ['units','checked','label','opacity'];
Expand Down Expand Up @@ -71,10 +68,23 @@ export class MapExtent extends HTMLElement {
super();
}
connectedCallback() {

if(this.querySelector('map-link[rel=query], map-link[rel=features]') && !this.shadowRoot) {
this.attachShadow({mode: 'open'});
}
let parentLayer = this.parentNode.nodeName.toUpperCase() === "LAYER-" ? this.parentNode : this.parentNode.host;
if (!parentLayer._layer) {
// for custom projection cases, the MapMLLayer has not yet created and binded with the layer- at this point,
// because the "createMap" event of mapml-viewer has not yet been dispatched, the map has not yet been created
// the event will be dispatched after defineCustomProjection > projection setter
// should wait until MapMLLayer is built
parentLayer.parentNode.addEventListener('createmap', (e) => {
this._layer = parentLayer._layer;
});
} else {
this._layer = parentLayer._layer;
}
}
disconnectedCallback() {

}
}
window.customElements.define('map-extent', MapExtent);
}
213 changes: 134 additions & 79 deletions src/map-feature.js

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/mapml-viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import DOMTokenList from './DOMTokenList.js';
import { MapLayer } from './layer.js';
import { MapCaption } from './map-caption.js';
import { MapFeature } from './map-feature.js';
import { MapExtent } from './map-extent.js';

export class MapViewer extends HTMLElement {
static get observedAttributes() {
Expand Down Expand Up @@ -1037,3 +1038,4 @@ window.customElements.define('mapml-viewer', MapViewer);
window.customElements.define('layer-', MapLayer);
window.customElements.define('map-caption',MapCaption);
window.customElements.define('map-feature', MapFeature);
window.customElements.define('map-extent', MapExtent);
10 changes: 8 additions & 2 deletions src/mapml/handlers/QueryHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,18 @@ export var QueryHandler = L.Handler.extend({
if(features.length) layer._mapmlFeatures = layer._mapmlFeatures.concat(features);
} else {
// synthesize a single feature from text or html content
let geom = "<map-geometry cs='gcrs'>"+e.latlng.lng+" "+e.latlng.lat+"</map-geometry>",
let geom = "<map-geometry cs='gcrs'><map-point><map-coordinates>"+e.latlng.lng+" "+e.latlng.lat+"</map-coordinates></map-point></map-geometry>",
feature = parser.parseFromString("<map-feature><map-properties>"+
response.text+"</map-properties>"+geom+"</map-feature>", "text/html").querySelector("map-feature");
layer._mapmlFeatures.push(feature);
}
if(lastOne) return displayFeaturesPopup(layer._mapmlFeatures, e.latlng);
if(lastOne) {
// create connection between queried <map-feature> and its parent <map-extent>
for (let feature of layer._mapmlFeatures) {
feature._extentEl = template._extentEl;
}
displayFeaturesPopup(layer._mapmlFeatures, e.latlng);
}
}).catch((err) => {
console.log('Looks like there was a problem. Status: ' + err.message);
});
Expand Down
22 changes: 18 additions & 4 deletions src/mapml/layers/FeatureLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ export var FeatureLayer = L.FeatureGroup.extend({
* M.MapML turns any MapML feature data into a Leaflet layer. Based on L.GeoJSON.
*/
initialize: function (mapml, options) {

/*
mapml:
1. for query: an array of map-feature elements that it fetches
2. for static templated feature: null
3. for non-templated feature: layer- (with no src) or mapml file (with src)
*/
L.setOptions(this, options);
if(this.options.static) {
this._container = L.DomUtil.create('div', 'leaflet-layer', this.options.pane);
Expand Down Expand Up @@ -68,15 +73,23 @@ export var FeatureLayer = L.FeatureGroup.extend({
};
},


// for query
showPaginationFeature: function(e){
if(this.options.query && this._mapmlFeatures[e.i]){
let feature = this._mapmlFeatures[e.i];
// remove the prev / next one <map-feature> from shadow if there is any
feature._extentEl.shadowRoot.firstChild?.remove();
this.clearLayers();
this.addData(feature, this.options.nativeCS, this.options.nativeZoom);
feature._featureGroup = this.addData(feature, this.options.nativeCS, this.options.nativeZoom);
feature._extentEl.shadowRoot.appendChild(feature);
e.popup._navigationBar.querySelector("p").innerText = (e.i + 1) + "/" + this.options._leafletLayer._totalFeatureCount;
e.popup._content.querySelector("iframe").setAttribute("sandbox", "allow-same-origin allow-forms");
e.popup._content.querySelector("iframe").srcdoc = feature.querySelector("map-properties").innerHTML;
// "zoom to here" link need to be re-set for every pagination
this._map.fire("attachZoomLink", {i:e.i, currFeature: feature});
this._map.once("popupclose", function (e) {
this.shadowRoot.innerHTML = '';
}, feature._extentEl);
}
},

Expand Down Expand Up @@ -217,9 +230,10 @@ export var FeatureLayer = L.FeatureGroup.extend({
feature = features[i];
var geometriesExist = feature.getElementsByTagName("map-geometry").length && feature.getElementsByTagName("map-coordinates").length;
if (geometriesExist) {
if (mapml.nodeType === Node.DOCUMENT_NODE && feature._DOMnode) {
if (mapml.nodeType === Node.DOCUMENT_NODE) {
// if the <map-feature> element has migrated from mapml file,
// the featureGroup object should bind with the **CLONED** map-feature element in DOM instead of the feature in mapml
if (!feature._DOMnode) feature._DOMnode = feature.cloneNode(true);
feature._DOMnode._featureGroup = this.addData(feature._DOMnode, nativeCS, nativeZoom);
} else {
feature._featureGroup = this.addData(feature, nativeCS, nativeZoom);
Expand Down
53 changes: 34 additions & 19 deletions src/mapml/layers/MapMLLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ export var MapMLLayer = L.Layer.extend({
opacity: extentEl._templateVars.opacity,
_leafletLayer: this,
crs: extentEl.crs,
extentZIndex: extentEl.extentZIndex
extentZIndex: extentEl.extentZIndex,
extentEl: extentEl._DOMnode || extentEl
}).addTo(this._map);
extentEl.templatedLayer.setZIndex();
this._setLayerElExtent();
Expand Down Expand Up @@ -144,14 +145,6 @@ export var MapMLLayer = L.Layer.extend({
var c = document.createElement('div');
c.classList.add("mapml-popup-content");
c.insertAdjacentHTML('afterbegin', properties.innerHTML);
c.insertAdjacentHTML('beforeend', `<a href="" class="zoomLink">${M.options.locale.popupZoom}</a>`);
let zoomLink = c.querySelector('a.zoomLink');
zoomLink.onclick = zoomLink.onkeydown = function (e) {
if (!(e instanceof MouseEvent) && e.keyCode !== 13) return;
e.preventDefault();
let mapmlFeature = geometry._featureEl ? geometry._featureEl : geometry._groupLayer._featureEl;
mapmlFeature.zoomTo();
};
geometry.bindPopup(c, {autoClose: false, minWidth: 165});
}
}
Expand Down Expand Up @@ -184,14 +177,6 @@ export var MapMLLayer = L.Layer.extend({
var c = document.createElement('div');
c.classList.add("mapml-popup-content");
c.insertAdjacentHTML('afterbegin', properties.innerHTML);
c.insertAdjacentHTML('beforeend', `<a href="" class="zoomLink">${M.options.locale.popupZoom}</a>`);
let zoomLink = c.querySelector('a.zoomLink');
zoomLink.onclick = zoomLink.onkeydown = function (e) {
if (!(e instanceof MouseEvent) && e.keyCode !== 13) return;
e.preventDefault();
let mapmlFeature = geometry._featureEl ? geometry._featureEl : geometry._groupLayer._featureEl;
mapmlFeature.zoomTo();
};
geometry.bindPopup(c, {autoClose: false, minWidth: 165});
}
},
Expand Down Expand Up @@ -258,6 +243,9 @@ export var MapMLLayer = L.Layer.extend({
_leafletLayer: this,
crs: this._extent.crs,
extentZIndex: this._extent._mapExtents[i].extentZIndex,
// when a <map-extent> migrates from a remote mapml file and attaches to the shadow of <layer- >
// this._extent._mapExtents[i] refers to the <map-extent> in remote mapml
extentEl: this._extent._mapExtents[i]._DOMnode || this._extent._mapExtents[i],
}).addTo(map);
this._extent._mapExtents[i].templatedLayer = this._templatedLayer;
if(this._templatedLayer._queries){
Expand All @@ -275,7 +263,6 @@ export var MapMLLayer = L.Layer.extend({
}
},


_validProjection : function(map){
let noLayer = false;
if(this._extent && this._extent._mapExtents){
Expand Down Expand Up @@ -1320,8 +1307,12 @@ export var MapMLLayer = L.Layer.extend({
if(popup._source._eventParents){ // check if the popup is for a feature or query
layer = popup._source._eventParents[Object.keys(popup._source._eventParents)[0]]; // get first parent of feature, there should only be one
group = popup._source.group;
// if the popup is for a static / templated feature, the "zoom to here" link can be attached once the popup opens
attachZoomLink.call(popup);
} else {
layer = popup._source._templatedLayer;
// if the popup is for a query, the "zoom to here" link should be re-attached every time new pagination features are displayed
map.on("attachZoomLink", attachZoomLink, popup);
}

if(popup._container.querySelector('nav[class="mapml-focus-buttons"]')){
Expand Down Expand Up @@ -1377,7 +1368,7 @@ export var MapMLLayer = L.Layer.extend({
map._controlContainer.querySelector("A:not([hidden])").focus();
}, popup);

let divider = L.DomUtil.create("hr");
let divider = L.DomUtil.create("hr", "mapml-popup-divider");

popup._navigationBar = div;
popup._content.appendChild(divider);
Expand Down Expand Up @@ -1448,12 +1439,36 @@ export var MapMLLayer = L.Layer.extend({
}
}

function attachZoomLink (e) {
// this === popup
let content = this._content,
featureEl = e ? e.currFeature : this._source._groupLayer._featureEl;
if (content.querySelector('a.mapml-zoom-link')) {
content.querySelector('a.mapml-zoom-link').remove();
}
if (!featureEl.querySelector('map-geometry')) return;
let tL = featureEl.extent.topLeft.gcrs,
bR = featureEl.extent.bottomRight.gcrs,
center = L.latLngBounds(L.latLng(tL.horizontal, tL.vertical), L.latLng(bR.horizontal, bR.vertical)).getCenter(true);
let zoomLink = document.createElement('a');
zoomLink.href = `#${featureEl.getMaxZoom()},${center.lng},${center.lat}`;
zoomLink.innerHTML = `${M.options.locale.popupZoom}`;
zoomLink.className = "mapml-zoom-link";
zoomLink.onclick = zoomLink.onkeydown = function (e) {
if (!(e instanceof MouseEvent) && e.keyCode !== 13) return;
e.preventDefault();
featureEl.zoomTo();
};
content.insertBefore(zoomLink, content.querySelector('hr.mapml-popup-divider'));
}

// if popup closes then the focusFeature handler can be removed
map.on("popupclose", removeHandlers);
function removeHandlers(removeEvent){
if (removeEvent.popup === popup){
map.off("keydown", focusFeature);
map.off("keydown", focusMap);
map.off("popupopen", attachZoomLink);
map.off('popupclose', removeHandlers);
if(group) group.setAttribute("aria-expanded", "false");
}
Expand Down
21 changes: 16 additions & 5 deletions src/mapml/layers/TemplatedFeaturesLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export var TemplatedFeaturesLayer = L.Layer.extend({
this.extentBounds=inputData.bounds;
this.isVisible = true;
this._template = template;
this._extentEl = options.extentEl;
this._container = L.DomUtil.create('div', 'leaflet-layer', options.pane);
L.extend(options, this.zoomBounds);
L.DomUtil.addClass(this._container, 'mapml-features-container');
Expand Down Expand Up @@ -39,7 +40,6 @@ export var TemplatedFeaturesLayer = L.Layer.extend({
var c = document.createElement('div');
c.classList.add("mapml-popup-content");
c.insertAdjacentHTML('afterbegin', properties.innerHTML);
c.insertAdjacentHTML('beforeend', `<a href="" class="zoomLink">${M.options.locale.popupZoom}</a>`);
geometry.bindPopup(c, {autoClose: false, minWidth: 108});
}
});
Expand Down Expand Up @@ -85,6 +85,10 @@ export var TemplatedFeaturesLayer = L.Layer.extend({
this.extentBounds.overlaps(mapBounds);

this._features.clearLayers();
// shadow may has not yet attached to <map-extent> for the first-time rendering
if (this._extentEl.shadowRoot) {
this._extentEl.shadowRoot.innerHTML = "";
}
this._removeCSS();
//Leave the layers cleared if the layer is not visible
if(!(this.isVisible) && steppedZoom === mapZoom){
Expand All @@ -96,6 +100,7 @@ export var TemplatedFeaturesLayer = L.Layer.extend({
var mapml, headers = new Headers({'Accept': 'text/mapml;q=0.9,application/geo+json;q=0.8'}),
parser = new DOMParser(),
features = this._features,
extentEl = this._extentEl,
map = this._map,
context = this,
MAX_PAGES = 10,
Expand All @@ -109,17 +114,23 @@ export var TemplatedFeaturesLayer = L.Layer.extend({
url = mapml.querySelector('map-link[rel=next]')? mapml.querySelector('map-link[rel=next]').getAttribute('href') : null;
url = url ? (new URL(url, base)).href: null;
// TODO if the xml parser barfed but the response is application/geo+json, use the parent addData method
let nativeZoom = mapml.querySelector("map-meta[name=zoom]") &&
let nativeZoom = extentEl._nativeZoom = mapml.querySelector("map-meta[name=zoom]") &&
+M._metaContentToObject(mapml.querySelector("map-meta[name=zoom]").getAttribute("content")).value || 0;
let nativeCS = mapml.querySelector("map-meta[name=cs]") &&
M._metaContentToObject(mapml.querySelector("map-meta[name=cs]").getAttribute("content")).content || "GCRS";
let nativeCS = extentEl._nativeCS = mapml.querySelector("map-meta[name=cs]") &&
M._metaContentToObject(mapml.querySelector("map-meta[name=cs]").getAttribute("content")).content || "PCRS";
features.addData(mapml, nativeCS, nativeZoom);
// "migrate" to extent's shadow
// make a clone, prevent the elements from being removed from mapml file
// same as _attachToLayer() in MapMLLayer.js
for (let el of mapml.querySelector('map-body').children) {
extentEl.shadowRoot.append(el._DOMnode);
el._DOMnode._extentEl = extentEl;
}
if (url && --limit) {
return _pullFeatureFeed(url, limit);
}
}));
};

this._url = url;
_pullFeatureFeed(url, MAX_PAGES)
.then(function() {
Expand Down
1 change: 1 addition & 0 deletions src/mapml/layers/TemplatedLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export var TemplatedLayer = L.Layer.extend({
let inputData = M._extractInputBounds(templates[i]);
templates[i].extentBounds = inputData.bounds;
templates[i].zoomBounds = inputData.zoomBounds;
templates[i]._extentEl = this.options.extentEl;
this._queries.push(L.extend(templates[i], this._setupQueryVars(templates[i])));
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/web-map.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { MapLayer } from './layer.js';
import { MapArea } from './map-area.js';
import { MapCaption } from './map-caption.js';
import { MapFeature } from './map-feature.js';
import { MapExtent } from './map-extent.js';

export class WebMap extends HTMLMapElement {
static get observedAttributes() {
Expand Down Expand Up @@ -1099,3 +1100,4 @@ window.customElements.define('layer-', MapLayer);
window.customElements.define('map-area', MapArea, {extends: 'area'});
window.customElements.define('map-caption',MapCaption);
window.customElements.define('map-feature', MapFeature);
window.customElements.define('map-extent', MapExtent);
9 changes: 6 additions & 3 deletions test/e2e/core/mapFeature.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<html lang="en">

<head>
<title>Feature Link Test</title>
<title>HTMLFeatureElement Test</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type="module" src="web-map.js"></script>
Expand Down Expand Up @@ -190,9 +190,12 @@ <h1>Event handler test</h1>
}

document.querySelector('.event').onblur = function () {
// this: mapFeature._groupEl
document.querySelector(".event").classList.add('test_2');
document.querySelector(".event").classList.add('blur_property_test');
}

document.querySelector('.event').addEventListener('blur', function () {
document.querySelector(".event").classList.add('blur_addEvtLsn_test');
})
</script>
</body>

Expand Down
Loading