Skip to content

Commit 6ded79f

Browse files
prushforthprushforAliyanH
authored
Fix error that occurs when removing and reattaching map from document (#827)
* First pass at fixing #813, detach / re-attach map/mapml-viewer fails. * Add test for (script) deletes, adds back a map or mapml-viewer to DOM * make tests fail if errors get thrown * Revert a change to web-map-index.html to allow it to work in place --------- Co-authored-by: prushfor <[email protected]> Co-authored-by: AliyanH <[email protected]>
1 parent 930b852 commit 6ded79f

File tree

7 files changed

+114
-28
lines changed

7 files changed

+114
-28
lines changed

index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
</noscript>
7373
</head>
7474
<body>
75-
<mapml-viewer projection="CBMTILE" zoom="2" lat="45" lon="-90" controls controlslist="geolocation">
75+
<mapml-viewer projection="CBMTILE" zoom="2" lat="45" lon="-90" controls controlslist="nofullscreen geolocation">
7676
<map-caption>A pleasing map of Canada</map-caption>
7777
<layer- label="CBMT" src="https://geogratis.gc.ca/mapml/en/cbmtile/cbmt/" checked></layer->
7878
<layer- label="Hat Guy" checked>

src/layer.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ export class MapLayer extends HTMLElement {
210210
}
211211
_validateDisabled() {
212212
setTimeout(() => {
213-
let layer = this._layer, map = layer._map;
213+
let layer = this._layer, map = layer?._map;
214214
if (map) {
215215
let count = 0, total=0, layerTypes = ["_staticTileLayer","_imageLayer","_mapmlvectors","_templatedLayer"];
216216
if(layer.validProjection){

src/mapml-viewer.js

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -130,17 +130,14 @@ export class MapViewer extends HTMLElement {
130130
}
131131
connectedCallback() {
132132

133-
this._createShadowRoot();
133+
this._initShadowRoot();
134134

135135
this._controlsList = new DOMTokenList(
136136
this.getAttribute("controlslist"),
137137
this, "controlslist",
138138
["noreload","nofullscreen","nozoom","nolayer","noscale","geolocation"]
139139
);
140140

141-
// the dimension attributes win, if they're there. A map does not
142-
// have an intrinsic size, unlike an image or video, and so must
143-
// have a defined width and height.
144141
var s = window.getComputedStyle(this),
145142
wpx = s.width, hpx=s.height,
146143
w = this.hasAttribute("width") ? this.getAttribute("width") : parseInt(wpx.replace('px','')),
@@ -195,11 +192,14 @@ export class MapViewer extends HTMLElement {
195192
}, 0);
196193
}
197194
}
198-
_createShadowRoot() {
195+
_initShadowRoot() {
196+
if (!this.shadowRoot) {
197+
this.attachShadow({mode: 'open'});
198+
}
199199
let tmpl = document.createElement('template');
200200
tmpl.innerHTML = `<link rel="stylesheet" href="${new URL("mapml.css", import.meta.url).href}">`; // jshint ignore:line
201201

202-
let shadowRoot = this.attachShadow({mode: 'open'});
202+
let shadowRoot = this.shadowRoot;
203203
this._container = document.createElement('div');
204204

205205
let output = "<output role='status' aria-live='polite' aria-atomic='true' class='mapml-screen-reader-output'></output>";
@@ -277,8 +277,11 @@ export class MapViewer extends HTMLElement {
277277
}
278278
}
279279
disconnectedCallback() {
280-
//this._removeEvents();
280+
while (this.shadowRoot.firstChild) {
281+
this.shadowRoot.removeChild(this.shadowRoot.firstChild);
282+
}
281283
delete this._map;
284+
this._deleteControls();
282285
}
283286
adoptedCallback() {
284287
// console.log('Custom map element moved to new page.');
@@ -431,6 +434,15 @@ export class MapViewer extends HTMLElement {
431434
}
432435
}
433436

437+
// delete the map controls that are private properties of this custom element
438+
_deleteControls() {
439+
delete this._layerControl;
440+
delete this._zoomControl;
441+
delete this._reloadButton;
442+
delete this._fullScreenControl;
443+
delete this._geolocationButton;
444+
delete this._scaleBar;
445+
}
434446
// Sets the control's visibility AND all its childrens visibility,
435447
// for the control element based on the Boolean hide parameter
436448
_setControlsVisibility(control, hide) {

src/web-map.js

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,17 +134,14 @@ export class WebMap extends HTMLMapElement {
134134
}
135135
connectedCallback() {
136136

137-
this._createShadowRoot();
137+
this._initShadowRoot();
138138

139139
this._controlsList = new DOMTokenList(
140140
this.getAttribute("controlslist"),
141141
this, "controlslist",
142142
["noreload","nofullscreen","nozoom","nolayer","noscale","geolocation"]
143143
);
144144

145-
// the dimension attributes win, if they're there. A map does not
146-
// have an intrinsic size, unlike an image or video, and so must
147-
// have a defined width and height.
148145
var s = window.getComputedStyle(this),
149146
wpx = s.width, hpx=s.height,
150147
w = this.hasAttribute("width") ? this.getAttribute("width") : parseInt(wpx.replace('px','')),
@@ -197,7 +194,7 @@ export class WebMap extends HTMLMapElement {
197194
}, 0);
198195
}
199196
}
200-
_createShadowRoot() {
197+
_initShadowRoot() {
201198
let tmpl = document.createElement('template');
202199
tmpl.innerHTML = `<link rel="stylesheet" href="${new URL("mapml.css", import.meta.url).href}">`; // jshint ignore:line
203200

@@ -319,8 +316,13 @@ export class WebMap extends HTMLMapElement {
319316
}
320317
}
321318
disconnectedCallback() {
322-
//this._removeEvents();
319+
let rootDiv = this.querySelector('.mapml-web-map');
320+
while (rootDiv.shadowRoot.firstChild) {
321+
rootDiv.shadowRoot.removeChild(rootDiv.shadowRoot.firstChild);
322+
}
323+
rootDiv.remove();
323324
delete this._map;
325+
this._deleteControls();
324326
}
325327
adoptedCallback() {
326328
// console.log('Custom map element moved to new page.');
@@ -473,6 +475,15 @@ export class WebMap extends HTMLMapElement {
473475
}
474476
}
475477

478+
// delete the map controls that are private properties of this custom element
479+
_deleteControls() {
480+
delete this._layerControl;
481+
delete this._zoomControl;
482+
delete this._reloadButton;
483+
delete this._fullScreenControl;
484+
delete this._geolocationButton;
485+
delete this._scaleBar;
486+
}
476487
// Sets the control's visibility AND all its childrens visibility,
477488
// for the control element based on the Boolean hide parameter
478489
_setControlsVisibility(control, hide) {

test/e2e/api/domApi-mapml-viewer.test.js

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ test.describe("mapml-viewer DOM API Tests", () => {
1919
let errorLogs = [];
2020
page.on("pageerror", (err) => {
2121
errorLogs.push(err.message);
22-
})
22+
});
2323
const viewerHandle = await page.evaluateHandle(()=> document.createElement("mapml-viewer"));
2424
const nn = await (await page.evaluateHandle(viewer => viewer.nodeName, viewerHandle)).jsonValue();
2525
expect(nn).toEqual('MAPML-VIEWER');
@@ -78,6 +78,37 @@ test.describe("mapml-viewer DOM API Tests", () => {
7878
(tileDiv) => tileDiv.firstChild.nodeName === "IMG");
7979
expect(layerVisible).toBe(true);
8080
});
81+
82+
test("Remove mapml-viewer from DOM, add it back in", async () => {
83+
// check for error messages in console
84+
let errorLogs = [];
85+
page.on("pageerror", (err) => {
86+
errorLogs.push(err.message);
87+
});
88+
// locators avoid flaky tests, allegedly
89+
const viewer = await page.locator('mapml-viewer');
90+
await viewer.evaluate(()=>{
91+
});
92+
expect(await viewer.evaluate(() => {
93+
let m = document.querySelector('mapml-viewer');
94+
document.body.removeChild(m);
95+
document.body.appendChild(m);
96+
let l = m.querySelector('layer-');
97+
return l.label;
98+
// the label attribute is ignored if the mapml document has a map-title
99+
// element, which is the case here. Since the layer loads over the
100+
// network, it means that the map is back to normal after being re-added
101+
/// to the DOM, if the label reads as follows:
102+
})).toEqual("Canada Base Map - Transportation (CBMT)");
103+
// takes a couple of seconds for the tiles to load
104+
105+
// check for error messages in console
106+
expect(errorLogs.length).toBe(0);
107+
108+
await page.waitForLoadState('networkidle');
109+
const layerTile = await page.locator("body > mapml-viewer .leaflet-tile-loaded:nth-child(1)");
110+
expect(await layerTile.evaluate(tile=>tile.firstChild.nodeName === "IMG")).toBe(true);
111+
});
81112

82113
test("Toggle all mapml-viewer controls by adding or removing controls attribute", async () => {
83114
await page.evaluateHandle(() => document.querySelector('layer-').setAttribute("hidden",""));
@@ -226,7 +257,7 @@ test.describe("mapml-viewer DOM API Tests", () => {
226257
// remove map for next test
227258
await page.evaluateHandle(() => document.querySelector('mapml-viewer').remove());
228259
});
229-
260+
230261
test.describe("controlslist test", () => {
231262

232263
test("map created with controlslist", async () => {
@@ -495,7 +526,7 @@ test.describe("mapml-viewer DOM API Tests", () => {
495526
return viewer.defineCustomProjection(template);
496527
}, viewerHandle);
497528
expect(custProj).toEqual("basic");
498-
await page.evaluate( (viewer) => {viewer.projection = "basic"}, viewerHandle);
529+
await page.evaluate( (viewer) => {viewer.projection = "basic";}, viewerHandle);
499530

500531
// Toggle Controls (T) contextmenu is disabled
501532
let toggleControlsBtn = await page.$eval("div > div.mapml-contextmenu > button:nth-child(10)",(btn) => btn.disabled);

test/e2e/api/domApi-web-map.test.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,36 @@ test.describe("web-map DOM API Tests", () => {
7979
(tileDiv) => tileDiv.firstChild.nodeName === "IMG");
8080
expect(layerVisible).toBe(true);
8181
});
82+
test("Remove map from DOM, add it back in", async () => {
83+
// check for error messages in console
84+
let errorLogs = [];
85+
page.on("pageerror", (err) => {
86+
errorLogs.push(err.message);
87+
});
88+
// locators avoid flaky tests, allegedly
89+
const viewer = await page.locator('map');
90+
await viewer.evaluate(()=>{
91+
});
92+
expect(await viewer.evaluate(() => {
93+
let m = document.querySelector('map');
94+
document.body.removeChild(m);
95+
document.body.appendChild(m);
96+
let l = m.querySelector('layer-');
97+
return l.label;
98+
// the label attribute is ignored if the mapml document has a map-title
99+
// element, which is the case here. Since the layer loads over the
100+
// network, it means that the map is back to normal after being re-added
101+
/// to the DOM, if the label reads as follows:
102+
})).toEqual("Canada Base Map - Transportation (CBMT)");
103+
// takes a couple of seconds for the tiles to load
104+
105+
// check for error messages in console
106+
expect(errorLogs.length).toBe(0);
107+
108+
await page.waitForLoadState('networkidle');
109+
const layerTile = await page.locator("body > map .leaflet-tile-loaded:nth-child(1)");
110+
expect(await layerTile.evaluate(tile=>tile.firstChild.nodeName === "IMG")).toBe(true);
111+
});
82112

83113
test("Toggle all web map controls by adding or removing controls attribute", async () => {
84114
await page.evaluateHandle(() => document.querySelector('layer-').setAttribute("hidden",""));

web-map-index.html

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,17 @@
2020
(e.g. when scripting is disabled or when custom/built-in elements isn't
2121
supported in the browser). */
2222
map[is="web-map"]:defined {
23-
/* Responsive map. */
24-
/* max-width: 100%; */
25-
26-
/* Full viewport. */
27-
/* width: 100%; */
28-
/* height: 100%; */
29-
30-
/* Remove default (native-like) border. */
31-
/* border: none; */
23+
/* Responsive map. */
24+
max-width: 100%;
25+
26+
/* Full viewport. */
27+
width: 100%;
28+
height: 100%;
29+
30+
/* Remove default (native-like) border. */
31+
border: none;
32+
33+
vertical-align: middle;
3234
}
3335

3436
/* Pre-style to avoid Layout Shift. */
@@ -71,7 +73,7 @@
7173
</noscript>
7274
</head>
7375
<body>
74-
<map is="web-map" projection="CBMTILE" zoom="2" lat="45" lon="-90" height = "600" width = "600" controls>
76+
<map is="web-map" projection="CBMTILE" zoom="2" lat="45" lon="-90" controls controlslist="nofullscreen geolocation">
7577
<layer- label="CBMT" src="https://geogratis.gc.ca/mapml/en/cbmtile/cbmt/" checked></layer->
7678
</map>
7779
</body>

0 commit comments

Comments
 (0)