Skip to content

Commit 1083cb0

Browse files
authored
Merge pull request #249 from ahmadayubi/reorderLayer
Ability to reorder layer in control panel, reflect changes in DOM and map
2 parents b16f5bf + 7d76fdb commit 1083cb0

File tree

5 files changed

+145
-105
lines changed

5 files changed

+145
-105
lines changed

src/layer.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export class MapLayer extends HTMLElement {
7171
// this is moved up here so that the layer control doesn't respond
7272
// to the layer being removed with the _onLayerChange execution
7373
// that is set up in _attached:
74+
if(this.hasAttribute("data-moving")) return;
7475
this._removeEvents();
7576
if (this._layer._map) {
7677
this._layer._map.removeLayer(this._layer);
@@ -83,6 +84,7 @@ export class MapLayer extends HTMLElement {
8384
connectedCallback() {
8485
//creates listener that waits for createmap event, this allows for delayed builds of maps
8586
//this allows a safeguard for the case where loading a custom TCRS takes longer than loading mapml-viewer.js/web-map.js
87+
if(this.hasAttribute("data-moving")) return;
8688
this.parentNode.addEventListener('createmap', ()=>{
8789
this._ready();
8890
// if the map has been attached, set this layer up wrt Leaflet map
@@ -235,9 +237,10 @@ export class MapLayer extends HTMLElement {
235237
for (var nodes = this.parentNode.children;i < nodes.length;i++) {
236238
if (this.parentNode.children[i].nodeName === "LAYER-") {
237239
if (this.parentNode.children[i] === this) {
238-
break;
240+
position = i + 1;
241+
} else if (this.parentNode.children[i]._layer) {
242+
this.parentNode.children[i]._layer.setZIndex(i+1);
239243
}
240-
position++;
241244
}
242245
}
243246
var proj = this.parentNode.projection ? this.parentNode.projection : "OSMTILE";

src/mapml.css

+4
Original file line numberDiff line numberDiff line change
@@ -354,3 +354,7 @@ summary {
354354
.leaflet-container .leaflet-control-container {
355355
visibility: unset!important;
356356
}
357+
358+
.mapml-draggable * {
359+
cursor: row-resize;
360+
}

src/mapml/layers/MapLayer.js

+61-14
Original file line numberDiff line numberDiff line change
@@ -487,19 +487,20 @@ export var MapMLLayer = L.Layer.extend({
487487
opacity = document.createElement('input'),
488488
opacityControl = document.createElement('details'),
489489
opacityControlSummary = document.createElement('summary'),
490-
opacityControlSummaryLabel = document.createElement('label');
490+
opacityControlSummaryLabel = document.createElement('label'),
491+
mapEl = this._layerEl.parentNode;
491492

492493
input.defaultChecked = this._map ? true: false;
493494
input.type = 'checkbox';
494495
input.className = 'leaflet-control-layers-selector';
495-
name.draggable = true;
496496
name.layer = this;
497497

498498
if (this._legendUrl) {
499499
var legendLink = document.createElement('a');
500500
legendLink.text = ' ' + this._title;
501501
legendLink.href = this._legendUrl;
502502
legendLink.target = '_blank';
503+
legendLink.draggable = false;
503504
name.appendChild(legendLink);
504505
} else {
505506
name.innerHTML = ' ' + this._title;
@@ -521,19 +522,65 @@ export var MapMLLayer = L.Layer.extend({
521522
opacity.setAttribute('step','0.1');
522523
opacity.value = this._container.style.opacity || '1.0';
523524

524-
L.DomEvent.on(opacity,'change', this._changeOpacity, this);
525-
L.DomEvent.on(name,'dragstart', function(event) {
526-
// will have to figure out how to drag and drop a whole element
527-
// with its contents in the case where the <layer->content</layer->
528-
// has no src but does have inline content.
529-
// Should be do-able, I think.
530-
if (this._href) {
531-
event.dataTransfer.setData("text/uri-list",this._href);
532-
// Why use a second .setData("text/plain"...) ? This is very important:
533-
// See https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types#link
534-
event.dataTransfer.setData("text/plain", this._href);
525+
fieldset.setAttribute("aria-grabbed", "false");
526+
527+
fieldset.onmousedown = (downEvent) => {
528+
if(downEvent.target.tagName.toLowerCase() === "input") return;
529+
downEvent.preventDefault();
530+
let control = fieldset,
531+
controls = fieldset.parentNode,
532+
moving = false, yPos = downEvent.clientY;
533+
534+
document.body.onmousemove = (moveEvent) => {
535+
moveEvent.preventDefault();
536+
537+
// Fixes flickering by only moving element when there is enough space
538+
let offset = Math.abs(yPos - moveEvent.clientY);
539+
moving = Math.abs(offset) > 5 || moving;
540+
541+
if(controls && !moving ||
542+
controls.getBoundingClientRect().top > control.getBoundingClientRect().top ||
543+
controls.getBoundingClientRect().bottom < control.getBoundingClientRect().bottom) return;
544+
545+
controls.classList.add("mapml-draggable");
546+
let x = moveEvent.clientX, y = moveEvent.clientY,
547+
root = mapEl.tagName === "MAPML-VIEWER" ? mapEl.shadowRoot : mapEl.querySelector(".web-map").shadowRoot,
548+
elementAt = root.elementFromPoint(x, y),
549+
swapControl = !elementAt || !elementAt.closest("fieldset") ? control : elementAt.closest("fieldset");
550+
551+
swapControl = offset <= swapControl.offsetHeight ? control : swapControl;
552+
553+
control.setAttribute("aria-grabbed", 'true');
554+
control.setAttribute("aria-dropeffect", "move");
555+
if(swapControl && controls === swapControl.parentNode){
556+
swapControl = swapControl !== control.nextSibling? swapControl : swapControl.nextSibling;
557+
if(control !== swapControl) yPos = moveEvent.clientY;
558+
controls.insertBefore(control, swapControl);
535559
}
536-
}, this);
560+
};
561+
562+
document.body.onmouseup = () => {
563+
control.setAttribute("aria-grabbed", "false");
564+
control.removeAttribute("aria-dropeffect");
565+
let controlsElems = controls.children,
566+
zIndex = 1;
567+
for(let c of controlsElems){
568+
let layerEl = c.querySelector("span").layer._layerEl;
569+
570+
layerEl.setAttribute("data-moving","");
571+
mapEl.insertAdjacentElement("beforeend", layerEl);
572+
layerEl.removeAttribute("data-moving");
573+
574+
575+
layerEl._layer.setZIndex(zIndex);
576+
zIndex++;
577+
}
578+
controls.classList.remove("mapml-draggable");
579+
document.body.onmousemove = document.body.onmouseup = null;
580+
};
581+
};
582+
583+
L.DomEvent.on(opacity,'change', this._changeOpacity, this);
537584

538585
fieldset.appendChild(details);
539586
details.appendChild(summary);

test/e2e/core/drag.html

+11-2
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,18 @@
2323
</head>
2424

2525
<body>
26-
<map is="web-map" projection="CBMTILE" zoom="2" lat="45.5052040" lon="-75.2202344" controls>
26+
<map is="web-map" style="width: 500px;height: 500px;" projection="CBMTILE" zoom="2" lat="45.5052040" lon="-75.2202344"
27+
controls>
2728
<layer- label="CBMT" src="data/cbmt.mapml" checked></layer->
28-
29+
<layer- label="Static MapML with tiles" checked>
30+
<meta name="zoom" content="min=0,max=10">
31+
<meta name="projection" content="CBMTILE">
32+
<link rel="license"
33+
href="https://www.nrcan.gc.ca/earth-sciences/geography/topographic-information/free-data-geogratis/licence/17285"
34+
title="Canada Base Map © Natural Resources Canada">
35+
<tile zoom="0" row="3" col="3" src="data/cbmt/0/c3_r3.png"></tile>
36+
</layer->
37+
<layer- label="vector states" src="data/us_pop_density.mapml" checked></layer->
2938
</map>
3039
</body>
3140

test/e2e/core/drag.test.js

+64-87
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ jest.setTimeout(30000);
77
beforeEach(async () => {
88
browser = await playwright[browserType].launch({
99
headless: ISHEADLESS,
10+
slowMo: 50,
1011
});
1112
context = await browser.newContext();
1213
page = await context.newPage();
@@ -32,103 +33,79 @@ jest.setTimeout(30000);
3233
dataTransfer,
3334
});
3435
await page.hover(".leaflet-top.leaflet-right");
35-
let vars = await page.$$("[draggable='true']");
36-
expect(vars.length).toBe(1);
36+
let vars = await page.$$("xpath=//html/body/map >> css=div > div.leaflet-control-container > div.leaflet-top.leaflet-right > div > section > div.leaflet-control-layers-overlays > fieldset");
37+
expect(vars.length).toBe(3);
3738
});
3839

3940
test("[" + browserType + "]" + " Drag and drop of layers", async () => {
40-
const dataTransfer = await page.evaluateHandle(
41-
() => new DataTransfer()
42-
);
4341
await page.hover(".leaflet-top.leaflet-right");
44-
await page.dispatchEvent("[draggable='true']", "dragstart", {
45-
dataTransfer,
46-
});
47-
await page.dispatchEvent(".leaflet-top.leaflet-right", "drop", {
48-
dataTransfer,
49-
});
42+
let control = await page.$("xpath=//html/body/map >> css=div > div.leaflet-control-container > div.leaflet-top.leaflet-right > div > section > div.leaflet-control-layers-overlays > fieldset:nth-child(1)");
43+
let controlBBox = await control.boundingBox();
44+
await page.mouse.move(controlBBox.x + controlBBox.width / 2, controlBBox.y + controlBBox.height / 2);
45+
await page.mouse.down();
46+
await page.mouse.move(50, 50);
47+
await page.mouse.up();
5048
await page.hover(".leaflet-top.leaflet-right");
51-
let vars = await page.$$("[draggable='true']");
52-
expect(vars.length).toBe(2);
49+
let vars = await page.$$("xpath=//html/body/map >> css=div > div.leaflet-control-container > div.leaflet-top.leaflet-right > div > section > div.leaflet-control-layers-overlays > fieldset");
50+
expect(vars.length).toBe(3);
5351
});
5452

55-
/* Comment in later on
56-
test("drag and drop of null object", async () => {
57-
const dataTransfer = await page.evaluateHandle(
58-
() => new DataTransfer()
59-
);
60-
await page.hover(".leaflet-top.leaflet-right");
61-
await page.dispatchEvent(".leaflet-control-zoom-in", "dragstart", {
62-
dataTransfer,
63-
});
64-
await page.dispatchEvent(".leaflet-top.leaflet-right", "drop", {
65-
dataTransfer,
53+
test("[" + browserType + "]" + " Moving layer down one in control overlay", async () => {
54+
await page.hover(".leaflet-top.leaflet-right");
55+
let control = await page.$("xpath=//html/body/map >> css=div > div.leaflet-control-container > div.leaflet-top.leaflet-right > div > section > div.leaflet-control-layers-overlays > fieldset:nth-child(1)");
56+
let controlBBox = await control.boundingBox();
57+
await page.mouse.move(controlBBox.x + controlBBox.width / 2, controlBBox.y + controlBBox.height / 2);
58+
await page.mouse.down();
59+
await page.mouse.move(controlBBox.x + controlBBox.width / 2, (controlBBox.y + controlBBox.height / 2) + 45);
60+
await page.mouse.up();
61+
await page.hover(".leaflet-top.leaflet-right");
62+
63+
const controlText = await page.$eval(
64+
"xpath=//html/body/map >> css=div > div.leaflet-control-container > div.leaflet-top.leaflet-right > div > section > div.leaflet-control-layers-overlays > fieldset:nth-child(2) > details > summary > label > span",
65+
(span) => span.innerText
66+
);
67+
const layerIndex = await page.$eval(
68+
"xpath=//html/body/map >> css=div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-overlay-pane > div:nth-child(1)",
69+
(div) => div.style.zIndex
70+
);
71+
const domLayer = await page.$eval(
72+
"body > map > layer-:nth-child(4)",
73+
(div) => div.label
74+
);
75+
76+
expect(controlText.toLowerCase()).toContain(domLayer.toLowerCase());
77+
expect(layerIndex).toEqual("2");
78+
expect(controlText).toBe(" Canada Base Map - Transportation (CBMT)");
6679
});
67-
await page.hover(".leaflet-top.leaflet-right");
68-
let vars = await page.$$("[draggable='true']");
69-
expect(vars.length).toBe(1);
70-
});
71-
*/
7280

73-
//adding layer in html can add any type of layer the user wants,
74-
//but how should that layer get treated by the map element,
75-
//should it be ignored or shown as undefined
76-
/*
77-
test("HTML - add additional MapML Layer", async () => {
78-
const { document } = new JSDOM(`
79-
<!doctype html>
80-
<html>
81-
<head>
82-
<title>index-map.html</title>
83-
<meta charset="UTF-8">
84-
<script type="module" src="dist/web-map.js"></script>
85-
<style>
86-
html {height: 100%} body,map {height: inherit} * {margin: 0;padding: 0;}
87-
</style>
88-
</head>
89-
<body>
90-
<map is="web-map" projection="CBMTILE" zoom="2" lat="45" lon="-90" controls >
91-
<layer- label='CBMT' src='https://geogratis.gc.ca/mapml/en/cbmtile/cbmt/' checked></layer->
92-
<layer- label='CBMT' src='https://geogratis.gc.ca/mapml/en/cbmtile/cbmt/' checked></layer->
93-
</map>
94-
</body>
95-
</html>
96-
`).window;
97-
const { select, update } = await domToPlaywright(page, document);
81+
test("[" + browserType + "]" + " Moving layer up one in control overlay", async () => {
82+
await page.hover(".leaflet-top.leaflet-right");
83+
let control = await page.$("xpath=//html/body/map >> css=div > div.leaflet-control-container > div.leaflet-top.leaflet-right > div > section > div.leaflet-control-layers-overlays > fieldset:nth-child(2)");
84+
let controlBBox = await control.boundingBox();
85+
await page.mouse.move(controlBBox.x + controlBBox.width / 2, controlBBox.y + controlBBox.height / 2);
86+
await page.mouse.down();
87+
await page.mouse.move(controlBBox.x + controlBBox.width / 2, (controlBBox.y + controlBBox.height / 2) - 45);
88+
await page.mouse.up();
89+
await page.hover(".leaflet-top.leaflet-right");
9890

99-
await update(document);
100-
await page.hover(".leaflet-top.leaflet-right");
101-
let vars = await page.$$("[draggable='true']");
102-
expect(vars.length).toBe(2);
103-
});
104-
test("HTML - add additional non-MapML Layer", async () => {
105-
const { document } = new JSDOM(`
106-
<!doctype html>
107-
<html>
108-
<head>
109-
<title>index-map.html</title>
110-
<meta charset="UTF-8">
111-
<script type="module" src="dist/web-map.js"></script>
112-
<style>
113-
html {height: 100%} body,map {height: inherit} * {margin: 0;padding: 0;}
114-
</style>
115-
</head>
116-
<body>
117-
<map is="web-map" projection="CBMTILE" zoom="2" lat="45" lon="-90" controls >
118-
<layer- label='CBMT' src='https://geogratis.gc.ca/mapml/en/cbmtile/cbmt/' checked></layer->
119-
<layer- label='CBMT' src='https://example.com/' checked></layer->
120-
</map>
121-
</body>
122-
</html>
123-
`).window;
124-
const { select, update } = await domToPlaywright(page, document);
91+
const controlText = await page.$eval(
92+
"xpath=//html/body/map >> css=div > div.leaflet-control-container > div.leaflet-top.leaflet-right > div > section > div.leaflet-control-layers-overlays > fieldset:nth-child(1) > details > summary > label > span",
93+
(span) => span.innerText
94+
);
95+
const layerIndex = await page.$eval(
96+
"xpath=//html/body/map >> css=div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-overlay-pane > div:nth-child(2)",
97+
(div) => div.style.zIndex
98+
);
99+
const domLayer = await page.$eval(
100+
"body > map > layer-:nth-child(3)",
101+
(div) => div.label
102+
);
103+
104+
expect(controlText.toLowerCase()).toContain(domLayer.toLowerCase());
105+
expect(layerIndex).toEqual("1");
106+
expect(controlText).toBe(" Static MapML With Tiles");
107+
});
125108

126-
await update(document);
127-
await page.hover(".leaflet-top.leaflet-right");
128-
let vars = await page.$$("[draggable='true']");
129-
expect(vars.length).toBe(1);
130-
});
131-
*/
132109
});
133110
}
134-
})();
111+
})();

0 commit comments

Comments
 (0)