Skip to content

Commit 1bbee84

Browse files
committed
Remove prefers-map-content hardcoded demo hack
Add implementation of _changeHandler that invokes _onAdd, _onRemove. Add media query matches to map-layer._validateDisabled Re-organize _onRemove so that stuff isn't deleted until it's no longer needed, Make call to registerMediaQuery(mq) conditional on there being a mq, make it the path through which a layer gets initialized only when that is true, so as to not perform _onAdd twice. Move detection of media query matches check out of core if (map) block in map-layer._validateDisabled() Remove vestige of old prefers-map-content demo implementation from both map-link.js and layer.js Make execution of map-extent's map-projectionchange handler conditional on the there being a parentLayer._layer property value. This specifically covers the case when this handler is invoked by the projection change event happening, in which the parent layer may be disabled due to a media condition and therefore there is no LayerGroup to add the extentLayer to. add map-layer media attribute as an observed attribute Make map-link._registerMediaQuery async, wait on mapml-viewer to be ready before trying to register a media query (which may depend on mapml-viewer.extent). Remove map-link media attribute from those attributes that are copied onto the 'rendered' <link> element, because the link element actually supports the media attribute, but not the media features we are designing (hence it always forces the element to be disabled). Update map-layer._registerMediaQuery so that when the observed media attribute is removed or set to the empty string, the map-layer goes through the initialization life cycle, as though it was newly connected to the DOM (it may go from disabled to enabled due to the removal). Clean up map-layer _registerMediaQuery Add tests for <map-layer media="..."> attribute Add tests for <map-link rel="stylesheet" media="..."> attributes Add test for <map-link rel="features" media="..."> attribute cycling
1 parent 345de98 commit 1bbee84

18 files changed

+388
-101
lines changed

src/layer.js

+79-75
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { createLayerControlHTML } from './mapml/elementSupport/layers/createLaye
66

77
export class BaseLayerElement extends HTMLElement {
88
static get observedAttributes() {
9-
return ['src', 'label', 'checked', 'hidden', 'opacity'];
9+
return ['src', 'label', 'checked', 'hidden', 'opacity', 'media'];
1010
}
1111
/* jshint ignore:start */
1212
#hasConnected;
@@ -53,6 +53,13 @@ export class BaseLayerElement extends HTMLElement {
5353
}
5454
}
5555

56+
get media() {
57+
return this.getAttribute('media');
58+
}
59+
set media(val) {
60+
this.setAttribute('media', val);
61+
}
62+
5663
get opacity() {
5764
// use ?? since 0 is falsy, || would return rhs in that case
5865
return +(this._opacity ?? this.getAttribute('opacity'));
@@ -114,24 +121,73 @@ export class BaseLayerElement extends HTMLElement {
114121
this._onAdd();
115122
}
116123
}
124+
break;
125+
case 'media':
126+
if (oldValue !== newValue) {
127+
this._registerMediaQuery(newValue);
128+
}
129+
break;
117130
}
118131
}
119132
}
133+
_registerMediaQuery(mq) {
134+
if (!this._changeHandler) {
135+
this._changeHandler = () => {
136+
this._onRemove();
137+
if (this._mql.matches) {
138+
this._onAdd();
139+
}
140+
// set the disabled 'read-only' attribute indirectly, via _validateDisabled
141+
this._validateDisabled();
142+
};
143+
}
144+
145+
if (mq) {
146+
// a new media query is being established
147+
let map = this.getMapEl();
148+
if (!map) return;
120149

150+
// Remove listener from the old media query (if it exists)
151+
if (this._mql) {
152+
this._mql.removeEventListener('change', this._changeHandler);
153+
}
154+
155+
this._mql = map.matchMedia(mq);
156+
this._changeHandler();
157+
this._mql.addEventListener('change', this._changeHandler);
158+
} else if (this._mql) {
159+
// the media attribute removed or query set to ''
160+
this._mql.removeEventListener('change', this._changeHandler);
161+
delete this._mql;
162+
// effectively, no / empty media attribute matches, do what changeHandler does
163+
this._onRemove();
164+
this._onAdd();
165+
this._validateDisabled();
166+
}
167+
}
168+
getMapEl() {
169+
return Util.getClosest(this, 'mapml-viewer,map[is=web-map]');
170+
}
121171
constructor() {
122172
// Always call super first in constructor
123173
super();
124174
// this._opacity is used to record the current opacity value (with or without updates),
125175
// the initial value of this._opacity should be set as opacity attribute value, if exists, or the default value 1.0
126176
this._opacity = this.opacity || 1.0;
127-
this._renderingMapContent = M.options.contentPreference;
128177
this.attachShadow({ mode: 'open' });
129178
}
130179
disconnectedCallback() {
131180
// if the map-layer node is removed from the dom, the layer should be
132181
// removed from the map and the layer control
133182
if (this.hasAttribute('data-moving')) return;
134183
this._onRemove();
184+
185+
if (this._mql) {
186+
if (this._changeHandler) {
187+
this._mql.removeEventListener('change', this._changeHandler);
188+
}
189+
delete this._mql;
190+
}
135191
}
136192

137193
_onRemove() {
@@ -141,13 +197,6 @@ export class BaseLayerElement extends HTMLElement {
141197
let l = this._layer,
142198
lc = this._layerControl,
143199
lchtml = this._layerControlHTML;
144-
// remove properties of layer involved in whenReady() logic
145-
delete this._layer;
146-
delete this._layerControl;
147-
delete this._layerControlHTML;
148-
delete this._fetchError;
149-
this.shadowRoot.innerHTML = '';
150-
if (this.src) this.innerHTML = '';
151200

152201
if (l) {
153202
l.off();
@@ -158,8 +207,16 @@ export class BaseLayerElement extends HTMLElement {
158207
}
159208

160209
if (lc && !this.hidden) {
210+
// lc.removeLayer depends on this._layerControlHTML, can't delete it until after
161211
lc.removeLayer(l);
162212
}
213+
// remove properties of layer involved in whenReady() logic
214+
delete this._layer;
215+
delete this._layerControl;
216+
delete this._layerControlHTML;
217+
delete this._fetchError;
218+
this.shadowRoot.innerHTML = '';
219+
if (this.src) this.innerHTML = '';
163220
}
164221

165222
connectedCallback() {
@@ -170,11 +227,17 @@ export class BaseLayerElement extends HTMLElement {
170227
this._createLayerControlHTML = createLayerControlHTML.bind(this);
171228
const doConnected = this._onAdd.bind(this);
172229
const doRemove = this._onRemove.bind(this);
230+
const registerMediaQuery = this._registerMediaQuery.bind(this);
231+
let mq = this.media;
173232
this.parentElement
174233
.whenReady()
175234
.then(() => {
176235
doRemove();
177-
doConnected();
236+
if (mq) {
237+
registerMediaQuery(mq);
238+
} else {
239+
doConnected();
240+
}
178241
})
179242
.catch((error) => {
180243
throw new Error('Map never became ready: ' + error);
@@ -189,20 +252,11 @@ export class BaseLayerElement extends HTMLElement {
189252
e.stopPropagation();
190253
// if user changes the style in layer control
191254
if (e.detail) {
192-
this._renderingMapContent = e.detail._renderingMapContent;
193255
this.src = e.detail.src;
194256
}
195257
},
196258
{ once: true }
197259
);
198-
this.addEventListener(
199-
'zoomchangesrc',
200-
function (e) {
201-
e.stopPropagation();
202-
this.src = e.detail.href;
203-
},
204-
{ once: true }
205-
);
206260
let base = this.baseURI ? this.baseURI : document.baseURI;
207261
const headers = new Headers();
208262
headers.append('Accept', 'text/mapml');
@@ -240,7 +294,6 @@ export class BaseLayerElement extends HTMLElement {
240294
.then(() => {
241295
// may throw:
242296
this.selectAlternateOrChangeProjection();
243-
this.checkForPreferredContent();
244297
})
245298
.then(() => {
246299
this._layer = mapMLLayer(new URL(this.src, base).href, this, {
@@ -278,7 +331,6 @@ export class BaseLayerElement extends HTMLElement {
278331
.then(() => {
279332
// may throw:
280333
this.selectAlternateOrChangeProjection();
281-
this.checkForPreferredContent();
282334
})
283335
.then(() => {
284336
this._layer = mapMLLayer(null, this, {
@@ -317,13 +369,6 @@ export class BaseLayerElement extends HTMLElement {
317369
);
318370
this.parentElement.projection = e.cause.mapprojection;
319371
}
320-
} else if (e.message === 'findmatchingpreferredcontent') {
321-
if (e.cause.href) {
322-
console.log(
323-
'Changing layer to matching preferred content at: ' + e.cause.href
324-
);
325-
this.src = e.cause.href;
326-
}
327372
} else if (e.message === 'Failed to fetch') {
328373
// cut short whenReady with the _fetchError property
329374
this._fetchError = true;
@@ -372,23 +417,6 @@ export class BaseLayerElement extends HTMLElement {
372417
}
373418
}
374419

375-
checkForPreferredContent() {
376-
let mapml = this.src ? this.shadowRoot : this;
377-
let availablePreferMapContents = mapml.querySelector(
378-
`map-link[rel="style"][media="prefers-map-content=${this._renderingMapContent}"][href]`
379-
);
380-
if (availablePreferMapContents) {
381-
// resolve href
382-
let url = new URL(
383-
availablePreferMapContents.getAttribute('href'),
384-
availablePreferMapContents.getBase()
385-
).href;
386-
throw new Error('findmatchingpreferredcontent', {
387-
cause: { href: url }
388-
});
389-
}
390-
}
391-
392420
copyRemoteContentToShadowRoot(mapml) {
393421
let shadowRoot = this.shadowRoot;
394422
// get the map-meta[name=projection/cs/extent/zoom] from map-head of remote mapml, attach them to the shadowroot
@@ -610,8 +638,13 @@ export class BaseLayerElement extends HTMLElement {
610638
setTimeout(() => {
611639
let layer = this._layer,
612640
map = layer?._map;
641+
// if there's a media query in play, check it early
642+
if (this._mql && !this._mql.matches) {
643+
this.setAttribute('disabled', '');
644+
this.disabled = true;
645+
return;
646+
}
613647
if (map) {
614-
this._validateLayerZoom({ zoom: map.getZoom() });
615648
// prerequisite: no inline and remote mapml elements exists at the same time
616649
const mapExtents = this.src
617650
? this.shadowRoot.querySelectorAll('map-extent')
@@ -664,35 +697,6 @@ export class BaseLayerElement extends HTMLElement {
664697
}
665698
}, 0);
666699
}
667-
_validateLayerZoom(e) {
668-
// get the min and max zooms from all extents
669-
let toZoom = e.zoom;
670-
let min = this.extent.zoom.minZoom;
671-
let max = this.extent.zoom.maxZoom;
672-
let inLink = this.src
673-
? this.shadowRoot.querySelector('map-link[rel=zoomin]')
674-
: this.querySelector('map-link[rel=zoomin]'),
675-
outLink = this.src
676-
? this.shadowRoot.querySelector('map-link[rel=zoomout]')
677-
: this.querySelector('map-link[rel=zoomout]');
678-
let targetURL;
679-
if (!(min <= toZoom && toZoom <= max)) {
680-
if (inLink && toZoom > max) {
681-
targetURL = inLink.href;
682-
} else if (outLink && toZoom < min) {
683-
targetURL = outLink.href;
684-
}
685-
if (targetURL) {
686-
this.dispatchEvent(
687-
new CustomEvent('zoomchangesrc', {
688-
detail: {
689-
href: targetURL
690-
}
691-
})
692-
);
693-
}
694-
}
695-
}
696700
// disable/italicize layer control elements based on the map-layer.disabled property
697701
toggleLayerControlDisabled() {
698702
let input = this._layerControlCheckbox,

src/map-extent.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ export class HTMLExtentElement extends HTMLElement {
431431

432432
_handleChange() {
433433
// add _extentLayer to map if map-extent is checked, otherwise remove it
434-
if (this.checked && !this.disabled) {
434+
if (this.checked && !this.disabled && this.parentLayer._layer) {
435435
// can be added to mapmllayer layerGroup no matter map-layer is checked or not
436436
this._extentLayer.addTo(this.parentLayer._layer);
437437
this._extentLayer.setZIndex(

src/map-link.js

+21-11
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,9 @@ export class HTMLLinkElement extends HTMLElement {
277277
case 'image':
278278
case 'features':
279279
case 'query':
280+
// because we skip the attributeChangedCallback for initialization,
281+
// respect the disabled attribute which can be set by the author prior
282+
// to initialization
280283
if (!this.disabled) {
281284
this._initTemplateVars();
282285
await this._createTemplatedLink();
@@ -296,7 +299,9 @@ export class HTMLLinkElement extends HTMLElement {
296299
//this._createLegendLink();
297300
break;
298301
case 'stylesheet':
299-
this._createStylesheetLink();
302+
if (!this.disabled) {
303+
this._createStylesheetLink();
304+
}
300305
break;
301306
case 'alternate':
302307
this._createAlternateLink(); // add media attribute
@@ -305,7 +310,10 @@ export class HTMLLinkElement extends HTMLElement {
305310
// this._createLicenseLink();
306311
break;
307312
}
308-
this._registerMediaQuery(this.media);
313+
// the media attribute uses / overrides the disabled attribute to enable or
314+
// disable the link, so at this point the #hasConnected must be true so
315+
// that the disabled attributeChangedCallback can have its desired side effect
316+
await this._registerMediaQuery(this.media);
309317
// create the type of templated leaflet layer appropriate to the rel value
310318
// image/map/features = templated(Image/Feature), tile=templatedTile,
311319
// this._tempatedTileLayer = Util.templatedTile(pane: this.extentElement._leafletLayer._container)
@@ -361,18 +369,16 @@ export class HTMLLinkElement extends HTMLElement {
361369
case 'image':
362370
case 'features':
363371
case 'query':
364-
if (!this.disabled) {
365-
this._initTemplateVars();
366-
await this._createTemplatedLink();
367-
this.getLayerEl()._validateDisabled();
368-
}
372+
this._initTemplateVars();
373+
await this._createTemplatedLink();
374+
this.getLayerEl()._validateDisabled();
369375
break;
370376
case 'stylesheet':
371377
this._createStylesheetLink();
372378
break;
373379
}
374380
}
375-
_registerMediaQuery(mq) {
381+
async _registerMediaQuery(mq) {
376382
if (!this._changeHandler) {
377383
// Define and bind the change handler once
378384
this._changeHandler = () => {
@@ -383,6 +389,9 @@ export class HTMLLinkElement extends HTMLElement {
383389
if (mq) {
384390
let map = this.getMapEl();
385391
if (!map) return;
392+
// have to wait until map has an extent i.e. is ready, because the
393+
// matchMedia function below relies on it for map related queries
394+
await map.whenReady();
386395

387396
// Remove listener from the old media query (if it exists)
388397
if (this._mql) {
@@ -397,6 +406,8 @@ export class HTMLLinkElement extends HTMLElement {
397406
// Clean up the existing listener
398407
this._mql.removeEventListener('change', this._changeHandler);
399408
delete this._mql;
409+
// unlike map-layer.disabled, map-link.disabled is an observed attribute
410+
this.disabled = false;
400411
}
401412
}
402413
_createAlternateLink(mapml) {
@@ -451,7 +462,7 @@ export class HTMLLinkElement extends HTMLElement {
451462

452463
function copyAttributes(source, target) {
453464
return Array.from(source.attributes).forEach((attribute) => {
454-
if (attribute.nodeName !== 'href')
465+
if (attribute.nodeName !== 'href' && attribute.nodeName !== 'media')
455466
target.setAttribute(attribute.nodeName, attribute.nodeValue);
456467
});
457468
}
@@ -989,8 +1000,7 @@ export class HTMLLinkElement extends HTMLElement {
9891000
layerEl.dispatchEvent(
9901001
new CustomEvent('changestyle', {
9911002
detail: {
992-
src: e.target.getAttribute('data-href'),
993-
preference: this.media['prefers-map-content']
1003+
src: e.target.getAttribute('data-href')
9941004
}
9951005
})
9961006
);

0 commit comments

Comments
 (0)