diff --git a/package.json b/package.json index 1d07ca8..dbddd1e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "leaflet-html", "type": "module", - "version": "0.11.0", + "version": "0.12.0", "description": "Leaflet maps expressed in HTML suitable for HTMX", "keywords": [ "leaflet", diff --git a/src/index.js b/src/index.js index dc34e85..825d3cc 100644 --- a/src/index.js +++ b/src/index.js @@ -7,6 +7,7 @@ import LMarker from "./l-marker.js"; import LOverlayLayers from "./l-overlay-layers.js"; import LPopup from "./l-popup.js"; import LTileLayer from "./l-tile-layer.js"; +import LTileLayerWMS from "./l-tile-layer-wms.js"; import LLatLngBounds from "./l-lat-lng-bounds.js"; import LImageOverlay from "./l-image-overlay.js"; import LVideoOverlay from "./l-video-overlay.js"; @@ -28,6 +29,7 @@ const init = (() => { customElements.define("l-overlay-layers", LOverlayLayers); customElements.define("l-layer-group", LLayerGroup); customElements.define("l-tile-layer", LTileLayer); + customElements.define("l-tile-layer-wms", LTileLayerWMS); customElements.define("l-marker-cluster-group", LMarkerClusterGroup); customElements.define("l-marker", LMarker); customElements.define("l-popup", LPopup); diff --git a/src/l-tile-layer-wms.js b/src/l-tile-layer-wms.js new file mode 100644 index 0000000..8977821 --- /dev/null +++ b/src/l-tile-layer-wms.js @@ -0,0 +1,60 @@ +// @ts-check +import { tileLayer } from "leaflet"; +import LLayer from "./l-layer.js"; +import { layerConnected } from "./events.js"; +import { htmlAttribute, optional, parse, partial } from "./parse.js"; + +class LTileLayerWMS extends LLayer { + constructor() { + super(); + this.layer = null; + } + + connectedCallback() { + const urlTemplate = parse(htmlAttribute("url-template"), this); + + const name = this.getAttribute("name"); + const schema = partial({ + // Leaflet.tileLayer default options: https://leafletjs.com/reference.html#tilelayer-wms-layers + layers: htmlAttribute("layers"), + styles: optional(htmlAttribute("styles")), + format: optional(htmlAttribute("format")), + transparent: optional(htmlAttribute("transparent")), + version: optional(htmlAttribute("version")), + crs: optional(htmlAttribute("crs")), + uppercase: optional(htmlAttribute("uppercase")), + + // Inherited option from Layer: https://leafletjs.com/reference.html#tilelayer-wms-attribution + attribution: optional(htmlAttribute("attribution")), + }); + + const standardOptions = parse(schema, this); + const nonStandardOptionsElement = this.getAttribute("options"); + const nonStandardOptions = () => { + if (nonStandardOptionsElement) { + try { + return JSON.parse(nonStandardOptionsElement); + } catch (e) { + console.error( + "Error whilst parsing JSON for options attribute in l-tile-layer-wms", + e, + ); + return {}; + } + } else { + return {}; + } + }; + + this.layer = tileLayer.wms(urlTemplate, { + ...standardOptions, + ...nonStandardOptions(), + }); + const event = new CustomEvent(layerConnected, { + detail: { name, layer: this.layer }, + bubbles: true, + }); + this.dispatchEvent(event); + } +} +export default LTileLayerWMS; diff --git a/src/l-tile-layer-wms.test.js b/src/l-tile-layer-wms.test.js new file mode 100644 index 0000000..001946b --- /dev/null +++ b/src/l-tile-layer-wms.test.js @@ -0,0 +1,93 @@ +// @vitest-environment happy-dom +import { tileLayer } from "leaflet"; +import { it, expect } from "vitest"; +import { layerConnected } from "./events"; +import "./index"; + +it("should create an l-tile-layer-wms with the correct options", async () => { + const urlTemplate = "http://ows.mundialis.de/services/service?"; + const el = document.createElement("l-tile-layer-wms"); + el.setAttribute("url-template", urlTemplate); + el.setAttribute("layers", "example-wms-layer"); + + let promise = new Promise((resolve) => { + el.addEventListener(layerConnected, (ev) => { + resolve(ev.detail); + }); + }); + document.body.appendChild(el); + + const actual = await promise; + const expected = { + name: null, + layer: tileLayer.wms(urlTemplate, { layers: "example-wms-layer" }), + }; + expect(actual).toEqual(expected); +}); + +it.each([ + ["http://example.com/wms", "styles", "default"], + ["http://example.com/wms", "format", "image/png"], + ["http://example.com/wms", "transparent", "true"], +])("should handle WMS options %s %s", (urlTemplate, key, value) => { + const el = document.createElement("l-tile-layer-wms"); + el.setAttribute("url-template", urlTemplate); + el.setAttribute("layers", "example layer ere"); + el.setAttribute(key, value); + document.body.appendChild(el); + + const actual = el.layer; + const expected = tileLayer.wms(urlTemplate, { + layers: "example layer ere", + [key]: value, + }); + expect(actual).toEqual(expected); +}); + +it("should support attribution", () => { + const urlTemplate = "http://example.com/wms"; + const attribution = "© OpenStreetMap contributors"; + const layers = "example-wms-layer"; + const el = document.createElement("l-tile-layer-wms"); + el.setAttribute("url-template", urlTemplate); + el.setAttribute("attribution", attribution); + el.setAttribute("layers", layers); + document.body.appendChild(el); + + const actual = el.layer; + const expected = tileLayer.wms(urlTemplate, { attribution, layers }); + expect(actual).toEqual(expected); +}); + +it("should parse valid JSON in the options attribute", () => { + const urlTemplate = "http://example.com/wms"; + const options = '{"height": 101, "bbox": "coords ere"}'; + const el = document.createElement("l-tile-layer-wms"); + el.setAttribute("url-template", urlTemplate); + el.setAttribute("layers", "example layer ere"); + el.setAttribute("options", options); + document.body.appendChild(el); + + const actual = el.layer; + const expected = tileLayer.wms(urlTemplate, { + layers: "example layer ere", + height: 101, + bbox: "coords ere", + }); + expect(actual).toEqual(expected); +}); + +it("should handle invalid JSON in the options attribute gracefully", () => { + const urlTemplate = "http://example.com/wms"; + const invalidJson = '{"height": 10, "bbox": "coords ere"'; // <- missing closing brace + const el = document.createElement("l-tile-layer-wms"); + el.setAttribute("url-template", urlTemplate); + el.setAttribute("layers", "example layer ere"); + el.setAttribute("options", invalidJson); + document.body.appendChild(el); + + // Expect layer creation to succeed but without additional options + const actual = el.layer; + const expected = tileLayer.wms(urlTemplate, { layers: "example layer ere" }); + expect(actual).toEqual(expected); +});