From 9bb421bfe8c4abcd59d32e65408e5d83f2f0ab50 Mon Sep 17 00:00:00 2001 From: AliyanH Date: Mon, 13 Mar 2023 11:02:11 -0400 Subject: [PATCH 1/5] initial prettier setup --- .prettierrc | 9 ++++ Gruntfile.js | 16 ++++++- package-lock.json | 105 ++++++++++++++++++++++++++-------------------- package.json | 1 + 4 files changed, 85 insertions(+), 46 deletions(-) create mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..268da43ae --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "singleQuote": true, + "overrides": [ + { + "files": ".firebaserc", + "options": { "parser": "json" } + } + ] +} diff --git a/Gruntfile.js b/Gruntfile.js index 2978a9da2..be438d19c 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -150,6 +150,19 @@ module.exports = function(grunt) { dest: 'dist/mapml.js', src: 'src/mapml/index.js' // Only one source file is permitted } + }, + prettier: { + options: { + // https://prettier.io/docs/en/options.html + progress: true + }, + files: { + src: [ + "src/**.js", + "src/mapml/**.js", + "src/mapml/**/**.js" + ] + } } }); @@ -160,8 +173,9 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-cssmin'); grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-rollup'); + grunt.loadNpmTasks('grunt-prettier'); - grunt.registerTask('test', ['jshint']); + grunt.registerTask('format', ['jshint', 'prettier']); grunt.registerTask('default', ['clean:dist', 'copy', 'jshint', 'rollup', 'uglify', 'cssmin','clean:tidyup']); grunt.registerTask('experiments',['clean:experiments','default','copy:experiments']); diff --git a/package-lock.json b/package-lock.json index 208db317c..f0c9ec99b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "grunt-contrib-uglify": "~5.0.0", "grunt-contrib-watch": "~1.1.0", "grunt-html": "^15.2.2", + "grunt-prettier": "^2.2.0", "grunt-rollup": "^11.3.0", "jest": "^26.6.3", "jest-circus": "^26.6.3", @@ -4190,21 +4191,6 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "node_modules/fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "deprecated": "\"Please update to latest v2.3 or v2.2\"", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -5086,6 +5072,19 @@ "node": ">= 8" } }, + "node_modules/grunt-prettier": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/grunt-prettier/-/grunt-prettier-2.2.0.tgz", + "integrity": "sha512-kl6+1sYEM7HezxZS0DEFYYpD7JtwsToD8ZK2kDpAd3SvHINbz2iwsghpPsmdGihUJ2FVo8XBIzACmNxBBCytqQ==", + "dev": true, + "dependencies": { + "prettier": "^2.0.5", + "progress": "^2.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/grunt-rollup": { "version": "11.3.0", "resolved": "https://registry.npmjs.org/grunt-rollup/-/grunt-rollup-11.3.0.tgz", @@ -7503,21 +7502,6 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-runner/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/jest-runner/node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -9777,6 +9761,21 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz", + "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-format": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", @@ -9855,6 +9854,15 @@ "node": ">=8" } }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/proj4": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/proj4/-/proj4-2.6.2.tgz", @@ -15498,13 +15506,6 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "dev": true, - "optional": true - }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -16183,6 +16184,16 @@ } } }, + "grunt-prettier": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/grunt-prettier/-/grunt-prettier-2.2.0.tgz", + "integrity": "sha512-kl6+1sYEM7HezxZS0DEFYYpD7JtwsToD8ZK2kDpAd3SvHINbz2iwsghpPsmdGihUJ2FVo8XBIzACmNxBBCytqQ==", + "dev": true, + "requires": { + "prettier": "^2.0.5", + "progress": "^2.0.0" + } + }, "grunt-rollup": { "version": "11.3.0", "resolved": "https://registry.npmjs.org/grunt-rollup/-/grunt-rollup-11.3.0.tgz", @@ -18008,14 +18019,6 @@ "jest-message-util": "^27.5.1" } }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true, - "peer": true - }, "get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -19757,6 +19760,12 @@ "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", "dev": true }, + "prettier": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz", + "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==", + "dev": true + }, "pretty-format": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", @@ -19816,6 +19825,12 @@ "fromentries": "^1.2.0" } }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, "proj4": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/proj4/-/proj4-2.6.2.tgz", diff --git a/package.json b/package.json index 6f9e2b7d7..88e1922fe 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "grunt-contrib-uglify": "~5.0.0", "grunt-contrib-watch": "~1.1.0", "grunt-html": "^15.2.2", + "grunt-prettier": "^2.2.0", "grunt-rollup": "^11.3.0", "jest": "^26.6.3", "jest-circus": "^26.6.3", From a7fe7fac58c5e7df05b68273b2715a93e4fa5267 Mon Sep 17 00:00:00 2001 From: AliyanH Date: Fri, 28 Apr 2023 12:19:48 -0400 Subject: [PATCH 2/5] update jshint options, and grunt task --- Gruntfile.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index fedd7e4d5..76a3d4fc0 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -49,8 +49,8 @@ module.exports = function(grunt) { }, // ensure that jshint keeps processing after an error force: true, - esversion: 11 - + esversion: 11, + laxbreak: true // used to fix differences with prettier } }, watch: { @@ -213,7 +213,7 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-rollup'); grunt.loadNpmTasks('grunt-prettier'); - grunt.registerTask('format', ['jshint', 'prettier']); + grunt.registerTask('format', ['prettier', 'jshint']); grunt.registerTask('default', ['clean:dist', 'copy:main', 'copy:images', 'jshint', 'rollup', 'uglify', 'cssmin','clean:tidyup']); grunt.registerTask('experiments',['clean:experiments','default','copy:experiments']); From ea84c4d3b775a3ee09f86bee6d964912c39505fe Mon Sep 17 00:00:00 2001 From: prushfor Date: Mon, 1 May 2023 14:55:47 -0400 Subject: [PATCH 3/5] Add test, update src files subject to prettier formatting Add replace jshint with format task in default build to include prettier Replace jshint single line hint with hint block to support prettier --- Gruntfile.js | 7 +++---- src/mapml-viewer.js | 6 +++++- src/web-map.js | 6 +++++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 76a3d4fc0..41145842b 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -196,9 +196,8 @@ module.exports = function(grunt) { }, files: { src: [ - "src/**.js", - "src/mapml/**.js", - "src/mapml/**/**.js" + "src/**/*.js", + "test/**/*.js" ] } } @@ -214,7 +213,7 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-prettier'); grunt.registerTask('format', ['prettier', 'jshint']); - grunt.registerTask('default', ['clean:dist', 'copy:main', 'copy:images', 'jshint', 'rollup', + grunt.registerTask('default', ['clean:dist', 'copy:main', 'copy:images', 'format', 'rollup', 'uglify', 'cssmin','clean:tidyup']); grunt.registerTask('experiments',['clean:experiments','default','copy:experiments']); grunt.registerTask('extension',['clean:extension','default','copy:extension']); diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index 93b606a5e..dbadc55be 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -198,7 +198,11 @@ export class MapViewer extends HTMLElement { this.attachShadow({mode: 'open'}); } let tmpl = document.createElement('template'); - tmpl.innerHTML = ``; // jshint ignore:line + /* jshint ignore:start */ + tmpl.innerHTML = ``; + /* jshint ignore:end */ let shadowRoot = this.shadowRoot; this._container = document.createElement('div'); diff --git a/src/web-map.js b/src/web-map.js index 41792a4ec..a9fa68b5f 100644 --- a/src/web-map.js +++ b/src/web-map.js @@ -197,7 +197,11 @@ export class WebMap extends HTMLMapElement { } _initShadowRoot() { let tmpl = document.createElement('template'); - tmpl.innerHTML = ``; // jshint ignore:line + /* jshint ignore:start */ + tmpl.innerHTML = ``; + /* jshint ignore:end */ const rootDiv = document.createElement('div'); rootDiv.classList.add('mapml-web-map'); From c79c068ea5224be6b0bb5ed274fc419ec9bc993a Mon Sep 17 00:00:00 2001 From: AliyanH Date: Mon, 1 May 2023 15:53:25 -0400 Subject: [PATCH 4/5] remove trailing commas --- .prettierrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.prettierrc b/.prettierrc index 268da43ae..c53976cdc 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,6 @@ { "singleQuote": true, + "trailingComma": "none", "overrides": [ { "files": ".firebaserc", From 5affeb3cc801c3d12d35de7860323f655689e702 Mon Sep 17 00:00:00 2001 From: AliyanH Date: Mon, 1 May 2023 16:09:49 -0400 Subject: [PATCH 5/5] Add formatted files --- src/layer.js | 345 +- src/map-a.js | 127 +- src/map-area.js | 101 +- src/map-caption.js | 69 +- src/map-extent.js | 46 +- src/map-feature.js | 1047 +++--- src/map-geometry.js | 14 +- src/map-input.js | 23 +- src/map-link.js | 44 +- src/map-meta.js | 21 +- src/map-properties.js | 28 +- src/map-select.js | 14 +- src/map-span.js | 28 +- src/map-style.js | 28 +- src/mapml-viewer.js | 1001 +++-- src/mapml/control/AttributionButton.js | 117 +- src/mapml/control/FullscreenButton.js | 324 +- src/mapml/control/GeolocationButton.js | 37 +- src/mapml/control/LayerControl.js | 349 +- src/mapml/control/ReloadButton.js | 27 +- src/mapml/control/ScaleBar.js | 188 +- src/mapml/features/feature.js | 287 +- src/mapml/features/featureGroup.js | 167 +- src/mapml/features/featureRenderer.js | 146 +- src/mapml/handlers/AnnounceMovement.js | 266 +- src/mapml/handlers/ContextMenu.js | 1046 +++--- src/mapml/handlers/FeatureIndex.js | 51 +- src/mapml/handlers/QueryHandler.js | 410 +- src/mapml/index.js | 1470 ++++---- src/mapml/keyboard.js | 100 +- src/mapml/layers/Crosshair.js | 71 +- src/mapml/layers/DebugOverlay.js | 164 +- src/mapml/layers/FeatureIndexOverlay.js | 439 ++- src/mapml/layers/FeatureLayer.js | 712 ++-- src/mapml/layers/ImageLayer.js | 203 +- src/mapml/layers/MapMLLayer.js | 3291 ++++++++++------- src/mapml/layers/StaticTileLayer.js | 151 +- src/mapml/layers/TemplatedFeaturesLayer.js | 583 +-- src/mapml/layers/TemplatedImageLayer.js | 461 +-- src/mapml/layers/TemplatedLayer.js | 425 ++- src/mapml/layers/TemplatedTileLayer.js | 933 ++--- src/mapml/options.js | 102 +- src/mapml/utils/Constants.js | 6 +- src/mapml/utils/DOMTokenList.js | 180 +- src/mapml/utils/Util.js | 1771 +++++---- src/web-map.js | 1021 +++-- test/e2e/api/domApi-mapml-viewer.test.js | 1180 ++++-- test/e2e/api/domApi-web-map.test.js | 1162 ++++-- test/e2e/api/locateApi.test.js | 106 +- test/e2e/core/ArrowKeyNavContextMenu.test.js | 251 +- test/e2e/core/announceMovement.test.js | 245 +- test/e2e/core/axisInferring.test.js | 64 +- test/e2e/core/debugMode.test.js | 142 +- test/e2e/core/drag.test.js | 100 +- test/e2e/core/dragDrop.test.js | 69 +- test/e2e/core/dragDropMap.test.js | 69 +- test/e2e/core/featureIndexOverlay.test.js | 349 +- test/e2e/core/featureLinks.test.js | 197 +- test/e2e/core/featureNavigation.test.js | 177 +- test/e2e/core/fullscreenControl.test.js | 114 +- test/e2e/core/history.test.js | 160 +- test/e2e/core/kbdAttribution.test.js | 66 +- test/e2e/core/layerAttributes.test.js | 182 +- test/e2e/core/layerContextMenu.test.js | 200 +- test/e2e/core/linkTypes.test.js | 135 +- test/e2e/core/mapContextMenu.test.js | 828 +++-- test/e2e/core/mapElement.test.js | 89 +- test/e2e/core/mapFeature.data.js | 2003 +++++----- test/e2e/core/mapFeature.test.js | 555 ++- test/e2e/core/mapSpan.test.js | 28 +- test/e2e/core/metaDefault.test.js | 112 +- test/e2e/core/mismatchedLayerWithMap.test.js | 70 +- test/e2e/core/missingMetaParameters.test.js | 62 +- test/e2e/core/popupTabNavigation.test.js | 491 ++- test/e2e/core/projectionChange.test.js | 99 +- test/e2e/core/reticle.test.js | 98 +- test/e2e/core/scroll.test.js | 20 +- test/e2e/core/styleParsing.test.js | 66 +- test/e2e/core/tms.test.js | 24 +- test/e2e/core/touchDevice.test.js | 53 +- test/e2e/core/zoomChangeProjection.test.js | 30 +- test/e2e/geojson/geojson2mapml.test.js | 271 +- test/e2e/geojson/geojson2mapmlJSON.js | 1009 ++--- test/e2e/geojson/mapml2geojson.test.js | 278 +- test/e2e/geojson/mapml2geojsonJSON.js | 1188 +++--- .../e2e/layers/CustomProjectionLayers.test.js | 77 +- .../layers/clientTemplatedTileLayer.test.js | 61 +- test/e2e/layers/featureLayer.test.js | 145 +- test/e2e/layers/general/extentProperty.js | 34 +- test/e2e/layers/general/isVisible.js | 75 +- test/e2e/layers/general/zoomLimit.js | 121 +- test/e2e/layers/layerControl.test.js | 26 +- test/e2e/layers/layerOpacityAttribute.test.js | 50 +- test/e2e/layers/multipleExtents.test.js | 665 +++- .../e2e/layers/multipleExtentsOpacity.test.js | 63 +- .../multipleHeterogeneousQueryExtents.test.js | 230 +- test/e2e/layers/multipleQueryExtents.test.js | 246 +- test/e2e/layers/queryLink.test.js | 310 +- test/e2e/layers/queryableMapExtent.test.js | 30 +- test/e2e/layers/staticTileLayer.test.js | 107 +- test/e2e/layers/step/featureStep.test.js | 29 +- test/e2e/layers/step/imageStep.test.js | 132 +- test/e2e/layers/step/request.js | 118 +- test/e2e/layers/step/tileStep.test.js | 29 +- test/e2e/layers/templatedFeatures.test.js | 152 +- .../layers/templatedFeaturesFilter.test.js | 70 +- test/e2e/layers/templatedImageLayer.test.js | 84 +- test/e2e/layers/templatedTileLayer.test.js | 130 +- test/e2e/mapml-viewer/cssDomination.test.js | 44 +- test/e2e/mapml-viewer/customTCRS.test.js | 68 +- test/e2e/mapml-viewer/locateButton.test.js | 126 +- test/e2e/mapml-viewer/mapml-viewer.test.js | 218 +- .../mapml-viewer/mapml-viewerCaption.test.js | 102 +- .../mapml-viewer/mapml-viewerControls.test.js | 41 +- ...pml-viewerHeightAndWidthAttributes.test.js | 126 +- .../mapml-viewer/mapml-viewerScale.test.js | 118 +- test/e2e/mapml-viewer/staticAttribute.test.js | 106 +- .../mapml-viewer/viewerContextMenu.test.js | 771 ++-- test/e2e/web-map/map.test.js | 97 +- test/e2e/web-map/mapCaption.test.js | 103 +- test/e2e/web-map/mapControls.test.js | 41 +- test/e2e/web-map/mapCssNoDomination.test.js | 28 +- .../mapHeightAndWidthAttributes.test.js | 118 +- test/e2e/web-map/mapScale.test.js | 118 +- test/e2e/web-map/mapStatic.test.js | 103 +- test/layers/StaticTileLayer.spec.js | 327 +- test/layers/mapMLLayer.spec.js | 244 +- test/projections/tcrs.spec.js | 36 +- test/server.js | 106 +- test/setup.js | 4 +- test/utils/boundsUtils.spec.js | 854 +++-- test/utils/options.spec.js | 6 +- test/utils/util.spec.js | 435 ++- 133 files changed, 21966 insertions(+), 16134 deletions(-) diff --git a/src/layer.js b/src/layer.js index ff730c619..7f8d480ef 100644 --- a/src/layer.js +++ b/src/layer.js @@ -1,12 +1,12 @@ -import './leaflet.js'; // a lightly modified version of Leaflet for use as browser module -import './mapml.js'; // modified URI to make the function a property of window scope (possibly a bad thing to do). +import './leaflet.js'; // a lightly modified version of Leaflet for use as browser module +import './mapml.js'; // modified URI to make the function a property of window scope (possibly a bad thing to do). export class MapLayer extends HTMLElement { static get observedAttributes() { return ['src', 'label', 'checked', 'hidden', 'opacity']; } get src() { - return this.hasAttribute('src')?this.getAttribute('src'):''; + return this.hasAttribute('src') ? this.getAttribute('src') : ''; } set src(val) { @@ -15,17 +15,17 @@ export class MapLayer extends HTMLElement { } } get label() { - return this.hasAttribute('label')?this.getAttribute('label'):''; + return this.hasAttribute('label') ? this.getAttribute('label') : ''; } set label(val) { if (val) { - this.setAttribute('label',val); + this.setAttribute('label', val); } } get checked() { return this.hasAttribute('checked'); } - + set checked(val) { if (val) { this.setAttribute('checked', ''); @@ -33,7 +33,7 @@ export class MapLayer extends HTMLElement { this.removeAttribute('checked'); } } - + get hidden() { return this.hasAttribute('hidden'); } @@ -46,28 +46,28 @@ export class MapLayer extends HTMLElement { } } - get opacity(){ + get opacity() { return this._layer._container.style.opacity || this._layer.options.opacity; } set opacity(val) { - if(+val > 1 || +val < 0) return; + if (+val > 1 || +val < 0) return; this._layer.changeOpacity(val); } constructor() { // Always call super first in constructor - super(); + super(); } disconnectedCallback() { // console.log('Custom map element removed from page.'); // if the map-layer node is removed from the dom, the layer should be - // removed from the map and the layer control + // removed from the map and the layer control // this is moved up here so that the layer control doesn't respond // to the layer being removed with the _onLayerChange execution // that is set up in _attached: - if(this.hasAttribute("data-moving")) return; + if (this.hasAttribute('data-moving')) return; this._onRemove(); } @@ -87,70 +87,76 @@ export class MapLayer extends HTMLElement { } connectedCallback() { - if(this.hasAttribute("data-moving")) return; + if (this.hasAttribute('data-moving')) return; this._onAdd(); } _onAdd() { - if(this.getAttribute('src') && !this.shadowRoot) { - this.attachShadow({mode: 'open'}); + if (this.getAttribute('src') && !this.shadowRoot) { + this.attachShadow({ mode: 'open' }); } //creates listener that waits for createmap event, this allows for delayed builds of maps //this allows a safeguard for the case where loading a custom TCRS takes longer than loading mapml-viewer.js/web-map.js - this.parentNode.addEventListener('createmap', ()=>{ - this._ready(); - // if the map has been attached, set this layer up wrt Leaflet map - if (this.parentNode._map) { + this.parentNode.addEventListener( + 'createmap', + () => { + this._ready(); + // if the map has been attached, set this layer up wrt Leaflet map + if (this.parentNode._map) { this._attachedToMap(); - } - if (this._layerControl && !this.hidden) { - this._layerControl.addOrUpdateOverlay(this._layer, this.label); - } - }, {once:true}); //listener stops listening after event occurs once + } + if (this._layerControl && !this.hidden) { + this._layerControl.addOrUpdateOverlay(this._layer, this.label); + } + }, + { once: true } + ); //listener stops listening after event occurs once //if map is already created then dispatch createmap event, allowing layer to be built - if(this.parentNode._map)this.parentNode.dispatchEvent(new CustomEvent('createmap')); + if (this.parentNode._map) + this.parentNode.dispatchEvent(new CustomEvent('createmap')); } adoptedCallback() { - // console.log('Custom map element moved to new page.'); + // console.log('Custom map element moved to new page.'); } attributeChangedCallback(name, oldValue, newValue) { - switch(name) { - case 'label': + switch (name) { + case 'label': if (oldValue !== newValue) { - this.dispatchEvent(new CustomEvent('labelchanged', {detail: - {target: this}})); + this.dispatchEvent( + new CustomEvent('labelchanged', { detail: { target: this } }) + ); } - break; - case 'checked': + break; + case 'checked': if (this._layer) { - if (typeof newValue === "string") { + if (typeof newValue === 'string') { this.parentElement._map.addLayer(this._layer); } else { this.parentElement._map.removeLayer(this._layer); } - this.dispatchEvent(new Event("change", { bubbles: true })); + this.dispatchEvent(new Event('change', { bubbles: true })); } - break; + break; case 'hidden': - var map = this.parentElement && this.parentElement._map; - if (map && this.parentElement.controls) { - if (typeof newValue === "string") { - if (this._layer) { - this.parentElement._layerControl.removeLayer(this._layer); - } + var map = this.parentElement && this.parentElement._map; + if (map && this.parentElement.controls) { + if (typeof newValue === 'string') { + if (this._layer) { + this.parentElement._layerControl.removeLayer(this._layer); + } } else { - this._layerControl = this.parentElement._layerControl; - this._layerControl.addOrUpdateOverlay(this._layer, this.label); - this._validateDisabled(); + this._layerControl = this.parentElement._layerControl; + this._layerControl.addOrUpdateOverlay(this._layer, this.label); + this._validateDisabled(); } - } - break; + } + break; case 'opacity': if (oldValue !== newValue && this._layer) { this.opacity = newValue; } - break; + break; case 'src': if (oldValue !== newValue && this._layer) { this._reload(); @@ -176,13 +182,16 @@ export class MapLayer extends HTMLElement { // currently only support a single link, don't care about type, lang etc. // TODO: add support for full LayerLegend object, and > one link. if (this._layer._legendUrl) { - this.legendLinks = - [{ type: 'application/octet-stream', - href: this._layer._legendUrl, - rel: 'legend', - lang: null, - hreflang: null, - sizes: null }]; + this.legendLinks = [ + { + type: 'application/octet-stream', + href: this._layer._legendUrl, + rel: 'legend', + lang: null, + hreflang: null, + sizes: null + } + ]; } if (this._layer._title) { this.label = this._layer._title; @@ -201,77 +210,113 @@ export class MapLayer extends HTMLElement { // re-use 'loadedmetadata' event from HTMLMediaElement inteface, applied // to MapML extent as metadata // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/loadedmetadata_event - this.dispatchEvent(new CustomEvent('loadedmetadata', {detail: - {target: this}})); + this.dispatchEvent( + new CustomEvent('loadedmetadata', { detail: { target: this } }) + ); } else { - this.dispatchEvent(new CustomEvent('error', {detail: - {target: this}})); + this.dispatchEvent( + new CustomEvent('error', { detail: { target: this } }) + ); } } _validateDisabled() { setTimeout(() => { - let layer = this._layer, map = layer?._map; + let layer = this._layer, + map = layer?._map; if (map) { - let count = 0, total=0, layerTypes = ["_staticTileLayer","_imageLayer","_mapmlvectors","_templatedLayer"]; - if(layer.validProjection){ - for(let j = 0 ;j { - if (mapLink.hasAttribute('href')) { - mapLink.setAttribute('href', decodeURI((new URL(mapLink.attributes.href.value, this.baseURI ? this.baseURI : document.baseURI)).href)); - } - else if (mapLink.hasAttribute('tref')) { - mapLink.setAttribute('tref', decodeURI((new URL(mapLink.attributes.tref.value, this.baseURI ? this.baseURI : document.baseURI)).href)); + mapLink.setAttribute( + 'href', + decodeURI( + new URL( + mapLink.attributes.href.value, + this.baseURI ? this.baseURI : document.baseURI + ).href + ) + ); + } else if (mapLink.hasAttribute('tref')) { + mapLink.setAttribute( + 'tref', + decodeURI( + new URL( + mapLink.attributes.tref.value, + this.baseURI ? this.baseURI : document.baseURI + ).href + ) + ); } }); } @@ -285,10 +330,10 @@ export class MapLayer extends HTMLElement { _onLayerChange() { if (this._layer._map) { - // can't disable observers, have to set a flag telling it where - // the 'event' comes from: either the api or a user click/tap - // may not be necessary -> this._apiToggleChecked = false; - this.checked = this._layer._map.hasLayer(this._layer); + // can't disable observers, have to set a flag telling it where + // the 'event' comes from: either the api or a user click/tap + // may not be necessary -> this._apiToggleChecked = false; + this.checked = this._layer._map.hasLayer(this._layer); } } _ready() { @@ -299,25 +344,41 @@ export class MapLayer extends HTMLElement { // be parsed from the second parameter here // IE 11 did not have a value for this.baseURI for some reason var base = this.baseURI ? this.baseURI : document.baseURI; - let opacity_value = this.hasAttribute("opacity")?this.getAttribute("opacity"):"1.0"; - this._layer = M.mapMLLayer(this.src ? (new URL(this.src, base)).href: null, this, {mapprojection:this.parentElement._map.options.projection,opacity:opacity_value}); + let opacity_value = this.hasAttribute('opacity') + ? this.getAttribute('opacity') + : '1.0'; + this._layer = M.mapMLLayer( + this.src ? new URL(this.src, base).href : null, + this, + { + mapprojection: this.parentElement._map.options.projection, + opacity: opacity_value + } + ); this._layer.on('extentload', this._onLayerExtentLoad, this); this._setUpEvents(); } _attachedToMap() { // set i to the position of this layer element in the set of layers - var i = 0, position = 1; - for (var nodes = this.parentNode.children;i < nodes.length;i++) { - if (this.parentNode.children[i].nodeName === "LAYER-") { + var i = 0, + position = 1; + for (var nodes = this.parentNode.children; i < nodes.length; i++) { + if (this.parentNode.children[i].nodeName === 'LAYER-') { if (this.parentNode.children[i] === this) { position = i + 1; } else if (this.parentNode.children[i]._layer) { - this.parentNode.children[i]._layer.setZIndex(i+1); + this.parentNode.children[i]._layer.setZIndex(i + 1); } } } - var proj = this.parentNode.projection ? this.parentNode.projection : "OSMTILE"; - L.setOptions(this._layer, {zIndex: position, mapprojection: proj, opacity: window.getComputedStyle(this).opacity}); + var proj = this.parentNode.projection + ? this.parentNode.projection + : 'OSMTILE'; + L.setOptions(this._layer, { + zIndex: position, + mapprojection: proj, + opacity: window.getComputedStyle(this).opacity + }); // make sure the Leaflet layer has a reference to the map this._layer._map = this.parentNode._map; // notify the layer that it is attached to a map (layer._map) @@ -326,12 +387,12 @@ export class MapLayer extends HTMLElement { if (this.checked) { this._layer.addTo(this._layer._map); } - + // add the handler which toggles the 'checked' property based on the // user checking/unchecking the layer from the layer control // this must be done *after* the layer is actually added to the map - this._layer.on('add remove', this._onLayerChange, this); - this._layer.on('add remove extentload', this._validateDisabled, this); + this._layer.on('add remove', this._onLayerChange, this); + this._layer.on('add remove extentload', this._validateDisabled, this); // if controls option is enabled, insert the layer into the overlays array if (this.parentNode._layerControl && !this.hidden) { @@ -340,11 +401,11 @@ export class MapLayer extends HTMLElement { } // toggle the this.disabled attribute depending on whether the layer // is: same prj as map, within view/zoom of map - this._layer._map.on('moveend', this._validateDisabled, this); + this._layer._map.on('moveend', this._validateDisabled, this); this._layer._map.on('checkdisabled', this._validateDisabled, this); // this is necessary to get the layer control to compare the layer // extents with the map extent & zoom, but it needs to be rethought TODO - // for one thing, layers which are checked by the author before + // for one thing, layers which are checked by the author before // adding to the map are displayed despite that they are not visible // See issue #26 // this._layer._map.fire('moveend'); @@ -355,48 +416,68 @@ export class MapLayer extends HTMLElement { } } _setUpEvents() { - this._layer.on('loadstart', - function () { - this.dispatchEvent(new CustomEvent('loadstart', {detail: - {target: this}})); - }, this); - this._layer.on('changestyle', - function(e) { - this.src = e.src; - this.dispatchEvent(new CustomEvent('changestyle', {detail: - {target: this}})); - },this); - this._layer.on('changeprojection', - function(e) { - this.src = e.href; - this.dispatchEvent(new CustomEvent('changeprojection', {detail: - {target: this}})); - },this); + this._layer.on( + 'loadstart', + function () { + this.dispatchEvent( + new CustomEvent('loadstart', { detail: { target: this } }) + ); + }, + this + ); + this._layer.on( + 'changestyle', + function (e) { + this.src = e.src; + this.dispatchEvent( + new CustomEvent('changestyle', { detail: { target: this } }) + ); + }, + this + ); + this._layer.on( + 'changeprojection', + function (e) { + this.src = e.href; + this.dispatchEvent( + new CustomEvent('changeprojection', { detail: { target: this } }) + ); + }, + this + ); } zoomTo() { - if(!this.extent) return; + if (!this.extent) return; let map = this._layer._map, - tL = this.extent.topLeft.pcrs, - bR = this.extent.bottomRight.pcrs, - layerBounds = L.bounds(L.point(tL.horizontal, tL.vertical), L.point(bR.horizontal, bR.vertical)), - center = map.options.crs.unproject(layerBounds.getCenter(true)); + tL = this.extent.topLeft.pcrs, + bR = this.extent.bottomRight.pcrs, + layerBounds = L.bounds( + L.point(tL.horizontal, tL.vertical), + L.point(bR.horizontal, bR.vertical) + ), + center = map.options.crs.unproject(layerBounds.getCenter(true)); - let maxZoom = this.extent.zoom.maxZoom, - minZoom = this.extent.zoom.minZoom; - map.setView(center, M.getMaxZoom(layerBounds, map, minZoom, maxZoom), {animate: false}); + let maxZoom = this.extent.zoom.maxZoom, + minZoom = this.extent.zoom.minZoom; + map.setView(center, M.getMaxZoom(layerBounds, map, minZoom, maxZoom), { + animate: false + }); } - mapml2geojson(options = {}){ + mapml2geojson(options = {}) { return M.mapml2geojson(this, options); } pasteFeature(feature) { - switch(typeof feature) { - case "string": + switch (typeof feature) { + case 'string': feature.trim(); - if (feature.slice(0,12) === "") { - this.insertAdjacentHTML("beforeend", feature); + if ( + feature.slice(0, 12) === '' + ) { + this.insertAdjacentHTML('beforeend', feature); } break; - case "object": + case 'object': if (feature.nodeName.toUpperCase() === 'MAP-FEATURE') { this.appendChild(feature); } diff --git a/src/map-a.js b/src/map-a.js index 38a1260f8..8b2488073 100644 --- a/src/map-a.js +++ b/src/map-a.js @@ -1,75 +1,70 @@ export class MapA extends HTMLElement { - static get observedAttributes() { - return ['href','target','type','inplace']; - } - get href() { - return this.hasAttribute('href') ? this.getAttribute('href') : ""; - } - set href(url) { - this.href = url; - } - get target() { - return this.hasAttribute('target') ? this.getAttribute('target') : ""; - } - set target(val) { - this.setAttribute('target', val); - } - get type() { - return this.hasAttribute('type') ? this.getAttribute('type') : ""; - } - set type(val) { - this.setAttribute('type', val); - } - get inplace() { - return this.hasAttribute('inplace') ? this.getAttribute('inplace') : ""; - } - set inplace(val) { - const hasInplace = Boolean(val); - if (hasInplace) { - this.setAttribute('inpalce', ''); - } else { - this.removeAttribute('inplace'); - } + static get observedAttributes() { + return ['href', 'target', 'type', 'inplace']; + } + get href() { + return this.hasAttribute('href') ? this.getAttribute('href') : ''; + } + set href(url) { + this.href = url; + } + get target() { + return this.hasAttribute('target') ? this.getAttribute('target') : ''; + } + set target(val) { + this.setAttribute('target', val); + } + get type() { + return this.hasAttribute('type') ? this.getAttribute('type') : ''; + } + set type(val) { + this.setAttribute('type', val); + } + get inplace() { + return this.hasAttribute('inplace') ? this.getAttribute('inplace') : ''; + } + set inplace(val) { + const hasInplace = Boolean(val); + if (hasInplace) { + this.setAttribute('inpalce', ''); + } else { + this.removeAttribute('inplace'); } + } - - attributeChangedCallback(name, oldValue, newValue) { - switch (name) { - case 'href': { - if (oldValue !== newValue) { - // handle side effects - } - break; } - case 'target': { - if (oldValue !== newValue) { - // handle side effects - } - break; + attributeChangedCallback(name, oldValue, newValue) { + switch (name) { + case 'href': { + if (oldValue !== newValue) { + // handle side effects } - case 'type': { - if (oldValue !== newValue) { - // handle side effects - } - break; + break; + } + case 'target': { + if (oldValue !== newValue) { + // handle side effects } - case 'inplace': { - if (oldValue !== newValue) { - // handle side effects - } - break; + break; + } + case 'type': { + if (oldValue !== newValue) { + // handle side effects } + break; + } + case 'inplace': { + if (oldValue !== newValue) { + // handle side effects + } + break; } } - constructor() { - // Always call super first in constructor - super(); - } - connectedCallback() { - - } - disconnectedCallback() { - - } } - window.customElements.define('map-a', MapA); - \ No newline at end of file + constructor() { + // Always call super first in constructor + super(); + } + connectedCallback() {} + disconnectedCallback() {} +} +window.customElements.define('map-a', MapA); diff --git a/src/map-area.js b/src/map-area.js index eab576de6..a50c32af4 100644 --- a/src/map-area.js +++ b/src/map-area.js @@ -1,36 +1,36 @@ -import './leaflet.js'; // a lightly modified version of Leaflet for use as browser module -import './mapml.js'; // refactored URI usage, replaced with URL standard +import './leaflet.js'; // a lightly modified version of Leaflet for use as browser module +import './mapml.js'; // refactored URI usage, replaced with URL standard export class MapArea extends HTMLAreaElement { static get observedAttributes() { - return ['coords','alt','href','shape','rel','type','target']; + return ['coords', 'alt', 'href', 'shape', 'rel', 'type', 'target']; } - // see comments below regarding attributeChangedCallback vs. getter/setter + // see comments below regarding attributeChangedCallback vs. getter/setter // usage. Effectively, the user of the element must use the property, not // the getAttribute/setAttribute/removeAttribute DOM API, because the latter // calls don't result in the getter/setter being called (so you have to use // the getter/setter directly) get alt() { - return this.hasAttribute('alt') ? this.getAttribute('alt') : ""; + return this.hasAttribute('alt') ? this.getAttribute('alt') : ''; } set alt(value) { this.setAttribute('controls', value); } get coords() { - return this.hasAttribute('coords') ? this.getAttribute('coords') : ""; + return this.hasAttribute('coords') ? this.getAttribute('coords') : ''; } set coords(coordinates) { - // what to do. Probably replace the feature with a new one, without changing + // what to do. Probably replace the feature with a new one, without changing // anything else... } get href() { - return this.hasAttribute('href') ? this.getAttribute('href') : ""; + return this.hasAttribute('href') ? this.getAttribute('href') : ''; } set href(url) { this.href = url; } get shape() { - return this.hasAttribute('shape') ? this.getAttribute('shape') : "default"; + return this.hasAttribute('shape') ? this.getAttribute('shape') : 'default'; } set shape(shape) { shape = shape.toLowerCase(); @@ -40,31 +40,29 @@ export class MapArea extends HTMLAreaElement { } } get rel() { - return this.hasAttribute('rel') ? this.getAttribute('rel') : ""; + return this.hasAttribute('rel') ? this.getAttribute('rel') : ''; } set rel(rel) { this.rel = rel; } get type() { - return this.hasAttribute('type') ? this.getAttribute('type') : ""; + return this.hasAttribute('type') ? this.getAttribute('type') : ''; } set type(type) { this.type = type; } get target() { - return this.hasAttribute('target') ? this.getAttribute('target') : ""; + return this.hasAttribute('target') ? this.getAttribute('target') : ''; } constructor() { // Always call super first in constructor super(); } - attributeChangedCallback(name, oldValue, newValue) { - - } + attributeChangedCallback(name, oldValue, newValue) {} connectedCallback() { // if the map has been attached, set this layer up wrt Leaflet map if (this.parentElement._map) { - this._attachedToMap(); + this._attachedToMap(); } } _attachedToMap() { @@ -78,44 +76,51 @@ export class MapArea extends HTMLAreaElement { // the img might have been scaled by CSS. // compute the style properties to be applied to the feature var options = this._styleToPathOptions(window.getComputedStyle(this)), - points = this.coords ? this._coordsToArray(this.coords): null; + points = this.coords ? this._coordsToArray(this.coords) : null; // scale points if the poster exists because responsive areas if (points && this.parentElement.poster) { - var worig = this.parentElement.poster.width, - wresp = this.parentElement.width, - wadjstmnt = (worig - wresp)/2, - horig = this.parentElement.poster.height, - hresp = this.parentElement.height, - hadjstmnt = (horig - hresp)/2; - for (var i = 0; i< points.length;i++) { + var worig = this.parentElement.poster.width, + wresp = this.parentElement.width, + wadjstmnt = (worig - wresp) / 2, + horig = this.parentElement.poster.height, + hresp = this.parentElement.height, + hadjstmnt = (horig - hresp) / 2; + for (var i = 0; i < points.length; i++) { points[i][0] = points[i][0] - wadjstmnt; points[i][1] = points[i][1] - hadjstmnt; } } if (this.shape === 'circle') { - var pixelRadius = parseInt(this.coords.split(",")[2]), - pointOnCirc = L.point(points[0]).add(L.point(0,pixelRadius)), - latLngOnCirc = map.containerPointToLatLng(pointOnCirc), - latLngCenter = map.containerPointToLatLng(points[0]), - radiusInMeters = map.distance(latLngCenter, latLngOnCirc); - this._feature = L.circle(latLngCenter, radiusInMeters, options).addTo(map); + var pixelRadius = parseInt(this.coords.split(',')[2]), + pointOnCirc = L.point(points[0]).add(L.point(0, pixelRadius)), + latLngOnCirc = map.containerPointToLatLng(pointOnCirc), + latLngCenter = map.containerPointToLatLng(points[0]), + radiusInMeters = map.distance(latLngCenter, latLngOnCirc); + this._feature = L.circle(latLngCenter, radiusInMeters, options).addTo( + map + ); } else if (!this.shape || this.shape === 'rect') { - var bounds = L.latLngBounds(map.containerPointToLatLng(points[0]), map.containerPointToLatLng(points[1])); + var bounds = L.latLngBounds( + map.containerPointToLatLng(points[0]), + map.containerPointToLatLng(points[1]) + ); this._feature = L.rectangle(bounds, options).addTo(map); } else if (this.shape === 'poly') { - this._feature = L.polygon(this._pointsToLatLngs(points),options).addTo(map); + this._feature = L.polygon(this._pointsToLatLngs(points), options).addTo( + map + ); } else { // whole initial area of map is a hyperlink - this._feature = L.rectangle(map.getBounds(),options).addTo(map); + this._feature = L.rectangle(map.getBounds(), options).addTo(map); } if (this.alt) { // other Leaflet features are implemented via SVG. SVG displays tooltips // based on the graphics child element. var title = L.SVG.create('title'), - titleText = document.createTextNode(this.alt); - title.appendChild(titleText); - this._feature._path.appendChild(title); + titleText = document.createTextNode(this.alt); + title.appendChild(titleText); + this._feature._path.appendChild(title); } if (this.href) { // conditionally act on click on an area link. If no link it should be an @@ -123,18 +128,30 @@ export class MapArea extends HTMLAreaElement { // implementation, we could actually use an image map replete with area // children which would provide the linking / cursor change behaviours // that are familiar to HTML authors versed in image maps. - this._feature.on('click', function() {if (this.href) {window.open(this.href);}}, this); + this._feature.on( + 'click', + function () { + if (this.href) { + window.open(this.href); + } + }, + this + ); } } - } + } disconnectedCallback() { this._map.removeLayer(this._feature); delete this._feature; } _coordsToArray(containerPoints) { // returns an array of arrays of coordinate pairs _coordsToArray("1,2,3,4") -> [[1,2],[3,4]] - for (var i=1, points = [], coords = containerPoints.split(",");i tags and update to aria-label - let mapcaption = this.parentElement.querySelector('map-caption').textContent; + constructor() { + super(); + } - this.observer = new MutationObserver(() => { - let mapcaptionupdate = this.parentElement.querySelector('map-caption').textContent; + // called when element is inserted into DOM (setup code) + connectedCallback() { + if ( + this.parentElement.nodeName === 'MAPML-VIEWER' || + this.parentElement.nodeName === 'MAP' + ) { + // calls MutationObserver; needed to observe changes to content between tags and update to aria-label + let mapcaption = + this.parentElement.querySelector('map-caption').textContent; - if (mapcaptionupdate !== mapcaption) { - this.parentElement.setAttribute('aria-label', this.parentElement.querySelector('map-caption').textContent); - } - }); + this.observer = new MutationObserver(() => { + let mapcaptionupdate = + this.parentElement.querySelector('map-caption').textContent; - this.observer.observe(this, { - characterData: true, - subtree: true, - attributes: true, - childList: true - }); - - // don't change aria-label if one already exists from user (checks when element is first created) - if (!this.parentElement.hasAttribute('aria-label')) { - const ariaLabel = this.textContent; - this.parentElement.setAttribute('aria-label', ariaLabel); - } + if (mapcaptionupdate !== mapcaption) { + this.parentElement.setAttribute( + 'aria-label', + this.parentElement.querySelector('map-caption').textContent + ); } + }); + + this.observer.observe(this, { + characterData: true, + subtree: true, + attributes: true, + childList: true + }); + + // don't change aria-label if one already exists from user (checks when element is first created) + if (!this.parentElement.hasAttribute('aria-label')) { + const ariaLabel = this.textContent; + this.parentElement.setAttribute('aria-label', ariaLabel); + } } - disconnectedCallback() { - this.observer.disconnect(); - } + } + disconnectedCallback() { + this.observer.disconnect(); + } } diff --git a/src/map-extent.js b/src/map-extent.js index a65056e5e..bb81f376e 100644 --- a/src/map-extent.js +++ b/src/map-extent.js @@ -1,21 +1,21 @@ export class MapExtent extends HTMLElement { static get observedAttributes() { - return ['units','checked','label','opacity']; + return ['units', 'checked', 'label', 'opacity']; } get units() { - return this.getAttribute('units'); + return this.getAttribute('units'); } set units(val) { // built in support for OSMTILE, CBMTILE, WGS84 and APSTILE - if (['OSMTILE','CBMTILE','WGS84','APSTILE'].includes(val)) { - this.setAttribute('units',val); + if (['OSMTILE', 'CBMTILE', 'WGS84', 'APSTILE'].includes(val)) { + this.setAttribute('units', val); } // else need to check with the mapml-viewer element if the custom projection is defined } get checked() { return this.hasAttribute('checked'); } - + set checked(val) { if (val) { this.setAttribute('checked', ''); @@ -24,34 +24,34 @@ export class MapExtent extends HTMLElement { } } get label() { - return this.hasAttribute('label')?this.getAttribute('label'):''; + return this.hasAttribute('label') ? this.getAttribute('label') : ''; } set label(val) { if (val) { - this.setAttribute('label',val); + this.setAttribute('label', val); } } - get opacity(){ + get opacity() { return this._opacity; } set opacity(val) { - if(+val > 1 || +val < 0) return; + if (+val > 1 || +val < 0) return; this.setAttribute('opacity', val); } attributeChangedCallback(name, oldValue, newValue) { - switch(name) { + switch (name) { case 'units': if (oldValue !== newValue) { // handle side effects } break; - case 'label': + case 'label': if (oldValue !== newValue) { // handle side effects } break; - case 'checked': + case 'checked': if (oldValue !== newValue) { // handle side effects } @@ -65,16 +65,22 @@ export class MapExtent extends HTMLElement { } constructor() { // Always call super first in constructor - super(); + super(); } connectedCallback() { - if(this.querySelector('map-link[rel=query], map-link[rel=features]') && !this.shadowRoot) { - this.attachShadow({mode: 'open'}); + if ( + this.querySelector('map-link[rel=query], map-link[rel=features]') && + !this.shadowRoot + ) { + this.attachShadow({ mode: 'open' }); } - let parentLayer = this.parentNode.nodeName.toUpperCase() === "LAYER-" ? this.parentNode : this.parentNode.host; + let parentLayer = + this.parentNode.nodeName.toUpperCase() === 'LAYER-' + ? this.parentNode + : this.parentNode.host; if (!parentLayer._layer) { // for custom projection cases, the MapMLLayer has not yet created and binded with the layer- at this point, - // because the "createMap" event of mapml-viewer has not yet been dispatched, the map has not yet been created + // because the "createMap" event of mapml-viewer has not yet been dispatched, the map has not yet been created // the event will be dispatched after defineCustomProjection > projection setter // should wait until MapMLLayer is built parentLayer.parentNode.addEventListener('createmap', (e) => { @@ -84,7 +90,5 @@ export class MapExtent extends HTMLElement { this._layer = parentLayer._layer; } } - disconnectedCallback() { - - } -} \ No newline at end of file + disconnectedCallback() {} +} diff --git a/src/map-feature.js b/src/map-feature.js index 66939394d..416886b8f 100644 --- a/src/map-feature.js +++ b/src/map-feature.js @@ -1,550 +1,655 @@ export class MapFeature extends HTMLElement { - static get observedAttributes() { - return ['zoom', 'onfocus', 'onclick', 'onblur']; - } + static get observedAttributes() { + return ['zoom', 'onfocus', 'onclick', 'onblur']; + } - get zoom() { - return +(this.hasAttribute("zoom") ? this.getAttribute("zoom") : 0); - } + get zoom() { + return +(this.hasAttribute('zoom') ? this.getAttribute('zoom') : 0); + } - set zoom(val) { - var parsedVal = parseInt(val,10); - if (!isNaN(parsedVal) && (parsedVal >= this.min && parsedVal <= this.max)) { - this.setAttribute('zoom', parsedVal); - } + set zoom(val) { + var parsedVal = parseInt(val, 10); + if (!isNaN(parsedVal) && parsedVal >= this.min && parsedVal <= this.max) { + this.setAttribute('zoom', parsedVal); } + } - get min() { - // fallback: the minimum zoom bound of layer- element - return +(this.hasAttribute("min") ? this.getAttribute("min") : this._layer._layerEl.extent.zoom.minZoom); - } + get min() { + // fallback: the minimum zoom bound of layer- element + return +(this.hasAttribute('min') + ? this.getAttribute('min') + : this._layer._layerEl.extent.zoom.minZoom); + } - set min(val) { - var parsedVal = parseInt(val,10); - if (!isNaN(parsedVal)) { - if (parsedVal >= this._layer._layerEl.extent.zoom.minZoom && parsedVal <= this._layer._layerEl.extent.zoom.maxZoom) { - this.setAttribute('min', parsedVal); - } else { - this.setAttribute('min', this._layer._layerEl.extent.zoom.minZoom); - } + set min(val) { + var parsedVal = parseInt(val, 10); + if (!isNaN(parsedVal)) { + if ( + parsedVal >= this._layer._layerEl.extent.zoom.minZoom && + parsedVal <= this._layer._layerEl.extent.zoom.maxZoom + ) { + this.setAttribute('min', parsedVal); + } else { + this.setAttribute('min', this._layer._layerEl.extent.zoom.minZoom); } } + } - get max() { - // fallback: the maximum zoom bound of layer- element - return +(this.hasAttribute("max") ? this.getAttribute("max") : this._layer._layerEl.extent.zoom.maxZoom); - } + get max() { + // fallback: the maximum zoom bound of layer- element + return +(this.hasAttribute('max') + ? this.getAttribute('max') + : this._layer._layerEl.extent.zoom.maxZoom); + } - set max(val) { - var parsedVal = parseInt(val,10); - if (!isNaN(parsedVal)) { - if (parsedVal >= this._layer._layerEl.extent.zoom.minZoom && parsedVal <= this._layer._layerEl.extent.zoom.maxZoom) { - this.setAttribute('max', parsedVal); - } else { - this.setAttribute('max', this._layer._layerEl.extent.zoom.maxZoom); - } + set max(val) { + var parsedVal = parseInt(val, 10); + if (!isNaN(parsedVal)) { + if ( + parsedVal >= this._layer._layerEl.extent.zoom.minZoom && + parsedVal <= this._layer._layerEl.extent.zoom.maxZoom + ) { + this.setAttribute('max', parsedVal); + } else { + this.setAttribute('max', this._layer._layerEl.extent.zoom.maxZoom); } } + } - get extent() { - if (this.isConnected) { - // if the feature extent is the first time to be calculated or the feature extent is changed (by changing - // the innertext of map-coordinates), then calculate feature extent by invoking the getFeatureExtent function - if (!this._getFeatureExtent) {this._getFeatureExtent = this._memoizeExtent();} - return this._getFeatureExtent(); + get extent() { + if (this.isConnected) { + // if the feature extent is the first time to be calculated or the feature extent is changed (by changing + // the innertext of map-coordinates), then calculate feature extent by invoking the getFeatureExtent function + if (!this._getFeatureExtent) { + this._getFeatureExtent = this._memoizeExtent(); } + return this._getFeatureExtent(); } + } - attributeChangedCallback(name, oldValue, newValue) { - switch (name) { - case 'zoom': { - if (oldValue !== newValue && this._layer) { - let layer = this._layer, + attributeChangedCallback(name, oldValue, newValue) { + switch (name) { + case 'zoom': { + if (oldValue !== newValue && this._layer) { + let layer = this._layer, layerEl = layer._layerEl, mapmlvectors = layer._mapmlvectors; - // if the vector layer only has static features, should update zoom bounds when zoom attribute is changed - if (mapmlvectors?._staticFeature) { - this._removeInFeatureList(oldValue); - let native = this._getNativeZoomAndCS(layer._content); - mapmlvectors.zoomBounds = mapmlvectors._getZoomBounds(layerEl.shadowRoot || layerEl, native.zoom); - } - this._removeFeature(); - this._updateFeature(); + // if the vector layer only has static features, should update zoom bounds when zoom attribute is changed + if (mapmlvectors?._staticFeature) { + this._removeInFeatureList(oldValue); + let native = this._getNativeZoomAndCS(layer._content); + mapmlvectors.zoomBounds = mapmlvectors._getZoomBounds( + layerEl.shadowRoot || layerEl, + native.zoom + ); } - break; + this._removeFeature(); + this._updateFeature(); } - case 'onfocus': - case 'onclick': - case 'onblur': - if (this._groupEl) { - // "synchronize" the onevent properties (i.e. onfocus, onclick, onblur) - // between the mapFeature and its associated element - this._groupEl[name] = this[name].bind(this._groupEl); - break; - } + break; } + case 'onfocus': + case 'onclick': + case 'onblur': + if (this._groupEl) { + // "synchronize" the onevent properties (i.e. onfocus, onclick, onblur) + // between the mapFeature and its associated element + this._groupEl[name] = this[name].bind(this._groupEl); + break; + } } + } - constructor() { - // Always call super first in constructor - super(); - } + constructor() { + // Always call super first in constructor + super(); + } - connectedCallback() { - // if mapFeature element is not connected to layer- or layer-'s shadowroot, - // or the parent layer- element has a "data-moving" attribute - if ((this.parentNode.nodeType !== document.DOCUMENT_FRAGMENT_NODE && this.parentNode.nodeName.toLowerCase() !== 'layer-') || - (this.parentNode.nodeType === document.DOCUMENT_FRAGMENT_NODE && this.parentNode.host.hasAttribute('data-moving')) || - (this.parentNode.nodeName.toLowerCase() === 'layer-' && this.parentNode.hasAttribute('data-moving'))) { + connectedCallback() { + // if mapFeature element is not connected to layer- or layer-'s shadowroot, + // or the parent layer- element has a "data-moving" attribute + if ( + (this.parentNode.nodeType !== document.DOCUMENT_FRAGMENT_NODE && + this.parentNode.nodeName.toLowerCase() !== 'layer-') || + (this.parentNode.nodeType === document.DOCUMENT_FRAGMENT_NODE && + this.parentNode.host.hasAttribute('data-moving')) || + (this.parentNode.nodeName.toLowerCase() === 'layer-' && + this.parentNode.hasAttribute('data-moving')) + ) { + return; + } + // set up the map-feature object properties + this._addFeature(); + // use observer to monitor the changes in mapFeature's subtree + // (i.e. map-properties, map-featurecaption, map-coordinates) + this._observer = new MutationObserver((mutationList) => { + for (let mutation of mutationList) { + // the attributes changes of element should be handled by attributeChangedCallback() + if (mutation.type === 'attributes' && mutation.target === this) { return; - } - // set up the map-feature object properties - this._addFeature(); - // use observer to monitor the changes in mapFeature's subtree - // (i.e. map-properties, map-featurecaption, map-coordinates) - this._observer = new MutationObserver((mutationList) => { - for (let mutation of mutationList) { - // the attributes changes of element should be handled by attributeChangedCallback() - if (mutation.type === 'attributes' && mutation.target === this) { - return; - } - // re-render feature if there is any observed change - this._removeFeature(); - this._updateFeature(); } - }); - this._observer.observe(this, { - childList: true, - subtree: true, - attributes: true, - attributeOldValue: true, - characterData: true - }); - } - - disconnectedCallback() { - if(this._layer._layerEl.hasAttribute("data-moving")) return; - this._removeFeature(); - this._observer.disconnect(); - } - - _removeFeature() { - // if the el is disconnected - // the el has already got removed at this point - if (this._groupEl?.isConnected) { - this._groupEl.remove(); + // re-render feature if there is any observed change + this._removeFeature(); + this._updateFeature(); } - // if the el has already been disconnected, - // then _map.removeLayer(layerEl._layer) has already been invoked (inside layerEl.disconnectedCallback()) - // this._featureGroup has already got removed at this point - if (this._featureGroup?._map){ - this._featureGroup._map.removeLayer(this._featureGroup); - let mapmlvectors = this._layer._mapmlvectors; - if (mapmlvectors) { - if (mapmlvectors._staticFeature) { - if (mapmlvectors._features[this.zoom]) { - this._removeInFeatureList(this.zoom); - } - let container = this._layer.shadowRoot || this._layer._layerEl; - // update zoom bounds of vector layer - mapmlvectors.zoomBounds = mapmlvectors._getZoomBounds(container, this._getNativeZoomAndCS(this._layer._content).zoom); + }); + this._observer.observe(this, { + childList: true, + subtree: true, + attributes: true, + attributeOldValue: true, + characterData: true + }); + } + + disconnectedCallback() { + if (this._layer._layerEl.hasAttribute('data-moving')) return; + this._removeFeature(); + this._observer.disconnect(); + } + + _removeFeature() { + // if the el is disconnected + // the el has already got removed at this point + if (this._groupEl?.isConnected) { + this._groupEl.remove(); + } + // if the el has already been disconnected, + // then _map.removeLayer(layerEl._layer) has already been invoked (inside layerEl.disconnectedCallback()) + // this._featureGroup has already got removed at this point + if (this._featureGroup?._map) { + this._featureGroup._map.removeLayer(this._featureGroup); + let mapmlvectors = this._layer._mapmlvectors; + if (mapmlvectors) { + if (mapmlvectors._staticFeature) { + if (mapmlvectors._features[this.zoom]) { + this._removeInFeatureList(this.zoom); } - mapmlvectors.options.properties = null; - delete mapmlvectors._layers[this._featureGroup._leaflet_id]; + let container = this._layer.shadowRoot || this._layer._layerEl; + // update zoom bounds of vector layer + mapmlvectors.zoomBounds = mapmlvectors._getZoomBounds( + container, + this._getNativeZoomAndCS(this._layer._content).zoom + ); } + mapmlvectors.options.properties = null; + delete mapmlvectors._layers[this._featureGroup._leaflet_id]; } - delete this._featureGroup; - delete this._groupEl; - // ensure that feature extent can be re-calculated everytime that map-feature element is updated / re-added - if (this._getFeatureExtent) delete this._getFeatureExtent; } + delete this._featureGroup; + delete this._groupEl; + // ensure that feature extent can be re-calculated everytime that map-feature element is updated / re-added + if (this._getFeatureExtent) delete this._getFeatureExtent; + } - _addFeature() { - let parentEl = (this.parentNode.nodeName.toUpperCase() === "LAYER-" || - this.parentNode.nodeName.toUpperCase() === "MAP-EXTENT" ? - this.parentNode : this.parentNode.host); + _addFeature() { + let parentEl = + this.parentNode.nodeName.toUpperCase() === 'LAYER-' || + this.parentNode.nodeName.toUpperCase() === 'MAP-EXTENT' + ? this.parentNode + : this.parentNode.host; - // arrow function is not hoisted, define before use - var _attachedToMap = (e) => { - if (!parentEl._layer._map) { - // if the parent layer- el has not yet added to the map (i.e. not yet rendered), wait until it is added - this._layer.once('attached', function () { + // arrow function is not hoisted, define before use + var _attachedToMap = (e) => { + if (!parentEl._layer._map) { + // if the parent layer- el has not yet added to the map (i.e. not yet rendered), wait until it is added + this._layer.once( + 'attached', + function () { this._map = this._layer._map; - }, this); - } else { - this._map = this._layer._map; - } - // "synchronize" the event handlers between map-feature and - if (!this.querySelector('map-geometry')) return; - if (!this._layer._mapmlvectors) { - // if vector layer has not yet created (i.e. the layer- is not yet rendered on the map / layer is empty) - let layerEl = this._layer._layerEl; - this._layer.once('add', this._setUpEvents, this); - if (!layerEl.querySelector('map-extent, map-tile') && !layerEl.hasAttribute('src') && layerEl.querySelectorAll('map-feature').length === 1) { - // if the map-feature is added to an empty layer, fire extentload to create vector layer - // must re-run _initialize of MapMLLayer.js to re-set layer._extent (layer._extent is null for an empty layer) - this._layer._initialize(layerEl); - this._layer.fire('extentload'); - } - return; - } else if (!this._featureGroup) { - // if the map-feature el or its subtree is updated - // this._featureGroup has been free in this._removeFeature() - this._updateFeature(); - } else { - this._setUpEvents(); + }, + this + ); + } else { + this._map = this._layer._map; + } + // "synchronize" the event handlers between map-feature and + if (!this.querySelector('map-geometry')) return; + if (!this._layer._mapmlvectors) { + // if vector layer has not yet created (i.e. the layer- is not yet rendered on the map / layer is empty) + let layerEl = this._layer._layerEl; + this._layer.once('add', this._setUpEvents, this); + if ( + !layerEl.querySelector('map-extent, map-tile') && + !layerEl.hasAttribute('src') && + layerEl.querySelectorAll('map-feature').length === 1 + ) { + // if the map-feature is added to an empty layer, fire extentload to create vector layer + // must re-run _initialize of MapMLLayer.js to re-set layer._extent (layer._extent is null for an empty layer) + this._layer._initialize(layerEl); + this._layer.fire('extentload'); } - }; - - if (!parentEl._layer) { - // for custom projection cases, the MapMLLayer has not yet created and binded with the layer- at this point, - // because the "createMap" event of mapml-viewer has not yet been dispatched, the map has not yet been created - // the event will be dispatched after defineCustomProjection > projection setter - // should wait until MapMLLayer is built - let parentLayer = parentEl.nodeName.toUpperCase() === "LAYER-" ? parentEl : (parentEl.parentElement || parentEl.parentNode.host); - parentLayer.parentNode.addEventListener('createmap', (e) => { - this._layer = parentLayer._layer; - _attachedToMap(); - }); + return; + } else if (!this._featureGroup) { + // if the map-feature el or its subtree is updated + // this._featureGroup has been free in this._removeFeature() + this._updateFeature(); } else { - this._layer = parentEl._layer; - _attachedToMap(); + this._setUpEvents(); } + }; + + if (!parentEl._layer) { + // for custom projection cases, the MapMLLayer has not yet created and binded with the layer- at this point, + // because the "createMap" event of mapml-viewer has not yet been dispatched, the map has not yet been created + // the event will be dispatched after defineCustomProjection > projection setter + // should wait until MapMLLayer is built + let parentLayer = + parentEl.nodeName.toUpperCase() === 'LAYER-' + ? parentEl + : parentEl.parentElement || parentEl.parentNode.host; + parentLayer.parentNode.addEventListener('createmap', (e) => { + this._layer = parentLayer._layer; + _attachedToMap(); + }); + } else { + this._layer = parentEl._layer; + _attachedToMap(); } + } - _updateFeature() { - let mapmlvectors = this._layer._mapmlvectors; - // if the parent layer has not yet rendered on the map - if (!mapmlvectors) return; - // if the is not removed, then regenerate featureGroup and update the mapmlvectors accordingly - let native = this._getNativeZoomAndCS(this._layer._content); - this._featureGroup = mapmlvectors.addData(this, native.cs, native.zoom); - mapmlvectors._layers[this._featureGroup._leaflet_id] = this._featureGroup; - this._groupEl = this._featureGroup.options.group; - if (mapmlvectors._staticFeature) { - let container = this._layer.shadowRoot || this._layer._layerEl; - // update zoom bounds of vector layer - mapmlvectors.zoomBounds = mapmlvectors._getZoomBounds(container, this._getNativeZoomAndCS(this._layer._content).zoom); - // add feature layers to map - mapmlvectors._resetFeatures(); - // update map's zoom limit - this._map._addZoomLimit(mapmlvectors); - L.extend(mapmlvectors.options, mapmlvectors.zoomBounds); - } - this._setUpEvents(); + _updateFeature() { + let mapmlvectors = this._layer._mapmlvectors; + // if the parent layer has not yet rendered on the map + if (!mapmlvectors) return; + // if the is not removed, then regenerate featureGroup and update the mapmlvectors accordingly + let native = this._getNativeZoomAndCS(this._layer._content); + this._featureGroup = mapmlvectors.addData(this, native.cs, native.zoom); + mapmlvectors._layers[this._featureGroup._leaflet_id] = this._featureGroup; + this._groupEl = this._featureGroup.options.group; + if (mapmlvectors._staticFeature) { + let container = this._layer.shadowRoot || this._layer._layerEl; + // update zoom bounds of vector layer + mapmlvectors.zoomBounds = mapmlvectors._getZoomBounds( + container, + this._getNativeZoomAndCS(this._layer._content).zoom + ); + // add feature layers to map + mapmlvectors._resetFeatures(); + // update map's zoom limit + this._map._addZoomLimit(mapmlvectors); + L.extend(mapmlvectors.options, mapmlvectors.zoomBounds); } + this._setUpEvents(); + } - _setUpEvents() { - ['click','focus','blur'].forEach(name => { - // onevent properties & onevent attributes - if (this[`on${name}`] && typeof this[`on${name}`] === "function") { - this._groupEl[`on${name}`] = this[`on${name}`]; + _setUpEvents() { + ['click', 'focus', 'blur'].forEach((name) => { + // onevent properties & onevent attributes + if (this[`on${name}`] && typeof this[`on${name}`] === 'function') { + this._groupEl[`on${name}`] = this[`on${name}`]; + } + // handle event handlers set via addEventlistener + // for HTMLElement + // when is clicked / focused / blurred + // should dispatch the click / focus / blur event listener on **linked HTMLFeatureElements** + this._groupEl.addEventListener(name, (e) => { + // this === mapFeature as arrow function does not have their own "this" pointer + // store onEvent handler of mapFeature if there is any to ensure that it will not be re-triggered when the cloned mouseevent is dispatched + // so that only the event handlers set on HTMLFeatureElement via addEventListener method will be triggered + const handler = this[`on${name}`]; // a deep copy, var handler will not change when this.onevent is set to null (i.e. store the onevent property) + this[`on${name}`] = null; + if (name === 'click') { + // dispatch a cloned mouseevent to trigger the click event handlers set on HTMLFeatureElement + this.dispatchEvent(new PointerEvent(name, { ...e })); + } else { + this.dispatchEvent(new FocusEvent(name, { ...e })); } - // handle event handlers set via addEventlistener - // for HTMLElement - // when is clicked / focused / blurred - // should dispatch the click / focus / blur event listener on **linked HTMLFeatureElements** - this._groupEl.addEventListener(name, (e) => { - // this === mapFeature as arrow function does not have their own "this" pointer - // store onEvent handler of mapFeature if there is any to ensure that it will not be re-triggered when the cloned mouseevent is dispatched - // so that only the event handlers set on HTMLFeatureElement via addEventListener method will be triggered - const handler = this[`on${name}`]; // a deep copy, var handler will not change when this.onevent is set to null (i.e. store the onevent property) - this[`on${name}`] = null; - if (name === 'click') { - // dispatch a cloned mouseevent to trigger the click event handlers set on HTMLFeatureElement - this.dispatchEvent(new PointerEvent (name, {...e})); - } else { - this.dispatchEvent(new FocusEvent (name, {...e})); - } - this[`on${name}`] = handler; - }); + this[`on${name}`] = handler; }); - } + }); + } - _getNativeZoomAndCS(content) { - // content: referred to if the has inline , or - // referred to remote mapml if the has a src attribute, and the fetched mapml contains - // referred to null otherwise (i.e. has fetched in shadow, the attaches to 's shadow) - let nativeZoom, nativeCS; - if (this._extentEl) { - // feature attaches to extent's shadow - if (this._extentEl.querySelector('map-link[rel=query]')) { - // for query, fallback zoom is the current map zoom level that the query is returned - nativeZoom = this._map.getZoom(); - nativeCS = 'pcrs'; - } else if (this._extentEl.querySelector('map-link[rel=features]')) { - // for templated feature, read fallback from the fetched mapml's map-meta[name=zoom / cs] - nativeZoom = this._extentEl._nativeZoom; - nativeCS = this._extentEl._nativeCS; - } - return {zoom: nativeZoom, cs: nativeCS}; - } else { - // feature attaches to layer- or layer-'s shadow - if (content.nodeType === Node.DOCUMENT_NODE) { - // for features migrated from mapml, read native zoom and cs from the remote mapml - return this._layer._mapmlvectors._getNativeVariables(content); - } else if (content.nodeName.toUpperCase() === "LAYER-") { - // for inline features, read native zoom and cs from inline map-meta - let zoomMeta = this.parentElement.querySelectorAll('map-meta[name=zoom]'), - zoomLength = zoomMeta?.length; - nativeZoom = zoomLength ? +(zoomMeta[zoomLength - 1].getAttribute('content')?.split(',').find(str => str.includes("value"))?.split('=')[1]) : 0; - - let csMeta = this.parentElement.querySelectorAll("map-meta[name=cs]"), - csLength = csMeta?.length; - nativeCS = csLength ? csMeta[csLength - 1].getAttribute('content') : 'pcrs'; - return {zoom: nativeZoom, cs: nativeCS}; - } + _getNativeZoomAndCS(content) { + // content: referred to if the has inline , or + // referred to remote mapml if the has a src attribute, and the fetched mapml contains + // referred to null otherwise (i.e. has fetched in shadow, the attaches to 's shadow) + let nativeZoom, nativeCS; + if (this._extentEl) { + // feature attaches to extent's shadow + if (this._extentEl.querySelector('map-link[rel=query]')) { + // for query, fallback zoom is the current map zoom level that the query is returned + nativeZoom = this._map.getZoom(); + nativeCS = 'pcrs'; + } else if (this._extentEl.querySelector('map-link[rel=features]')) { + // for templated feature, read fallback from the fetched mapml's map-meta[name=zoom / cs] + nativeZoom = this._extentEl._nativeZoom; + nativeCS = this._extentEl._nativeCS; + } + return { zoom: nativeZoom, cs: nativeCS }; + } else { + // feature attaches to layer- or layer-'s shadow + if (content.nodeType === Node.DOCUMENT_NODE) { + // for features migrated from mapml, read native zoom and cs from the remote mapml + return this._layer._mapmlvectors._getNativeVariables(content); + } else if (content.nodeName.toUpperCase() === 'LAYER-') { + // for inline features, read native zoom and cs from inline map-meta + let zoomMeta = this.parentElement.querySelectorAll( + 'map-meta[name=zoom]' + ), + zoomLength = zoomMeta?.length; + nativeZoom = zoomLength + ? +zoomMeta[zoomLength - 1] + .getAttribute('content') + ?.split(',') + .find((str) => str.includes('value')) + ?.split('=')[1] + : 0; + + let csMeta = this.parentElement.querySelectorAll('map-meta[name=cs]'), + csLength = csMeta?.length; + nativeCS = csLength + ? csMeta[csLength - 1].getAttribute('content') + : 'pcrs'; + return { zoom: nativeZoom, cs: nativeCS }; } } + } - // Util functions: - // internal method to calculate the extent of the feature and store it in cache for the first time - // and return cache when feature's extent is repeatedly requested - // for .extent - _memoizeExtent () { - // memoize calculated extent - let extentCache; - return function () { - if (extentCache && this._getFeatureExtent) { - // if the extent has already been calculated and is not updated, return stored extent - return extentCache; - } else { - // calculate feature extent - let map = this._map, - geometry = this.querySelector('map-geometry'), - native = this._getNativeZoomAndCS(this._layer._content), - cs = geometry.getAttribute('cs') || native.cs, - // zoom level that the feature rendered at - zoom = this.zoom || native.zoom, - shapes = geometry.querySelectorAll("map-point, map-linestring, map-polygon, map-multipoint, map-multilinestring"), - bboxExtent = [Infinity, Infinity, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY]; - for (let shape of shapes) { - let coord = shape.querySelectorAll('map-coordinates'); - for (let i = 0; i < coord.length; ++i) { - bboxExtent = _updateExtent(shape, coord[i], bboxExtent); - } - } - let topLeft = L.point(bboxExtent[0], bboxExtent[1]); - let bottomRight = L.point(bboxExtent[2], bboxExtent[3]); - let pcrsBound = M.boundsToPCRSBounds(L.bounds(topLeft, bottomRight), zoom, map.options.projection, cs); - if (shapes.length === 1 && shapes[0].tagName.toUpperCase() === "MAP-POINT") { - let projection = map.options.projection, - maxZoom = this.hasAttribute('max') ? +this.getAttribute('max') : M[projection].options.resolutions.length - 1, - tileCenter = M[projection].options.crs.tile.bounds.getCenter(), - pixel = M[projection].transformation.transform(pcrsBound.min, M[projection].scale(+this.zoom || maxZoom)); - pcrsBound = M.pixelToPCRSBounds(L.bounds(pixel.subtract(tileCenter), pixel.add(tileCenter)), this.zoom || maxZoom, projection); + // Util functions: + // internal method to calculate the extent of the feature and store it in cache for the first time + // and return cache when feature's extent is repeatedly requested + // for .extent + _memoizeExtent() { + // memoize calculated extent + let extentCache; + return function () { + if (extentCache && this._getFeatureExtent) { + // if the extent has already been calculated and is not updated, return stored extent + return extentCache; + } else { + // calculate feature extent + let map = this._map, + geometry = this.querySelector('map-geometry'), + native = this._getNativeZoomAndCS(this._layer._content), + cs = geometry.getAttribute('cs') || native.cs, + // zoom level that the feature rendered at + zoom = this.zoom || native.zoom, + shapes = geometry.querySelectorAll( + 'map-point, map-linestring, map-polygon, map-multipoint, map-multilinestring' + ), + bboxExtent = [ + Infinity, + Infinity, + Number.NEGATIVE_INFINITY, + Number.NEGATIVE_INFINITY + ]; + for (let shape of shapes) { + let coord = shape.querySelectorAll('map-coordinates'); + for (let i = 0; i < coord.length; ++i) { + bboxExtent = _updateExtent(shape, coord[i], bboxExtent); } - let result = M._convertAndFormatPCRS(pcrsBound, map); - // memoize calculated result - extentCache = result; - return result; } - }; - - // update the bboxExtent - function _updateExtent(shape, coord, bboxExtent) { - let data = coord.innerHTML.trim().replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').split(/[<>\ ]/g); - switch (shape.tagName) { - case "MAP-POINT": - bboxExtent = M._updateExtent(bboxExtent, +data[0], +data[1]); - break; - case "MAP-LINESTRING": - case "MAP-POLYGON": - case "MAP-MULTIPOINT": - case "MAP-MULTILINESTRING": - for (let i = 0; i < data.length; i += 2) { - bboxExtent = M._updateExtent(bboxExtent, +data[i], +data[i + 1]); - } - break; - default: - break; + let topLeft = L.point(bboxExtent[0], bboxExtent[1]); + let bottomRight = L.point(bboxExtent[2], bboxExtent[3]); + let pcrsBound = M.boundsToPCRSBounds( + L.bounds(topLeft, bottomRight), + zoom, + map.options.projection, + cs + ); + if ( + shapes.length === 1 && + shapes[0].tagName.toUpperCase() === 'MAP-POINT' + ) { + let projection = map.options.projection, + maxZoom = this.hasAttribute('max') + ? +this.getAttribute('max') + : M[projection].options.resolutions.length - 1, + tileCenter = M[projection].options.crs.tile.bounds.getCenter(), + pixel = M[projection].transformation.transform( + pcrsBound.min, + M[projection].scale(+this.zoom || maxZoom) + ); + pcrsBound = M.pixelToPCRSBounds( + L.bounds(pixel.subtract(tileCenter), pixel.add(tileCenter)), + this.zoom || maxZoom, + projection + ); } - return bboxExtent; + let result = M._convertAndFormatPCRS(pcrsBound, map); + // memoize calculated result + extentCache = result; + return result; } - } + }; - // find and remove the feature from mapmlvectors._features if vector layer only contains static features, helper function - // prevent it from being rendered again when zooming in / out (mapmlvectors.resetFeature() is invoked) - _removeInFeatureList(zoom) { - let mapmlvectors = this._layer._mapmlvectors; - for (let i = 0; i < mapmlvectors._features[zoom].length; ++i) { - let feature = mapmlvectors._features[zoom][i]; - if (feature._leaflet_id === this._featureGroup._leaflet_id) { - mapmlvectors._features[zoom].splice(i, 1); + // update the bboxExtent + function _updateExtent(shape, coord, bboxExtent) { + let data = coord.innerHTML + .trim() + .replace(/<[^>]+>/g, '') + .replace(/\s+/g, ' ') + .split(/[<>\ ]/g); + switch (shape.tagName) { + case 'MAP-POINT': + bboxExtent = M._updateExtent(bboxExtent, +data[0], +data[1]); + break; + case 'MAP-LINESTRING': + case 'MAP-POLYGON': + case 'MAP-MULTIPOINT': + case 'MAP-MULTILINESTRING': + for (let i = 0; i < data.length; i += 2) { + bboxExtent = M._updateExtent(bboxExtent, +data[i], +data[i + 1]); + } + break; + default: break; - } } + return bboxExtent; } + } - getMaxZoom() { - let tL = this.extent.topLeft.pcrs, - bR = this.extent.bottomRight.pcrs, - bound = L.bounds(L.point(tL.horizontal, tL.vertical), L.point(bR.horizontal, bR.vertical)); - let projection = this._map.options.projection, - layerZoomBounds = this._layer._layerEl.extent.zoom, - minZoom = layerZoomBounds.minZoom ? layerZoomBounds.minZoom : 0, - maxZoom = layerZoomBounds.maxZoom ? layerZoomBounds.maxZoom : M[projection].options.resolutions.length - 1; - let newZoom; - if (this.hasAttribute('zoom')) { - // if there is a zoom attribute set to the map-feature, zoom to the zoom attribute value - newZoom = this.zoom; - } else { - // if not, calculate the maximum zoom level that can show the feature completely - newZoom = M.getMaxZoom(bound, this._map, minZoom, maxZoom); - if (this.max < newZoom) { - // if the calculated zoom is greater than the value of max zoom attribute, go with max zoom attribute - newZoom = this.max; - } else if (this.min > newZoom) { - // if the calculated zoom is less than the value of min zoom attribute, go with min zoom attribute - newZoom = this.min; - } - } - // prevent overzooming / underzooming - if (newZoom < minZoom) { - newZoom = minZoom; - } else if (newZoom > maxZoom) { - newZoom = maxZoom; + // find and remove the feature from mapmlvectors._features if vector layer only contains static features, helper function + // prevent it from being rendered again when zooming in / out (mapmlvectors.resetFeature() is invoked) + _removeInFeatureList(zoom) { + let mapmlvectors = this._layer._mapmlvectors; + for (let i = 0; i < mapmlvectors._features[zoom].length; ++i) { + let feature = mapmlvectors._features[zoom][i]; + if (feature._leaflet_id === this._featureGroup._leaflet_id) { + mapmlvectors._features[zoom].splice(i, 1); + break; } + } + } - // should check whether the extent after zooming falls into the templated extent bound - return newZoom; + getMaxZoom() { + let tL = this.extent.topLeft.pcrs, + bR = this.extent.bottomRight.pcrs, + bound = L.bounds( + L.point(tL.horizontal, tL.vertical), + L.point(bR.horizontal, bR.vertical) + ); + let projection = this._map.options.projection, + layerZoomBounds = this._layer._layerEl.extent.zoom, + minZoom = layerZoomBounds.minZoom ? layerZoomBounds.minZoom : 0, + maxZoom = layerZoomBounds.maxZoom + ? layerZoomBounds.maxZoom + : M[projection].options.resolutions.length - 1; + let newZoom; + if (this.hasAttribute('zoom')) { + // if there is a zoom attribute set to the map-feature, zoom to the zoom attribute value + newZoom = this.zoom; + } else { + // if not, calculate the maximum zoom level that can show the feature completely + newZoom = M.getMaxZoom(bound, this._map, minZoom, maxZoom); + if (this.max < newZoom) { + // if the calculated zoom is greater than the value of max zoom attribute, go with max zoom attribute + newZoom = this.max; + } else if (this.min > newZoom) { + // if the calculated zoom is less than the value of min zoom attribute, go with min zoom attribute + newZoom = this.min; + } + } + // prevent overzooming / underzooming + if (newZoom < minZoom) { + newZoom = minZoom; + } else if (newZoom > maxZoom) { + newZoom = maxZoom; } - // internal support for returning a GeoJSON representation of geometry - // The options object can contain the following: - // propertyFunction - function(), A function that maps the features' element to a GeoJSON "properties" member. - // transform - Bool, Transform coordinates to gcrs values, defaults to True - // mapml2geojson: Object -> GeoJSON - mapml2geojson(options) { - let defaults = { - propertyFunction: null, - transform: true - }; - // assign default values for undefined options - options = Object.assign({}, defaults, options); + // should check whether the extent after zooming falls into the templated extent bound + return newZoom; + } - let json = { - type: "Feature", - properties: {}, - geometry: {} + // internal support for returning a GeoJSON representation of geometry + // The options object can contain the following: + // propertyFunction - function(), A function that maps the features' element to a GeoJSON "properties" member. + // transform - Bool, Transform coordinates to gcrs values, defaults to True + // mapml2geojson: Object -> GeoJSON + mapml2geojson(options) { + let defaults = { + propertyFunction: null, + transform: true + }; + // assign default values for undefined options + options = Object.assign({}, defaults, options); + + let json = { + type: 'Feature', + properties: {}, + geometry: {} + }; + let el = this.querySelector('map-properties'); + if (!el) { + json.properties = null; + } else if (typeof options.propertyFunction === 'function') { + json.properties = options.propertyFunction(el); + } else if (el.querySelector('table')) { + // setting properties when table presented + let table = el.querySelector('table').cloneNode(true); + json.properties = M._table2properties(table); + } else { + // when no table present, strip any possible html tags to only get text + json.properties = { + prop0: el.innerHTML.replace(/(<([^>]+)>)/gi, '').replace(/\s/g, '') }; - let el = this.querySelector('map-properties'); - if (!el) { - json.properties = null; - } else if (typeof options.propertyFunction === "function") { - json.properties = options.propertyFunction(el); - } else if (el.querySelector('table')) { - // setting properties when table presented - let table = (el.querySelector('table')).cloneNode(true); - json.properties = M._table2properties(table); - } else { - // when no table present, strip any possible html tags to only get text - json.properties = {prop0: (el.innerHTML).replace(/(<([^>]+)>)/ig, '').replace(/\s/g, '')}; - } + } - // transform to gcrs if options.transform = true (default) - let source = null, dest = null; - if (options.transform) { - source = new proj4.Proj(this._map.options.crs.code); - dest = new proj4.Proj('EPSG:4326'); - if (this._map.options.crs.code === "EPSG:3857" || this._map.options.crs.code === "EPSG:4326") { - options.transform = false; - } + // transform to gcrs if options.transform = true (default) + let source = null, + dest = null; + if (options.transform) { + source = new proj4.Proj(this._map.options.crs.code); + dest = new proj4.Proj('EPSG:4326'); + if ( + this._map.options.crs.code === 'EPSG:3857' || + this._map.options.crs.code === 'EPSG:4326' + ) { + options.transform = false; } + } - let collection = this.querySelector("map-geometry").querySelector("map-geometrycollection"), - shapes = this.querySelector("map-geometry").querySelectorAll("map-point, map-polygon, map-linestring, map-multipoint, map-multipolygon, map-multilinestring"); + let collection = this.querySelector('map-geometry').querySelector( + 'map-geometrycollection' + ), + shapes = this.querySelector('map-geometry').querySelectorAll( + 'map-point, map-polygon, map-linestring, map-multipoint, map-multipolygon, map-multilinestring' + ); - if (collection) { - json.geometry.type = "GeometryCollection"; - json.geometry.geometries = []; - for (let shape of shapes) { - json.geometry.geometries.push(M._geometry2geojson(shape, source, dest, options.transform)); - } - } else { - json.geometry = M._geometry2geojson(shapes[0], source, dest, options.transform); + if (collection) { + json.geometry.type = 'GeometryCollection'; + json.geometry.geometries = []; + for (let shape of shapes) { + json.geometry.geometries.push( + M._geometry2geojson(shape, source, dest, options.transform) + ); } - return json; + } else { + json.geometry = M._geometry2geojson( + shapes[0], + source, + dest, + options.transform + ); } + return json; + } - // a method that simulates a click, or invoking the user-defined click event - // event (optional): a MouseEvent object, can be passed as an argument of the user-defined click event handlers - click(event) { - let g = this._groupEl, - rect = g.getBoundingClientRect(); - if (!event) { - event = new MouseEvent ("click", { - clientX: rect.x + rect.width / 2, - clientY: rect.y + rect.height / 2, - button: 0 - }); + // a method that simulates a click, or invoking the user-defined click event + // event (optional): a MouseEvent object, can be passed as an argument of the user-defined click event handlers + click(event) { + let g = this._groupEl, + rect = g.getBoundingClientRect(); + if (!event) { + event = new MouseEvent('click', { + clientX: rect.x + rect.width / 2, + clientY: rect.y + rect.height / 2, + button: 0 + }); + } + if (typeof this.onclick === 'function') { + this.onclick.call(this._groupEl, event); + return; + } else { + let properties = this.querySelector('map-properties'); + if (g.getAttribute('role') === 'link') { + for (let path of g.children) { + path.mousedown.call(this._featureGroup, event); + path.mouseup.call(this._featureGroup, event); + } } - if (typeof this.onclick === 'function') { - this.onclick.call(this._groupEl, event); - return; - } else { - let properties = this.querySelector('map-properties'); - if (g.getAttribute('role') === 'link') { - for (let path of g.children) { - path.mousedown.call(this._featureGroup, event); - path.mouseup.call(this._featureGroup, event); + // for custom projection, layer- element may disconnect and re-attach to the map after the click + // so check whether map-feature element is still connected before any further operations + if (properties && this.isConnected) { + let featureGroup = this._featureGroup, + shapes = featureGroup._layers; + // close popup if the popup is currently open + for (let id in shapes) { + if (shapes[id].isPopupOpen()) { + shapes[id].closePopup(); } } - // for custom projection, layer- element may disconnect and re-attach to the map after the click - // so check whether map-feature element is still connected before any further operations - if (properties && this.isConnected) { - let featureGroup = this._featureGroup, - shapes = featureGroup._layers; - // close popup if the popup is currently open - for (let id in shapes) { - if (shapes[id].isPopupOpen()) { - shapes[id].closePopup(); - } - } - if (featureGroup.isPopupOpen()) { - featureGroup.closePopup(); - } else { - featureGroup.openPopup(); - } + if (featureGroup.isPopupOpen()) { + featureGroup.closePopup(); + } else { + featureGroup.openPopup(); } } } - - // a method that sets the current focus to the element, or invoking the user-defined focus event - // event (optional): a FocusEvent object, can be passed as an argument of the user-defined focus event handlers - // options (optional): as options parameter for native HTMLelemnt - // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus - focus(event, options) { - let g = this._groupEl; - if (typeof this.onfocus === 'function') { - this.onfocus.call(this._groupEl, event); - return; - } else { - g.focus(options); - } - } + } - // a method that makes the element lose focus, or invoking the user-defined blur event - // event (optional): a FocusEvent object, can be passed as an argument of the user-defined blur event handlers - blur(event) { - if (typeof this.onblur === 'function') { - this.onblur.call(this._groupEl, event); - } else if (document.activeElement.shadowRoot?.activeElement === this._groupEl || - document.activeElement.shadowRoot?.activeElement.parentNode === this._groupEl) { - this._groupEl.blur(); - // set focus to the map container - this._map._container.focus(); - } + // a method that sets the current focus to the element, or invoking the user-defined focus event + // event (optional): a FocusEvent object, can be passed as an argument of the user-defined focus event handlers + // options (optional): as options parameter for native HTMLelemnt + // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus + focus(event, options) { + let g = this._groupEl; + if (typeof this.onfocus === 'function') { + this.onfocus.call(this._groupEl, event); + return; + } else { + g.focus(options); } + } - // a method that can the viewport to be centred on the feature's extent - zoomTo() { - let extent = this.extent, - map = this._map; - let tL = extent.topLeft.pcrs, - bR = extent.bottomRight.pcrs, - bound = L.bounds(L.point(tL.horizontal, tL.vertical), L.point(bR.horizontal, bR.vertical)), - center = map.options.crs.unproject(bound.getCenter(true)); - map.setView(center, this.getMaxZoom(), {animate: false}); + // a method that makes the element lose focus, or invoking the user-defined blur event + // event (optional): a FocusEvent object, can be passed as an argument of the user-defined blur event handlers + blur(event) { + if (typeof this.onblur === 'function') { + this.onblur.call(this._groupEl, event); + } else if ( + document.activeElement.shadowRoot?.activeElement === this._groupEl || + document.activeElement.shadowRoot?.activeElement.parentNode === + this._groupEl + ) { + this._groupEl.blur(); + // set focus to the map container + this._map._container.focus(); } - } \ No newline at end of file + } + + // a method that can the viewport to be centred on the feature's extent + zoomTo() { + let extent = this.extent, + map = this._map; + let tL = extent.topLeft.pcrs, + bR = extent.bottomRight.pcrs, + bound = L.bounds( + L.point(tL.horizontal, tL.vertical), + L.point(bR.horizontal, bR.vertical) + ), + center = map.options.crs.unproject(bound.getCenter(true)); + map.setView(center, this.getMaxZoom(), { animate: false }); + } +} diff --git a/src/map-geometry.js b/src/map-geometry.js index 270e75d09..e6db1c02f 100644 --- a/src/map-geometry.js +++ b/src/map-geometry.js @@ -6,11 +6,11 @@ export class MapGeometry extends HTMLElement { return this.getAttribute('cs'); } set cs(val) { - if (['tcrs','tilematrix','pcrs','gcrs','map','tile'].includes(val)) { + if (['tcrs', 'tilematrix', 'pcrs', 'gcrs', 'map', 'tile'].includes(val)) { this.setAttribute('cs', val); } } - + attributeChangedCallback(name, oldValue, newValue) { switch (name) { case 'cs': { @@ -23,13 +23,9 @@ export class MapGeometry extends HTMLElement { } constructor() { // Always call super first in constructor - super(); - } - connectedCallback() { - - } - disconnectedCallback() { - + super(); } + connectedCallback() {} + disconnectedCallback() {} } window.customElements.define('map-geometry', MapGeometry); diff --git a/src/map-input.js b/src/map-input.js index 11b3f136b..bd92e01b1 100644 --- a/src/map-input.js +++ b/src/map-input.js @@ -2,7 +2,18 @@ import { MapLink } from './map-link.js'; export class MapInput extends HTMLElement { static get observedAttributes() { - return ['name','type','value','axis','units','position','rel','min','max','step']; + return [ + 'name', + 'type', + 'value', + 'axis', + 'units', + 'position', + 'rel', + 'min', + 'max', + 'step' + ]; } get name() { @@ -141,13 +152,9 @@ export class MapInput extends HTMLElement { } constructor() { // Always call super first in constructor - super(); - } - connectedCallback() { - - } - disconnectedCallback() { - + super(); } + connectedCallback() {} + disconnectedCallback() {} } window.customElements.define('map-input', MapInput); diff --git a/src/map-link.js b/src/map-link.js index d7883e85b..969bf7a41 100644 --- a/src/map-link.js +++ b/src/map-link.js @@ -1,13 +1,22 @@ export class MapLink extends HTMLElement { static get observedAttributes() { - return ['type','rel','title','href','hreflang','tref','tms','projection']; + return [ + 'type', + 'rel', + 'title', + 'href', + 'hreflang', + 'tref', + 'tms', + 'projection' + ]; } get type() { return this.getAttribute('type'); } set type(val) { // improve this - if (val === "text/mapml" || val.startsWith('image/')) { + if (val === 'text/mapml' || val.startsWith('image/')) { this.setAttribute('type', val); } } @@ -16,7 +25,22 @@ export class MapLink extends HTMLElement { } set rel(val) { // improve this - if (['license','alternate','self','style','tile','image','features','zoomin','zoomout','legend','query','stylesheet'].includes(val)) { + if ( + [ + 'license', + 'alternate', + 'self', + 'style', + 'tile', + 'image', + 'features', + 'zoomin', + 'zoomout', + 'legend', + 'query', + 'stylesheet' + ].includes(val) + ) { this.setAttribute('type', val); } } @@ -69,11 +93,11 @@ export class MapLink extends HTMLElement { } set projection(val) { // improve this - if (['OSMTILE','CBMTILE','WGS84','APSTILE'].includes(val)) { + if (['OSMTILE', 'CBMTILE', 'WGS84', 'APSTILE'].includes(val)) { this.setAttribute('projection', val); } } - + attributeChangedCallback(name, oldValue, newValue) { //['type','rel','title','href','hreflang','tref','tms','projection']; switch (name) { @@ -121,13 +145,9 @@ export class MapLink extends HTMLElement { } constructor() { // Always call super first in constructor - super(); - } - connectedCallback() { - - } - disconnectedCallback() { - + super(); } + connectedCallback() {} + disconnectedCallback() {} } window.customElements.define('map-link', MapLink); diff --git a/src/map-meta.js b/src/map-meta.js index 108474c6b..ef3d92344 100644 --- a/src/map-meta.js +++ b/src/map-meta.js @@ -1,12 +1,12 @@ export class MapMeta extends HTMLElement { static get observedAttributes() { - return ['name','content']; + return ['name', 'content']; } get name() { return this.getAttribute('name'); } set name(val) { - if (['projection','extent','cs','zoom'].includes(val)) { + if (['projection', 'extent', 'cs', 'zoom'].includes(val)) { this.setAttribute('name', val); } } @@ -15,12 +15,15 @@ export class MapMeta extends HTMLElement { } set content(val) { // improve this - if (this.name === 'cs' && !['tcrs','tilematrix','pcrs','gcrs','map','tile'].includes(val)) { + if ( + this.name === 'cs' && + !['tcrs', 'tilematrix', 'pcrs', 'gcrs', 'map', 'tile'].includes(val) + ) { return; } this.setAttribute('content', val); } - + attributeChangedCallback(name, oldValue, newValue) { switch (name) { case 'name': { @@ -39,13 +42,9 @@ export class MapMeta extends HTMLElement { } constructor() { // Always call super first in constructor - super(); - } - connectedCallback() { - - } - disconnectedCallback() { - + super(); } + connectedCallback() {} + disconnectedCallback() {} } window.customElements.define('map-meta', MapMeta); diff --git a/src/map-properties.js b/src/map-properties.js index e39c32791..f3ef7c392 100644 --- a/src/map-properties.js +++ b/src/map-properties.js @@ -1,20 +1,14 @@ export class MapProperties extends HTMLElement { - static get observedAttributes() { - return; - } + static get observedAttributes() { + return; + } - attributeChangedCallback(name, oldValue, newValue) { - } - constructor() { - // Always call super first in constructor - super(); - } - connectedCallback() { - - } - disconnectedCallback() { - - } + attributeChangedCallback(name, oldValue, newValue) {} + constructor() { + // Always call super first in constructor + super(); } - window.customElements.define('map-properties', MapProperties); - \ No newline at end of file + connectedCallback() {} + disconnectedCallback() {} +} +window.customElements.define('map-properties', MapProperties); diff --git a/src/map-select.js b/src/map-select.js index 1e26e3d9c..fdb130ceb 100644 --- a/src/map-select.js +++ b/src/map-select.js @@ -1,6 +1,6 @@ export class MapSelect extends HTMLElement { static get observedAttributes() { - return ['name','id']; + return ['name', 'id']; } get name() { return this.getAttribute('name'); @@ -14,7 +14,7 @@ export class MapSelect extends HTMLElement { set id(val) { this.setAttribute('id', val); } - + attributeChangedCallback(name, oldValue, newValue) { switch (name) { case 'name': @@ -31,13 +31,9 @@ export class MapSelect extends HTMLElement { } constructor() { // Always call super first in constructor - super(); - } - connectedCallback() { - - } - disconnectedCallback() { - + super(); } + connectedCallback() {} + disconnectedCallback() {} } window.customElements.define('map-select', MapSelect); diff --git a/src/map-span.js b/src/map-span.js index 73fc903bd..c9406958d 100644 --- a/src/map-span.js +++ b/src/map-span.js @@ -1,20 +1,14 @@ export class MapSpan extends HTMLElement { - static get observedAttributes() { - return; - } + static get observedAttributes() { + return; + } - attributeChangedCallback(name, oldValue, newValue) { - } - constructor() { - // Always call super first in constructor - super(); - } - connectedCallback() { - - } - disconnectedCallback() { - - } + attributeChangedCallback(name, oldValue, newValue) {} + constructor() { + // Always call super first in constructor + super(); } - window.customElements.define('map-span', MapSpan); - \ No newline at end of file + connectedCallback() {} + disconnectedCallback() {} +} +window.customElements.define('map-span', MapSpan); diff --git a/src/map-style.js b/src/map-style.js index fc4b9680b..ac1847638 100644 --- a/src/map-style.js +++ b/src/map-style.js @@ -1,20 +1,14 @@ export class MapStyle extends HTMLElement { - static get observedAttributes() { - return; - } + static get observedAttributes() { + return; + } - attributeChangedCallback(name, oldValue, newValue) { - } - constructor() { - // Always call super first in constructor - super(); - } - connectedCallback() { - - } - disconnectedCallback() { - - } + attributeChangedCallback(name, oldValue, newValue) {} + constructor() { + // Always call super first in constructor + super(); } - window.customElements.define('map-style', MapStyle); - \ No newline at end of file + connectedCallback() {} + disconnectedCallback() {} +} +window.customElements.define('map-style', MapStyle); diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index dbadc55be..1210f4b72 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -1,5 +1,5 @@ -import './leaflet.js'; // bundled with proj4, proj4leaflet, modularized -import './mapml.js'; +import './leaflet.js'; // bundled with proj4, proj4leaflet, modularized +import './mapml.js'; import DOMTokenList from './DOMTokenList.js'; import { MapLayer } from './layer.js'; import { MapCaption } from './map-caption.js'; @@ -8,7 +8,17 @@ import { MapExtent } from './map-extent.js'; export class MapViewer extends HTMLElement { static get observedAttributes() { - return ['lat', 'lon', 'zoom', 'projection', 'width', 'height', 'controls', 'static', 'controlslist']; + return [ + 'lat', + 'lon', + 'zoom', + 'projection', + 'width', + 'height', + 'controls', + 'static', + 'controlslist' + ]; } // see comments below regarding attributeChangedCallback vs. getter/setter // usage. Effectively, the user of the element must use the property, not @@ -21,7 +31,7 @@ export class MapViewer extends HTMLElement { set controls(value) { const hasControls = Boolean(value); if (hasControls) { - this.setAttribute('controls',''); + this.setAttribute('controls', ''); } else { this.removeAttribute('controls'); } @@ -34,90 +44,91 @@ export class MapViewer extends HTMLElement { this.setAttribute('controlslist', value); } get width() { - return (window.getComputedStyle(this).width).replace('px',''); + return window.getComputedStyle(this).width.replace('px', ''); } set width(val) { //img.height or img.width setters change or add the corresponding attributes - this.setAttribute("width", val); + this.setAttribute('width', val); } get height() { - return (window.getComputedStyle(this).height).replace('px',''); + return window.getComputedStyle(this).height.replace('px', ''); } set height(val) { //img.height or img.width setters change or add the corresponding attributes - this.setAttribute("height", val); + this.setAttribute('height', val); } get lat() { - return this.hasAttribute("lat") ? this.getAttribute("lat") : "0"; + return this.hasAttribute('lat') ? this.getAttribute('lat') : '0'; } set lat(val) { if (val) { - this.setAttribute("lat", val); + this.setAttribute('lat', val); } } get lon() { - return this.hasAttribute("lon") ? this.getAttribute("lon") : "0"; + return this.hasAttribute('lon') ? this.getAttribute('lon') : '0'; } set lon(val) { if (val) { - this.setAttribute("lon", val); + this.setAttribute('lon', val); } } get projection() { - return this.hasAttribute("projection") ? this.getAttribute("projection") : ""; + return this.hasAttribute('projection') + ? this.getAttribute('projection') + : ''; } set projection(val) { - if(val && M[val]){ + if (val && M[val]) { this.setAttribute('projection', val); - if (this._map && this._map.options.projection !== val){ + if (this._map && this._map.options.projection !== val) { this._map.options.crs = M[val]; this._map.options.projection = val; - for(let layer of this.querySelectorAll("layer-")){ - layer.removeAttribute("disabled"); + for (let layer of this.querySelectorAll('layer-')) { + layer.removeAttribute('disabled'); let reAttach = this.removeChild(layer); this.appendChild(reAttach); } - if(this._debug) for(let i = 0; i<2;i++) this.toggleDebug(); + if (this._debug) for (let i = 0; i < 2; i++) this.toggleDebug(); } else this.dispatchEvent(new CustomEvent('createmap')); - } else throw new Error("Undefined Projection"); + } else throw new Error('Undefined Projection'); } get zoom() { - return this.hasAttribute("zoom") ? this.getAttribute("zoom") : 0; + return this.hasAttribute('zoom') ? this.getAttribute('zoom') : 0; } set zoom(val) { - var parsedVal = parseInt(val,10); - if (!isNaN(parsedVal) && (parsedVal >= 0 && parsedVal <= 25)) { - this.setAttribute('zoom', parsedVal); - } + var parsedVal = parseInt(val, 10); + if (!isNaN(parsedVal) && parsedVal >= 0 && parsedVal <= 25) { + this.setAttribute('zoom', parsedVal); + } } get layers() { return this.getElementsByTagName('layer-'); } - get extent(){ + get extent() { let map = this._map, pcrsBounds = M.pixelToPCRSBounds( map.getPixelBounds(), map.getZoom(), - map.options.projection); + map.options.projection + ); let formattedExtent = M._convertAndFormatPCRS(pcrsBounds, map); - if(map.getMaxZoom() !== Infinity){ + if (map.getMaxZoom() !== Infinity) { formattedExtent.zoom = { - minZoom:map.getMinZoom(), - maxZoom:map.getMaxZoom() + minZoom: map.getMinZoom(), + maxZoom: map.getMaxZoom() }; } - return (formattedExtent); + return formattedExtent; } get static() { return this.hasAttribute('static'); } set static(value) { const isStatic = Boolean(value); - if (isStatic) - this.setAttribute('static', ''); - else - this.removeAttribute('static'); + if (isStatic) this.setAttribute('static', ''); + else this.removeAttribute('static'); } constructor() { @@ -130,25 +141,36 @@ export class MapViewer extends HTMLElement { this._traversalCall = false; } connectedCallback() { - this._initShadowRoot(); this._controlsList = new DOMTokenList( - this.getAttribute("controlslist"), - this, "controlslist", - ["noreload","nofullscreen","nozoom","nolayer","noscale","geolocation"] + this.getAttribute('controlslist'), + this, + 'controlslist', + [ + 'noreload', + 'nofullscreen', + 'nozoom', + 'nolayer', + 'noscale', + 'geolocation' + ] ); var s = window.getComputedStyle(this), - wpx = s.width, hpx=s.height, - w = this.hasAttribute("width") ? this.getAttribute("width") : parseInt(wpx.replace('px','')), - h = this.hasAttribute("height") ? this.getAttribute("height") : parseInt(hpx.replace('px','')); + wpx = s.width, + hpx = s.height, + w = this.hasAttribute('width') + ? this.getAttribute('width') + : parseInt(wpx.replace('px', '')), + h = this.hasAttribute('height') + ? this.getAttribute('height') + : parseInt(hpx.replace('px', '')); this._changeWidth(w); - this._changeHeight(h); - + this._changeHeight(h); // wait for createmap event before creating leaflet map - // this allows a safeguard for the case where loading a custom TCRS takes + // this allows a safeguard for the case where loading a custom TCRS takes // longer than loading mapml-viewer.js/web-map.js // the REASON we need a synchronous event listener (see comment below) // is because the mapml-viewer element has / can have a size of 0 up until after @@ -156,12 +178,14 @@ export class MapViewer extends HTMLElement { // perhaps a browser rendering cycle?? this.addEventListener('createmap', this._createMap); - let custom = !(["CBMTILE","APSTILE","OSMTILE","WGS84"].includes(this.projection)); + let custom = !['CBMTILE', 'APSTILE', 'OSMTILE', 'WGS84'].includes( + this.projection + ); // this is worth a read, because dispatchEvent is synchronous // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent // In particular: // "All applicable event handlers are called and return before dispatchEvent() returns." - if (!custom) { + if (!custom) { this.dispatchEvent(new CustomEvent('createmap')); } // https://github.com/Maps4HTML/Web-Map-Custom-Element/issues/274 @@ -184,7 +208,7 @@ export class MapViewer extends HTMLElement { let mapcaptionupdate = this.querySelector('map-caption'); if (mapcaptionupdate !== mapcaption) { this.removeAttribute('aria-label'); - } + } }); this.mapCaptionObserver.observe(this, { childList: true @@ -195,7 +219,7 @@ export class MapViewer extends HTMLElement { } _initShadowRoot() { if (!this.shadowRoot) { - this.attachShadow({mode: 'open'}); + this.attachShadow({ mode: 'open' }); } let tmpl = document.createElement('template'); /* jshint ignore:start */ @@ -207,39 +231,38 @@ export class MapViewer extends HTMLElement { let shadowRoot = this.shadowRoot; this._container = document.createElement('div'); - let output = ""; - this._container.insertAdjacentHTML("beforeend", output); + let output = + ""; + this._container.insertAdjacentHTML('beforeend', output); // Set default styles for the map element. let mapDefaultCSS = document.createElement('style'); mapDefaultCSS.innerHTML = - `:host {` + - `all: initial;` + // Reset properties inheritable from html/body, as some inherited styles may cause unexpected issues with the map element's components (https://github.com/Maps4HTML/Web-Map-Custom-Element/issues/140). - `contain: layout size;` + // Contain layout and size calculations within the map element. - `display: inline-block;` + // This together with dimension properties is required so that Leaflet isn't working with a height=0 box by default. - `height: 150px;` + // Provide a "default object size" (https://github.com/Maps4HTML/HTML-Map-Element/issues/31). - `width: 300px;` + - `border-width: 2px;` + // Set a default border for contrast, similar to UA default for iframes. - `border-style: inset;` + - `}` + - `:host([frameborder="0"]) {` + - `border-width: 0;` + - `}` + - `:host([hidden]) {` + - `display: none!important;` + - `}` + - `:host .leaflet-control-container {` + - `visibility: hidden!important;` + // Visibility hack to improve percieved performance (mitigate FOUC) – visibility is unset in mapml.css! (https://github.com/Maps4HTML/Web-Map-Custom-Element/issues/154). - `}`; + `:host {` + + `all: initial;` + // Reset properties inheritable from html/body, as some inherited styles may cause unexpected issues with the map element's components (https://github.com/Maps4HTML/Web-Map-Custom-Element/issues/140). + `contain: layout size;` + // Contain layout and size calculations within the map element. + `display: inline-block;` + // This together with dimension properties is required so that Leaflet isn't working with a height=0 box by default. + `height: 150px;` + // Provide a "default object size" (https://github.com/Maps4HTML/HTML-Map-Element/issues/31). + `width: 300px;` + + `border-width: 2px;` + // Set a default border for contrast, similar to UA default for iframes. + `border-style: inset;` + + `}` + + `:host([frameborder="0"]) {` + + `border-width: 0;` + + `}` + + `:host([hidden]) {` + + `display: none!important;` + + `}` + + `:host .leaflet-control-container {` + + `visibility: hidden!important;` + // Visibility hack to improve percieved performance (mitigate FOUC) – visibility is unset in mapml.css! (https://github.com/Maps4HTML/Web-Map-Custom-Element/issues/154). + `}`; // Hide all (light DOM) children of the map element. let hideElementsCSS = document.createElement('style'); hideElementsCSS.innerHTML = - `mapml-viewer > * {` + - `display: none!important;` + - `}`; + `mapml-viewer > * {` + `display: none!important;` + `}`; this.appendChild(hideElementsCSS); - + // Make the Leaflet container element programmatically identifiable // (https://github.com/Leaflet/Leaflet/issues/7193). this._container.setAttribute('role', 'region'); @@ -248,7 +271,6 @@ export class MapViewer extends HTMLElement { shadowRoot.appendChild(mapDefaultCSS); shadowRoot.appendChild(tmpl.content.cloneNode(true)); shadowRoot.appendChild(this._container); - } _createMap() { if (!this._map) { @@ -276,7 +298,8 @@ export class MapViewer extends HTMLElement { this._toggleControls(); this._crosshair = M.crosshair().addTo(this._map); - if(M.options.featureIndexOverlayOption) this._featureIndexOverlay = M.featureIndexOverlay().addTo(this._map); + if (M.options.featureIndexOverlayOption) + this._featureIndexOverlay = M.featureIndexOverlay().addTo(this._map); this._setUpEvents(); } @@ -284,16 +307,16 @@ export class MapViewer extends HTMLElement { disconnectedCallback() { while (this.shadowRoot.firstChild) { this.shadowRoot.removeChild(this.shadowRoot.firstChild); - } + } delete this._map; this._deleteControls(); } adoptedCallback() { -// console.log('Custom map element moved to new page.'); + // console.log('Custom map element moved to new page.'); } attributeChangedCallback(name, oldValue, newValue) { -// console.log('Attribute: ' + name + ' changed from: '+ oldValue + ' to: '+newValue); + // console.log('Attribute: ' + name + ' changed from: '+ oldValue + ' to: '+newValue); // "Best practice": handle side-effects in this callback // https://developers.google.com/web/fundamentals/web-components/best-practices // https://developers.google.com/web/fundamentals/web-components/best-practices#avoid-reentrancy @@ -310,7 +333,7 @@ export class MapViewer extends HTMLElement { break; ... } */ - switch(name) { + switch (name) { case 'controlslist': if (this._controlsList) { if (this._controlsList.valueSet === false) { @@ -318,58 +341,62 @@ export class MapViewer extends HTMLElement { } this._toggleControls(); } - break; + break; case 'controls': if (oldValue !== null && newValue === null) { this._hideControls(); } else if (oldValue === null && newValue !== null) { this._showControls(); } - break; - case 'height': + break; + case 'height': if (oldValue !== newValue) { this._changeHeight(newValue); } - break; - case 'width': + break; + case 'width': if (oldValue !== newValue) { this._changeWidth(newValue); } - break; + break; case 'static': this._toggleStatic(); - break; + break; } } // Creates All map controls and adds them to the map, when created. _createControls() { let mapSize = this._map.getSize().y, - totalSize = 0; + totalSize = 0; - this._layerControl = M.layerControl(null,{"collapsed": true, mapEl: this}).addTo(this._map); + this._layerControl = M.layerControl(null, { + collapsed: true, + mapEl: this + }).addTo(this._map); let scaleValue = M.options.announceScale; - if (scaleValue === "metric") { - scaleValue = {"metric": true, "imperial": false}; + if (scaleValue === 'metric') { + scaleValue = { metric: true, imperial: false }; } - if (scaleValue === "imperial") { - scaleValue = {"metric": false, "imperial": true}; + if (scaleValue === 'imperial') { + scaleValue = { metric: false, imperial: true }; } - if (!this._scaleBar) this._scaleBar = M.scaleBar(scaleValue).addTo(this._map); + if (!this._scaleBar) + this._scaleBar = M.scaleBar(scaleValue).addTo(this._map); // Only add controls if there is enough top left vertical space - if (!this._zoomControl && (totalSize + 93) <= mapSize){ + if (!this._zoomControl && totalSize + 93 <= mapSize) { totalSize += 93; this._zoomControl = L.control.zoom().addTo(this._map); } - if (!this._reloadButton && (totalSize + 49) <= mapSize){ + if (!this._reloadButton && totalSize + 49 <= mapSize) { totalSize += 49; this._reloadButton = M.reloadButton().addTo(this._map); } - if (!this._fullScreenControl && (totalSize + 49) <= mapSize){ + if (!this._fullScreenControl && totalSize + 49 <= mapSize) { totalSize += 49; this._fullScreenControl = M.fullscreenButton().addTo(this._map); } @@ -378,64 +405,64 @@ export class MapViewer extends HTMLElement { this._geolocationButton = M.geolocationButton().addTo(this._map); } } - + // Sets controls by hiding/unhiding them based on the map attribute _toggleControls() { if (this.controls === false) { this._hideControls(); - this._map.contextMenu.toggleContextMenuItem("Controls", "disabled"); - } else { + this._map.contextMenu.toggleContextMenuItem('Controls', 'disabled'); + } else { this._showControls(); - this._map.contextMenu.toggleContextMenuItem("Controls", "enabled"); + this._map.contextMenu.toggleContextMenuItem('Controls', 'enabled'); } } _hideControls() { - this._setControlsVisibility("fullscreen",true); - this._setControlsVisibility("layercontrol",true); - this._setControlsVisibility("reload",true); - this._setControlsVisibility("zoom",true); - this._setControlsVisibility("geolocation",true); - this._setControlsVisibility("scale",true); + this._setControlsVisibility('fullscreen', true); + this._setControlsVisibility('layercontrol', true); + this._setControlsVisibility('reload', true); + this._setControlsVisibility('zoom', true); + this._setControlsVisibility('geolocation', true); + this._setControlsVisibility('scale', true); } _showControls() { - this._setControlsVisibility("fullscreen",false); - this._setControlsVisibility("layercontrol",false); - this._setControlsVisibility("reload",false); - this._setControlsVisibility("zoom",false); - this._setControlsVisibility("geolocation",true); - this._setControlsVisibility("scale",false); - + this._setControlsVisibility('fullscreen', false); + this._setControlsVisibility('layercontrol', false); + this._setControlsVisibility('reload', false); + this._setControlsVisibility('zoom', false); + this._setControlsVisibility('geolocation', true); + this._setControlsVisibility('scale', false); + // prune the controls shown if necessary // this logic could be embedded in _showControls // but would require being able to iterate the domain of supported tokens // for the controlslist if (this._controlsList) { this._controlsList.forEach((value) => { - switch(value.toLowerCase()) { + switch (value.toLowerCase()) { case 'nofullscreen': - this._setControlsVisibility("fullscreen",true); - break; + this._setControlsVisibility('fullscreen', true); + break; case 'nolayer': - this._setControlsVisibility("layercontrol",true); - break; + this._setControlsVisibility('layercontrol', true); + break; case 'noreload': - this._setControlsVisibility("reload",true); - break; + this._setControlsVisibility('reload', true); + break; case 'nozoom': - this._setControlsVisibility("zoom",true); - break; + this._setControlsVisibility('zoom', true); + break; case 'geolocation': - this._setControlsVisibility("geolocation",false); - break; + this._setControlsVisibility('geolocation', false); + break; case 'noscale': - this._setControlsVisibility("scale",true); - break; + this._setControlsVisibility('scale', true); + break; } }); } if (this._layerControl && this._layerControl._layers.length === 0) { - this._layerControl._container.setAttribute("hidden",""); + this._layerControl._container.setAttribute('hidden', ''); } } @@ -452,55 +479,55 @@ export class MapViewer extends HTMLElement { // for the control element based on the Boolean hide parameter _setControlsVisibility(control, hide) { let container; - switch(control) { - case "zoom": + switch (control) { + case 'zoom': if (this._zoomControl) { container = this._zoomControl._container; } break; - case "reload": + case 'reload': if (this._reloadButton) { container = this._reloadButton._container; } break; - case "fullscreen": + case 'fullscreen': if (this._fullScreenControl) { container = this._fullScreenControl._container; } break; - case "layercontrol": + case 'layercontrol': if (this._layerControl) { container = this._layerControl._container; } break; - case "geolocation": + case 'geolocation': if (this._geolocationButton) { container = this._geolocationButton._container; } break; - case "scale": + case 'scale': if (this._scaleBar) { container = this._scaleBar._container; } - break; + break; } if (container) { if (hide) { // setting the visibility for all the children of the element - [ ...container.children].forEach((childEl) => { - childEl.setAttribute("hidden",""); + [...container.children].forEach((childEl) => { + childEl.setAttribute('hidden', ''); }); - container.setAttribute("hidden",""); + container.setAttribute('hidden', ''); } else { // setting the visibility for all the children of the element - [ ...container.children].forEach((childEl) => { - childEl.removeAttribute("hidden"); + [...container.children].forEach((childEl) => { + childEl.removeAttribute('hidden'); }); - container.removeAttribute("hidden"); + container.removeAttribute('hidden'); } } } - _toggleStatic(){ + _toggleStatic() { const isStatic = this.hasAttribute('static'); if (this._map) { if (isStatic) { @@ -522,179 +549,308 @@ export class MapViewer extends HTMLElement { } } } - + _dropHandler(event) { event.preventDefault(); - let text = event.dataTransfer.getData("text"); + let text = event.dataTransfer.getData('text'); M._pasteLayer(this, text); } _dragoverHandler(event) { event.preventDefault(); - event.dataTransfer.dropEffect = "copy"; + event.dataTransfer.dropEffect = 'copy'; } _removeEvents() { if (this._map) { this._map.off(); - this.removeEventListener("drop", this._dropHandler, false); - this.removeEventListener("dragover", this._dragoverHandler, false); + this.removeEventListener('drop', this._dropHandler, false); + this.removeEventListener('dragover', this._dragoverHandler, false); } } _setUpEvents() { - this.addEventListener("drop", this._dropHandler, false); - this.addEventListener("dragover", this._dragoverHandler, false); - this.addEventListener("change", - function(e) { - if(e.target.tagName === "LAYER-"){ - this.dispatchEvent(new CustomEvent("layerchange", {details:{target: this, originalEvent: e}})); - } - }, false); + this.addEventListener('drop', this._dropHandler, false); + this.addEventListener('dragover', this._dragoverHandler, false); + this.addEventListener( + 'change', + function (e) { + if (e.target.tagName === 'LAYER-') { + this.dispatchEvent( + new CustomEvent('layerchange', { + details: { target: this, originalEvent: e } + }) + ); + } + }, + false + ); this.parentElement.addEventListener('keyup', function (e) { - if(e.keyCode === 9 && document.activeElement.nodeName === "MAPML-VIEWER"){ - document.activeElement.dispatchEvent(new CustomEvent('mapfocused', {detail: - {target: this}})); + if ( + e.keyCode === 9 && + document.activeElement.nodeName === 'MAPML-VIEWER' + ) { + document.activeElement.dispatchEvent( + new CustomEvent('mapfocused', { detail: { target: this } }) + ); } }); - // pasting layer-, links and geojson using Ctrl+V + // pasting layer-, links and geojson using Ctrl+V this.addEventListener('keydown', function (e) { - if(e.keyCode === 86 && e.ctrlKey){ - navigator.clipboard - .readText() - .then( - (layer) => { - M._pasteLayer(this, layer); - }); - // Prevents default spacebar event on all of mapml-viewer - } else if (e.keyCode === 32 && - this.shadowRoot.activeElement.nodeName !== "INPUT") { + if (e.keyCode === 86 && e.ctrlKey) { + navigator.clipboard.readText().then((layer) => { + M._pasteLayer(this, layer); + }); + // Prevents default spacebar event on all of mapml-viewer + } else if ( + e.keyCode === 32 && + this.shadowRoot.activeElement.nodeName !== 'INPUT' + ) { e.preventDefault(); - this._map.fire('keypress', {originalEvent: e}); + this._map.fire('keypress', { originalEvent: e }); } }); this.parentElement.addEventListener('mousedown', function (e) { - if(document.activeElement.nodeName === "MAPML-VIEWER"){ - document.activeElement.dispatchEvent(new CustomEvent('mapfocused', {detail: - {target: this}})); + if (document.activeElement.nodeName === 'MAPML-VIEWER') { + document.activeElement.dispatchEvent( + new CustomEvent('mapfocused', { detail: { target: this } }) + ); } }); - this._map.on('locationfound', + this._map.on( + 'locationfound', function (e) { - this.dispatchEvent(new CustomEvent('maplocationfound', {detail: - {latlng: e.latlng, accuracy: e.accuracy} - })); - },this); - this._map.on('locationerror', + this.dispatchEvent( + new CustomEvent('maplocationfound', { + detail: { latlng: e.latlng, accuracy: e.accuracy } + }) + ); + }, + this + ); + this._map.on( + 'locationerror', function (e) { - this.dispatchEvent(new CustomEvent('locationerror', {detail: - {error:e.message} - })); - },this); - this._map.on('load', + this.dispatchEvent( + new CustomEvent('locationerror', { detail: { error: e.message } }) + ); + }, + this + ); + this._map.on( + 'load', function () { - this.dispatchEvent(new CustomEvent('load', {detail: {target: this}})); - }, this); - this._map.on('preclick', + this.dispatchEvent( + new CustomEvent('load', { detail: { target: this } }) + ); + }, + this + ); + this._map.on( + 'preclick', function (e) { - this.dispatchEvent(new CustomEvent('preclick', {detail: - {lat: e.latlng.lat, lon: e.latlng.lng, - x: e.containerPoint.x, y: e.containerPoint.y} - })); - }, this); - this._map.on('click', + this.dispatchEvent( + new CustomEvent('preclick', { + detail: { + lat: e.latlng.lat, + lon: e.latlng.lng, + x: e.containerPoint.x, + y: e.containerPoint.y + } + }) + ); + }, + this + ); + this._map.on( + 'click', function (e) { - this.dispatchEvent(new CustomEvent('click', {detail: - {lat: e.latlng.lat, lon: e.latlng.lng, - x: e.containerPoint.x, y: e.containerPoint.y} - })); - }, this); - this._map.on('dblclick', + this.dispatchEvent( + new CustomEvent('click', { + detail: { + lat: e.latlng.lat, + lon: e.latlng.lng, + x: e.containerPoint.x, + y: e.containerPoint.y + } + }) + ); + }, + this + ); + this._map.on( + 'dblclick', function (e) { - this.dispatchEvent(new CustomEvent('dblclick', {detail: - {lat: e.latlng.lat, lon: e.latlng.lng, - x: e.containerPoint.x, y: e.containerPoint.y} - })); - }, this); - this._map.on('mousemove', + this.dispatchEvent( + new CustomEvent('dblclick', { + detail: { + lat: e.latlng.lat, + lon: e.latlng.lng, + x: e.containerPoint.x, + y: e.containerPoint.y + } + }) + ); + }, + this + ); + this._map.on( + 'mousemove', function (e) { - this.dispatchEvent(new CustomEvent('mousemove', {detail: - {lat: e.latlng.lat, lon: e.latlng.lng, - x: e.containerPoint.x, y: e.containerPoint.y} - })); - }, this); - this._map.on('mouseover', + this.dispatchEvent( + new CustomEvent('mousemove', { + detail: { + lat: e.latlng.lat, + lon: e.latlng.lng, + x: e.containerPoint.x, + y: e.containerPoint.y + } + }) + ); + }, + this + ); + this._map.on( + 'mouseover', function (e) { - this.dispatchEvent(new CustomEvent('mouseover', {detail: - {lat: e.latlng.lat, lon: e.latlng.lng, - x: e.containerPoint.x, y: e.containerPoint.y} - })); - }, this); - this._map.on('mouseout', + this.dispatchEvent( + new CustomEvent('mouseover', { + detail: { + lat: e.latlng.lat, + lon: e.latlng.lng, + x: e.containerPoint.x, + y: e.containerPoint.y + } + }) + ); + }, + this + ); + this._map.on( + 'mouseout', function (e) { - this.dispatchEvent(new CustomEvent('mouseout', {detail: - {lat: e.latlng.lat, lon: e.latlng.lng, - x: e.containerPoint.x, y: e.containerPoint.y} - })); - }, this); - this._map.on('mousedown', + this.dispatchEvent( + new CustomEvent('mouseout', { + detail: { + lat: e.latlng.lat, + lon: e.latlng.lng, + x: e.containerPoint.x, + y: e.containerPoint.y + } + }) + ); + }, + this + ); + this._map.on( + 'mousedown', function (e) { - this.dispatchEvent(new CustomEvent('mousedown', {detail: - {lat: e.latlng.lat, lon: e.latlng.lng, - x: e.containerPoint.x, y: e.containerPoint.y} - })); - },this); - this._map.on('mouseup', + this.dispatchEvent( + new CustomEvent('mousedown', { + detail: { + lat: e.latlng.lat, + lon: e.latlng.lng, + x: e.containerPoint.x, + y: e.containerPoint.y + } + }) + ); + }, + this + ); + this._map.on( + 'mouseup', function (e) { - this.dispatchEvent(new CustomEvent('mouseup', {detail: - {lat: e.latlng.lat, lon: e.latlng.lng, - x: e.containerPoint.x, y: e.containerPoint.y} - })); - }, this); - this._map.on('contextmenu', + this.dispatchEvent( + new CustomEvent('mouseup', { + detail: { + lat: e.latlng.lat, + lon: e.latlng.lng, + x: e.containerPoint.x, + y: e.containerPoint.y + } + }) + ); + }, + this + ); + this._map.on( + 'contextmenu', function (e) { - this.dispatchEvent(new CustomEvent('contextmenu', {detail: - {lat: e.latlng.lat, lon: e.latlng.lng, - x: e.containerPoint.x, y: e.containerPoint.y} - })); - }, this); - this._map.on('movestart', + this.dispatchEvent( + new CustomEvent('contextmenu', { + detail: { + lat: e.latlng.lat, + lon: e.latlng.lng, + x: e.containerPoint.x, + y: e.containerPoint.y + } + }) + ); + }, + this + ); + this._map.on( + 'movestart', function () { this._updateMapCenter(); - this.dispatchEvent(new CustomEvent('movestart', {detail: - {target: this}})); - }, this); - this._map.on('move', + this.dispatchEvent( + new CustomEvent('movestart', { detail: { target: this } }) + ); + }, + this + ); + this._map.on( + 'move', function () { this._updateMapCenter(); - this.dispatchEvent(new CustomEvent('move', {detail: - {target: this}})); - }, this); - this._map.on('moveend', + this.dispatchEvent( + new CustomEvent('move', { detail: { target: this } }) + ); + }, + this + ); + this._map.on( + 'moveend', function () { this._updateMapCenter(); this._addToHistory(); - this.dispatchEvent(new CustomEvent('moveend', {detail: - {target: this}})); - }, this); - this._map.on('zoomstart', + this.dispatchEvent( + new CustomEvent('moveend', { detail: { target: this } }) + ); + }, + this + ); + this._map.on( + 'zoomstart', function () { this._updateMapCenter(); - this.dispatchEvent(new CustomEvent('zoomstart', {detail: - {target: this}})); - }, this); - this._map.on('zoom', + this.dispatchEvent( + new CustomEvent('zoomstart', { detail: { target: this } }) + ); + }, + this + ); + this._map.on( + 'zoom', function () { this._updateMapCenter(); - this.dispatchEvent(new CustomEvent('zoom', {detail: - {target: this}})); - }, this); - this._map.on('zoomend', + this.dispatchEvent( + new CustomEvent('zoom', { detail: { target: this } }) + ); + }, + this + ); + this._map.on( + 'zoomend', function () { this._updateMapCenter(); - this.dispatchEvent(new CustomEvent('zoomend', {detail: - {target: this}})); - }, this); - this.addEventListener('fullscreenchange', function(event) { + this.dispatchEvent( + new CustomEvent('zoomend', { detail: { target: this } }) + ); + }, + this + ); + this.addEventListener('fullscreenchange', function (event) { if (document.fullscreenElement === null) { // full-screen mode has been exited this._map.contextMenu.setViewFullScreenInnerHTML('view'); @@ -702,8 +858,8 @@ export class MapViewer extends HTMLElement { this._map.contextMenu.setViewFullScreenInnerHTML('exit'); } }); - this.addEventListener('keydown', function(event) { - if (document.activeElement.nodeName === "MAPML-VIEWER") { + this.addEventListener('keydown', function (event) { + if (document.activeElement.nodeName === 'MAPML-VIEWER') { // Check if Ctrl+R is pressed and map is focused if (event.ctrlKey && event.keyCode === 82) { // Prevent default browser behavior @@ -722,7 +878,8 @@ export class MapViewer extends HTMLElement { }); } - locate(options) { //options: https://leafletjs.com/reference.html#locate-options + locate(options) { + //options: https://leafletjs.com/reference.html#locate-options if (this._geolocationButton) { this._geolocationButton.stop(); } @@ -733,35 +890,35 @@ export class MapViewer extends HTMLElement { } this._map.locate(options); } else { - this._map.locate({setView: true, maxZoom: 16}); + this._map.locate({ setView: true, maxZoom: 16 }); } } - toggleDebug(){ - if(this._debug){ + toggleDebug() { + if (this._debug) { this._debug.remove(); this._debug = undefined; } else { this._debug = M.debugOverlay().addTo(this._map); } } - + _changeWidth(width) { if (this._container) { - this._container.style.width = width+"px"; - this.shadowRoot.styleSheets[0].cssRules[0].style.width = width+"px"; + this._container.style.width = width + 'px'; + this.shadowRoot.styleSheets[0].cssRules[0].style.width = width + 'px'; } if (this._map) { - this._map.invalidateSize(false); + this._map.invalidateSize(false); } } _changeHeight(height) { if (this._container) { - this._container.style.height = height+"px"; - this.shadowRoot.styleSheets[0].cssRules[0].style.height = height+"px"; + this._container.style.height = height + 'px'; + this.shadowRoot.styleSheets[0].cssRules[0].style.height = height + 'px'; } if (this._map) { - this._map.invalidateSize(false); + this._map.invalidateSize(false); } } zoomTo(lat, lon, zoom) { @@ -784,17 +941,18 @@ export class MapViewer extends HTMLElement { * Adds to the maps history on moveends * @private */ - _addToHistory(){ - if(this._traversalCall > 0) { // this._traversalCall tracks how many consecutive moveends to ignore from history - this._traversalCall--; // this is useful for ignoring moveends corresponding to back, forward and reload + _addToHistory() { + if (this._traversalCall > 0) { + // this._traversalCall tracks how many consecutive moveends to ignore from history + this._traversalCall--; // this is useful for ignoring moveends corresponding to back, forward and reload return; } let mapLocation = this._map.getPixelBounds().getCenter(); let location = { zoom: this._map.getZoom(), - x:mapLocation.x, - y:mapLocation.y, + x: mapLocation.x, + y: mapLocation.y }; this._historyIndex++; this._history.splice(this._historyIndex, 0, location); @@ -804,48 +962,50 @@ export class MapViewer extends HTMLElement { } if (this._historyIndex === 0) { // when at initial state of map, disable back, forward, and reload items - this._map.contextMenu.toggleContextMenuItem("Back", "disabled"); // back contextmenu item - this._map.contextMenu.toggleContextMenuItem("Forward", "disabled");// forward contextmenu item - this._map.contextMenu.toggleContextMenuItem("Reload", "disabled"); // reload contextmenu item + this._map.contextMenu.toggleContextMenuItem('Back', 'disabled'); // back contextmenu item + this._map.contextMenu.toggleContextMenuItem('Forward', 'disabled'); // forward contextmenu item + this._map.contextMenu.toggleContextMenuItem('Reload', 'disabled'); // reload contextmenu item this._reloadButton?.disable(); } else { - this._map.contextMenu.toggleContextMenuItem("Back", "enabled"); // back contextmenu item - this._map.contextMenu.toggleContextMenuItem("Forward", "disabled");// forward contextmenu item - this._map.contextMenu.toggleContextMenuItem("Reload", "enabled"); // reload contextmenu item + this._map.contextMenu.toggleContextMenuItem('Back', 'enabled'); // back contextmenu item + this._map.contextMenu.toggleContextMenuItem('Forward', 'disabled'); // forward contextmenu item + this._map.contextMenu.toggleContextMenuItem('Reload', 'enabled'); // reload contextmenu item this._reloadButton?.enable(); } } /** * Allow user to move back in history */ - back(){ + back() { let history = this._history; let curr = history[this._historyIndex]; - if(this._historyIndex > 0){ - this._map.contextMenu.toggleContextMenuItem("Forward", "enabled");// forward contextmenu item + if (this._historyIndex > 0) { + this._map.contextMenu.toggleContextMenuItem('Forward', 'enabled'); // forward contextmenu item this._historyIndex--; let prev = history[this._historyIndex]; // Disable back, reload contextmenu item when at the end of history if (this._historyIndex === 0) { - this._map.contextMenu.toggleContextMenuItem("Back", "disabled"); // back contextmenu item - this._map.contextMenu.toggleContextMenuItem("Reload", "disabled"); // reload contextmenu item + this._map.contextMenu.toggleContextMenuItem('Back', 'disabled'); // back contextmenu item + this._map.contextMenu.toggleContextMenuItem('Reload', 'disabled'); // reload contextmenu item this._reloadButton?.disable(); } - if(prev.zoom !== curr.zoom){ - this._traversalCall = 2; // allows the next 2 moveends to be ignored from history + if (prev.zoom !== curr.zoom) { + this._traversalCall = 2; // allows the next 2 moveends to be ignored from history let currScale = this._map.options.crs.scale(curr.zoom); // gets the scale of the current zoom level let prevScale = this._map.options.crs.scale(prev.zoom); // gets the scale of the previous zoom level let scale = currScale / prevScale; // used to convert the previous pixel location to be in terms of the current zoom level - this._map.panBy([((prev.x * scale) - curr.x), ((prev.y * scale) - curr.y)], {animate: false}); + this._map.panBy([prev.x * scale - curr.x, prev.y * scale - curr.y], { + animate: false + }); this._map.setZoom(prev.zoom); } else { this._traversalCall = 1; - this._map.panBy([(prev.x - curr.x), (prev.y - curr.y)]); + this._map.panBy([prev.x - curr.x, prev.y - curr.y]); } } } @@ -853,21 +1013,21 @@ export class MapViewer extends HTMLElement { /** * Allows user to move forward in history */ - forward(){ + forward() { let history = this._history; let curr = history[this._historyIndex]; - if(this._historyIndex < history.length - 1){ - this._map.contextMenu.toggleContextMenuItem("Back", "enabled"); // back contextmenu item - this._map.contextMenu.toggleContextMenuItem("Reload", "enabled"); // reload contextmenu item + if (this._historyIndex < history.length - 1) { + this._map.contextMenu.toggleContextMenuItem('Back', 'enabled'); // back contextmenu item + this._map.contextMenu.toggleContextMenuItem('Reload', 'enabled'); // reload contextmenu item this._reloadButton?.enable(); this._historyIndex++; let next = history[this._historyIndex]; // disable forward contextmenu item, when at the end of forward history if (this._historyIndex + 1 === this._history.length) { - this._map.contextMenu.toggleContextMenuItem("Forward", "disabled"); // forward contextmenu item + this._map.contextMenu.toggleContextMenuItem('Forward', 'disabled'); // forward contextmenu item } - if(next.zoom !== curr.zoom){ + if (next.zoom !== curr.zoom) { this._traversalCall = 2; // allows the next 2 moveends to be ignored from history let currScale = this._map.options.crs.scale(curr.zoom); // gets the scale of the current zoom level @@ -875,11 +1035,13 @@ export class MapViewer extends HTMLElement { let scale = currScale / nextScale; // used to convert the next pixel location to be in terms of the current zoom level - this._map.panBy([((next.x * scale) - curr.x), ((next.y * scale) - curr.y)], {animate: false}); + this._map.panBy([next.x * scale - curr.x, next.y * scale - curr.y], { + animate: false + }); this._map.setZoom(next.zoom); } else { this._traversalCall = 1; - this._map.panBy([(next.x - curr.x), (next.y - curr.y)]); + this._map.panBy([next.x - curr.x, next.y - curr.y]); } } } @@ -887,24 +1049,24 @@ export class MapViewer extends HTMLElement { /** * Allows the user to reload/reset the map's location to it's initial location */ - reload(){ + reload() { let initialLocation = this._history.shift(); let mapLocation = this._map.getPixelBounds().getCenter(); let curr = { zoom: this._map.getZoom(), - x:mapLocation.x, - y:mapLocation.y, + x: mapLocation.x, + y: mapLocation.y }; - this._map.contextMenu.toggleContextMenuItem("Back", "disabled"); // back contextmenu item - this._map.contextMenu.toggleContextMenuItem("Forward", "disabled");// forward contextmenu item - this._map.contextMenu.toggleContextMenuItem("Reload", "disabled"); // reload contextmenu item + this._map.contextMenu.toggleContextMenuItem('Back', 'disabled'); // back contextmenu item + this._map.contextMenu.toggleContextMenuItem('Forward', 'disabled'); // forward contextmenu item + this._map.contextMenu.toggleContextMenuItem('Reload', 'disabled'); // reload contextmenu item this._reloadButton?.disable(); this._history = [initialLocation]; this._historyIndex = 0; - if(initialLocation.zoom !== curr.zoom) { + if (initialLocation.zoom !== curr.zoom) { this._traversalCall = 2; // ignores the next 2 moveend events let currScale = this._map.options.crs.scale(curr.zoom); // gets the scale of the current zoom level @@ -912,31 +1074,49 @@ export class MapViewer extends HTMLElement { let scale = currScale / initScale; - this._map.panBy([((initialLocation.x * scale) - curr.x), ((initialLocation.y * scale) - curr.y)], {animate: false}); + this._map.panBy( + [ + initialLocation.x * scale - curr.x, + initialLocation.y * scale - curr.y + ], + { animate: false } + ); this._map.setZoom(initialLocation.zoom); - } else { // if it's on the same zoom level as the initial location, no need to calculate scales + } else { + // if it's on the same zoom level as the initial location, no need to calculate scales this._traversalCall = 1; - this._map.panBy([(initialLocation.x- curr.x), (initialLocation.y - curr.y)]); + this._map.panBy([initialLocation.x - curr.x, initialLocation.y - curr.y]); } } - _toggleFullScreen(){ + _toggleFullScreen() { this._map.toggleFullscreen(); } - viewSource(){ - let blob = new Blob([this._source],{type:"text/plain"}), - url = URL.createObjectURL(blob); + viewSource() { + let blob = new Blob([this._source], { type: 'text/plain' }), + url = URL.createObjectURL(blob); window.open(url); URL.revokeObjectURL(url); } defineCustomProjection(jsonTemplate) { let t = JSON.parse(jsonTemplate); - if (t === undefined || !t.proj4string || !t.projection || !t.resolutions || !t.origin || !t.bounds) throw new Error('Incomplete TCRS Definition'); - if (t.projection.indexOf(":") >= 0) throw new Error('":" is not permitted in projection name'); + if ( + t === undefined || + !t.proj4string || + !t.projection || + !t.resolutions || + !t.origin || + !t.bounds + ) + throw new Error('Incomplete TCRS Definition'); + if (t.projection.indexOf(':') >= 0) + throw new Error('":" is not permitted in projection name'); if (M[t.projection.toUpperCase()]) return t.projection.toUpperCase(); - let tileSize = [256, 512, 1024, 2048, 4096].includes(t.tilesize)?t.tilesize:256; + let tileSize = [256, 512, 1024, 2048, 4096].includes(t.tilesize) + ? t.tilesize + : 256; M[t.projection] = new L.Proj.CRS(t.projection, t.proj4string, { origin: t.origin, @@ -945,102 +1125,171 @@ export class MapViewer extends HTMLElement { crs: { tcrs: { horizontal: { - name: "x", - min: 0, - max: zoom => (Math.round(M[t.projection].options.bounds.getSize().x / M[t.projection].options.resolutions[zoom])) + name: 'x', + min: 0, + max: (zoom) => + Math.round( + M[t.projection].options.bounds.getSize().x / + M[t.projection].options.resolutions[zoom] + ) }, vertical: { - name: "y", - min:0, - max: zoom => (Math.round(M[t.projection].options.bounds.getSize().y / M[t.projection].options.resolutions[zoom])) + name: 'y', + min: 0, + max: (zoom) => + Math.round( + M[t.projection].options.bounds.getSize().y / + M[t.projection].options.resolutions[zoom] + ) }, - bounds: zoom => L.bounds([M[t.projection].options.crs.tcrs.horizontal.min, - M[t.projection].options.crs.tcrs.vertical.min], - [M[t.projection].options.crs.tcrs.horizontal.max(zoom), - M[t.projection].options.crs.tcrs.vertical.max(zoom)]) + bounds: (zoom) => + L.bounds( + [ + M[t.projection].options.crs.tcrs.horizontal.min, + M[t.projection].options.crs.tcrs.vertical.min + ], + [ + M[t.projection].options.crs.tcrs.horizontal.max(zoom), + M[t.projection].options.crs.tcrs.vertical.max(zoom) + ] + ) }, pcrs: { horizontal: { - name: "easting", - get min() {return M[t.projection].options.bounds.min.x;}, - get max() {return M[t.projection].options.bounds.max.x;} - }, + name: 'easting', + get min() { + return M[t.projection].options.bounds.min.x; + }, + get max() { + return M[t.projection].options.bounds.max.x; + } + }, vertical: { - name: "northing", - get min() {return M[t.projection].options.bounds.min.y;}, - get max() {return M[t.projection].options.bounds.max.y;} + name: 'northing', + get min() { + return M[t.projection].options.bounds.min.y; + }, + get max() { + return M[t.projection].options.bounds.max.y; + } }, - get bounds() {return M[t.projection].options.bounds;} - }, + get bounds() { + return M[t.projection].options.bounds; + } + }, gcrs: { horizontal: { - name: "longitude", + name: 'longitude', // set min/max axis values from EPSG registry area of use, retrieved 2019-07-25 - get min() {return M[t.projection].unproject(M.OSMTILE.options.bounds.min).lng;}, - get max() {return M[t.projection].unproject(M.OSMTILE.options.bounds.max).lng;} - }, + get min() { + return M[t.projection].unproject(M.OSMTILE.options.bounds.min) + .lng; + }, + get max() { + return M[t.projection].unproject(M.OSMTILE.options.bounds.max) + .lng; + } + }, vertical: { - name: "latitude", + name: 'latitude', // set min/max axis values from EPSG registry area of use, retrieved 2019-07-25 - get min() {return M[t.projection].unproject(M.OSMTILE.options.bounds.min).lat;}, - get max() {return M[t.projection].unproject(M.OSMTILE.options.bounds.max).lat;} + get min() { + return M[t.projection].unproject(M.OSMTILE.options.bounds.min) + .lat; + }, + get max() { + return M[t.projection].unproject(M.OSMTILE.options.bounds.max) + .lat; + } }, - get bounds() {return L.latLngBounds( - [M[t.projection].options.crs.gcrs.vertical.min,M[t.projection].options.crs.gcrs.horizontal.min], - [M[t.projection].options.crs.gcrs.vertical.max,M[t.projection].options.crs.gcrs.horizontal.max]);} + get bounds() { + return L.latLngBounds( + [ + M[t.projection].options.crs.gcrs.vertical.min, + M[t.projection].options.crs.gcrs.horizontal.min + ], + [ + M[t.projection].options.crs.gcrs.vertical.max, + M[t.projection].options.crs.gcrs.horizontal.max + ] + ); + } }, map: { horizontal: { - name: "i", + name: 'i', min: 0, - max: map => map.getSize().x + max: (map) => map.getSize().x }, vertical: { - name: "j", + name: 'j', min: 0, - max: map => map.getSize().y + max: (map) => map.getSize().y }, - bounds: map => L.bounds(L.point([0,0]),map.getSize()) + bounds: (map) => L.bounds(L.point([0, 0]), map.getSize()) }, tile: { horizontal: { - name: "i", + name: 'i', min: 0, - max: tileSize, + max: tileSize }, vertical: { - name: "j", + name: 'j', min: 0, - max: tileSize, + max: tileSize }, - get bounds() {return L.bounds( - [M[t.projection].options.crs.tile.horizontal.min,M[t.projection].options.crs.tile.vertical.min], - [M[t.projection].options.crs.tile.horizontal.max,M[t.projection].options.crs.tile.vertical.max]);} + get bounds() { + return L.bounds( + [ + M[t.projection].options.crs.tile.horizontal.min, + M[t.projection].options.crs.tile.vertical.min + ], + [ + M[t.projection].options.crs.tile.horizontal.max, + M[t.projection].options.crs.tile.vertical.max + ] + ); + } }, tilematrix: { horizontal: { - name: "column", + name: 'column', min: 0, - max: zoom => (Math.round(M[t.projection].options.crs.tcrs.horizontal.max(zoom) / M[t.projection].options.crs.tile.bounds.getSize().x)) + max: (zoom) => + Math.round( + M[t.projection].options.crs.tcrs.horizontal.max(zoom) / + M[t.projection].options.crs.tile.bounds.getSize().x + ) }, vertical: { - name: "row", + name: 'row', min: 0, - max: zoom => (Math.round(M[t.projection].options.crs.tcrs.vertical.max(zoom) / M[t.projection].options.crs.tile.bounds.getSize().y)) + max: (zoom) => + Math.round( + M[t.projection].options.crs.tcrs.vertical.max(zoom) / + M[t.projection].options.crs.tile.bounds.getSize().y + ) }, - bounds: zoom => L.bounds( - [M[t.projection].options.crs.tilematrix.horizontal.min, - M[t.projection].options.crs.tilematrix.vertical.min], - [M[t.projection].options.crs.tilematrix.horizontal.max(zoom), - M[t.projection].options.crs.tilematrix.vertical.max(zoom)]) + bounds: (zoom) => + L.bounds( + [ + M[t.projection].options.crs.tilematrix.horizontal.min, + M[t.projection].options.crs.tilematrix.vertical.min + ], + [ + M[t.projection].options.crs.tilematrix.horizontal.max(zoom), + M[t.projection].options.crs.tilematrix.vertical.max(zoom) + ] + ) } - }, - }); //creates crs using L.Proj + } + }); //creates crs using L.Proj M[t.projection.toUpperCase()] = M[t.projection]; //adds the projection uppercase to global M return t.projection; } - geojson2mapml(json, options = {}){ + geojson2mapml(json, options = {}) { if (options.projection === undefined) { options.projection = this.projection; } @@ -1052,6 +1301,6 @@ export class MapViewer extends HTMLElement { // need to provide options { extends: ... } for custom built-in elements window.customElements.define('mapml-viewer', MapViewer); window.customElements.define('layer-', MapLayer); -window.customElements.define('map-caption',MapCaption); +window.customElements.define('map-caption', MapCaption); window.customElements.define('map-feature', MapFeature); window.customElements.define('map-extent', MapExtent); diff --git a/src/mapml/control/AttributionButton.js b/src/mapml/control/AttributionButton.js index baf0aadc2..f658fff1f 100644 --- a/src/mapml/control/AttributionButton.js +++ b/src/mapml/control/AttributionButton.js @@ -1,73 +1,88 @@ export var AttributionButton = L.Control.Attribution.extend({ - options: { - prefix: 'Maps for HTML Community Group | Slava Ukraini Leaflet ' - }, + options: { + prefix: + 'Maps for HTML Community Group | Slava Ukraini Leaflet ' + }, - onAdd: function (map) { - map.attributionControl = this; - this._container = L.DomUtil.create('details', 'leaflet-control-attribution'); - L.DomEvent.disableClickPropagation(this._container); + onAdd: function (map) { + map.attributionControl = this; + this._container = L.DomUtil.create( + 'details', + 'leaflet-control-attribution' + ); + L.DomEvent.disableClickPropagation(this._container); - for (var i in map._layers) { - if (map._layers[i].getAttribution) { - this.addAttribution(map._layers[i].getAttribution()); - } - } + for (var i in map._layers) { + if (map._layers[i].getAttribution) { + this.addAttribution(map._layers[i].getAttribution()); + } + } - this._update(); + this._update(); - map.on('layeradd', this._addAttribution, this); + map.on('layeradd', this._addAttribution, this); - let dialog = document.createElement("dialog"); - dialog.setAttribute("class", "shortcuts-dialog"); - dialog.setAttribute("autofocus", ""); - dialog.onclick = function (e) { - e.stopPropagation(); - }; - dialog.innerHTML = `${M.options.locale.kbdShortcuts} ` + - `
    ${M.options.locale.kbdMovement}
  • ${M.options.locale.kbdPanUp}
  • ${M.options.locale.kbdPanDown}
  • ${M.options.locale.kbdPanLeft}
  • ${M.options.locale.kbdPanRight}
  • + ${M.options.locale.btnZoomIn}
  • - ${M.options.locale.btnZoomOut}
  • shift + ←/↑/→/↓ 3x ${M.options.locale.kbdPanIncrement}
  • ctrl + ←/↑/→/↓ 0.2x ${M.options.locale.kbdPanIncrement}
  • shift + +/- ${M.options.locale.kbdZoom}
` + - `
    ${M.options.locale.kbdFeature}
  • ←/↑ ${M.options.locale.kbdPrevFeature}
  • →/↓ ${M.options.locale.kbdNextFeature}
`; - map._container.appendChild(dialog); + let dialog = document.createElement('dialog'); + dialog.setAttribute('class', 'shortcuts-dialog'); + dialog.setAttribute('autofocus', ''); + dialog.onclick = function (e) { + e.stopPropagation(); + }; + dialog.innerHTML = + `${M.options.locale.kbdShortcuts} ` + + `
    ${M.options.locale.kbdMovement}
  • ${M.options.locale.kbdPanUp}
  • ${M.options.locale.kbdPanDown}
  • ${M.options.locale.kbdPanLeft}
  • ${M.options.locale.kbdPanRight}
  • + ${M.options.locale.btnZoomIn}
  • - ${M.options.locale.btnZoomOut}
  • shift + ←/↑/→/↓ 3x ${M.options.locale.kbdPanIncrement}
  • ctrl + ←/↑/→/↓ 0.2x ${M.options.locale.kbdPanIncrement}
  • shift + +/- ${M.options.locale.kbdZoom}
` + + `
    ${M.options.locale.kbdFeature}
  • ←/↑ ${M.options.locale.kbdPrevFeature}
  • →/↓ ${M.options.locale.kbdNextFeature}
`; + map._container.appendChild(dialog); - return this._container; - }, + return this._container; + }, - _update: function () { - if (!this._map) { return; } + _update: function () { + if (!this._map) { + return; + } - var attribs = []; + var attribs = []; - for (var i in this._attributions) { - if (this._attributions[i]) { - attribs.push(i); - } - } + for (var i in this._attributions) { + if (this._attributions[i]) { + attribs.push(i); + } + } - var prefixAndAttribs = []; + var prefixAndAttribs = []; - if (this.options.prefix) { - prefixAndAttribs.push(this.options.prefix); - } - if (attribs.length) { - prefixAndAttribs.push(attribs.join(', ')); - } - this._container.innerHTML = `` + '
' + ` | ` + prefixAndAttribs.join(' ') + '
'; - this._container.setAttribute("role","group"); - this._container.setAttribute("aria-label",`${M.options.locale.btnAttribution}`); - } + if (this.options.prefix) { + prefixAndAttribs.push(this.options.prefix); + } + if (attribs.length) { + prefixAndAttribs.push(attribs.join(', ')); + } + this._container.innerHTML = + `` + + '
' + + ` | ` + + prefixAndAttribs.join(' ') + + '
'; + this._container.setAttribute('role', 'group'); + this._container.setAttribute( + 'aria-label', + `${M.options.locale.btnAttribution}` + ); + } }); L.Map.mergeOptions({ - attributionControl: false, - toggleableAttributionControl: true + attributionControl: false, + toggleableAttributionControl: true }); L.Map.addInitHook(function () { - if (this.options.toggleableAttributionControl) { - new AttributionButton().addTo(this); - } + if (this.options.toggleableAttributionControl) { + new AttributionButton().addTo(this); + } }); export var attributionButton = function (options) { - return new AttributionButton(options); + return new AttributionButton(options); }; diff --git a/src/mapml/control/FullscreenButton.js b/src/mapml/control/FullscreenButton.js index d19ca1242..2ade90123 100644 --- a/src/mapml/control/FullscreenButton.js +++ b/src/mapml/control/FullscreenButton.js @@ -1,164 +1,170 @@ export var FullscreenButton = L.Control.extend({ - options: { - position: 'topleft', - title: { - 'false': 'View Fullscreen', - 'true': 'Exit Fullscreen' - } - }, - - onAdd: function (map) { - var container = L.DomUtil.create('div', 'leaflet-control-fullscreen leaflet-bar leaflet-control'); - - this.link = L.DomUtil.create('a', 'leaflet-control-fullscreen-button leaflet-bar-part', container); - this.link.href = '#'; - this.link.setAttribute('role', 'button'); - - this._map = map; - this._map.on('fullscreenchange', this._toggleTitle, this); - this._toggleTitle(); - - L.DomEvent.on(this.link, 'click', this._click, this); - - return container; - }, - - onRemove: function (map) { - map.off('fullscreenchange', this._toggleTitle, this); - }, - - _click: function (e) { - L.DomEvent.stopPropagation(e); - L.DomEvent.preventDefault(e); - this._map.toggleFullscreen(this.options); - }, - - _toggleTitle: function() { - this.link.title = this.options.title[this._map.isFullscreen()]; - } + options: { + position: 'topleft', + title: { + false: 'View Fullscreen', + true: 'Exit Fullscreen' + } + }, + + onAdd: function (map) { + var container = L.DomUtil.create( + 'div', + 'leaflet-control-fullscreen leaflet-bar leaflet-control' + ); + + this.link = L.DomUtil.create( + 'a', + 'leaflet-control-fullscreen-button leaflet-bar-part', + container + ); + this.link.href = '#'; + this.link.setAttribute('role', 'button'); + + this._map = map; + this._map.on('fullscreenchange', this._toggleTitle, this); + this._toggleTitle(); + + L.DomEvent.on(this.link, 'click', this._click, this); + + return container; + }, + + onRemove: function (map) { + map.off('fullscreenchange', this._toggleTitle, this); + }, + + _click: function (e) { + L.DomEvent.stopPropagation(e); + L.DomEvent.preventDefault(e); + this._map.toggleFullscreen(this.options); + }, + + _toggleTitle: function () { + this.link.title = this.options.title[this._map.isFullscreen()]; + } +}); + +L.Map.include({ + isFullscreen: function () { + return this._isFullscreen || false; + }, + + toggleFullscreen: function (options) { + // the element can't contain a shadow root, so we used a child
+ // can contain a shadow root, so return it directly + var mapEl = this.getContainer().getRootNode().host, + container = mapEl.nodeName === 'DIV' ? mapEl.parentElement : mapEl; + if (this.isFullscreen()) { + if (options && options.pseudoFullscreen) { + this._disablePseudoFullscreen(container); + } else if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.mozCancelFullScreen) { + document.mozCancelFullScreen(); + } else if (document.webkitCancelFullScreen) { + document.webkitCancelFullScreen(); + } else if (document.msExitFullscreen) { + document.msExitFullscreen(); + } else { + this._disablePseudoFullscreen(container); + } + } else { + if (options && options.pseudoFullscreen) { + this._enablePseudoFullscreen(container); + } else if (container.requestFullscreen) { + container.requestFullscreen(); + } else if (container.mozRequestFullScreen) { + container.mozRequestFullScreen(); + } else if (container.webkitRequestFullscreen) { + container.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); + } else if (container.msRequestFullscreen) { + container.msRequestFullscreen(); + } else { + this._enablePseudoFullscreen(container); + } + } + }, + + _enablePseudoFullscreen: function (container) { + L.DomUtil.addClass(container, 'leaflet-pseudo-fullscreen'); + this._setFullscreen(true); + this.fire('fullscreenchange'); + }, + + _disablePseudoFullscreen: function (container) { + L.DomUtil.removeClass(container, 'leaflet-pseudo-fullscreen'); + this._setFullscreen(false); + this.fire('fullscreenchange'); + }, + + _setFullscreen: function (fullscreen) { + this._isFullscreen = fullscreen; + var container = this.getContainer().getRootNode().host; + if (fullscreen) { + L.DomUtil.addClass(container, 'mapml-fullscreen-on'); + } else { + L.DomUtil.removeClass(container, 'mapml-fullscreen-on'); + } + this.invalidateSize(); + }, + + _onFullscreenChange: function (e) { + var fullscreenElement = + document.fullscreenElement || + document.mozFullScreenElement || + document.webkitFullscreenElement || + document.msFullscreenElement, + mapEl = this.getContainer().getRootNode().host, + container = mapEl.nodeName === 'DIV' ? mapEl.parentElement : mapEl; + + if (fullscreenElement === container && !this._isFullscreen) { + this._setFullscreen(true); + this.fire('fullscreenchange'); + } else if (fullscreenElement !== container && this._isFullscreen) { + this._setFullscreen(false); + this.fire('fullscreenchange'); + } + } +}); + +L.Map.mergeOptions({ + fullscreenControl: false +}); + +L.Map.addInitHook(function () { + if (this.options.fullscreenControl) { + this.fullscreenControl = new FullscreenButton( + this.options.fullscreenControl + ); + this.addControl(this.fullscreenControl); + } + + var fullscreenchange; + + if ('onfullscreenchange' in document) { + fullscreenchange = 'fullscreenchange'; + } else if ('onmozfullscreenchange' in document) { + fullscreenchange = 'mozfullscreenchange'; + } else if ('onwebkitfullscreenchange' in document) { + fullscreenchange = 'webkitfullscreenchange'; + } else if ('onmsfullscreenchange' in document) { + fullscreenchange = 'MSFullscreenChange'; + } + + if (fullscreenchange) { + var onFullscreenChange = L.bind(this._onFullscreenChange, this); + + this.whenReady(function () { + L.DomEvent.on(document, fullscreenchange, onFullscreenChange); }); - L.Map.include({ - isFullscreen: function () { - return this._isFullscreen || false; - }, - - toggleFullscreen: function (options) { - // the element can't contain a shadow root, so we used a child
- // can contain a shadow root, so return it directly - var mapEl = this.getContainer().getRootNode().host, - container = mapEl.nodeName === "DIV" ? mapEl.parentElement : mapEl; - if (this.isFullscreen()) { - if (options && options.pseudoFullscreen) { - this._disablePseudoFullscreen(container); - } else if (document.exitFullscreen) { - document.exitFullscreen(); - } else if (document.mozCancelFullScreen) { - document.mozCancelFullScreen(); - } else if (document.webkitCancelFullScreen) { - document.webkitCancelFullScreen(); - } else if (document.msExitFullscreen) { - document.msExitFullscreen(); - } else { - this._disablePseudoFullscreen(container); - } - } else { - if (options && options.pseudoFullscreen) { - this._enablePseudoFullscreen(container); - } else if (container.requestFullscreen) { - container.requestFullscreen(); - } else if (container.mozRequestFullScreen) { - container.mozRequestFullScreen(); - } else if (container.webkitRequestFullscreen) { - container.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); - } else if (container.msRequestFullscreen) { - container.msRequestFullscreen(); - } else { - this._enablePseudoFullscreen(container); - } - } - - }, - - _enablePseudoFullscreen: function (container) { - L.DomUtil.addClass(container, 'leaflet-pseudo-fullscreen'); - this._setFullscreen(true); - this.fire('fullscreenchange'); - }, - - _disablePseudoFullscreen: function (container) { - L.DomUtil.removeClass(container, 'leaflet-pseudo-fullscreen'); - this._setFullscreen(false); - this.fire('fullscreenchange'); - }, - - _setFullscreen: function(fullscreen) { - this._isFullscreen = fullscreen; - var container = this.getContainer().getRootNode().host; - if (fullscreen) { - L.DomUtil.addClass(container, 'mapml-fullscreen-on'); - } else { - L.DomUtil.removeClass(container, 'mapml-fullscreen-on'); - } - this.invalidateSize(); - }, - - _onFullscreenChange: function (e) { - var fullscreenElement = - document.fullscreenElement || - document.mozFullScreenElement || - document.webkitFullscreenElement || - document.msFullscreenElement, - mapEl = this.getContainer().getRootNode().host, - container = mapEl.nodeName === "DIV" ? mapEl.parentElement : mapEl; - - - if (fullscreenElement === container && !this._isFullscreen) { - this._setFullscreen(true); - this.fire('fullscreenchange'); - } else if (fullscreenElement !== container && this._isFullscreen) { - this._setFullscreen(false); - this.fire('fullscreenchange'); - } - } + this.on('unload', function () { + L.DomEvent.off(document, fullscreenchange, onFullscreenChange); }); + } +}); - L.Map.mergeOptions({ - fullscreenControl: false - }); - - L.Map.addInitHook(function () { - if (this.options.fullscreenControl) { - this.fullscreenControl = new FullscreenButton(this.options.fullscreenControl); - this.addControl(this.fullscreenControl); - } - - var fullscreenchange; - - if ('onfullscreenchange' in document) { - fullscreenchange = 'fullscreenchange'; - } else if ('onmozfullscreenchange' in document) { - fullscreenchange = 'mozfullscreenchange'; - } else if ('onwebkitfullscreenchange' in document) { - fullscreenchange = 'webkitfullscreenchange'; - } else if ('onmsfullscreenchange' in document) { - fullscreenchange = 'MSFullscreenChange'; - } - - if (fullscreenchange) { - var onFullscreenChange = L.bind(this._onFullscreenChange, this); - - this.whenReady(function () { - L.DomEvent.on(document, fullscreenchange, onFullscreenChange); - }); - - this.on('unload', function () { - L.DomEvent.off(document, fullscreenchange, onFullscreenChange); - }); - } - }); - - export var fullscreenButton = function (options) { - return new FullscreenButton(options); - }; - +export var fullscreenButton = function (options) { + return new FullscreenButton(options); +}; diff --git a/src/mapml/control/GeolocationButton.js b/src/mapml/control/GeolocationButton.js index 1ba633aa8..56be01a3c 100644 --- a/src/mapml/control/GeolocationButton.js +++ b/src/mapml/control/GeolocationButton.js @@ -1,26 +1,33 @@ export var GeolocationButton = L.Control.extend({ options: { - position: 'bottomright' + position: 'bottomright' }, onAdd: function (map) { - this.locateControl = L.control.locate({ - showPopup: false, - strings: { - title: M.options.locale.btnLocTrackOff - }, - position: this.options.position, - locateOptions: { - maxZoom: 16 - } - }).addTo(map); + this.locateControl = L.control + .locate({ + showPopup: false, + strings: { + title: M.options.locale.btnLocTrackOff + }, + position: this.options.position, + locateOptions: { + maxZoom: 16 + } + }) + .addTo(map); var container = this.locateControl._container; var button = this.locateControl; - var observer = new MutationObserver(function(mutations) { - if (container.classList.contains('active') && container.classList.contains('following')) { + var observer = new MutationObserver(function (mutations) { + if ( + container.classList.contains('active') && + container.classList.contains('following') + ) { container.firstChild.title = M.options.locale.btnLocTrackOn; - button._marker.bindTooltip(M.options.locale.btnMyLocTrackOn,{permanent:true}); + button._marker.bindTooltip(M.options.locale.btnMyLocTrackOn, { + permanent: true + }); } else if (container.classList.contains('active')) { container.firstChild.title = M.options.locale.btnLocTrackLastKnown; button._marker.bindTooltip(M.options.locale.btnMyLastKnownLocTrackOn); @@ -30,7 +37,7 @@ export var GeolocationButton = L.Control.extend({ }); var observerConfig = { attributes: true, attributeFilter: ['class'] }; observer.observe(container, observerConfig); - + return container; }, diff --git a/src/mapml/control/LayerControl.js b/src/mapml/control/LayerControl.js index 4fb40ca92..bd21a3014 100644 --- a/src/mapml/control/LayerControl.js +++ b/src/mapml/control/LayerControl.js @@ -1,172 +1,213 @@ export var LayerControl = L.Control.Layers.extend({ - /* removes 'base' layers as a concept */ - options: { - autoZIndex: false, - sortLayers: true, - sortFunction: function (layerA, layerB) { - return layerA.options.zIndex < layerB.options.zIndex ? -1 : (layerA.options.zIndex > layerB.options.zIndex ? 1 : 0); - } - }, - initialize: function (overlays, options) { - L.setOptions(this, options); - - // the _layers array contains objects like {layer: layer, name: "name", overlay: true} - // the array index is the id of the layer returned by L.stamp(layer) which I guess is a unique hash - this._layerControlInputs = []; - this._layers = []; - this._lastZIndex = 0; - this._handlingClick = false; + /* removes 'base' layers as a concept */ + options: { + autoZIndex: false, + sortLayers: true, + sortFunction: function (layerA, layerB) { + return layerA.options.zIndex < layerB.options.zIndex + ? -1 + : layerA.options.zIndex > layerB.options.zIndex + ? 1 + : 0; + } + }, + initialize: function (overlays, options) { + L.setOptions(this, options); - for (var i in overlays) { - this._addLayer(overlays[i], i, true); - } - }, - onAdd: function () { - this._initLayout(); - this._map.on('validate', this._validateInput, this); - L.DomEvent.on(this.options.mapEl, "layerchange", this._validateInput, this); - // Adding event on layer control button - L.DomEvent.on(this._container.getElementsByTagName("a")[0], 'keydown', this._focusFirstLayer, this._container); - L.DomEvent.on(this._container, 'contextmenu', this._preventDefaultContextMenu, this); - this._update(); - //this._validateExtents(); - if(this._layers.length < 1 && !this._map._showControls){ - this._container.setAttribute("hidden",""); - } else{ - this._map._showControls = true; - } - return this._container; - }, - onRemove: function (map) { - map.off('validate', this._validateInput, this); - L.DomEvent.off(this._container.getElementsByTagName("a")[0], 'keydown', this._focusFirstLayer, this._container); - // remove layer-registerd event handlers so that if the control is not - // on the map it does not generate layer events - for (var i = 0; i < this._layers.length; i++) { - this._layers[i].layer.off('add remove', this._onLayerChange, this); - this._layers[i].layer.off('extentload', this._validateInput, this); - } - }, - addOrUpdateOverlay: function (layer, name) { - var alreadyThere = false; - for (var i=0;i 0){ - this._container.removeAttribute("hidden"); - this._map._showControls = true; + // the _layers array contains objects like {layer: layer, name: "name", overlay: true} + // the array index is the id of the layer returned by L.stamp(layer) which I guess is a unique hash + this._layerControlInputs = []; + this._layers = []; + this._lastZIndex = 0; + this._handlingClick = false; + + for (var i in overlays) { + this._addLayer(overlays[i], i, true); + } + }, + onAdd: function () { + this._initLayout(); + this._map.on('validate', this._validateInput, this); + L.DomEvent.on(this.options.mapEl, 'layerchange', this._validateInput, this); + // Adding event on layer control button + L.DomEvent.on( + this._container.getElementsByTagName('a')[0], + 'keydown', + this._focusFirstLayer, + this._container + ); + L.DomEvent.on( + this._container, + 'contextmenu', + this._preventDefaultContextMenu, + this + ); + this._update(); + //this._validateExtents(); + if (this._layers.length < 1 && !this._map._showControls) { + this._container.setAttribute('hidden', ''); + } else { + this._map._showControls = true; + } + return this._container; + }, + onRemove: function (map) { + map.off('validate', this._validateInput, this); + L.DomEvent.off( + this._container.getElementsByTagName('a')[0], + 'keydown', + this._focusFirstLayer, + this._container + ); + // remove layer-registerd event handlers so that if the control is not + // on the map it does not generate layer events + for (var i = 0; i < this._layers.length; i++) { + this._layers[i].layer.off('add remove', this._onLayerChange, this); + this._layers[i].layer.off('extentload', this._validateInput, this); + } + }, + addOrUpdateOverlay: function (layer, name) { + var alreadyThere = false; + for (var i = 0; i < this._layers.length; i++) { + if (this._layers[i].layer === layer) { + alreadyThere = true; + this._layers[i].name = name; + // replace the controls with updated controls if necessary. + break; } - return (this._map) ? this._update() : this; - }, - removeLayer: function (layer) { - L.Control.Layers.prototype.removeLayer.call(this, layer); - if(this._layers.length === 0){ - this._container.setAttribute("hidden", ""); + } + if (!alreadyThere) { + this.addOverlay(layer, name); + } + if (this._layers.length > 0) { + this._container.removeAttribute('hidden'); + this._map._showControls = true; + } + return this._map ? this._update() : this; + }, + removeLayer: function (layer) { + L.Control.Layers.prototype.removeLayer.call(this, layer); + if (this._layers.length === 0) { + this._container.setAttribute('hidden', ''); + } + }, + _validateInput: function (e) { + for (let i = 0; i < this._layers.length; i++) { + if (!this._layers[i].input.labels[0]) continue; + let label = this._layers[i].input.labels[0].getElementsByTagName('span'), + input = this._layers[i].input.labels[0].getElementsByTagName('input'); + input[0].checked = this._layers[i].layer._layerEl.checked; + if ( + this._layers[i].layer._layerEl.disabled && + this._layers[i].layer._layerEl.checked + ) { + input[0].closest('fieldset').disabled = true; + label[0].style.fontStyle = 'italic'; + } else { + input[0].closest('fieldset').disabled = false; + label[0].style.fontStyle = 'normal'; } - }, - _validateInput: function (e) { - for (let i = 0; i < this._layers.length; i++) { - if(!this._layers[i].input.labels[0])continue; - let label = this._layers[i].input.labels[0].getElementsByTagName("span"), - input = this._layers[i].input.labels[0].getElementsByTagName("input"); - input[0].checked = this._layers[i].layer._layerEl.checked; - if(this._layers[i].layer._layerEl.disabled && this._layers[i].layer._layerEl.checked){ - input[0].closest("fieldset").disabled = true; - label[0].style.fontStyle = "italic"; - } else { - input[0].closest("fieldset").disabled = false; - label[0].style.fontStyle = "normal"; - } - // check if an extent is disabled and disable it - if(this._layers[i].layer._extent && this._layers[i].layer._extent._mapExtents){ - for(let j = 0; j < this._layers[i].layer._extent._mapExtents.length; j++){ - let input = this._layers[i].layer._extent._mapExtents[j].extentAnatomy, - label = input.getElementsByClassName("mapml-layer-item-name")[0]; - if(this._layers[i].layer._extent._mapExtents[j].disabled && this._layers[i].layer._extent._mapExtents[j].checked){ - label.style.fontStyle = "italic"; - input.disabled = true; - } else { - label.style.fontStyle = "normal"; - input.disabled = false; - } + // check if an extent is disabled and disable it + if ( + this._layers[i].layer._extent && + this._layers[i].layer._extent._mapExtents + ) { + for ( + let j = 0; + j < this._layers[i].layer._extent._mapExtents.length; + j++ + ) { + let input = + this._layers[i].layer._extent._mapExtents[j].extentAnatomy, + label = input.getElementsByClassName('mapml-layer-item-name')[0]; + if ( + this._layers[i].layer._extent._mapExtents[j].disabled && + this._layers[i].layer._extent._mapExtents[j].checked + ) { + label.style.fontStyle = 'italic'; + input.disabled = true; + } else { + label.style.fontStyle = 'normal'; + input.disabled = false; } } } + } + }, - }, + // focus the first layer in the layer control when enter is pressed + _focusFirstLayer: function (e) { + if ( + e.key === 'Enter' && + this.className === + 'leaflet-control-layers leaflet-control leaflet-control-layers-expanded' + ) { + var elem = + this.children[1].children[2].children[0].children[0].children[0] + .children[0]; + if (elem) setTimeout(() => elem.focus(), 0); + } + }, - // focus the first layer in the layer control when enter is pressed - _focusFirstLayer: function(e){ - if (e.key === 'Enter' && this.className === 'leaflet-control-layers leaflet-control leaflet-control-layers-expanded') { - var elem = this.children[1].children[2].children[0].children[0].children[0].children[0]; - if(elem) setTimeout(() => elem.focus(), 0); - } - }, - - _withinZoomBounds: function(zoom, range) { - return range.min <= zoom && zoom <= range.max; - }, - _addItem: function (obj) { - var layercontrols = obj.layer.getLayerUserControlsHTML(); - // the input is required by Leaflet... - obj.input = layercontrols.querySelector('input.leaflet-control-layers-selector'); + _withinZoomBounds: function (zoom, range) { + return range.min <= zoom && zoom <= range.max; + }, + _addItem: function (obj) { + var layercontrols = obj.layer.getLayerUserControlsHTML(); + // the input is required by Leaflet... + obj.input = layercontrols.querySelector( + 'input.leaflet-control-layers-selector' + ); - this._layerControlInputs.push(obj.input); - obj.input.layerId = L.stamp(obj.layer); + this._layerControlInputs.push(obj.input); + obj.input.layerId = L.stamp(obj.layer); - L.DomEvent.on(obj.input, 'click', this._onInputClick, this); - // this is necessary because when there are several layers in the - // layer control, the response to the last one can be a long time - // after the info is first displayed, so we have to go back and - // verify the layer element is not disabled and can have an enabled input. - obj.layer.on('extentload', this._validateInput, this); - this._overlaysList.appendChild(layercontrols); - return layercontrols; - }, + L.DomEvent.on(obj.input, 'click', this._onInputClick, this); + // this is necessary because when there are several layers in the + // layer control, the response to the last one can be a long time + // after the info is first displayed, so we have to go back and + // verify the layer element is not disabled and can have an enabled input. + obj.layer.on('extentload', this._validateInput, this); + this._overlaysList.appendChild(layercontrols); + return layercontrols; + }, - //overrides collapse and conditionally collapses the panel - collapse: function(e){ - if ( - e.target.tagName === "SELECT" || - e.relatedTarget && + //overrides collapse and conditionally collapses the panel + collapse: function (e) { + if ( + e.target.tagName === 'SELECT' || + (e.relatedTarget && e.relatedTarget.parentElement && - ( - e.relatedTarget.className === "mapml-contextmenu mapml-layer-menu" || - e.relatedTarget.parentElement.className === "mapml-contextmenu mapml-layer-menu" - ) || - (this._map && this._map.contextMenu._layerMenu.style.display === "block") - ) return this; + (e.relatedTarget.className === 'mapml-contextmenu mapml-layer-menu' || + e.relatedTarget.parentElement.className === + 'mapml-contextmenu mapml-layer-menu')) || + (this._map && this._map.contextMenu._layerMenu.style.display === 'block') + ) + return this; - L.DomUtil.removeClass(this._container, 'leaflet-control-layers-expanded'); - if (e.originalEvent?.pointerType === 'touch') { - this._container._isExpanded = false; - } - return this; - }, - _preventDefaultContextMenu: function (e) { - let latlng = this._map.mouseEventToLatLng(e); - let containerPoint = this._map.mouseEventToContainerPoint(e); - e.preventDefault(); - // for touch devices, when the layer control is not expanded, - // the layer context menu should not show on map - if (!this._container._isExpanded && e.pointerType === 'touch') { - this._container._isExpanded = true; - return; - } - this._map.fire('contextmenu', - { originalEvent: e, - containerPoint: containerPoint, - latlng: latlng }); + L.DomUtil.removeClass(this._container, 'leaflet-control-layers-expanded'); + if (e.originalEvent?.pointerType === 'touch') { + this._container._isExpanded = false; + } + return this; + }, + _preventDefaultContextMenu: function (e) { + let latlng = this._map.mouseEventToLatLng(e); + let containerPoint = this._map.mouseEventToContainerPoint(e); + e.preventDefault(); + // for touch devices, when the layer control is not expanded, + // the layer context menu should not show on map + if (!this._container._isExpanded && e.pointerType === 'touch') { + this._container._isExpanded = true; + return; } + this._map.fire('contextmenu', { + originalEvent: e, + containerPoint: containerPoint, + latlng: latlng + }); + } }); export var layerControl = function (layers, options) { - return new LayerControl(layers, options); + return new LayerControl(layers, options); }; diff --git a/src/mapml/control/ReloadButton.js b/src/mapml/control/ReloadButton.js index 3ae9217a1..90bbc256f 100644 --- a/src/mapml/control/ReloadButton.js +++ b/src/mapml/control/ReloadButton.js @@ -1,17 +1,17 @@ export var ReloadButton = L.Control.extend({ options: { - position: 'topleft', + position: 'topleft' }, onAdd: function (map) { - let container = L.DomUtil.create("div", "mapml-reload-button leaflet-bar"); + let container = L.DomUtil.create('div', 'mapml-reload-button leaflet-bar'); - let link = L.DomUtil.create("button", "mapml-reload-button", container); + let link = L.DomUtil.create('button', 'mapml-reload-button', container); link.innerHTML = ""; link.title = M.options.locale.cmReload; - link.setAttribute("type", "button"); - link.classList.add("mapml-button"); - link.setAttribute('aria-label', "Reload"); + link.setAttribute('type', 'button'); + link.classList.add('mapml-button'); + link.setAttribute('aria-label', 'Reload'); L.DomEvent.disableClickPropagation(link); L.DomEvent.on(link, 'click', L.DomEvent.stop); @@ -49,12 +49,15 @@ export var ReloadButton = L.Control.extend({ _updateDisabled: function () { setTimeout(() => { - L.DomUtil.removeClass(this._reloadButton, "leaflet-disabled"); - this._reloadButton.setAttribute("aria-disabled", "false"); + L.DomUtil.removeClass(this._reloadButton, 'leaflet-disabled'); + this._reloadButton.setAttribute('aria-disabled', 'false'); - if (this._map && (this._disabled || this._map.options.mapEl._history.length <= 1)) { - L.DomUtil.addClass(this._reloadButton, "leaflet-disabled"); - this._reloadButton.setAttribute("aria-disabled", "true"); + if ( + this._map && + (this._disabled || this._map.options.mapEl._history.length <= 1) + ) { + L.DomUtil.addClass(this._reloadButton, 'leaflet-disabled'); + this._reloadButton.setAttribute('aria-disabled', 'true'); } }, 0); } @@ -62,4 +65,4 @@ export var ReloadButton = L.Control.extend({ export var reloadButton = function (options) { return new ReloadButton(options); -}; \ No newline at end of file +}; diff --git a/src/mapml/control/ScaleBar.js b/src/mapml/control/ScaleBar.js index 93d4f9512..7e099001c 100644 --- a/src/mapml/control/ScaleBar.js +++ b/src/mapml/control/ScaleBar.js @@ -1,92 +1,106 @@ export var ScaleBar = L.Control.Scale.extend({ - options: { - maxWidth: 100, - updateWhenIdle: true, - position: 'bottomleft' - }, - - onAdd: function (map) { - - // create output tag for screenreader to read from - let outputScale = ""; - map._container.insertAdjacentHTML("beforeend", outputScale); - - // initialize _container - this._container = L.DomUtil.create('div','mapml-control-scale'); - let scaleControl = L.Control.Scale.prototype.onAdd.call(this, map); - this._container.appendChild(scaleControl); - this._container.setAttribute('tabindex',0); - this._scaleControl = this; - - // run on load - setTimeout(() => { - this._updateOutput(); - this._focusOutput(); - }, 0); - - // update whenever map is zoomed or dragged - map.on('zoomend moveend', this._updateOutput, this); - - // have screenreader read out everytime the map is focused - this._map._container.addEventListener('focus', () => this._focusOutput()); - - return this._container; - }, - - onRemove: function (map) { - map.off('zoomend moveend', this._updateOutput, this); - }, - - getContainer: function () { - return this._container; - }, - - _pixelsToDistance: function (px, units) { - let dpi = window.devicePixelRatio * 96; // default dpi - if (units === "metric") { - return px / dpi * 2.54; // inches to cm - } - return px / dpi; - }, - - _scaleLength: function (scale) { - let scaleLength = scale.getAttribute("style"); - let finalLength = parseInt(scaleLength.match(/width:\s*(\d+)px/)[1]); - - return finalLength; - - }, - - _focusOutput: function () { + options: { + maxWidth: 100, + updateWhenIdle: true, + position: 'bottomleft' + }, + + onAdd: function (map) { + // create output tag for screenreader to read from + let outputScale = + ""; + map._container.insertAdjacentHTML('beforeend', outputScale); + + // initialize _container + this._container = L.DomUtil.create('div', 'mapml-control-scale'); + let scaleControl = L.Control.Scale.prototype.onAdd.call(this, map); + this._container.appendChild(scaleControl); + this._container.setAttribute('tabindex', 0); + this._scaleControl = this; + + // run on load + setTimeout(() => { + this._updateOutput(); + this._focusOutput(); + }, 0); + + // update whenever map is zoomed or dragged + map.on('zoomend moveend', this._updateOutput, this); + + // have screenreader read out everytime the map is focused + this._map._container.addEventListener('focus', () => this._focusOutput()); + + return this._container; + }, + + onRemove: function (map) { + map.off('zoomend moveend', this._updateOutput, this); + }, + + getContainer: function () { + return this._container; + }, + + _pixelsToDistance: function (px, units) { + let dpi = window.devicePixelRatio * 96; // default dpi + if (units === 'metric') { + return (px / dpi) * 2.54; // inches to cm + } + return px / dpi; + }, + + _scaleLength: function (scale) { + let scaleLength = scale.getAttribute('style'); + let finalLength = parseInt(scaleLength.match(/width:\s*(\d+)px/)[1]); + + return finalLength; + }, + + _focusOutput: function () { + setTimeout(() => { + let outputFocus = this._map._container.querySelector( + '.mapml-screen-reader-output-scale' + ); + outputFocus.textContent = ''; setTimeout(() => { - let outputFocus = this._map._container.querySelector('.mapml-screen-reader-output-scale'); - outputFocus.textContent = ""; - setTimeout(() => { - outputFocus.textContent = this._container.getAttribute('aria-label'); - }, 100); - }, 0); - }, - - _updateOutput: function () { - let output = ""; - let scaleLine = this._scaleControl.getContainer().getElementsByClassName('leaflet-control-scale-line')[0]; - - if (this.options.metric) { - let distance = parseFloat((this._pixelsToDistance(this._scaleLength(scaleLine),"metric")).toFixed(1)); - output = `${distance} centimeters to ${scaleLine.textContent.trim()}`; - output = output.replace(/(\d+)\s*m\b/g, "$1 meters"); - output = output.replace(/ km/g, " kilometers"); - } else { - let distance = parseFloat((this._pixelsToDistance(this._scaleLength(scaleLine),"imperial")).toFixed(1)); - output = `${distance} inches to ${scaleLine.textContent.trim()}`; - output = output.replace(/ft/g, "feet"); - output = output.replace(/mi/g, "miles"); - } - - this._container.setAttribute('aria-label', output); - this._map._container.querySelector(".mapml-screen-reader-output-scale").textContent = output; + outputFocus.textContent = this._container.getAttribute('aria-label'); + }, 100); + }, 0); + }, + + _updateOutput: function () { + let output = ''; + let scaleLine = this._scaleControl + .getContainer() + .getElementsByClassName('leaflet-control-scale-line')[0]; + + if (this.options.metric) { + let distance = parseFloat( + this._pixelsToDistance(this._scaleLength(scaleLine), 'metric').toFixed( + 1 + ) + ); + output = `${distance} centimeters to ${scaleLine.textContent.trim()}`; + output = output.replace(/(\d+)\s*m\b/g, '$1 meters'); + output = output.replace(/ km/g, ' kilometers'); + } else { + let distance = parseFloat( + this._pixelsToDistance( + this._scaleLength(scaleLine), + 'imperial' + ).toFixed(1) + ); + output = `${distance} inches to ${scaleLine.textContent.trim()}`; + output = output.replace(/ft/g, 'feet'); + output = output.replace(/mi/g, 'miles'); } - }); + + this._container.setAttribute('aria-label', output); + this._map._container.querySelector( + '.mapml-screen-reader-output-scale' + ).textContent = output; + } +}); export var scaleBar = function (options) { - return new ScaleBar(options); + return new ScaleBar(options); }; diff --git a/src/mapml/features/feature.js b/src/mapml/features/feature.js index 2ba180a0a..cb501139e 100644 --- a/src/mapml/features/feature.js +++ b/src/mapml/features/feature.js @@ -19,7 +19,6 @@ * ]; */ export var Feature = L.Path.extend({ - /** * Initializes the M.Feature * @param {HTMLElement} markup - The markup representation of the feature @@ -28,14 +27,17 @@ export var Feature = L.Path.extend({ initialize: function (markup, options) { this.type = markup.tagName.toUpperCase(); - if(this.type === "MAP-POINT" || this.type === "MAP-MULTIPOINT") options.fillOpacity = 1; + if (this.type === 'MAP-POINT' || this.type === 'MAP-MULTIPOINT') + options.fillOpacity = 1; - if(options.wrappers.length > 0) + if (options.wrappers.length > 0) options = Object.assign(this._convertWrappers(options.wrappers), options); L.setOptions(this, options); this.group = this.options.group; - this.options.interactive = this.options.link || (this.options.properties && this.options.onEachFeature); + this.options.interactive = + this.options.link || + (this.options.properties && this.options.onEachFeature); this._parts = []; this._markup = markup; @@ -43,7 +45,7 @@ export var Feature = L.Path.extend({ this._convertMarkup(); - if(markup.querySelector('map-span') || markup.querySelector('map-a')){ + if (markup.querySelector('map-span') || markup.querySelector('map-a')) { this._generateOutlinePoints(); } @@ -57,70 +59,100 @@ export var Feature = L.Path.extend({ * @param leafletLayer */ attachLinkHandler: function (elem, link, leafletLayer) { - let dragStart, container = document.createElement('div'), p = document.createElement('p'), hovered = false; + let dragStart, + container = document.createElement('div'), + p = document.createElement('p'), + hovered = false; container.classList.add('mapml-link-preview'); container.appendChild(p); elem.classList.add('map-a'); - if (link.visited) elem.classList.add("map-a-visited"); - elem.mousedown = e => dragStart = {x:e.clientX, y:e.clientY}; + if (link.visited) elem.classList.add('map-a-visited'); + elem.mousedown = (e) => (dragStart = { x: e.clientX, y: e.clientY }); L.DomEvent.on(elem, 'mousedown', elem.mousedown, this); elem.mouseup = (e) => { if (e.button !== 0) return; // don't trigger when button isn't left click - let onTop = true, nextLayer = this.options._leafletLayer._layerEl.nextElementSibling; - while(nextLayer && onTop){ - if(nextLayer.tagName && nextLayer.tagName.toUpperCase() === "LAYER-") + let onTop = true, + nextLayer = this.options._leafletLayer._layerEl.nextElementSibling; + while (nextLayer && onTop) { + if (nextLayer.tagName && nextLayer.tagName.toUpperCase() === 'LAYER-') onTop = !(nextLayer.checked && nextLayer._layer.queryable); nextLayer = nextLayer.nextElementSibling; } - if(onTop && dragStart) { + if (onTop && dragStart) { //M._handleLink gets called twice, once in the target phase on the path element, then in the bubble phase on the g element //Using stopPropagation leaves the mouse in the mousedown state - if(e.eventPhase === Event.BUBBLING_PHASE) return; - let dist = Math.sqrt(Math.pow(dragStart.x - e.clientX, 2) + Math.pow(dragStart.y - e.clientY, 2)); - if (dist <= 5){ + if (e.eventPhase === Event.BUBBLING_PHASE) return; + let dist = Math.sqrt( + Math.pow(dragStart.x - e.clientX, 2) + + Math.pow(dragStart.y - e.clientY, 2) + ); + if (dist <= 5) { link.visited = true; - elem.setAttribute("stroke", "#6c00a2"); - elem.classList.add("map-a-visited"); + elem.setAttribute('stroke', '#6c00a2'); + elem.classList.add('map-a-visited'); M._handleLink(link, leafletLayer); } } }; - L.DomEvent.on(elem, "mouseup", elem.mouseup, this); - L.DomEvent.on(elem, "keypress", (e) => { - L.DomEvent.stop(e); - if(e.keyCode === 13 || e.keyCode === 32) { - link.visited = true; - elem.setAttribute("stroke", "#6c00a2"); - elem.classList.add("map-a-visited"); - M._handleLink(link, leafletLayer); - } - }, this); - L.DomEvent.on(elem, 'mouseenter keyup', (e) => { - if(e.target !== e.currentTarget) return; - hovered = true; - let resolver = document.createElement('a'), mapWidth = this._map.getContainer().clientWidth; - resolver.href = link.url; - p.innerHTML = resolver.href; + L.DomEvent.on(elem, 'mouseup', elem.mouseup, this); + L.DomEvent.on( + elem, + 'keypress', + (e) => { + L.DomEvent.stop(e); + if (e.keyCode === 13 || e.keyCode === 32) { + link.visited = true; + elem.setAttribute('stroke', '#6c00a2'); + elem.classList.add('map-a-visited'); + M._handleLink(link, leafletLayer); + } + }, + this + ); + L.DomEvent.on( + elem, + 'mouseenter keyup', + (e) => { + if (e.target !== e.currentTarget) return; + hovered = true; + let resolver = document.createElement('a'), + mapWidth = this._map.getContainer().clientWidth; + resolver.href = link.url; + p.innerHTML = resolver.href; - this._map.getContainer().appendChild(container); + this._map.getContainer().appendChild(container); - while(p.clientWidth > mapWidth/2){ - p.innerHTML = p.innerHTML.substring(0, p.innerHTML.length - 5) + "..."; - } - setTimeout(()=>{ - if(hovered) p.innerHTML = resolver.href; - }, 1000); - }, this); - L.DomEvent.on(elem, 'mouseout keydown mousedown', (e) => { - if(e.target !== e.currentTarget || !container.parentElement) return; - hovered = false; - this._map.getContainer().removeChild(container); - }, this); - L.DomEvent.on(leafletLayer._map.getContainer(),'mouseout mouseenter click', (e) => { //adds a lot of event handlers - if(!container.parentElement) return; - hovered = false; - this._map.getContainer().removeChild(container); - }, this); + while (p.clientWidth > mapWidth / 2) { + p.innerHTML = + p.innerHTML.substring(0, p.innerHTML.length - 5) + '...'; + } + setTimeout(() => { + if (hovered) p.innerHTML = resolver.href; + }, 1000); + }, + this + ); + L.DomEvent.on( + elem, + 'mouseout keydown mousedown', + (e) => { + if (e.target !== e.currentTarget || !container.parentElement) return; + hovered = false; + this._map.getContainer().removeChild(container); + }, + this + ); + L.DomEvent.on( + leafletLayer._map.getContainer(), + 'mouseout mouseenter click', + (e) => { + //adds a lot of event handlers + if (!container.parentElement) return; + hovered = false; + this._map.getContainer().removeChild(container); + }, + this + ); }, /** @@ -131,7 +163,9 @@ export var Feature = L.Path.extend({ * @private */ _project: function (addedMap, tileOrigin = undefined, zoomingTo = undefined) { - let map = addedMap || this._map, origin = tileOrigin || map.getPixelOrigin(), zoom = zoomingTo === undefined ? map.getZoom() : zoomingTo; + let map = addedMap || this._map, + origin = tileOrigin || map.getPixelOrigin(), + zoom = zoomingTo === undefined ? map.getZoom() : zoomingTo; for (let p of this._parts) { p.pixelRings = this._convertRing(p.rings, map, origin, zoom); for (let subP of p.subrings) { @@ -141,7 +175,9 @@ export var Feature = L.Path.extend({ if (!this._outline) return; this.pixelOutline = []; for (let o of this._outline) { - this.pixelOutline = this.pixelOutline.concat(this._convertRing(o, map, origin, zoom)); + this.pixelOutline = this.pixelOutline.concat( + this._convertRing(o, map, origin, zoom) + ); } }, @@ -156,7 +192,8 @@ export var Feature = L.Path.extend({ */ _convertRing: function (r, map, origin, zoom) { // TODO: Implement Ramer-Douglas-Peucer Algo for simplifying points - let scale = map.options.crs.scale(zoom), parts = []; + let scale = map.options.crs.scale(zoom), + parts = []; for (let sub of r) { let interm = []; for (let p of sub.points) { @@ -183,17 +220,19 @@ export var Feature = L.Path.extend({ * @private */ _convertWrappers: function (elems) { - if(!elems || elems.length === 0) return; - let classList = '', output = {}; - for(let elem of elems){ - if(elem.tagName.toUpperCase() !== "MAP-A" && elem.className){ - classList +=`${elem.className} `; - } else if(!output.link && elem.getAttribute("href")) { + if (!elems || elems.length === 0) return; + let classList = '', + output = {}; + for (let elem of elems) { + if (elem.tagName.toUpperCase() !== 'MAP-A' && elem.className) { + classList += `${elem.className} `; + } else if (!output.link && elem.getAttribute('href')) { let link = {}; - link.url = elem.getAttribute("href"); - if(elem.hasAttribute("target")) link.target = elem.getAttribute("target"); - if(elem.hasAttribute("type")) link.type = elem.getAttribute("type"); - if(elem.hasAttribute("inplace")) link.inPlace = true; + link.url = elem.getAttribute('href'); + if (elem.hasAttribute('target')) + link.target = elem.getAttribute('target'); + if (elem.hasAttribute('type')) link.type = elem.getAttribute('type'); + if (elem.hasAttribute('inplace')) link.inPlace = true; output.link = link; } } @@ -210,26 +249,43 @@ export var Feature = L.Path.extend({ let attr = this._markup.attributes; this.featureAttributes = {}; - if(this.options.link && this._markup.parentElement.tagName.toUpperCase() === "MAP-A" && this._markup.parentElement.parentElement.tagName.toUpperCase() !== "MAP-GEOMETRY") - this.featureAttributes.tabindex = "0"; - for(let i = 0; i < attr.length; i++){ + if ( + this.options.link && + this._markup.parentElement.tagName.toUpperCase() === 'MAP-A' && + this._markup.parentElement.parentElement.tagName.toUpperCase() !== + 'MAP-GEOMETRY' + ) + this.featureAttributes.tabindex = '0'; + for (let i = 0; i < attr.length; i++) { this.featureAttributes[attr[i].name] = attr[i].value; } let first = true; - for (let c of this._markup.querySelectorAll('map-coordinates')) { //loops through the coordinates of the child - let ring = [], subRings = []; - this._coordinateToArrays(c, ring, subRings, this.options.className); //creates an array of pcrs points for the main ring and the subparts - if (!first && this.type === "MAP-POLYGON") { + for (let c of this._markup.querySelectorAll('map-coordinates')) { + //loops through the coordinates of the child + let ring = [], + subRings = []; + this._coordinateToArrays(c, ring, subRings, this.options.className); //creates an array of pcrs points for the main ring and the subparts + if (!first && this.type === 'MAP-POLYGON') { this._parts[0].rings.push(ring[0]); if (subRings.length > 0) this._parts[0].subrings = this._parts[0].subrings.concat(subRings); - } else if (this.type === "MAP-MULTIPOINT") { + } else if (this.type === 'MAP-MULTIPOINT') { for (let point of ring[0].points.concat(subRings)) { - this._parts.push({ rings: [{ points: [point] }], subrings: [], cls:`${point.cls || ""} ${this.options.className || ""}`.trim() }); + this._parts.push({ + rings: [{ points: [point] }], + subrings: [], + cls: `${point.cls || ''} ${this.options.className || ''}`.trim() + }); } } else { - this._parts.push({ rings: ring, subrings: subRings, cls: `${this.featureAttributes.class || ""} ${this.options.className || ""}`.trim() }); + this._parts.push({ + rings: ring, + subrings: subRings, + cls: `${this.featureAttributes.class || ''} ${ + this.options.className || '' + }`.trim() + }); } first = false; } @@ -240,32 +296,49 @@ export var Feature = L.Path.extend({ * @private */ _generateOutlinePoints: function () { - if (this.type === "MAP-MULTIPOINT" || this.type === "MAP-POINT" || this.type === "MAP-LINESTRING" || this.type === "MAP-MULTILINESTRING") return; + if ( + this.type === 'MAP-MULTIPOINT' || + this.type === 'MAP-POINT' || + this.type === 'MAP-LINESTRING' || + this.type === 'MAP-MULTILINESTRING' + ) + return; this._outline = []; for (let coords of this._markup.querySelectorAll('map-coordinates')) { - let nodes = coords.childNodes, cur = 0, tempDiv = document.createElement('div'), nodeLength = nodes.length; - for(let i = 0; i < nodes.length; i++){ - if(nodes[i].textContent.trim().length === 0){ + let nodes = coords.childNodes, + cur = 0, + tempDiv = document.createElement('div'), + nodeLength = nodes.length; + for (let i = 0; i < nodes.length; i++) { + if (nodes[i].textContent.trim().length === 0) { nodes[i].remove(); } } for (let n of nodes) { let line = []; - if (!n.tagName) { //no tagName means it's text content - let c = '', ind = (((cur - 1)%nodes.length) + nodes.length) % nodes.length; // this equation turns Javascript's % to how it behaves in C for example + if (!n.tagName) { + //no tagName means it's text content + let c = '', + ind = (((cur - 1) % nodes.length) + nodes.length) % nodes.length; // this equation turns Javascript's % to how it behaves in C for example if (nodes[ind].tagName) { let prev = nodes[ind].textContent.trim().split(/\s+/); c += `${prev[prev.length - 2]} ${prev[prev.length - 1]} `; } c += n.textContent; - ind = (((cur + 1)%nodes.length) + nodes.length) % nodes.length; // this is equivalent to C/C++'s (cur + 1) % nodes.length + ind = (((cur + 1) % nodes.length) + nodes.length) % nodes.length; // this is equivalent to C/C++'s (cur + 1) % nodes.length if (nodes[ind].tagName) { let next = nodes[ind].textContent.trim().split(/\s+/); c += `${next[0]} ${next[1]} `; } tempDiv.innerHTML = c; - this._coordinateToArrays(tempDiv, line, [], true, this.featureAttributes.class || this.options.className); + this._coordinateToArrays( + tempDiv, + line, + [], + true, + this.featureAttributes.class || this.options.className + ); this._outline.push(line); } cur++; @@ -283,16 +356,37 @@ export var Feature = L.Path.extend({ * @param parents * @private */ - _coordinateToArrays: function (coords, main, subParts, isFirst = true, cls = undefined, parents = []) { + _coordinateToArrays: function ( + coords, + main, + subParts, + isFirst = true, + cls = undefined, + parents = [] + ) { for (let span of coords.children) { - this._coordinateToArrays(span, main, subParts, false, span.getAttribute("class"), parents.concat([span])); + this._coordinateToArrays( + span, + main, + subParts, + false, + span.getAttribute('class'), + parents.concat([span]) + ); } - let noSpan = coords.textContent.replace(/(<([^>]+)>)/ig, ''), - pairs = noSpan.match(/(\S+\s+\S+)/gim), local = [], bounds; + let noSpan = coords.textContent.replace(/(<([^>]+)>)/gi, ''), + pairs = noSpan.match(/(\S+\s+\S+)/gim), + local = [], + bounds; for (let p of pairs) { let numPair = []; p.split(/\s+/gim).forEach(M._parseNumber, numPair); - let point = M.pointToPCRSPoint(L.point(numPair), this.options.zoom, this.options.projection, this.options.nativeCS); + let point = M.pointToPCRSPoint( + L.point(numPair), + this.options.zoom, + this.options.projection, + this.options.nativeCS + ); local.push(point); bounds = bounds ? bounds.extend(point) : L.bounds(point, point); } @@ -305,20 +399,23 @@ export var Feature = L.Path.extend({ if (isFirst) { main.push({ points: local }); } else { - let attrMap = {}, attr = coords.attributes, wrapperAttr = this._convertWrappers(parents); - if(wrapperAttr.link) attrMap.tabindex = "0"; - for(let i = 0; i < attr.length; i++){ - if(attr[i].name === "class") continue; + let attrMap = {}, + attr = coords.attributes, + wrapperAttr = this._convertWrappers(parents); + if (wrapperAttr.link) attrMap.tabindex = '0'; + for (let i = 0; i < attr.length; i++) { + if (attr[i].name === 'class') continue; attrMap[attr[i].name] = attr[i].value; } subParts.unshift({ points: local, center: bounds.getCenter(), - cls: `${cls || ""} ${wrapperAttr.className || ""}`.trim(), + cls: `${cls || ''} ${wrapperAttr.className || ''}`.trim(), attr: attrMap, link: wrapperAttr.link, linkTarget: wrapperAttr.linkTarget, - linkType: wrapperAttr.linkType}); + linkType: wrapperAttr.linkType + }); } }, @@ -353,7 +450,7 @@ export var Feature = L.Path.extend({ getPCRSCenter: function () { return this._bounds.getCenter(); - }, + } }); /** @@ -364,4 +461,4 @@ export var Feature = L.Path.extend({ */ export var feature = function (markup, options) { return new Feature(markup, options); -}; \ No newline at end of file +}; diff --git a/src/mapml/features/featureGroup.js b/src/mapml/features/featureGroup.js index 2ccb119b9..906cc86ae 100644 --- a/src/mapml/features/featureGroup.js +++ b/src/mapml/features/featureGroup.js @@ -1,35 +1,47 @@ export var FeatureGroup = L.FeatureGroup.extend({ - /** * Initialize the feature group * @param {M.Feature[]} layers * @param {Object} options */ initialize: function (layers, options) { - if(options.wrappers && options.wrappers.length > 0) - options = Object.assign(M.Feature.prototype._convertWrappers(options.wrappers), options); + if (options.wrappers && options.wrappers.length > 0) + options = Object.assign( + M.Feature.prototype._convertWrappers(options.wrappers), + options + ); L.LayerGroup.prototype.initialize.call(this, layers, options); this._featureEl = this.options.mapmlFeature; - if((this.options.onEachFeature && this.options.properties) || this.options.link) { - L.DomUtil.addClass(this.options.group, "leaflet-interactive"); + if ( + (this.options.onEachFeature && this.options.properties) || + this.options.link + ) { + L.DomUtil.addClass(this.options.group, 'leaflet-interactive'); let firstLayer = layers[Object.keys(layers)[0]]; - if(layers.length === 1 && firstLayer.options.link) this.options.link = firstLayer.options.link; - if(this.options.link){ - M.Feature.prototype.attachLinkHandler.call(this, this.options.group, this.options.link, this.options._leafletLayer); + if (layers.length === 1 && firstLayer.options.link) + this.options.link = firstLayer.options.link; + if (this.options.link) { + M.Feature.prototype.attachLinkHandler.call( + this, + this.options.group, + this.options.link, + this.options._leafletLayer + ); this.options.group.setAttribute('role', 'link'); } else { - this.options.group.setAttribute("aria-expanded", "false"); + this.options.group.setAttribute('aria-expanded', 'false'); this.options.group.setAttribute('role', 'button'); this.options.onEachFeature(this.options.properties, this); - this.off("click", this._openPopup); + this.off('click', this._openPopup); } } - - L.DomEvent.on(this.options.group, "keyup keydown", this._handleFocus, this); + + L.DomEvent.on(this.options.group, 'keyup keydown', this._handleFocus, this); this.options.group.setAttribute('aria-label', this.options.accessibleTitle); - if(this.options.featureID) this.options.group.setAttribute("data-fid", this.options.featureID); + if (this.options.featureID) + this.options.group.setAttribute('data-fid', this.options.featureID); for (let feature of layers) { feature._groupLayer = this; } @@ -42,16 +54,21 @@ export var FeatureGroup = L.FeatureGroup.extend({ updateInteraction: function () { let map = this._map || this.options._leafletLayer._map; - if(this.options.onEachFeature || this.options.link) - map.featureIndex.addToIndex(this, this.getPCRSCenter(), this.options.group); + if (this.options.onEachFeature || this.options.link) + map.featureIndex.addToIndex( + this, + this.getPCRSCenter(), + this.options.group + ); for (let layerID in this._layers) { let layer = this._layers[layerID]; - for(let part of layer._parts){ - if(layer.featureAttributes && layer.featureAttributes.tabindex) + for (let part of layer._parts) { + if (layer.featureAttributes && layer.featureAttributes.tabindex) map.featureIndex.addToIndex(layer, layer.getPCRSCenter(), part.path); - for(let subPart of part.subrings) { - if(subPart.attr && subPart.attr.tabindex) map.featureIndex.addToIndex(layer, subPart.center, subPart.path); + for (let subPart of part.subrings) { + if (subPart.attr && subPart.attr.tabindex) + map.featureIndex.addToIndex(layer, subPart.center, subPart.path); } } } @@ -65,15 +82,18 @@ export var FeatureGroup = L.FeatureGroup.extend({ * @returns {Boolean} * @private */ - _checkRender: function(zoom, vectorMinZoom, vectorMaxZoom) { + _checkRender: function (zoom, vectorMinZoom, vectorMaxZoom) { let minZoom = this._featureEl.getAttribute('min'), - maxZoom = this._featureEl.getAttribute('max'); + maxZoom = this._featureEl.getAttribute('max'); // if the current map zoom falls below/above the zoom bounds of the vector layer if (zoom > vectorMaxZoom || zoom < vectorMinZoom) return false; // if no min and max attribute present if (minZoom === null && maxZoom === null) return true; // if the current map zoom falls below/above the [min, max] range - if ((minZoom !== null && zoom < +minZoom) || (maxZoom !== null && zoom > +maxZoom)) { + if ( + (minZoom !== null && zoom < +minZoom) || + (maxZoom !== null && zoom > +maxZoom) + ) { return false; } return true; @@ -84,58 +104,83 @@ export var FeatureGroup = L.FeatureGroup.extend({ * @param {L.DOMEvent} e - Event that occurred * @private */ - _handleFocus: function(e) { - // tab, shift, cr, esc, up, left, down, right, - if(([9, 16, 27, 37, 38, 39, 40].includes(e.keyCode)) && e.type === "keydown"){ + _handleFocus: function (e) { + // tab, shift, cr, esc, up, left, down, right, + if ( + [9, 16, 27, 37, 38, 39, 40].includes(e.keyCode) && + e.type === 'keydown' + ) { let index = this._map.featureIndex.currentIndex; // Down/right arrow keys replicate tabbing through the feature index // Up/left arrow keys replicate shift-tabbing through the feature index - if(e.keyCode === 37 || e.keyCode === 38) { + if (e.keyCode === 37 || e.keyCode === 38) { L.DomEvent.stop(e); - this._map.featureIndex.inBoundFeatures[index].path.setAttribute("tabindex", -1); - if(index === 0) { - this._map.featureIndex.inBoundFeatures[this._map.featureIndex.inBoundFeatures.length - 1].path.focus(); - this._map.featureIndex.currentIndex = this._map.featureIndex.inBoundFeatures.length - 1; + this._map.featureIndex.inBoundFeatures[index].path.setAttribute( + 'tabindex', + -1 + ); + if (index === 0) { + this._map.featureIndex.inBoundFeatures[ + this._map.featureIndex.inBoundFeatures.length - 1 + ].path.focus(); + this._map.featureIndex.currentIndex = + this._map.featureIndex.inBoundFeatures.length - 1; } else { this._map.featureIndex.inBoundFeatures[index - 1].path.focus(); this._map.featureIndex.currentIndex--; } } else if (e.keyCode === 39 || e.keyCode === 40) { L.DomEvent.stop(e); - this._map.featureIndex.inBoundFeatures[index].path.setAttribute("tabindex", -1); - if(index === this._map.featureIndex.inBoundFeatures.length - 1) { + this._map.featureIndex.inBoundFeatures[index].path.setAttribute( + 'tabindex', + -1 + ); + if (index === this._map.featureIndex.inBoundFeatures.length - 1) { this._map.featureIndex.inBoundFeatures[0].path.focus(); this._map.featureIndex.currentIndex = 0; } else { this._map.featureIndex.inBoundFeatures[index + 1].path.focus(); this._map.featureIndex.currentIndex++; } - } else if(e.keyCode === 27){ - let shadowRoot = this._map.options.mapEl.shadowRoot ? this._map.options.mapEl.shadowRoot : - this._map.options.mapEl.querySelector(".mapml-web-map").shadowRoot; - if(shadowRoot.activeElement.nodeName !== "g") return; + } else if (e.keyCode === 27) { + let shadowRoot = this._map.options.mapEl.shadowRoot + ? this._map.options.mapEl.shadowRoot + : this._map.options.mapEl.querySelector('.mapml-web-map').shadowRoot; + if (shadowRoot.activeElement.nodeName !== 'g') return; this._map._container.focus(); } else if (e.keyCode === 9) { let obj = this; setTimeout(function () { - obj._map.featureIndex.inBoundFeatures[0].path.setAttribute("tabindex", 0); + obj._map.featureIndex.inBoundFeatures[0].path.setAttribute( + 'tabindex', + 0 + ); }, 0); } - // tab, shift, cr, esc, right, left, up, down, - // 1, 2, 3, 4, 5, 6, 7 (featureIndexOverlay available index items + // tab, shift, cr, esc, right, left, up, down, + // 1, 2, 3, 4, 5, 6, 7 (featureIndexOverlay available index items // [8, 9] being allocated to next, previous menu items). - } else if (!([9, 16, 13, 27, 37, 38, 39, 40, 49, 50, 51, 52, 53, 54, 55].includes(e.keyCode))){ + } else if ( + ![9, 16, 13, 27, 37, 38, 39, 40, 49, 50, 51, 52, 53, 54, 55].includes( + e.keyCode + ) + ) { this._map.featureIndex.currentIndex = 0; this._map.featureIndex.inBoundFeatures[0].path.focus(); } - + // 27 added so that the tooltip opens when dismissing popup with 'esc' key - if(e.target.tagName.toUpperCase() !== "G") return; - if(([9, 13, 16, 37, 38, 39, 40, 49, 50, 51, 52, 53, 54, 55, 27].includes(e.keyCode)) && e.type === "keyup") { + if (e.target.tagName.toUpperCase() !== 'G') return; + if ( + [9, 13, 16, 37, 38, 39, 40, 49, 50, 51, 52, 53, 54, 55, 27].includes( + e.keyCode + ) && + e.type === 'keyup' + ) { this.openTooltip(); - } else if (e.keyCode === 13 || e.keyCode === 32){ + } else if (e.keyCode === 13 || e.keyCode === 32) { this.closeTooltip(); - if(!this.options.link && this.options.onEachFeature){ + if (!this.options.link && this.options.onEachFeature) { L.DomEvent.stop(e); this.openPopup(); } @@ -149,7 +194,7 @@ export var FeatureGroup = L.FeatureGroup.extend({ * @param layer */ addLayer: function (layer) { - if(!layer.options.link && layer.options.interactive) { + if (!layer.options.link && layer.options.interactive) { this.options.onEachFeature(this.options.properties, layer); } L.FeatureGroup.prototype.addLayer.call(this, layer); @@ -160,10 +205,16 @@ export var FeatureGroup = L.FeatureGroup.extend({ * @param e * @private */ - _previousFeature: function(e){ + _previousFeature: function (e) { L.DomEvent.stop(e); - this._map.featureIndex.currentIndex = Math.max(this._map.featureIndex.currentIndex - 1, 0); - let prevFocus = this._map.featureIndex.inBoundFeatures[this._map.featureIndex.currentIndex]; + this._map.featureIndex.currentIndex = Math.max( + this._map.featureIndex.currentIndex - 1, + 0 + ); + let prevFocus = + this._map.featureIndex.inBoundFeatures[ + this._map.featureIndex.currentIndex + ]; prevFocus.path.focus(); this._map.closePopup(); }, @@ -173,17 +224,23 @@ export var FeatureGroup = L.FeatureGroup.extend({ * @param e * @private */ - _nextFeature: function(e){ + _nextFeature: function (e) { L.DomEvent.stop(e); - this._map.featureIndex.currentIndex = Math.min(this._map.featureIndex.currentIndex + 1, this._map.featureIndex.inBoundFeatures.length - 1); - let nextFocus = this._map.featureIndex.inBoundFeatures[this._map.featureIndex.currentIndex]; + this._map.featureIndex.currentIndex = Math.min( + this._map.featureIndex.currentIndex + 1, + this._map.featureIndex.inBoundFeatures.length - 1 + ); + let nextFocus = + this._map.featureIndex.inBoundFeatures[ + this._map.featureIndex.currentIndex + ]; nextFocus.path.focus(); this._map.closePopup(); }, getPCRSCenter: function () { let bounds; - for(let l in this._layers){ + for (let l in this._layers) { let layer = this._layers[l]; if (!bounds) { bounds = L.bounds(layer.getPCRSCenter(), layer.getPCRSCenter()); @@ -192,7 +249,7 @@ export var FeatureGroup = L.FeatureGroup.extend({ } } return bounds.getCenter(); - }, + } }); /** @@ -203,4 +260,4 @@ export var FeatureGroup = L.FeatureGroup.extend({ */ export var featureGroup = function (layers, options) { return new FeatureGroup(layers, options); -}; \ No newline at end of file +}; diff --git a/src/mapml/features/featureRenderer.js b/src/mapml/features/featureRenderer.js index 89a428648..87c25d134 100644 --- a/src/mapml/features/featureRenderer.js +++ b/src/mapml/features/featureRenderer.js @@ -7,7 +7,7 @@ export var FeatureRenderer = L.SVG.extend({ /** * Override method of same name from L.SVG, use the this._container property * to set up the role="none presentation" on featureGroupu container, - * per this recommendation: + * per this recommendation: * https://github.com/Maps4HTML/Web-Map-Custom-Element/pull/471#issuecomment-845192246 * @private overrides ancestor method so that we have a _container to work with */ @@ -16,11 +16,11 @@ export var FeatureRenderer = L.SVG.extend({ // note you have to pass 'this' as the first arg L.SVG.prototype._initContainer.call(this); // knowing that the previous method call creates the this._container, we - // access it and set the role="none presetation" which suppresses the + // access it and set the role="none presetation" which suppresses the // announcement of "Graphic" on each feature focus. this._container.setAttribute('role', 'none presentation'); }, - + /** * Creates all the appropriate path elements for a M.Feature * @param {M.Feature} layer - The M.Feature that needs paths generated @@ -28,29 +28,44 @@ export var FeatureRenderer = L.SVG.extend({ * @private */ _initPath: function (layer, stampLayer = true) { - - if(layer._outline) { + if (layer._outline) { let outlinePath = L.SVG.create('path'); - if (layer.options.className) L.DomUtil.addClass(outlinePath, layer.featureAttributes.class || layer.options.className); + if (layer.options.className) + L.DomUtil.addClass( + outlinePath, + layer.featureAttributes.class || layer.options.className + ); L.DomUtil.addClass(outlinePath, 'mapml-feature-outline'); - outlinePath.style.fill = "none"; + outlinePath.style.fill = 'none'; layer.outlinePath = outlinePath; } //creates the main parts and sub parts paths for (let p of layer._parts) { - if (p.rings){ - this._createPath(p, layer.options.className, layer.featureAttributes['aria-label'], layer.options.interactive, layer.featureAttributes); - if(layer.outlinePath) p.path.style.stroke = "none"; + if (p.rings) { + this._createPath( + p, + layer.options.className, + layer.featureAttributes['aria-label'], + layer.options.interactive, + layer.featureAttributes + ); + if (layer.outlinePath) p.path.style.stroke = 'none'; } if (p.subrings) { for (let r of p.subrings) { - this._createPath(r, layer.options.className, r.attr['aria-label'], (r.link !== undefined), r.attr); + this._createPath( + r, + layer.options.className, + r.attr['aria-label'], + r.link !== undefined, + r.attr + ); } } this._updateStyle(layer); } - if(stampLayer){ + if (stampLayer) { let stamp = L.stamp(layer); this._layers[stamp] = layer; } @@ -65,14 +80,20 @@ export var FeatureRenderer = L.SVG.extend({ * @param {Object} attr - Attributes map * @private */ - _createPath: function (ring, cls, title, interactive = false, attr = undefined) { + _createPath: function ( + ring, + cls, + title, + interactive = false, + attr = undefined + ) { let p = L.SVG.create('path'); ring.path = p; - if(!attr) { + if (!attr) { if (title) p.setAttribute('aria-label', title); } else { - for(let [name, value] of Object.entries(attr)){ - if(name === "id" || name === "tabindex") continue; + for (let [name, value] of Object.entries(attr)) { + if (name === 'id' || name === 'tabindex') continue; p.setAttribute(name, value); } } @@ -92,28 +113,39 @@ export var FeatureRenderer = L.SVG.extend({ * @private */ _addPath: function (layer, container = undefined, interactive = true) { - if (!this._rootGroup && !container) { this._initContainer(); } - let c = container || this._rootGroup, outlineAdded = false; - if(interactive) { + if (!this._rootGroup && !container) { + this._initContainer(); + } + let c = container || this._rootGroup, + outlineAdded = false; + if (interactive) { layer.addInteractiveTarget(layer.group); } for (let p of layer._parts) { - if (p.path) - layer.group.appendChild(p.path); - if (interactive){ - if(layer.options.link) layer.attachLinkHandler(p.path, layer.options.link, layer.options._leafletLayer); + if (p.path) layer.group.appendChild(p.path); + if (interactive) { + if (layer.options.link) + layer.attachLinkHandler( + p.path, + layer.options.link, + layer.options._leafletLayer + ); layer.addInteractiveTarget(p.path); } - if(!outlineAdded && layer.pixelOutline) { + if (!outlineAdded && layer.pixelOutline) { layer.group.appendChild(layer.outlinePath); outlineAdded = true; } for (let subP of p.subrings) { if (subP.path) { - if (subP.link){ - layer.attachLinkHandler(subP.path, subP.link, layer.options._leafletLayer); + if (subP.link) { + layer.attachLinkHandler( + subP.path, + subP.link, + layer.options._leafletLayer + ); layer.addInteractiveTarget(subP.path); } layer.group.appendChild(subP.path); @@ -135,11 +167,10 @@ export var FeatureRenderer = L.SVG.extend({ L.DomUtil.remove(p.path); } for (let subP of p.subrings) { - if (subP.path) - L.DomUtil.remove(subP.path); + if (subP.path) L.DomUtil.remove(subP.path); } } - if(layer.outlinePath) L.DomUtil.remove(layer.outlinePath); + if (layer.outlinePath) L.DomUtil.remove(layer.outlinePath); layer.removeInteractiveTarget(layer.group); L.DomUtil.remove(layer.group); delete this._layers[L.stamp(layer)]; @@ -151,11 +182,18 @@ export var FeatureRenderer = L.SVG.extend({ * @private */ _updateFeature: function (layer) { - if (layer.pixelOutline) this._setPath(layer.outlinePath, this.geometryToPath(layer.pixelOutline, false)); + if (layer.pixelOutline) + this._setPath( + layer.outlinePath, + this.geometryToPath(layer.pixelOutline, false) + ); for (let p of layer._parts) { this._setPath(p.path, this.geometryToPath(p.pixelRings, layer.isClosed)); for (let subP of p.subrings) { - this._setPath(subP.path, this.geometryToPath(subP.pixelSubrings, false)); + this._setPath( + subP.path, + this.geometryToPath(subP.pixelSubrings, false) + ); } } }, @@ -167,7 +205,9 @@ export var FeatureRenderer = L.SVG.extend({ * @private */ _pointToMarker: function (p) { - return `M${p.x} ${p.y} L${p.x - 12.5} ${p.y - 30} C${p.x - 12.5} ${p.y - 50}, ${p.x + 12.5} ${p.y - 50}, ${p.x + 12.5} ${p.y - 30} L${p.x} ${p.y}z`; + return `M${p.x} ${p.y} L${p.x - 12.5} ${p.y - 30} C${p.x - 12.5} ${ + p.y - 50 + }, ${p.x + 12.5} ${p.y - 50}, ${p.x + 12.5} ${p.y - 30} L${p.x} ${p.y}z`; }, /** @@ -182,8 +222,7 @@ export var FeatureRenderer = L.SVG.extend({ this._updatePathStyle(p.path, layer, true); } for (let subP of p.subrings) { - if (subP.path) - this._updatePathStyle(subP.path, layer); + if (subP.path) this._updatePathStyle(subP.path, layer); } } }, @@ -197,9 +236,15 @@ export var FeatureRenderer = L.SVG.extend({ * @private */ _updatePathStyle: function (path, layer, isMain = false, isOutline = false) { - if (!path || !layer) { return; } - let options = layer.options, isClosed = layer.isClosed; - if ((options.stroke && (!isClosed || isOutline)) || (isMain && !layer.outlinePath)) { + if (!path || !layer) { + return; + } + let options = layer.options, + isClosed = layer.isClosed; + if ( + (options.stroke && (!isClosed || isOutline)) || + (isMain && !layer.outlinePath) + ) { path.setAttribute('stroke', options.color); path.setAttribute('stroke-opacity', options.opacity); path.setAttribute('stroke-width', options.weight); @@ -218,17 +263,20 @@ export var FeatureRenderer = L.SVG.extend({ path.removeAttribute('stroke-dashoffset'); } - if (options.link){ - path.setAttribute("stroke", options.link.visited?"#6c00a2":"#0000EE"); - path.setAttribute("stroke-opacity", "1"); - path.setAttribute("stroke-width", "1px"); - path.setAttribute("stroke-dasharray", "none"); + if (options.link) { + path.setAttribute( + 'stroke', + options.link.visited ? '#6c00a2' : '#0000EE' + ); + path.setAttribute('stroke-opacity', '1'); + path.setAttribute('stroke-width', '1px'); + path.setAttribute('stroke-dasharray', 'none'); } } else { path.setAttribute('stroke', 'none'); } - if(isClosed && !isOutline) { + if (isClosed && !isOutline) { if (!options.fill) { path.setAttribute('fill', options.fillColor || options.color); path.setAttribute('fill-opacity', options.fillOpacity); @@ -258,7 +306,13 @@ export var FeatureRenderer = L.SVG.extend({ * @returns {string} */ geometryToPath: function (rings, closed) { - let str = '', i, j, len, len2, points, p; + let str = '', + i, + j, + len, + len2, + points, + p; for (i = 0, len = rings.length; i < len; i++) { points = rings[i]; @@ -272,7 +326,7 @@ export var FeatureRenderer = L.SVG.extend({ str += closed ? 'z' : ''; } return str || 'M0 0'; - }, + } }); /** @@ -282,4 +336,4 @@ export var FeatureRenderer = L.SVG.extend({ */ export var featureRenderer = function (options) { return new FeatureRenderer(options); -}; \ No newline at end of file +}; diff --git a/src/mapml/handlers/AnnounceMovement.js b/src/mapml/handlers/AnnounceMovement.js index a6d9b1232..f0beef557 100644 --- a/src/mapml/handlers/AnnounceMovement.js +++ b/src/mapml/handlers/AnnounceMovement.js @@ -1,130 +1,142 @@ export var AnnounceMovement = L.Handler.extend({ - addHooks: function () { - this._map.on({ - layeradd: this.totalBounds, - layerremove: this.totalBounds, - }); - - this._map.options.mapEl.addEventListener('moveend', this.announceBounds); - this._map.dragging._draggable.addEventListener('dragstart', this.dragged); - this._map.options.mapEl.addEventListener('mapfocused', this.focusAnnouncement); - }, - removeHooks: function () { - this._map.off({ - layeradd: this.totalBounds, - layerremove: this.totalBounds, - }); - - this._map.options.mapEl.removeEventListener('moveend', this.announceBounds); - this._map.dragging._draggable.removeEventListener('dragstart', this.dragged); - this._map.options.mapEl.removeEventListener('mapfocused', this.focusAnnouncement); - }, - - focusAnnouncement: function () { - let mapEl = this; - setTimeout(function (){ - let el = mapEl.querySelector(".mapml-web-map") ? mapEl.querySelector(".mapml-web-map").shadowRoot.querySelector(".leaflet-container") : - mapEl.shadowRoot.querySelector(".leaflet-container"); - - let mapZoom = mapEl._map.getZoom(); - let location = M._gcrsToTileMatrix(mapEl); - let standard = M.options.locale.amZoom + " " + mapZoom; - - if(mapZoom === mapEl._map._layersMaxZoom){ - standard = M.options.locale.amMaxZoom + " " + standard; - } - else if(mapZoom === mapEl._map._layersMinZoom){ - standard = M.options.locale.amMinZoom + " " + standard; - } - - el.setAttribute("aria-roledescription", "region " + standard); - setTimeout(function () { - el.removeAttribute("aria-roledescription"); - }, 2000); - }, 0); - }, - - announceBounds: function () { - if(this._traversalCall > 0){ - return; - } - let mapZoom = this._map.getZoom(); - let mapBounds = M.pixelToPCRSBounds(this._map.getPixelBounds(),mapZoom,this._map.options.projection); - - let visible = true; - if(this._map.totalLayerBounds){ - visible = mapZoom <= this._map._layersMaxZoom && mapZoom >= this._map._layersMinZoom && - this._map.totalLayerBounds.overlaps(mapBounds); - } - - let output = this.querySelector(".mapml-web-map") ? this.querySelector(".mapml-web-map").shadowRoot.querySelector(".mapml-screen-reader-output") : - this.shadowRoot.querySelector(".mapml-screen-reader-output"); - - //GCRS to TileMatrix - let location = M._gcrsToTileMatrix(this); - let standard = M.options.locale.amZoom + " " + mapZoom; - - if(!visible){ - let outOfBoundsPos = this._history[this._historyIndex]; - let inBoundsPos = this._history[this._historyIndex - 1]; - this.back(); - this._history.pop(); - - if(outOfBoundsPos.zoom !== inBoundsPos.zoom){ - output.innerText = M.options.locale.amZoomedOut; - } - else if(this._map.dragging._draggable.wasDragged){ - output.innerText = M.options.locale.amDraggedOut; - } - else if(outOfBoundsPos.x > inBoundsPos.x){ - output.innerText = M.options.locale.amEastBound; - } - else if(outOfBoundsPos.x < inBoundsPos.x){ - output.innerText = M.options.locale.amWestBound; - } - else if(outOfBoundsPos.y < inBoundsPos.y){ - output.innerText = M.options.locale.amNorthBound; - } - else if(outOfBoundsPos.y > inBoundsPos.y){ - output.innerText = M.options.locale.amSouthBound; - } - - } - else{ - let prevZoom = this._history[this._historyIndex - 1] ? this._history[this._historyIndex - 1].zoom : this._history[this._historyIndex].zoom; - if(mapZoom === this._map._layersMaxZoom && mapZoom !== prevZoom){ - output.innerText = M.options.locale.amMaxZoom + " " + standard; - } - else if(mapZoom === this._map._layersMinZoom && mapZoom !== prevZoom){ - output.innerText = M.options.locale.amMinZoom + " " + standard; - } - else { - output.innerText = standard; - } - } - this._map.dragging._draggable.wasDragged = false; - }, - - totalBounds: function () { - let layers = Object.keys(this._layers); - let bounds = L.bounds(); - - layers.forEach(i => { - if(this._layers[i].layerBounds){ - if(!bounds){ - let point = this._layers[i].layerBounds.getCenter(); - bounds = L.bounds(point, point); - } - bounds.extend(this._layers[i].layerBounds.min); - bounds.extend(this._layers[i].layerBounds.max); - } - }); - - this.totalLayerBounds = bounds; - }, - - dragged: function () { - this.wasDragged = true; + addHooks: function () { + this._map.on({ + layeradd: this.totalBounds, + layerremove: this.totalBounds + }); + + this._map.options.mapEl.addEventListener('moveend', this.announceBounds); + this._map.dragging._draggable.addEventListener('dragstart', this.dragged); + this._map.options.mapEl.addEventListener( + 'mapfocused', + this.focusAnnouncement + ); + }, + removeHooks: function () { + this._map.off({ + layeradd: this.totalBounds, + layerremove: this.totalBounds + }); + + this._map.options.mapEl.removeEventListener('moveend', this.announceBounds); + this._map.dragging._draggable.removeEventListener( + 'dragstart', + this.dragged + ); + this._map.options.mapEl.removeEventListener( + 'mapfocused', + this.focusAnnouncement + ); + }, + + focusAnnouncement: function () { + let mapEl = this; + setTimeout(function () { + let el = mapEl.querySelector('.mapml-web-map') + ? mapEl + .querySelector('.mapml-web-map') + .shadowRoot.querySelector('.leaflet-container') + : mapEl.shadowRoot.querySelector('.leaflet-container'); + + let mapZoom = mapEl._map.getZoom(); + let location = M._gcrsToTileMatrix(mapEl); + let standard = M.options.locale.amZoom + ' ' + mapZoom; + + if (mapZoom === mapEl._map._layersMaxZoom) { + standard = M.options.locale.amMaxZoom + ' ' + standard; + } else if (mapZoom === mapEl._map._layersMinZoom) { + standard = M.options.locale.amMinZoom + ' ' + standard; + } + + el.setAttribute('aria-roledescription', 'region ' + standard); + setTimeout(function () { + el.removeAttribute('aria-roledescription'); + }, 2000); + }, 0); + }, + + announceBounds: function () { + if (this._traversalCall > 0) { + return; + } + let mapZoom = this._map.getZoom(); + let mapBounds = M.pixelToPCRSBounds( + this._map.getPixelBounds(), + mapZoom, + this._map.options.projection + ); + + let visible = true; + if (this._map.totalLayerBounds) { + visible = + mapZoom <= this._map._layersMaxZoom && + mapZoom >= this._map._layersMinZoom && + this._map.totalLayerBounds.overlaps(mapBounds); } -}); \ No newline at end of file + let output = this.querySelector('.mapml-web-map') + ? this.querySelector('.mapml-web-map').shadowRoot.querySelector( + '.mapml-screen-reader-output' + ) + : this.shadowRoot.querySelector('.mapml-screen-reader-output'); + + //GCRS to TileMatrix + let location = M._gcrsToTileMatrix(this); + let standard = M.options.locale.amZoom + ' ' + mapZoom; + + if (!visible) { + let outOfBoundsPos = this._history[this._historyIndex]; + let inBoundsPos = this._history[this._historyIndex - 1]; + this.back(); + this._history.pop(); + + if (outOfBoundsPos.zoom !== inBoundsPos.zoom) { + output.innerText = M.options.locale.amZoomedOut; + } else if (this._map.dragging._draggable.wasDragged) { + output.innerText = M.options.locale.amDraggedOut; + } else if (outOfBoundsPos.x > inBoundsPos.x) { + output.innerText = M.options.locale.amEastBound; + } else if (outOfBoundsPos.x < inBoundsPos.x) { + output.innerText = M.options.locale.amWestBound; + } else if (outOfBoundsPos.y < inBoundsPos.y) { + output.innerText = M.options.locale.amNorthBound; + } else if (outOfBoundsPos.y > inBoundsPos.y) { + output.innerText = M.options.locale.amSouthBound; + } + } else { + let prevZoom = this._history[this._historyIndex - 1] + ? this._history[this._historyIndex - 1].zoom + : this._history[this._historyIndex].zoom; + if (mapZoom === this._map._layersMaxZoom && mapZoom !== prevZoom) { + output.innerText = M.options.locale.amMaxZoom + ' ' + standard; + } else if (mapZoom === this._map._layersMinZoom && mapZoom !== prevZoom) { + output.innerText = M.options.locale.amMinZoom + ' ' + standard; + } else { + output.innerText = standard; + } + } + this._map.dragging._draggable.wasDragged = false; + }, + + totalBounds: function () { + let layers = Object.keys(this._layers); + let bounds = L.bounds(); + + layers.forEach((i) => { + if (this._layers[i].layerBounds) { + if (!bounds) { + let point = this._layers[i].layerBounds.getCenter(); + bounds = L.bounds(point, point); + } + bounds.extend(this._layers[i].layerBounds.min); + bounds.extend(this._layers[i].layerBounds.max); + } + }); + + this.totalLayerBounds = bounds; + }, + + dragged: function () { + this.wasDragged = true; + } +}); diff --git a/src/mapml/handlers/ContextMenu.js b/src/mapml/handlers/ContextMenu.js index e405d2df6..57b6254c9 100644 --- a/src/mapml/handlers/ContextMenu.js +++ b/src/mapml/handlers/ContextMenu.js @@ -9,7 +9,11 @@ The above copyright notice and this permission notice shall be included in all c /* global M */ export var ContextMenu = L.Handler.extend({ - _touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart', + _touchstart: L.Browser.msPointer + ? 'MSPointerDown' + : L.Browser.pointer + ? 'pointerdown' + : 'touchstart', initialize: function (map) { L.Handler.prototype.initialize.call(this, map); @@ -19,61 +23,62 @@ export var ContextMenu = L.Handler.extend({ //setting the items in the context menu and their callback functions this._items = [ { - text: M.options.locale.cmBack + " (Alt+Left Arrow)", - callback:this._goBack + text: M.options.locale.cmBack + ' (Alt+Left Arrow)', + callback: this._goBack }, { - text: M.options.locale.cmForward + " (Alt+Right Arrow)", - callback:this._goForward + text: M.options.locale.cmForward + ' (Alt+Right Arrow)', + callback: this._goForward }, { - text: M.options.locale.cmReload + " (Ctrl+R)", - callback:this._reload + text: M.options.locale.cmReload + ' (Ctrl+R)', + callback: this._reload }, { - text: M.options.locale.btnFullScreen + " (F)", - callback:this._toggleFullScreen + text: M.options.locale.btnFullScreen + ' (F)', + callback: this._toggleFullScreen }, { - spacer:"-" + spacer: '-' }, { - text: M.options.locale.cmCopyCoords + " (C)", - callback:this._copyCoords, - hideOnSelect:false, - popup:true, - submenu:[ + text: M.options.locale.cmCopyCoords + ' (C)', + callback: this._copyCoords, + hideOnSelect: false, + popup: true, + submenu: [ { text: M.options.locale.cmCopyMapML, - callback:this._copyMapML + callback: this._copyMapML }, { text: M.options.locale.cmCopyExtent, - callback:this._copyExtent + callback: this._copyExtent }, { text: M.options.locale.cmCopyLocation, - callback:this._copyLocation + callback: this._copyLocation } ] }, { - text: M.options.locale.cmPasteLayer + " (P)", - callback:this._paste + text: M.options.locale.cmPasteLayer + ' (P)', + callback: this._paste }, { - spacer:"-" + spacer: '-' }, { - text: M.options.locale.cmToggleControls + " (T)", - callback:this._toggleControls - }, { - text: M.options.locale.cmToggleDebug + " (D)", - callback:this._toggleDebug + text: M.options.locale.cmToggleControls + ' (T)', + callback: this._toggleControls }, { - text: M.options.locale.cmViewSource + " (V)", - callback:this._viewSource + text: M.options.locale.cmToggleDebug + ' (D)', + callback: this._toggleDebug + }, + { + text: M.options.locale.cmViewSource + ' (V)', + callback: this._viewSource } ]; @@ -83,48 +88,59 @@ export var ContextMenu = L.Handler.extend({ this.defLocCS = M.options.defaultLocCoor; this._layerItems = [ { - text: M.options.locale.lmZoomToLayer + " (Z)", - callback:this._zoomToLayer + text: M.options.locale.lmZoomToLayer + ' (Z)', + callback: this._zoomToLayer }, { - text: M.options.locale.lmCopyLayer + " (L)", - callback:this._copyLayer + text: M.options.locale.lmCopyLayer + ' (L)', + callback: this._copyLayer } ]; this._mapMenuVisible = false; this._keyboardEvent = false; - this._container = L.DomUtil.create("div", "mapml-contextmenu", map._container); + this._container = L.DomUtil.create( + 'div', + 'mapml-contextmenu', + map._container + ); this._container.setAttribute('hidden', ''); - + for (let i = 0; i < 6; i++) { this._items[i].el = this._createItem(this._container, this._items[i]); } - - this._coordMenu = L.DomUtil.create("div", "mapml-contextmenu mapml-submenu", this._container); - this._coordMenu.id = "mapml-copy-submenu"; + + this._coordMenu = L.DomUtil.create( + 'div', + 'mapml-contextmenu mapml-submenu', + this._container + ); + this._coordMenu.id = 'mapml-copy-submenu'; this._coordMenu.setAttribute('hidden', ''); - + this._clickEvent = null; - - for(let i =0;i + this.t.innerHTML = `

@@ -152,63 +167,99 @@ export var ContextMenu = L.Handler.extend({ addHooks: function () { var container = this._map.getContainer(); - L.DomEvent - .on(container, 'mouseleave', this._hide, this) - .on(document, 'keydown', this._onKeyDown, this); + L.DomEvent.on(container, 'mouseleave', this._hide, this).on( + document, + 'keydown', + this._onKeyDown, + this + ); if (L.Browser.touch) { L.DomEvent.on(document, this._touchstart, this._hide, this); } - this._map.on({ - contextmenu: this._show, - mousedown: this._hide, - zoomstart: this._hide - }, this); + this._map.on( + { + contextmenu: this._show, + mousedown: this._hide, + zoomstart: this._hide + }, + this + ); }, removeHooks: function () { var container = this._map.getContainer(); - L.DomEvent - .off(container, 'mouseleave', this._hide, this) - .off(document, 'keydown', this._onKeyDown, this); + L.DomEvent.off(container, 'mouseleave', this._hide, this).off( + document, + 'keydown', + this._onKeyDown, + this + ); if (L.Browser.touch) { L.DomEvent.off(document, this._touchstart, this._hide, this); } - this._map.off({ - contextmenu: this._show, - mousedown: this._hide, - zoomstart: this._hide - }, this); + this._map.off( + { + contextmenu: this._show, + mousedown: this._hide, + zoomstart: this._hide + }, + this + ); }, _updateCS: function () { - if (this.defExtCS !== M.options.defaultExtCoor || this.defLocCS !== M.options.defaultLocCoor) { + if ( + this.defExtCS !== M.options.defaultExtCoor || + this.defLocCS !== M.options.defaultLocCoor + ) { this.defExtCS = M.options.defaultExtCoor; this.defLocCS = M.options.defaultLocCoor; } }, _copyExtent: function (e) { - let context = e instanceof KeyboardEvent ? this._map.contextMenu : this.contextMenu, - coord = context.defExtCS? context.defExtCS.toLowerCase() : 'pcrs', - tL = e instanceof KeyboardEvent? this.extent.topLeft[coord] : this.options.mapEl.extent.topLeft[coord], - bR = e instanceof KeyboardEvent? this.extent.bottomRight[coord] : this.options.mapEl.extent.bottomRight[coord], - data = ""; - switch(coord) { + let context = + e instanceof KeyboardEvent ? this._map.contextMenu : this.contextMenu, + coord = context.defExtCS ? context.defExtCS.toLowerCase() : 'pcrs', + tL = + e instanceof KeyboardEvent + ? this.extent.topLeft[coord] + : this.options.mapEl.extent.topLeft[coord], + bR = + e instanceof KeyboardEvent + ? this.extent.bottomRight[coord] + : this.options.mapEl.extent.bottomRight[coord], + data = ''; + switch (coord) { case 'MapML': - default: + default: if (coord === 'pcrs') { - data = ``; + data = ``; } else if (coord === 'gcrs') { data = ``; } else if (coord === 'tcrs') { - data = ``; - } else if (coord === 'tilematrix') { - data = ``; + data = ``; + } else if (coord === 'tilematrix') { + data = ``; } else { console.log('not support'); } @@ -218,38 +269,45 @@ export var ContextMenu = L.Handler.extend({ }, _zoomToLayer: function (e) { - let context = e instanceof KeyboardEvent ? this._map.contextMenu : this.contextMenu; + let context = + e instanceof KeyboardEvent ? this._map.contextMenu : this.contextMenu; context._layerClicked.layer._layerEl.zoomTo(); }, _copyLayer: function (e) { - let context = e instanceof KeyboardEvent ? this._map.contextMenu : this.contextMenu, + let context = + e instanceof KeyboardEvent ? this._map.contextMenu : this.contextMenu, layerElem = context._layerClicked.layer._layerEl; context._copyData(layerElem.getOuterHTML()); }, - _goForward: function(e){ - let mapEl = e instanceof KeyboardEvent?this._map.options.mapEl:this.options.mapEl; + _goForward: function (e) { + let mapEl = + e instanceof KeyboardEvent ? this._map.options.mapEl : this.options.mapEl; mapEl.forward(); }, - _goBack: function(e){ - let mapEl = e instanceof KeyboardEvent?this._map.options.mapEl:this.options.mapEl; + _goBack: function (e) { + let mapEl = + e instanceof KeyboardEvent ? this._map.options.mapEl : this.options.mapEl; mapEl.back(); }, - _reload: function(e){ - let mapEl = e instanceof KeyboardEvent?this._map.options.mapEl:this.options.mapEl; + _reload: function (e) { + let mapEl = + e instanceof KeyboardEvent ? this._map.options.mapEl : this.options.mapEl; mapEl.reload(); }, - _toggleFullScreen: function(e){ - let mapEl = e instanceof KeyboardEvent?this._map.options.mapEl:this.options.mapEl; + _toggleFullScreen: function (e) { + let mapEl = + e instanceof KeyboardEvent ? this._map.options.mapEl : this.options.mapEl; mapEl._toggleFullScreen(); }, - _toggleControls: function(e){ - let mapEl = e instanceof KeyboardEvent?this._map.options.mapEl:this.options.mapEl; + _toggleControls: function (e) { + let mapEl = + e instanceof KeyboardEvent ? this._map.options.mapEl : this.options.mapEl; if (mapEl.controls) { mapEl.controls = false; } else { @@ -257,41 +315,52 @@ export var ContextMenu = L.Handler.extend({ } }, - _copyMapML: function(e){ - let context = e instanceof KeyboardEvent ? this._map.contextMenu : this.contextMenu, - mapEl = e instanceof KeyboardEvent?this._map.options.mapEl:this.options.mapEl; - context._copyData(mapEl.outerHTML.replace(/
.*?<\/div>|