Skip to content

Commit d2f7bec

Browse files
prushforthAliyanH
andauthored
Fix issue #935 (#954)
* Fix issue #935 * Add test / test data for map-extent issue. Closes #935 * Fix Layer reordering bug. closes #955 Update LayerControl._layers object which was being neglected and not being updated. Added a line to properly sort the layerControl based on the MapML Layer zIndex. * Update call to sortFunction in copied / locally modified LayerControl. _update function to use the options.sortFunction if present. tbd: if this should be offered via PR to upstream project. * Add test for re-order bug #955 --------- Co-authored-by: AliyanH <aliyanulhaq@gmail.com>
1 parent 263d14a commit d2f7bec

File tree

9 files changed

+341
-27
lines changed

9 files changed

+341
-27
lines changed

src/map-extent.js

+37-22
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,13 @@ export class MapExtent extends HTMLElement {
176176
let extentsRootFieldset =
177177
this.parentLayer._propertiesGroupAnatomy;
178178
let position = Array.from(
179-
this.parentNode.querySelectorAll('map-extent:not([hidden])')
179+
this.parentLayer.src
180+
? this.parentLayer.shadowRoot.querySelectorAll(
181+
':host > map-extent:not([hidden])'
182+
)
183+
: this.parentLayer.querySelectorAll(
184+
':scope > map-extent:not([hidden])'
185+
)
180186
).indexOf(this);
181187
if (newValue !== null) {
182188
// remove from layer control (hide from user)
@@ -190,12 +196,18 @@ export class MapExtent extends HTMLElement {
190196
this._layerControlHTML
191197
);
192198
} else if (position > 0) {
193-
this.parentNode
194-
.querySelectorAll('map-extent:not([hidden])')
195-
[position - 1]._layerControlHTML.insertAdjacentElement(
196-
'afterend',
197-
this._layerControlHTML
198-
);
199+
Array.from(
200+
this.parentLayer.src
201+
? this.parentLayer.shadowRoot.querySelectorAll(
202+
':host > map-extent:not([hidden])'
203+
)
204+
: this.parentLayer.querySelectorAll(
205+
':scope > map-extent:not([hidden])'
206+
)
207+
)[position - 1]._layerControlHTML.insertAdjacentElement(
208+
'afterend',
209+
this._layerControlHTML
210+
);
199211
}
200212
}
201213
this._validateLayerControlContainerHidden();
@@ -220,16 +232,13 @@ export class MapExtent extends HTMLElement {
220232
async connectedCallback() {
221233
// this.parentNode.host returns the layer- element when parentNode is
222234
// the shadow root
223-
this.parentLayer =
224-
this.parentNode.nodeName.toUpperCase() === 'LAYER-'
225-
? this.parentNode
226-
: this.parentNode.host;
235+
this.parentLayer = this.getLayerEl();
227236
if (
228237
this.hasAttribute('data-moving') ||
229238
this.parentLayer.hasAttribute('data-moving')
230239
)
231240
return;
232-
this.mapEl = this.parentLayer.closest('mapml-viewer,map[is=web-map]');
241+
this.mapEl = this.getMapEl();
233242
await this.mapEl.whenProjectionDefined(this.units).catch(() => {
234243
throw new Error('Undefined projection:' + this.units);
235244
});
@@ -252,7 +261,9 @@ export class MapExtent extends HTMLElement {
252261
opacity: this.opacity,
253262
crs: M[this.units],
254263
extentZIndex: Array.from(
255-
this.parentLayer.querySelectorAll('map-extent')
264+
this.parentLayer.src
265+
? this.parentLayer.shadowRoot.querySelectorAll(':host > map-extent')
266+
: this.parentLayer.querySelectorAll(':scope > map-extent')
256267
).indexOf(this),
257268
extentEl: this
258269
});
@@ -418,9 +429,11 @@ export class MapExtent extends HTMLElement {
418429
// can be added to mapmllayer layerGroup no matter layer- is checked or not
419430
this._extentLayer.addTo(this.parentLayer._layer);
420431
this._extentLayer.setZIndex(
421-
Array.from(this.parentLayer.querySelectorAll('map-extent')).indexOf(
422-
this
423-
)
432+
Array.from(
433+
this.parentLayer.src
434+
? this.parentLayer.shadowRoot.querySelectorAll(':host > map-extent')
435+
: this.parentLayer.querySelectorAll(':scope > map-extent')
436+
).indexOf(this)
424437
);
425438
} else {
426439
this.parentLayer._layer?.removeLayer(this._extentLayer);
@@ -430,13 +443,15 @@ export class MapExtent extends HTMLElement {
430443
}
431444
_validateLayerControlContainerHidden() {
432445
let extentsFieldset = this.parentLayer._propertiesGroupAnatomy;
433-
let nodeToSearch = this.parentLayer.src
434-
? this.parentLayer.shadowRoot
435-
: this.parentLayer;
436446
if (!extentsFieldset) return;
437-
if (
438-
nodeToSearch.querySelectorAll('map-extent:not([hidden])').length === 0
439-
) {
447+
const numberOfVisibleSublayers = (
448+
this.parentLayer.src
449+
? this.parentLayer.shadowRoot.querySelectorAll(
450+
':host > map-extent:not([hidden])'
451+
)
452+
: this.parentLayer.querySelectorAll(':scope > map-extent:not([hidden])')
453+
).length;
454+
if (numberOfVisibleSublayers === 0) {
440455
extentsFieldset.setAttribute('hidden', '');
441456
} else {
442457
extentsFieldset.removeAttribute('hidden');

src/mapml/control/LayerControl.js

+45-2
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,52 @@ export var LayerControl = L.Control.Layers.extend({
9898
}
9999
},
100100

101-
_withinZoomBounds: function (zoom, range) {
102-
return range.min <= zoom && zoom <= range.max;
101+
// imported from leaflet with slight modifications
102+
// for layerControl ordering based on zIndex
103+
_update: function () {
104+
if (!this._container) {
105+
return this;
106+
}
107+
108+
L.DomUtil.empty(this._baseLayersList);
109+
L.DomUtil.empty(this._overlaysList);
110+
111+
this._layerControlInputs = [];
112+
var baseLayersPresent,
113+
overlaysPresent,
114+
i,
115+
obj,
116+
baseLayersCount = 0;
117+
118+
// <----------- MODIFICATION from the default _update method
119+
// sort the layercontrol layers object based on the zIndex
120+
// provided by MapMLLayer
121+
if (this.options.sortLayers) {
122+
this._layers.sort((a, b) =>
123+
this.options.sortFunction(a.layer, b.layer, a.name, b.name)
124+
);
125+
}
126+
// -------------------------------------------------->
127+
for (i = 0; i < this._layers.length; i++) {
128+
obj = this._layers[i];
129+
this._addItem(obj);
130+
overlaysPresent = overlaysPresent || obj.overlay;
131+
baseLayersPresent = baseLayersPresent || !obj.overlay;
132+
baseLayersCount += !obj.overlay ? 1 : 0;
133+
}
134+
135+
// Hide base layers section if there's only one layer.
136+
if (this.options.hideSingleBase) {
137+
baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
138+
this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
139+
}
140+
141+
this._separator.style.display =
142+
overlaysPresent && baseLayersPresent ? '' : 'none';
143+
144+
return this;
103145
},
146+
104147
_addItem: function (obj) {
105148
var layercontrols = obj.layer._layerEl._layerControlHTML;
106149
// the input is required by Leaflet...

src/mapml/elementSupport/extents/createLayerControlForExtent.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ export var createLayerControlExtentHTML = function () {
3131
extentSettings
3232
),
3333
extentOpacitySummary = L.DomUtil.create('summary', '', opacityControl),
34-
mapEl = this.parentLayer.parentNode,
35-
layerEl = this.parentLayer,
34+
mapEl = this.getMapEl(),
35+
layerEl = this.getLayerEl(),
3636
opacity = L.DomUtil.create('input', '', opacityControl);
3737
extentSettings.hidden = true;
3838
extent.setAttribute('aria-grabbed', 'false');
@@ -227,7 +227,8 @@ export var createLayerControlExtentHTML = function () {
227227
let extentEl = c.querySelector('span').extent;
228228

229229
extentEl.setAttribute('data-moving', '');
230-
layerEl.insertAdjacentElement('beforeend', extentEl);
230+
const node = layerEl.src ? layerEl.shadowRoot : layerEl;
231+
node.append(extentEl);
231232
extentEl.removeAttribute('data-moving');
232233

233234
extentEl.extentZIndex = zIndex;

test/e2e/core/drag.test.js

+80
Original file line numberDiff line numberDiff line change
@@ -128,4 +128,84 @@ test.describe('UI Drag&Drop Test', () => {
128128
expect(layerIndex).toEqual('1');
129129
expect(controlText).toBe('Static MapML with tiles');
130130
});
131+
test('Re-order checked bug (#955) test', async () => {
132+
await page.waitForTimeout(500);
133+
const layerControl = page.locator('.leaflet-top.leaflet-right');
134+
await layerControl.hover();
135+
const overlaysList = page.locator('.leaflet-control-layers-overlays');
136+
const startingLowestLayer = overlaysList
137+
.getByRole('group', { name: 'Canada Base Map - Transportation (CBMT)' })
138+
.first();
139+
140+
// assert that CBMT is first of three layers
141+
const cbmtIsFirstInLayerControl = await overlaysList.evaluate((l) => {
142+
return (
143+
l.firstElementChild.querySelector('.mapml-layer-item-name')
144+
.textContent === 'Canada Base Map - Transportation (CBMT)'
145+
);
146+
});
147+
expect(cbmtIsFirstInLayerControl).toBe(true);
148+
149+
const fromBox = await startingLowestLayer.boundingBox();
150+
const fromPos = {
151+
x: fromBox.x + fromBox.width / 2,
152+
y: fromBox.y + fromBox.height / 3
153+
};
154+
let toPos = { x: fromPos.x, y: fromPos.y + fromBox.height * 1.1 };
155+
156+
// move (drag/drop) CBMT to the top of the layer stack
157+
await page.mouse.move(fromPos.x, fromPos.y);
158+
await page.mouse.down();
159+
await page.mouse.move(toPos.x, toPos.y);
160+
await page.mouse.up();
161+
await page.mouse.move(toPos.x, toPos.y);
162+
toPos = { x: toPos.x, y: toPos.y + fromBox.height * 1.1 };
163+
await page.mouse.down();
164+
await page.mouse.move(toPos.x, toPos.y);
165+
await page.mouse.up();
166+
167+
let cbmtIsTopOfLayerControl = await overlaysList.evaluate((l) => {
168+
return (
169+
l.querySelectorAll(':scope > fieldset').length === 3 &&
170+
l.lastElementChild.querySelector('.mapml-layer-item-name')
171+
.textContent === 'Canada Base Map - Transportation (CBMT)'
172+
);
173+
});
174+
// assert that CBMT is third of three layers
175+
expect(cbmtIsTopOfLayerControl).toBe(true);
176+
177+
const cbmtCheckbox = overlaysList
178+
.getByRole('checkbox', {
179+
name: 'Canada Base Map - Transportation (CBMT)'
180+
})
181+
.first();
182+
let cbmtIsChecked = await cbmtCheckbox.isChecked();
183+
expect(cbmtIsChecked).toBe(true);
184+
await page.pause();
185+
await cbmtCheckbox.click();
186+
cbmtIsChecked = await cbmtCheckbox.isChecked();
187+
expect(cbmtIsChecked).toBe(false);
188+
// cbmt layer should still be on top of layer control despite that it's unchecked
189+
cbmtIsTopOfLayerControl = await overlaysList.evaluate((l) => {
190+
return (
191+
l.querySelectorAll(':scope > fieldset').length === 3 &&
192+
l.lastElementChild.querySelector('.mapml-layer-item-name')
193+
.textContent === 'Canada Base Map - Transportation (CBMT)'
194+
);
195+
});
196+
// assert that CBMT is still third of three layers
197+
expect(cbmtIsTopOfLayerControl).toBe(true);
198+
await cbmtCheckbox.click();
199+
cbmtIsChecked = await cbmtCheckbox.isChecked();
200+
expect(cbmtIsChecked).toBe(true);
201+
cbmtIsTopOfLayerControl = await overlaysList.evaluate((l) => {
202+
return (
203+
l.querySelectorAll(':scope > fieldset').length === 3 &&
204+
l.lastElementChild.querySelector('.mapml-layer-item-name')
205+
.textContent === 'Canada Base Map - Transportation (CBMT)'
206+
);
207+
});
208+
// assert that CBMT is still third of three layers
209+
expect(cbmtIsTopOfLayerControl).toBe(true);
210+
});
131211
});
799 KB
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { test, expect, chromium } from '@playwright/test';
2+
3+
test.describe('map-extent checked order tests', () => {
4+
let page;
5+
let context;
6+
test.beforeAll(async function () {
7+
context = await chromium.launchPersistentContext('', { slowMo: 500 });
8+
page =
9+
context.pages().find((page) => page.url() === 'about:blank') ||
10+
(await context.newPage());
11+
await page.goto('map-extent-checked.html');
12+
});
13+
test('map-extent layer control order correct when cycling checked state', async () => {
14+
// Fixed #935 https://github.com/Maps4HTML/Web-Map-Custom-Element/issues/935
15+
/*
16+
Go to this map map-extent-checked.html
17+
18+
Open the layer control for the layer settings.
19+
20+
Un-check the imagery layer <map-extent>
21+
Check the imagery layer <map-extent>
22+
23+
What should happen:
24+
The imagery layer <map-extent> should draw underneath the states layer.
25+
26+
What actually happens:
27+
The imagery layer <map-extent> draws on top of the states layer.
28+
* */
29+
const layerControl = page.locator('.leaflet-top.leaflet-right');
30+
await layerControl.hover();
31+
const layerSettings = layerControl.getByTitle('Layer Settings');
32+
await layerSettings.click();
33+
const imageryExtentCheckbox = layerControl.getByRole('checkbox', {
34+
name: 'Extent One'
35+
});
36+
await imageryExtentCheckbox.click(); // turn it off
37+
await imageryExtentCheckbox.click(); // turn it on
38+
const ext1 = page.getByTestId('ext1');
39+
let imageryZIndex = await ext1.evaluate((e) => {
40+
return +e._extentLayer._container.style.zIndex;
41+
});
42+
expect(imageryZIndex).toEqual(0);
43+
const ext2 = page.getByTestId('ext2');
44+
let statesZIndex = await ext2.evaluate((e) => {
45+
return +e._extentLayer._container.style.zIndex;
46+
});
47+
expect(statesZIndex).toEqual(1);
48+
// re-order them via the layer control
49+
const imageryFieldset = layerControl.getByRole('group', {
50+
name: 'Extent One'
51+
});
52+
let controlBBox = await imageryFieldset.boundingBox();
53+
let from = {
54+
x: controlBBox.x + controlBBox.width / 2,
55+
y: controlBBox.y + controlBBox.height / 2
56+
},
57+
to = { x: from.x, y: from.y + controlBBox.height * 1.1 };
58+
59+
await page.mouse.move(from.x, from.y);
60+
await page.mouse.down();
61+
await page.mouse.move(to.x, to.y);
62+
await page.mouse.up();
63+
imageryZIndex = await ext1.evaluate((e) => {
64+
return +e._extentLayer._container.style.zIndex;
65+
});
66+
expect(imageryZIndex).toEqual(1);
67+
statesZIndex = await ext2.evaluate((e) => {
68+
return +e._extentLayer._container.style.zIndex;
69+
});
70+
expect(statesZIndex).toEqual(0);
71+
72+
await page.mouse.move(from.x, from.y);
73+
await page.mouse.down();
74+
await page.mouse.move(to.x, to.y);
75+
await page.mouse.up();
76+
await imageryExtentCheckbox.click(); // turn it off
77+
await imageryExtentCheckbox.click(); // turn it on
78+
imageryZIndex = await ext1.evaluate((e) => {
79+
return +e._extentLayer._container.style.zIndex;
80+
});
81+
expect(imageryZIndex).toEqual(0);
82+
statesZIndex = await ext2.evaluate((e) => {
83+
return +e._extentLayer._container.style.zIndex;
84+
});
85+
expect(statesZIndex).toEqual(1);
86+
// TO DO re-order them via the DOM (insertAdjacentHTML),
87+
// ensure that
88+
// a) render order/z-index is correct
89+
// b) render order is reflected in layer control order as well
90+
// see https://github.com/Maps4HTML/Web-Map-Custom-Element/issues/956
91+
});
92+
});

0 commit comments

Comments
 (0)