Skip to content

Commit

Permalink
Add media attribute to map-layer, map-link, map-style
Browse files Browse the repository at this point in the history
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

Update linux image for map-link-media test

Co-authored-by: Yushan Mu <[email protected]>

Initial implementation of map-style media attribute
Refactor enable/disableLink methods to _enable/_disableLink
Update / fix test title for map-link (should not be 'map-layer')

Update implementation of map-style media attribute
Add test for map-style media attribute.
  • Loading branch information
prushforth committed Dec 21, 2024
1 parent 345de98 commit 0fc60ce
Show file tree
Hide file tree
Showing 25 changed files with 577 additions and 125 deletions.
154 changes: 79 additions & 75 deletions src/layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { createLayerControlHTML } from './mapml/elementSupport/layers/createLaye

export class BaseLayerElement extends HTMLElement {
static get observedAttributes() {
return ['src', 'label', 'checked', 'hidden', 'opacity'];
return ['src', 'label', 'checked', 'hidden', 'opacity', 'media'];
}
/* jshint ignore:start */
#hasConnected;
Expand Down Expand Up @@ -53,6 +53,13 @@ export class BaseLayerElement extends HTMLElement {
}
}

get media() {
return this.getAttribute('media');
}
set media(val) {
this.setAttribute('media', val);
}

get opacity() {
// use ?? since 0 is falsy, || would return rhs in that case
return +(this._opacity ?? this.getAttribute('opacity'));
Expand Down Expand Up @@ -114,24 +121,73 @@ export class BaseLayerElement extends HTMLElement {
this._onAdd();
}
}
break;
case 'media':
if (oldValue !== newValue) {
this._registerMediaQuery(newValue);
}
break;
}
}
}
_registerMediaQuery(mq) {
if (!this._changeHandler) {
this._changeHandler = () => {
this._onRemove();
if (this._mql.matches) {
this._onAdd();
}
// set the disabled 'read-only' attribute indirectly, via _validateDisabled
this._validateDisabled();
};
}

if (mq) {
// a new media query is being established
let map = this.getMapEl();
if (!map) return;

// Remove listener from the old media query (if it exists)
if (this._mql) {
this._mql.removeEventListener('change', this._changeHandler);
}

this._mql = map.matchMedia(mq);
this._changeHandler();
this._mql.addEventListener('change', this._changeHandler);
} else if (this._mql) {
// the media attribute removed or query set to ''
this._mql.removeEventListener('change', this._changeHandler);
delete this._mql;
// effectively, no / empty media attribute matches, do what changeHandler does
this._onRemove();
this._onAdd();
this._validateDisabled();
}
}
getMapEl() {
return Util.getClosest(this, 'mapml-viewer,map[is=web-map]');
}
constructor() {
// Always call super first in constructor
super();
// this._opacity is used to record the current opacity value (with or without updates),
// the initial value of this._opacity should be set as opacity attribute value, if exists, or the default value 1.0
this._opacity = this.opacity || 1.0;
this._renderingMapContent = M.options.contentPreference;
this.attachShadow({ mode: 'open' });
}
disconnectedCallback() {
// if the map-layer node is removed from the dom, the layer should be
// removed from the map and the layer control
if (this.hasAttribute('data-moving')) return;
this._onRemove();

if (this._mql) {
if (this._changeHandler) {
this._mql.removeEventListener('change', this._changeHandler);
}
delete this._mql;
}
}

_onRemove() {
Expand All @@ -141,13 +197,6 @@ export class BaseLayerElement extends HTMLElement {
let l = this._layer,
lc = this._layerControl,
lchtml = this._layerControlHTML;
// remove properties of layer involved in whenReady() logic
delete this._layer;
delete this._layerControl;
delete this._layerControlHTML;
delete this._fetchError;
this.shadowRoot.innerHTML = '';
if (this.src) this.innerHTML = '';

if (l) {
l.off();
Expand All @@ -158,8 +207,16 @@ export class BaseLayerElement extends HTMLElement {
}

if (lc && !this.hidden) {
// lc.removeLayer depends on this._layerControlHTML, can't delete it until after
lc.removeLayer(l);
}
// remove properties of layer involved in whenReady() logic
delete this._layer;
delete this._layerControl;
delete this._layerControlHTML;
delete this._fetchError;
this.shadowRoot.innerHTML = '';
if (this.src) this.innerHTML = '';
}

connectedCallback() {
Expand All @@ -170,11 +227,17 @@ export class BaseLayerElement extends HTMLElement {
this._createLayerControlHTML = createLayerControlHTML.bind(this);
const doConnected = this._onAdd.bind(this);
const doRemove = this._onRemove.bind(this);
const registerMediaQuery = this._registerMediaQuery.bind(this);
let mq = this.media;
this.parentElement
.whenReady()
.then(() => {
doRemove();
doConnected();
if (mq) {
registerMediaQuery(mq);
} else {
doConnected();
}
})
.catch((error) => {
throw new Error('Map never became ready: ' + error);
Expand All @@ -189,20 +252,11 @@ export class BaseLayerElement extends HTMLElement {
e.stopPropagation();
// if user changes the style in layer control
if (e.detail) {
this._renderingMapContent = e.detail._renderingMapContent;
this.src = e.detail.src;
}
},
{ once: true }
);
this.addEventListener(
'zoomchangesrc',
function (e) {
e.stopPropagation();
this.src = e.detail.href;
},
{ once: true }
);
let base = this.baseURI ? this.baseURI : document.baseURI;
const headers = new Headers();
headers.append('Accept', 'text/mapml');
Expand Down Expand Up @@ -240,7 +294,6 @@ export class BaseLayerElement extends HTMLElement {
.then(() => {
// may throw:
this.selectAlternateOrChangeProjection();
this.checkForPreferredContent();
})
.then(() => {
this._layer = mapMLLayer(new URL(this.src, base).href, this, {
Expand Down Expand Up @@ -278,7 +331,6 @@ export class BaseLayerElement extends HTMLElement {
.then(() => {
// may throw:
this.selectAlternateOrChangeProjection();
this.checkForPreferredContent();
})
.then(() => {
this._layer = mapMLLayer(null, this, {
Expand Down Expand Up @@ -317,13 +369,6 @@ export class BaseLayerElement extends HTMLElement {
);
this.parentElement.projection = e.cause.mapprojection;
}
} else if (e.message === 'findmatchingpreferredcontent') {
if (e.cause.href) {
console.log(
'Changing layer to matching preferred content at: ' + e.cause.href
);
this.src = e.cause.href;
}
} else if (e.message === 'Failed to fetch') {
// cut short whenReady with the _fetchError property
this._fetchError = true;
Expand Down Expand Up @@ -372,23 +417,6 @@ export class BaseLayerElement extends HTMLElement {
}
}

checkForPreferredContent() {
let mapml = this.src ? this.shadowRoot : this;
let availablePreferMapContents = mapml.querySelector(
`map-link[rel="style"][media="prefers-map-content=${this._renderingMapContent}"][href]`
);
if (availablePreferMapContents) {
// resolve href
let url = new URL(
availablePreferMapContents.getAttribute('href'),
availablePreferMapContents.getBase()
).href;
throw new Error('findmatchingpreferredcontent', {
cause: { href: url }
});
}
}

copyRemoteContentToShadowRoot(mapml) {
let shadowRoot = this.shadowRoot;
// get the map-meta[name=projection/cs/extent/zoom] from map-head of remote mapml, attach them to the shadowroot
Expand Down Expand Up @@ -610,8 +638,13 @@ export class BaseLayerElement extends HTMLElement {
setTimeout(() => {
let layer = this._layer,
map = layer?._map;
// if there's a media query in play, check it early
if (this._mql && !this._mql.matches) {
this.setAttribute('disabled', '');
this.disabled = true;
return;
}
if (map) {
this._validateLayerZoom({ zoom: map.getZoom() });
// prerequisite: no inline and remote mapml elements exists at the same time
const mapExtents = this.src
? this.shadowRoot.querySelectorAll('map-extent')
Expand Down Expand Up @@ -664,35 +697,6 @@ export class BaseLayerElement extends HTMLElement {
}
}, 0);
}
_validateLayerZoom(e) {
// get the min and max zooms from all extents
let toZoom = e.zoom;
let min = this.extent.zoom.minZoom;
let max = this.extent.zoom.maxZoom;
let inLink = this.src
? this.shadowRoot.querySelector('map-link[rel=zoomin]')
: this.querySelector('map-link[rel=zoomin]'),
outLink = this.src
? this.shadowRoot.querySelector('map-link[rel=zoomout]')
: this.querySelector('map-link[rel=zoomout]');
let targetURL;
if (!(min <= toZoom && toZoom <= max)) {
if (inLink && toZoom > max) {
targetURL = inLink.href;
} else if (outLink && toZoom < min) {
targetURL = outLink.href;
}
if (targetURL) {
this.dispatchEvent(
new CustomEvent('zoomchangesrc', {
detail: {
href: targetURL
}
})
);
}
}
}
// disable/italicize layer control elements based on the map-layer.disabled property
toggleLayerControlDisabled() {
let input = this._layerControlCheckbox,
Expand Down
2 changes: 1 addition & 1 deletion src/map-extent.js
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ export class HTMLExtentElement extends HTMLElement {

_handleChange() {
// add _extentLayer to map if map-extent is checked, otherwise remove it
if (this.checked && !this.disabled) {
if (this.checked && !this.disabled && this.parentLayer._layer) {
// can be added to mapmllayer layerGroup no matter map-layer is checked or not
this._extentLayer.addTo(this.parentLayer._layer);
this._extentLayer.setZIndex(
Expand Down
Loading

0 comments on commit 0fc60ce

Please sign in to comment.