leaflet-html 0.1.7 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
package/src/index.js CHANGED
@@ -1,191 +1,32 @@
1
1
  // @ts-check
2
+ import LBaseLayers from "./l-base-layers.js";
3
+ import LControlLayers from "./l-control-layers.js";
4
+ import LLayerGroup from "./l-layer-group.js";
5
+ import LMap from "./l-map.js";
6
+ import LMarker from "./l-marker.js";
7
+ import LOverlayLayers from "./l-overlay-layers.js";
8
+ import LPopup from "./l-popup.js";
9
+ import LTileLayer from "./l-tile-layer.js";
10
+ import LLatLngBounds from "./l-lat-lng-bounds.js";
11
+ import LImageOverlay from "./l-image-overlay.js";
12
+ import LVideoOverlay from "./l-video-overlay.js";
13
+ import LGeoJSON from "./l-geojson.js";
2
14
 
3
- // Helpers
4
- const selector = (noun) => `[data-${noun}]`;
5
-
6
- /**
7
- * Parse L.tileLayer args from element attributes
8
- */
9
- const parseTileLayer = (el) => {
10
- const { urlTemplate } = el.dataset;
11
- const {
12
- attribution = null,
13
- maxZoom = "18",
14
- minZoom = "0",
15
- subdomains = "abc",
16
- } = el.dataset;
17
- const options = { attribution, maxZoom, minZoom, subdomains };
18
- return [urlTemplate, options];
19
- };
20
-
21
- /**
22
- * Parse L.imageOverlay args from element attributes
23
- */
24
- const parseImageOverlay = (el) => {
25
- let { url, bounds } = el.dataset;
26
- bounds = JSON.parse(bounds);
27
- const { opacity } = el.dataset;
28
- const options = { opacity: parseFloat(opacity) };
29
- return [url, bounds, options];
30
- };
31
-
32
- /**
33
- * Parse L.imageOverlay args from element attributes
34
- */
35
- const parseVideoOverlay = (el) => {
36
- let { url, bounds } = el.dataset;
37
- url = JSON.parse(url);
38
- bounds = JSON.parse(bounds);
39
- const {
40
- opacity,
41
- errorOverlayUrl,
42
- autoplay = true,
43
- muted = true,
44
- playsInline = true,
45
- } = el.dataset;
46
- const options = {
47
- opacity: parseFloat(opacity),
48
- errorOverlayUrl,
49
- autoplay,
50
- muted,
51
- playsInline,
52
- };
53
- return [url, bounds, options];
54
- };
55
-
56
- /**
57
- * @param {HTMLElement} el
58
- */
59
- const parseLatLngBounds = (el) => {
60
- let { bounds } = el.dataset;
61
- if (typeof bounds === "undefined") {
62
- throw Error("data-bounds not specified")
63
- }
64
- return [JSON.parse(bounds)];
65
- }
66
-
67
- const render = () => {
68
- // Render Leaflet API calls
69
- document.querySelectorAll(selector("leaflet-html")).forEach((el) => {
70
- const { center, zoom } = el.dataset;
71
- const map = L.map(el).setView(JSON.parse(center), parseInt(zoom));
72
-
73
- // L.latLngBounds
74
- el.querySelectorAll(selector("lat-lng-bounds")).forEach((el) => {
75
- const bounds = L.latLngBounds(...parseLatLngBounds(el))
76
- // TODO: encapsulate this design pattern
77
- const observer = new MutationObserver(function (mutations) {
78
- mutations.forEach((mutation) => {
79
- let [bounds] = parseLatLngBounds(mutation.target)
80
- map.flyToBounds(bounds); // TODO: Use HTML attrs for fly/fit bounds
81
- });
82
- });
83
- observer.observe(el, {
84
- attributes: true,
85
- attributeFilter: ["data-bounds"],
86
- });
87
- map.fitBounds(bounds)
88
- })
89
-
90
- // L.tileLayers
91
- el.querySelectorAll(selector("tile-layer")).forEach((el) => {
92
- L.tileLayer(...parseTileLayer(el)).addTo(map);
93
- });
94
-
95
- el.querySelectorAll(selector("image-overlay")).forEach((el) => {
96
- L.imageOverlay(...parseImageOverlay(el)).addTo(map);
97
- });
98
-
99
- el.querySelectorAll(selector("video-overlay")).forEach((el) => {
100
- L.videoOverlay(...parseVideoOverlay(el)).addTo(map);
101
- });
102
-
103
- // L.control.layers
104
- el.querySelectorAll(selector("control-layers")).forEach((el) => {
105
- const baseMaps = {};
106
-
107
- // L.tileLayers
108
- el.querySelectorAll(selector("tile-layer")).forEach((el) => {
109
- const { name, show } = el.dataset;
110
- baseMaps[name] = L.tileLayer(...parseTileLayer(el));
111
- if (show != null) {
112
- baseMaps[name].addTo(map);
113
- }
114
- });
115
-
116
- const overlayMaps = {};
117
- // L.layerGroup
118
- el.querySelectorAll(selector("layer-group")).forEach((el) => {
119
- const { name } = el.dataset;
120
- const layers = [];
121
-
122
- const observer = new MutationObserver(function (mutations) {
123
- const group = overlayMaps[name];
124
-
125
- mutations.forEach((mutation) => {
126
- mutation.addedNodes.forEach((node) => {
127
- const { latLng } = node.dataset; // MutationObserver needed
128
- const layer = L.marker(JSON.parse(latLng));
129
- group.addLayer(layer);
130
- map.addLayer(layer);
131
- });
132
-
133
- mutation.removedNodes.forEach((node) => {
134
- const { _leafletId } = node.dataset;
135
- const layer = group.getLayer(_leafletId);
136
- group.removeLayer(layer);
137
-
138
- map.removeLayer(layer);
139
- });
140
- });
141
- });
142
- observer.observe(el, { childList: true });
143
-
144
- // L.marker
145
- el.querySelectorAll(selector("marker")).forEach((el) => {
146
- const { latLng } = el.dataset;
147
- const { opacity = "1.0" } = el.dataset;
148
- const options = { opacity: parseFloat(opacity) };
149
- const marker = L.marker(JSON.parse(latLng), options).addTo(map);
150
- el.dataset._leafletId = L.stamp(marker); // Save ID for later
151
-
152
- const observer = new MutationObserver(function (mutations) {
153
- mutations.forEach((mutation) => {
154
- const { latLng } = mutation.target.dataset;
155
- marker.setLatLng(JSON.parse(latLng));
156
- });
157
- });
158
- observer.observe(el, {
159
- attributes: true,
160
- attributeFilter: ["data-lat-lng"],
161
- });
162
-
163
- // marker.bindPopup
164
- el.querySelectorAll(selector("popup")).forEach((el) => {
165
- const { content } = el.dataset;
166
- marker.bindPopup(content);
167
- const observer = new MutationObserver(function () {
168
- marker.getPopup().setContent(el.dataset.content);
169
- });
170
- observer.observe(el, {
171
- attributes: true,
172
- attributeFilter: ["data-content"],
173
- });
174
- });
175
-
176
- layers.push(marker);
177
- });
178
-
179
- overlayMaps[name] = L.layerGroup(layers);
180
- });
181
-
182
- L.control.layers(baseMaps, overlayMaps).addTo(map);
183
- });
184
- });
185
- };
186
15
 
187
16
  const init = (() => {
188
- document.addEventListener("DOMContentLoaded", render);
17
+ // Custom elements (order of definition is important)
18
+ customElements.define("l-map", LMap)
19
+ customElements.define("l-control-layers", LControlLayers)
20
+ customElements.define("l-base-layers", LBaseLayers)
21
+ customElements.define("l-overlay-layers", LOverlayLayers)
22
+ customElements.define("l-layer-group", LLayerGroup)
23
+ customElements.define("l-tile-layer", LTileLayer)
24
+ customElements.define("l-marker", LMarker)
25
+ customElements.define("l-popup", LPopup)
26
+ customElements.define("l-lat-lng-bounds", LLatLngBounds)
27
+ customElements.define("l-image-overlay", LImageOverlay)
28
+ customElements.define("l-video-overlay", LVideoOverlay)
29
+ customElements.define("l-geojson", LGeoJSON)
189
30
  })();
190
31
 
191
32
  export default init;
@@ -0,0 +1,16 @@
1
+ import { mapAddTo } from "./events.js"
2
+
3
+ class LBaseLayers extends HTMLElement {
4
+ constructor() {
5
+ super()
6
+ }
7
+
8
+ connectedCallback() {
9
+ this.addEventListener(mapAddTo, (ev) => {
10
+ ev.detail["type"] = "base"
11
+ })
12
+ }
13
+ }
14
+
15
+ export default LBaseLayers
16
+
@@ -0,0 +1,34 @@
1
+ import { mapAddTo } from "./events.js"
2
+
3
+ class LControlLayers extends HTMLElement {
4
+ constructor() {
5
+ super()
6
+ }
7
+
8
+ connectedCallback() {
9
+ const base = {}
10
+ const overlay = {}
11
+ const control = L.control.layers(base, overlay)
12
+
13
+ this.addEventListener(mapAddTo, (ev) => {
14
+ const { type, name, layer } = ev.detail
15
+ if (type === "overlay") {
16
+ control.addOverlay(layer, name)
17
+ } else if (type === "base") {
18
+ control.addBaseLayer(layer, name)
19
+ }
20
+ ev.preventDefault()
21
+ })
22
+
23
+ const event = new CustomEvent(mapAddTo, {
24
+ cancelable: true,
25
+ bubbles: true,
26
+ detail: {
27
+ layer: control
28
+ }
29
+ })
30
+ this.dispatchEvent(event)
31
+ }
32
+ }
33
+
34
+ export default LControlLayers
@@ -0,0 +1,20 @@
1
+ import { mapAddTo } from "./events.js"
2
+
3
+ class LGeoJSON extends HTMLElement {
4
+ constructor() {
5
+ super()
6
+ }
7
+
8
+ connectedCallback() {
9
+ const layer = L.geoJSON(JSON.parse(this.getAttribute("geojson")))
10
+ this.dispatchEvent(new CustomEvent(mapAddTo, {
11
+ bubbles: true,
12
+ cancelable: true,
13
+ detail: {
14
+ layer
15
+ }
16
+ }))
17
+ }
18
+ }
19
+
20
+ export default LGeoJSON
@@ -0,0 +1,41 @@
1
+ import { mapAddTo } from "./events.js"
2
+
3
+ class LImageOverlay extends HTMLElement {
4
+ static observedAttributes = ["url", "bounds", "opacity"]
5
+
6
+ constructor() {
7
+ super()
8
+ this.layer = null
9
+ }
10
+
11
+ connectedCallback() {
12
+ const url = this.getAttribute("url")
13
+ const bounds = JSON.parse(this.getAttribute("bounds"))
14
+ const options = {
15
+ opacity: parseFloat(this.getAttribute("opacity") || "1.0"),
16
+ alt: this.getAttribute("alt") || ""
17
+ }
18
+ this.layer = L.imageOverlay(url, bounds, options)
19
+ this.dispatchEvent(new CustomEvent(mapAddTo, {
20
+ cancelable: true,
21
+ bubbles: true,
22
+ detail: {
23
+ layer: this.layer
24
+ }
25
+ }))
26
+ }
27
+
28
+ attributeChangedCallback(name, _oldValue, newValue) {
29
+ if (this.layer !== null) {
30
+ if (name === "url") {
31
+ this.layer.setUrl(newValue)
32
+ } else if (name === "bounds") {
33
+ this.layer.setBounds(JSON.parse(newValue))
34
+ } else if (name === "opacity") {
35
+ this.layer.setOpacity(parseFloat(newValue))
36
+ }
37
+ }
38
+ }
39
+ }
40
+
41
+ export default LImageOverlay
@@ -0,0 +1,20 @@
1
+ class LLatLngBounds extends HTMLElement {
2
+ static observedAttributes = ["bounds"]
3
+
4
+ constructor() {
5
+ super()
6
+ }
7
+
8
+ attributeChangedCallback(_name, _oldValue, newValue) {
9
+ const event = new CustomEvent("map:bounds", {
10
+ bubbles: true,
11
+ detail: {
12
+ bounds: JSON.parse(newValue),
13
+ method: this.getAttribute("method") || "fitBounds"
14
+ }
15
+ })
16
+ this.dispatchEvent(event)
17
+ }
18
+ }
19
+
20
+ export default LLatLngBounds
@@ -0,0 +1,39 @@
1
+ import { mapAddTo } from "./events.js"
2
+
3
+ class LLayerGroup extends HTMLElement {
4
+ constructor() {
5
+ super()
6
+ }
7
+
8
+ connectedCallback() {
9
+ const name = this.getAttribute("name")
10
+ const group = L.layerGroup()
11
+ const event = new CustomEvent(mapAddTo, {
12
+ cancelable: true,
13
+ bubbles: true,
14
+ detail: {
15
+ layer: group,
16
+ name
17
+ }
18
+ })
19
+ this.dispatchEvent(event)
20
+
21
+ this.addEventListener(mapAddTo, (ev) => {
22
+ ev.stopPropagation()
23
+ group.addLayer(ev.detail.layer)
24
+ })
25
+
26
+ const observer = new MutationObserver(function(mutations) {
27
+ mutations.forEach((mutation) => {
28
+ mutation.removedNodes.forEach((node) => {
29
+ const leafletId = node.getAttribute("leaflet-id");
30
+ const layer = group.getLayer(leafletId);
31
+ group.removeLayer(layer);
32
+ });
33
+ });
34
+ });
35
+ observer.observe(this, { childList: true })
36
+ }
37
+ }
38
+
39
+ export default LLayerGroup
package/src/l-map.js ADDED
@@ -0,0 +1,33 @@
1
+ // @ts-check
2
+ import { layerRemove, mapAddTo } from "./events.js"
3
+
4
+ class LMap extends HTMLElement {
5
+ constructor() {
6
+ super()
7
+
8
+ this.map = null
9
+ this.addEventListener("map:bounds", (ev) => {
10
+ const { bounds, method } = ev.detail
11
+ this.map[method](bounds)
12
+ })
13
+ }
14
+
15
+ connectedCallback() {
16
+ this.map = L.map(this)
17
+ const center = this.getAttribute("center")
18
+ const zoom = this.getAttribute("zoom")
19
+ if ((center !== null) && (zoom !== null)) {
20
+ this.map.setView(JSON.parse(center), parseInt(zoom))
21
+ }
22
+ this.addEventListener(mapAddTo, (ev) => {
23
+ const layer = ev.detail.layer
24
+ layer.addTo(this.map)
25
+ })
26
+
27
+ this.addEventListener(layerRemove, (ev) => {
28
+ this.map.remove(ev.detail.layer)
29
+ })
30
+ }
31
+ }
32
+
33
+ export default LMap
@@ -0,0 +1,45 @@
1
+ import { mapAddTo, popupAdd } from "./events.js";
2
+
3
+ class LMarker extends HTMLElement {
4
+ static observedAttributes = ["lat-lng", "opacity"]
5
+
6
+ constructor() {
7
+ super()
8
+ this.layer = null
9
+ }
10
+
11
+ connectedCallback() {
12
+ const latLng = JSON.parse(this.getAttribute("lat-lng"))
13
+ const opacity = parseFloat(this.getAttribute("opacity") || "1.0")
14
+ this.layer = L.marker(latLng, { opacity });
15
+ this.setAttribute("leaflet-id", L.stamp(this.layer))
16
+
17
+ this.addEventListener(popupAdd, (ev) => {
18
+ const { content } = ev.detail
19
+ this.layer.bindPopup(content)
20
+ })
21
+
22
+ const event = new CustomEvent(mapAddTo, {
23
+ cancelable: true,
24
+ bubbles: true,
25
+ detail: {
26
+ layer: this.layer
27
+ }
28
+ })
29
+ this.dispatchEvent(event)
30
+ }
31
+
32
+ attributeChangedCallback(name, _oldValue, newValue) {
33
+ if (this.layer !== null) {
34
+ if (name === "lat-lng") {
35
+ this.layer.setLatLng(JSON.parse(newValue))
36
+ }
37
+ if (name === "opacity") {
38
+ this.layer.setOpacity(parseFloat(newValue))
39
+ }
40
+ }
41
+ }
42
+ }
43
+
44
+
45
+ export default LMarker
@@ -0,0 +1,15 @@
1
+ import { mapAddTo } from "./events.js"
2
+
3
+ class LOverlayLayers extends HTMLElement {
4
+ constructor() {
5
+ super()
6
+ }
7
+
8
+ connectedCallback() {
9
+ this.addEventListener(mapAddTo, (ev) => {
10
+ ev.detail["type"] = "overlay"
11
+ })
12
+ }
13
+ }
14
+
15
+ export default LOverlayLayers
package/src/l-popup.js ADDED
@@ -0,0 +1,23 @@
1
+ import { popupAdd } from "./events.js"
2
+
3
+ class LPopup extends HTMLElement {
4
+ constructor() {
5
+ super()
6
+ }
7
+
8
+ connectedCallback() {
9
+ const content = this.getAttribute("content")
10
+ const event = new CustomEvent(popupAdd, {
11
+ cancelable: true,
12
+ bubbles: true,
13
+ detail: {
14
+ content
15
+ }
16
+ })
17
+ this.dispatchEvent(event)
18
+ }
19
+ }
20
+
21
+
22
+ export default LPopup
23
+
@@ -0,0 +1,19 @@
1
+ import { mapAddTo } from "./events.js"
2
+
3
+ class LTileLayer extends HTMLElement {
4
+ constructor() {
5
+ super()
6
+ }
7
+
8
+ connectedCallback() {
9
+ const name = this.getAttribute("name")
10
+ const urlTemplate = this.getAttribute("url-template")
11
+ const attribution = this.getAttribute("attribution")
12
+ const options = { attribution }
13
+ const layer = L.tileLayer(urlTemplate, options)
14
+ const event = new CustomEvent(mapAddTo, { detail: { name, layer }, bubbles: true})
15
+ this.dispatchEvent(event)
16
+ }
17
+ }
18
+
19
+ export default LTileLayer
@@ -0,0 +1,30 @@
1
+ import { mapAddTo } from "./events.js"
2
+
3
+ class LVideoOverlay extends HTMLElement {
4
+ constructor() {
5
+ super()
6
+ }
7
+
8
+ connectedCallback() {
9
+ const url = JSON.parse(this.getAttribute("url"))
10
+ const bounds = JSON.parse(this.getAttribute("bounds"))
11
+ const options = {
12
+ opacity: parseFloat(this.getAttribute("opacity") || "1.0"),
13
+ alt: this.getAttribute("alt") || "",
14
+ autoplay: true,
15
+ muted: true,
16
+ playsInline: true
17
+ }
18
+ const layer = L.videoOverlay(url, bounds, options)
19
+ this.dispatchEvent(new CustomEvent(mapAddTo, {
20
+ cancelable: true,
21
+ bubbles: true,
22
+ detail: {
23
+ layer
24
+ }
25
+ }))
26
+ }
27
+ }
28
+
29
+ export default LVideoOverlay
30
+