├── .npmignore
├── README.md
├── example
├── index.html
├── main.1d19a39e7c585bf6fcfc.js
└── style.1d19a39e7c585bf6fcfc.css
├── package.json
└── src
├── L.multiControl.css
└── L.multiControl.js
/.npmignore:
--------------------------------------------------------------------------------
1 | example/
2 | example.gif
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # L.multiControl
2 | Leaflet plugin to implements layers control with multiple functionality such as opacity, color, bringToFront, bringToBack, zoomToLayer, delete and legend.
3 |
4 | 
5 |
6 | ### [DEMO](https://serene-heyrovsky-2fb229.netlify.app/)
7 |
8 | -----------------------------------------------------------------------------------
9 | ## Requirements
10 |
11 |
12 | - Leaflet
13 | - Font Awesome
14 |
15 |
16 | This plugin require font awesome library to display icons in buttons.
17 |
18 | ## Install
19 |
20 | ### NPM
21 |
22 | ```
23 | npm i leaflet-multicontrol
24 | ```
25 | ## Supported features
26 |
27 | - L.marker
28 | - L.polygon
29 | - L.GeoJSON
30 |
31 |
32 | If you want to add support to specific feature please let me know in an issue or pull request.
33 |
34 | ## Usage Example
35 |
36 | An easy way to implement layers control with multiple functionality. This plugin detects layer properties and will displays buttons (inputs) depend on it.
37 |
38 | ### Create features
39 |
40 | ```javascript
41 | const marker = L.marker([51.5, -0.09]).addTo(map);
42 | const marker2 = L.marker([51.51, -0.09]);
43 | const marker3 = L.marker([51.52, -0.09]);
44 | const polygon = L.polygon([[51.51, -0.1],[51.5, -0.08],[51.53, -0.07],[51.50, -0.06]], {color: '#FF0000'}).addTo(map);
45 | const polygon2 = L.polygon([[51.51, -0.1],[51.5, -0.08],[51.53, -0.07],[51.50, -0.06]], {color: '#0122FF'}).addTo(map);
46 |
47 | const mylines = [{
48 | "type": "LineString",
49 | "coordinates": [[-0.1,51.51], [-0.07,51.53]]
50 | }, {
51 | "type": "LineString",
52 | "coordinates": [[-0.1,51.5], [-0.07,51.50]]
53 | }];
54 | const geojson = L.geoJSON(null).addTo(map);
55 | geojson.addData(mylines);
56 |
57 | const states = [{
58 | "type": "Feature",
59 | "properties": {"party": "Republican"},
60 | "geometry": {
61 | "type": "Polygon",
62 | "coordinates": [[
63 | [-104.05, 48.99],
64 | [-97.22, 48.98],
65 | [-96.58, 45.94],
66 | [-104.03, 45.94],
67 | [-104.05, 48.99]
68 | ]]
69 | }
70 | }, {
71 | "type": "Feature",
72 | "properties": {"party": "Democrat"},
73 | "geometry": {
74 | "type": "Polygon",
75 | "coordinates": [[
76 | [-109.05, 41.00],
77 | [-102.06, 40.99],
78 | [-102.03, 36.99],
79 | [-109.04, 36.99],
80 | [-109.05, 41.00]
81 | ]]
82 | }
83 | }, {
84 | "type": "Feature",
85 | "properties": {"party": "Democrat"},
86 | "geometry": {
87 | "type": "Polygon",
88 | "coordinates": [[
89 | [-109.05, 41.00],
90 | [-102.06, 40.99],
91 | [-102.03, 36.99],
92 | [-109.04, 36.99],
93 | [-109.05, 41.00]
94 | ]]
95 | }
96 | }];
97 |
98 | const geojsonStates = L.geoJSON(states, {style: function(state) {
99 | return (state.properties.party === 'Republican')
100 | ? {fillColor:'red', color:'red', opacity:1, legendLabel: state.properties.party} : {fillColor:'blue', color:'blue', opacity:1, legendLabel: state.properties.party}
101 | }}).addTo(map);
102 |
103 | ```
104 |
105 | ### Implements
106 |
107 | ```javascript
108 | const overlays = [
109 | {name: 'Marker', layer: marker},
110 | {name: 'Marker2', layer: marker2},
111 | {name: 'polygon', layer: polygon},
112 | {name: 'polygon2', layer: polygon2},
113 | {name: 'geojson', layer: geojson},
114 | {name: 'geojsonStates', layer: geojsonStates},
115 | ];
116 |
117 | const legend = L.multiControl(overlays, {position:'topright', label: 'Control de capas'}).addTo(map);
118 | ```
119 | #### Note: If you have a geojson with classification representation (such as example "geojsonStates") and you want visualize it in legend, you should add "legendLabel" property in L.path config that are included in style function.
120 |
121 | L.multiControl receives two arguments:
122 |
123 | - The first is an array of overlays objects
124 | - The second is an object with control options
125 |
126 |
127 | ### Overlay config
128 |
129 | | Property | Type | Required | Description |
130 | | ------------|--- | -------- | ----------------------------------------- |
131 | | name | String |true| Name of the layer. |
132 | | layer | Leaflet layer | true | A leaflet layer included in Supported features |
133 |
134 |
135 | ### Options
136 | | Option | Type | Default | Description |
137 | | ------------|--- | -------- | ----------------------------------------- |
138 | | position |String | 'topright' | Position of the control. Options: [leaflet control positions](https://docs.eegeo.com/eegeo.js/v0.1.665/docs/leaflet/L.Control/#control-positions) |
139 | | label |String | 'Layer Control' | Label that will be display in the header |
140 |
141 | ## Methods
142 |
143 | | Method | Description |
144 | | ------------|----------------------------------------- |
145 | | toggle() | Method to collapse / expand the control |
146 | | addOverlay(overlay config) | Method to add Overlay into control |
147 |
148 | ## Rules
149 |
150 |
151 | - Layers names must be unique
152 |
153 |
154 |
155 |
156 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 | L.multiControl
--------------------------------------------------------------------------------
/example/main.1d19a39e7c585bf6fcfc.js:
--------------------------------------------------------------------------------
1 | (()=>{var e={79:()=>{function e(e){return function(e){if(Array.isArray(e))return r(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,n){if(!e)return;if("string"==typeof e)return r(e,n);var t=Object.prototype.toString.call(e).slice(8,-1);"Object"===t&&e.constructor&&(t=e.constructor.name);if("Map"===t||"Set"===t)return Array.from(e);if("Arguments"===t||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t))return r(e,n)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function r(e,r){(null==r||r>e.length)&&(r=e.length);for(var n=0,t=new Array(r);n0){var n=L.DomUtil.create("div");this.overlays.forEach((function(r,t){var o=e.createChild(r,t);n.append(o)})),r.append(n)}return r},createHead:function(){var e=this,r=L.DomUtil.create("div","leaflet-controllable-legend-head"),n=L.DomUtil.create("div","left"),t=L.DomUtil.create("div","right");n.innerHTML='\n \n '.concat(this.options.label,"
\n ");var o=L.DomUtil.create("button","btn fas fa-compress"),a=L.DomUtil.create("button","btn fas fa-times");return a.addEventListener("click",(function(){return e._map.removeControl(e._container)})),o.addEventListener("click",(function(r){e.toggle(),r.stopPropagation()})),t.append(o),t.append(a),r.append(n),r.append(t),r},toggleLayer:function(e,r){e.target.checked?r.addTo(this._map):this._map.removeLayer(r)},createChild:function(r,n){var t,o=this;this.validateNames();var a=this.defineLayerElements(r.layer),i=L.DomUtil.create("div","child-container"),l=L.DomUtil.create("div","childOverlay"),c=L.DomUtil.create("div","left"),d=L.DomUtil.create("div","right"),s=L.DomUtil.create("i","fas fa-caret-right"),p=L.DomUtil.create("input","switch"),u=L.DomUtil.create("button","btn fas fa-crosshairs"),f=L.DomUtil.create("button","btn fas fa-times"),m=L.DomUtil.create("div","inputsContainer hidden");p.setAttribute("type","checkbox"),p.setAttribute("id","".concat(r.name+n));var g=L.DomUtil.create("label");g.setAttribute("for","".concat(r.name+n)),g.innerText=r.name;var h=L.DomUtil.create("div","hidden");p.addEventListener("click",(function(e){o.toggleLayer(e,r.layer),s.classList.toggle("fa-caret-right"),s.classList.toggle("fa-caret-down"),m.classList.toggle("hidden"),h.classList.toggle("hidden"),e.stopPropagation()})),null!==(t=r.layer)&&void 0!==t&&t._map&&p.click(),g.addEventListener("click",(function(e){return e.stopPropagation()})),f.addEventListener("click",(function(e){o._map.removeLayer(r.layer),i.remove(),o.overlays.splice(o.findIndexByName(r.name),1),o.evalLength(),e.stopPropagation()})),u.addEventListener("click",(function(e){a.zoom(),e.stopPropagation()})),l.addEventListener("click",(function(e){return p.click()})),c.append(s),c.append(p),c.append(g),d.append(u),d.append(f),l.append(c),l.append(d),i.append(l);var v=L.DomUtil.create("div","left"),y=L.DomUtil.create("div","right");if(m.append(v),m.append(y),i.append(m),a.opacity){var b=L.DomUtil.create("input");b.type="range",b.min=0,b.max=1,b.step=.1,b.value=a.opacity.value,b.addEventListener("input",(function(e){return a.opacity.func(e.target.value)})),v.append(b)}if(a.color&&a.legend){var x,w=document.createElement("input");if(w.type="color",w.value=a.color.value(),i.append(h),r.layer.options.style){var k=r.layer.getLayers().map((function(e){return{color:e.options.color,elemName:e.options.legendLabel}})),C=e(new Map(k.map((function(e){return[e.color,e]}))).values());x=this.createLegend(C),h.append(x)}else{var T={elemName:r.name,color:a.color.value()};x=this.createLegend([T]),h.append(x),v.prepend(w)}w.addEventListener("input",(function(e){a.color.func(e.target.value),x.firstChild.firstChild.style.backgroundColor=e.target.value}))}if(a.bringToFront){var D=L.DomUtil.create("button","btn fas fa-arrow-up");D.setAttribute("type","button"),D.addEventListener("click",(function(e){return a.bringToFront()})),y.append(D)}if(a.bringToBack){var U=L.DomUtil.create("button","btn fas fa-arrow-down");U.setAttribute("type","button"),U.addEventListener("click",(function(e){return a.bringToBack()})),y.append(U)}return i},defineLayerElements:function(e){var r=this,n={opacity:void 0,color:void 0,bringToFront:void 0,bringToBack:void 0,legend:void 0,zoom:void 0};return e instanceof L.Marker?(n.opacity={value:1,func:function(r){return e.setOpacity(r)}},n.zoom=function(){return r._map.setView(e.getLatLng(),r._map.getMaxZoom())}):e instanceof L.Polygon?(n.opacity={value:.2,func:function(r){return e.setStyle({fillOpacity:r})}},n.color={value:function(){return e.options.color||"#3388ff"},func:function(r){return e.setStyle({fillColor:r,color:r})}},n.legend=!0,n.bringToFront=function(){return e.bringToFront()},n.bringToBack=function(){return e.bringToBack()},n.zoom=function(){return r._map.fitBounds(e.getBounds())}):e instanceof L.GeoJSON&&(n.opacity={value:.2,func:function(r){return e.setStyle({fillOpacity:r,opacity:r})}},n.color={value:function(){return e.options.color||"#3388ff"},func:function(r){return e.setStyle({fillColor:r,color:r})}},n.legend=!0,n.bringToFront=function(){return e.bringToFront()},n.bringToBack=function(){return e.bringToBack()},n.zoom=function(){return r._map.fitBounds(e.getBounds())}),n},createLegend:function(e){var r=L.DomUtil.create("div","ml-2");return e.forEach((function(e){var n=document.createElement("div"),t=document.createElement("div");t.style.float="left",t.style.margin="0.1rem",t.style.width="10px",t.style.height="10px",t.style.backgroundColor=e.color,n.append(t);var o=document.createElement("div");o.innerText=e.elemName,o.style.fontSize="10px",n.append(o),r.append(n)})),r},addOverlay:function(e){this.overlays.push(e);var r=document.querySelector(".leaflet-controllable-legend-body").firstChild,n=r.childNodes.length,t=this.createChild(e,n);r.append(t)},toggle:function(){this._container.childNodes.forEach((function(e){return e.classList.toggle("hidden")}))},evalLength:function(){0===this.overlays.length&&this.toggle()},findIndexByName:function(e){return this.overlays.findIndex((function(r){return r.name===e}))},validateNames:function(){this.overlays.map((function(e){return e.name})).sort().reduce((function(e,r){if(e!==r)return r;throw new Error("Overlays names must be unique. Repeated name: '".concat(r,"'"))}))}}),L.multiControl=function(e,r){return new L.Control.multiControl(e,r)}},897:(e,r,n)=>{"use strict";n.d(r,{Z:()=>a});var t=n(645),o=n.n(t)()((function(e){return e[1]}));o.push([e.id,'.mr-2 {\r\n margin-right: 5px;\r\n}\r\n.mr-4 {\r\n margin-right: 10px;\r\n}\r\n\r\n.ml-2 {\r\n margin-left: 5px;\r\n}\r\n\r\n.pointer {\r\n cursor: pointer;\r\n}\r\n\r\n.hidden {\r\n display: none !important;\r\n}\r\n\r\n.leaflet-controllable-legend {\r\n width: auto;\r\n height: auto;\r\n background-color:white;\r\n box-shadow: 0 7px 3px -4px rgb(0 0 0 / 30%), 0 8px 8px rgb(0 0 0 / 20%);\r\n border-radius:4px;\r\n}\r\n\r\n.leaflet-controllable-legend-head{\r\n background-color:#333333;\r\n color:white;\r\n height: auto;\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n justify-content: space-between;\r\n padding: 5px;\r\n font-size:0.8rem;\r\n border-top-left-radius: 4px;\r\n border-top-right-radius: 4px;\r\n}\r\n\r\n.leaflet-controllable-legend-head div{\r\n display: flex;\r\n align-items: center;\r\n}\r\n\r\n.leaflet-controllable-legend-body {\r\n min-width: 170px;\r\n height: auto;\r\n padding:0.3rem;\r\n max-height: 365px;\r\n overflow-y: auto;\r\n}\r\n.leaflet-controllable-legend-body::-webkit-scrollbar {\r\n width: 6px;\r\n}\r\n \r\n.leaflet-controllable-legend-body::-webkit-scrollbar-track {\r\n background-color: #e4e4e4;\r\n border-radius: 100px;\r\n}\r\n \r\n.leaflet-controllable-legend-body::-webkit-scrollbar-thumb {\r\n background-color: #333333af;\r\n border-radius: 100px;\r\n}\r\n\r\n.child-container{\r\n padding: 0.2rem;\r\n border: 1px solid #c4c4c4;\r\n margin-bottom: 5px;\r\n border-radius: 4px;\r\n}\r\n\r\n.inputsContainer {\r\n display: flex;\r\n align-items: center;\r\n padding: 3px;\r\n justify-content: space-between;\r\n}\r\n\r\n.childOverlay{\r\n display: flex;\r\n padding:0.2rem;\r\n cursor:pointer;\r\n justify-content: space-between;\r\n}\r\n\r\n.collapse {\r\n width: 1.8rem !important;\r\n height: 1.8rem !important;\r\n font-size: 1.8rem;\r\n padding: 0.25rem;\r\n cursor: pointer;\r\n color: #333333 !important;\r\n}\r\n\r\n.collapse:hover {\r\n background-color: #eeeeee;\r\n}\r\n\r\n.collapse:before{\r\n content: "\\f5fd";\r\n}\r\n\r\n.left {\r\n display: flex;\r\n align-items: center;\r\n margin-right: 10px;\r\n}\r\n\r\n.right {\r\n display: flex;\r\n align-items: center;\r\n}\r\n\r\n.child-container:hover{\r\n background-color:#eeeeee;\r\n}\r\n\r\n.switch {\r\n position: relative;\r\n background: white;\r\n width: 40px;\r\n height: 20px;\r\n -webkit-appearance: initial;\r\n border-radius: 10px;\r\n outline: none;\r\n cursor: pointer;\r\n border: 1px solid #ddd;\r\n}\r\n\r\n.switch:after {\r\n position: absolute;\r\n top: 5%;\r\n display: block;\r\n width: 45%;\r\n height: 90%;\r\n background: #fff;\r\n box-sizing: border-box;\r\n transition: all 0.3s ease-in 0s;\r\n color: black;\r\n border: #888 1px solid;\r\n border-radius: 10px;\r\n}\r\n\r\n.switch:after {\r\n left: 2%;\r\n content: "";\r\n}\r\n\r\n.switch:checked:after {\r\n left: 53%;\r\n content: "";\r\n background: #333333;\r\n}\r\n\r\n.childOverlay label{\r\n cursor: pointer;\r\n}\r\n\r\ninput[type="range"] {\r\n height:4px;\r\n width: 60px;\r\n accent-color:#333333;\r\n}\r\n\r\ninput[type="color"] {\r\n\twidth: 30px;\r\n\theight: 21px;\r\n\tborder: none;\r\n\tbackground: none;\r\n}\r\n\r\n.btn{\r\n border: 0;\r\n font-size: 0.7rem;\r\n\tbackground-color:transparent;\r\n\tcolor: currentColor;\r\n}\r\n\r\n.btn:hover{\r\n cursor:pointer;\r\n\topacity: 0.4\r\n}',""]);const a=o},645:e=>{"use strict";e.exports=function(e){var r=[];return r.toString=function(){return this.map((function(r){var n=e(r);return r[2]?"@media ".concat(r[2]," {").concat(n,"}"):n})).join("")},r.i=function(e,n,t){"string"==typeof e&&(e=[[null,e,""]]);var o={};if(t)for(var a=0;a{"use strict";var t,o=function(){return void 0===t&&(t=Boolean(window&&document&&document.all&&!window.atob)),t},a=function(){var e={};return function(r){if(void 0===e[r]){var n=document.querySelector(r);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(e){n=null}e[r]=n}return e[r]}}(),i=[];function l(e){for(var r=-1,n=0;n{var r=e&&e.__esModule?()=>e.default:()=>e;return n.d(r,{a:r}),r},n.d=(e,r)=>{for(var t in r)n.o(r,t)&&!n.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},n.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),(()=>{"use strict";var e=n(379),r=n.n(e),t=n(897),o={insert:"head",singleton:!1};r()(t.Z,o);t.Z.locals;n(79);var a=L.map("map").setView([51.505,-.09],13);L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",{attribution:'© OpenStreetMap contributors'}).addTo(a);var i=L.marker([51.5,-.09]).addTo(a),l=L.marker([51.51,-.09]),c=L.marker([51.52,-.09]),d=L.polygon([[51.51,-.1],[51.5,-.08],[51.53,-.07],[51.5,-.06]],{color:"#FF0000"}).addTo(a),s=L.polygon([[51.51,-.1],[51.5,-.08],[51.53,-.07],[51.5,-.06]],{color:"#0122FF"}).addTo(a),p=L.geoJSON(null).addTo(a);p.addData([{type:"LineString",coordinates:[[-.1,51.51],[-.07,51.53]]},{type:"LineString",coordinates:[[-.1,51.5],[-.07,51.5]]}]);var u=[{name:"Marker",layer:i},{name:"Marker2",layer:l},{name:"polygon",layer:d},{name:"polygon2",layer:s},{name:"geojson",layer:p},{name:"geojsonStates",layer:L.geoJSON([{type:"Feature",properties:{party:"Republican"},geometry:{type:"Polygon",coordinates:[[[-104.05,48.99],[-97.22,48.98],[-96.58,45.94],[-104.03,45.94],[-104.05,48.99]]]}},{type:"Feature",properties:{party:"Democrat"},geometry:{type:"Polygon",coordinates:[[[-109.05,41],[-102.06,40.99],[-102.03,36.99],[-109.04,36.99],[-109.05,41]]]}},{type:"Feature",properties:{party:"Democrat"},geometry:{type:"Polygon",coordinates:[[[-109.05,41],[-102.06,40.99],[-102.03,36.99],[-109.04,36.99],[-109.05,41]]]}}],{style:function(e){return"Republican"===e.properties.party?{fillColor:"red",color:"red",opacity:1,legendLabel:e.properties.party}:{fillColor:"blue",color:"blue",opacity:1,legendLabel:e.properties.party}}}).addTo(a)}];L.multiControl(u,{position:"topright",label:"Control de capas"}).addTo(a).addOverlay({name:"Marker3",layer:c})})()})();
--------------------------------------------------------------------------------
/example/style.1d19a39e7c585bf6fcfc.css:
--------------------------------------------------------------------------------
1 | #map,body,html{height:100%;margin:0;width:100%}
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "leaflet-multicontrol",
3 | "version": "1.0.3",
4 | "description": "Leaflet plugin to implements layers control with multiple functionality such as opacity, color, bringToFront, bringToBack, zoomToLayer, delete and legend.",
5 | "main": "./src/L.multiControl.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/clavijojuan/L.multiControl.git"
12 | },
13 | "keywords": [
14 | "leaflet",
15 | "map",
16 | "layers",
17 | "control",
18 | "legend",
19 | "javascript",
20 | "webmapping",
21 | "webgis",
22 | "opacity",
23 | "color",
24 | "legend"
25 | ],
26 | "author": "Juan Camilo Clavijo Sandoval",
27 | "license": "MIT",
28 | "bugs": {
29 | "url": "https://github.com/clavijojuan/L.multiControl/issues"
30 | },
31 | "homepage": "https://github.com/clavijojuan/L.multiControl#readme"
32 | }
33 |
--------------------------------------------------------------------------------
/src/L.multiControl.css:
--------------------------------------------------------------------------------
1 | .mr-2 {
2 | margin-right: 5px;
3 | }
4 | .mr-4 {
5 | margin-right: 10px;
6 | }
7 |
8 | .ml-2 {
9 | margin-left: 5px;
10 | }
11 |
12 | .pointer {
13 | cursor: pointer;
14 | }
15 |
16 | .hidden {
17 | display: none !important;
18 | }
19 |
20 | .leaflet-controllable-legend {
21 | width: auto;
22 | height: auto;
23 | background-color:white;
24 | box-shadow: 0 7px 3px -4px rgb(0 0 0 / 30%), 0 8px 8px rgb(0 0 0 / 20%);
25 | border-radius:4px;
26 | }
27 |
28 | .leaflet-controllable-legend-head{
29 | background-color:#333333;
30 | color:white;
31 | height: auto;
32 | display: flex;
33 | flex-direction: row;
34 | align-items: center;
35 | justify-content: space-between;
36 | padding: 5px;
37 | font-size:0.8rem;
38 | border-top-left-radius: 4px;
39 | border-top-right-radius: 4px;
40 | }
41 |
42 | .leaflet-controllable-legend-head div{
43 | display: flex;
44 | align-items: center;
45 | }
46 |
47 | .leaflet-controllable-legend-body {
48 | min-width: 170px;
49 | height: auto;
50 | padding:0.3rem;
51 | max-height: 365px;
52 | overflow-y: auto;
53 | }
54 | .leaflet-controllable-legend-body::-webkit-scrollbar {
55 | width: 6px;
56 | }
57 |
58 | .leaflet-controllable-legend-body::-webkit-scrollbar-track {
59 | background-color: #e4e4e4;
60 | border-radius: 100px;
61 | }
62 |
63 | .leaflet-controllable-legend-body::-webkit-scrollbar-thumb {
64 | background-color: #333333af;
65 | border-radius: 100px;
66 | }
67 |
68 | .child-container{
69 | padding: 0.2rem;
70 | border: 1px solid #c4c4c4;
71 | margin-bottom: 5px;
72 | border-radius: 4px;
73 | }
74 |
75 | .inputsContainer {
76 | display: flex;
77 | align-items: center;
78 | padding: 3px;
79 | justify-content: space-between;
80 | }
81 |
82 | .childOverlay{
83 | display: flex;
84 | padding:0.2rem;
85 | cursor:pointer;
86 | justify-content: space-between;
87 | }
88 |
89 | .collapse {
90 | font-size: 1.8rem;
91 | padding: 0.25rem;
92 | cursor: pointer;
93 | color: #333333 !important;
94 | }
95 |
96 | .collapse:hover {
97 | background-color: #eeeeee;
98 | }
99 |
100 | .collapse:before{
101 | content: "\f5fd";
102 | }
103 |
104 | .left {
105 | display: flex;
106 | align-items: center;
107 | margin-right: 10px;
108 | }
109 |
110 | .right {
111 | display: flex;
112 | align-items: center;
113 | }
114 |
115 | .child-container:hover{
116 | background-color:#eeeeee;
117 | }
118 |
119 | .switch {
120 | position: relative;
121 | background: white;
122 | width: 40px;
123 | height: 20px;
124 | -webkit-appearance: initial;
125 | border-radius: 10px;
126 | outline: none;
127 | cursor: pointer;
128 | border: 1px solid #ddd;
129 | }
130 |
131 | .switch:after {
132 | position: absolute;
133 | top: 5%;
134 | display: block;
135 | width: 45%;
136 | height: 90%;
137 | background: #fff;
138 | box-sizing: border-box;
139 | transition: all 0.3s ease-in 0s;
140 | color: black;
141 | border: #888 1px solid;
142 | border-radius: 10px;
143 | }
144 |
145 | .switch:after {
146 | left: 2%;
147 | content: "";
148 | }
149 |
150 | .switch:checked:after {
151 | left: 53%;
152 | content: "";
153 | background: #333333;
154 | }
155 |
156 | .childOverlay label{
157 | cursor: pointer;
158 | }
159 |
160 | input[type="range"] {
161 | height:4px;
162 | width: 60px;
163 | accent-color:#333333;
164 | }
165 |
166 | input[type="color"] {
167 | width: 30px;
168 | height: 21px;
169 | border: none;
170 | background: none;
171 | }
172 |
173 | .btn{
174 | border: 0;
175 | font-size: 0.7rem;
176 | background-color:transparent;
177 | color: currentColor;
178 | }
179 |
180 | .btn:hover{
181 | cursor:pointer;
182 | opacity: 0.4
183 | }
--------------------------------------------------------------------------------
/src/L.multiControl.js:
--------------------------------------------------------------------------------
1 | L.Control.multiControl = L.Control.extend({
2 | options: {
3 | position: 'topright',
4 | label: 'Layer Control'
5 | },
6 |
7 | initialize: function(overlays, options){
8 | this.overlays = overlays;
9 | L.Util.setOptions(this, options);
10 | },
11 |
12 | onAdd: function (map){
13 | this._map = map
14 | const container = this._container = this.createStructure();
15 | if(!this.overlays) this.toggle();
16 | return container;
17 | },
18 |
19 | createStructure: function(){
20 | const container = L.DomUtil.create('div', 'leaflet-controllable-legend');
21 | const collapseIcon = L.DomUtil.create('div', 'fas collapse hidden');
22 | collapseIcon.addEventListener('click', () => this.toggle());
23 |
24 | const head = this.createHead();
25 | const body = this.createBody();
26 |
27 | container.append(collapseIcon);
28 | container.append(head);
29 | container.append(body);
30 |
31 | L.DomEvent.disableClickPropagation(container);
32 |
33 | return container
34 | },
35 |
36 | createBody: function(){
37 | const body = L.DomUtil.create('div', 'leaflet-controllable-legend-body');
38 | const overlaysContainer = L.DomUtil.create('div');
39 | body.append(overlaysContainer);
40 |
41 | if(this.overlays && this.overlays.length > 0){
42 | this.overlays.forEach((overlay, i)=>{
43 | const div = this.createChild(overlay, i);
44 | overlaysContainer.append(div);
45 | });
46 | }
47 |
48 | return body
49 | },
50 |
51 | createHead() {
52 | const head = L.DomUtil.create('div', 'leaflet-controllable-legend-head');
53 | const left = L.DomUtil.create('div', 'left');
54 | const right = L.DomUtil.create('div', 'right');
55 |
56 | left.innerHTML = `
57 |
58 | ${this.options.label}
59 | `
60 |
61 | const toggle = L.DomUtil.create('button', 'btn fas fa-compress');
62 | const close = L.DomUtil.create('button', 'btn fas fa-times');
63 | close.addEventListener('click', () => this._map.removeControl(this._container));
64 | toggle.addEventListener('click', (e) => {
65 | this.toggle();
66 | e.stopPropagation();
67 | });
68 |
69 | right.append(toggle);
70 | right.append(close);
71 |
72 | head.append(left);
73 | head.append(right);
74 |
75 | return head
76 | },
77 |
78 | toggleLayer(e, layer){
79 | if(e.target.checked){
80 | layer.addTo(this._map)
81 | }
82 | else {
83 | this._map.removeLayer(layer)
84 | }
85 | },
86 |
87 | createChild(overlay, i){
88 |
89 | this.validateNames();
90 |
91 | const elements = this.defineLayerElements(overlay.layer),
92 | div = L.DomUtil.create('div', 'child-container'),
93 | childOverlay = L.DomUtil.create('div', 'childOverlay'),
94 | left = L.DomUtil.create('div', 'left'),
95 | right = L.DomUtil.create('div', 'right'),
96 | caret = L.DomUtil.create('i', 'fas fa-caret-right'),
97 | check = L.DomUtil.create('input', 'switch'),
98 | zoom = L.DomUtil.create('button', 'btn fas fa-crosshairs'),
99 | close = L.DomUtil.create('button', 'btn fas fa-times'),
100 | inputsContainer = L.DomUtil.create('div', 'inputsContainer hidden')
101 |
102 | check.setAttribute('type', 'checkbox');
103 | check.setAttribute('id', `${overlay.name+i}`);
104 |
105 | const label = L.DomUtil.create('label');
106 | label.setAttribute("for", `${overlay.name+i}`);
107 | label.innerText = overlay.name;
108 |
109 | const legend = L.DomUtil.create('div', 'hidden');
110 |
111 | check.addEventListener('click', (e) => {
112 | this.toggleLayer(e, overlay.layer);
113 | caret.classList.toggle('fa-caret-right');
114 | caret.classList.toggle('fa-caret-down');
115 | inputsContainer.classList.toggle('hidden');
116 | legend.classList.toggle('hidden');
117 | e.stopPropagation();
118 | });
119 |
120 | if(overlay.layer?._map) check.click();
121 |
122 | label.addEventListener('click', e => e.stopPropagation());
123 |
124 | close.addEventListener('click', (e) => {
125 | this._map.removeLayer(overlay.layer);
126 | div.remove();
127 | this.overlays.splice(this.findIndexByName(overlay.name), 1)
128 | this.evalLength();
129 | e.stopPropagation();
130 | })
131 |
132 | zoom.addEventListener('click', (e) => {
133 | elements.zoom();
134 | e.stopPropagation();
135 | })
136 |
137 | childOverlay.addEventListener('click', e => check.click());
138 |
139 | left.append(caret)
140 | left.append(check)
141 | left.append(label)
142 | right.append(zoom)
143 | right.append(close)
144 |
145 | childOverlay.append(left)
146 | childOverlay.append(right)
147 |
148 |
149 | div.append(childOverlay);
150 |
151 | const inputsContainerLeft = L.DomUtil.create('div', 'left');
152 | const inputsContainerRight = L.DomUtil.create('div', 'right');
153 |
154 | inputsContainer.append(inputsContainerLeft);
155 | inputsContainer.append(inputsContainerRight);
156 | div.append(inputsContainer);
157 |
158 | if(elements.opacity){
159 | const range = L.DomUtil.create('input');
160 | range.type = 'range';
161 | range.min = 0;
162 | range.max = 1;
163 | range.step = 0.1;
164 | range.value = elements.opacity.value;
165 | range.addEventListener('input', (e) => elements.opacity.func(e.target.value));
166 | inputsContainerLeft.append(range);
167 | }
168 |
169 | if(elements.color && elements.legend){
170 |
171 | const color = document.createElement('input');
172 | color.type = 'color';
173 | color.value = elements.color.value();
174 |
175 | div.append(legend);
176 |
177 | let legendContainer;
178 | if(overlay.layer.options.style){
179 | const groups = overlay.layer.getLayers().map((sublayer)=>{
180 | return {
181 | color: sublayer.options.color,
182 | elemName: sublayer.options.legendLabel || overlay.name
183 | }
184 | });
185 |
186 | let uniques = [...new Map(groups.map((item) => [item["color"], item])).values()];
187 |
188 | legendContainer = this.createLegend(uniques);
189 | legend.append(legendContainer);
190 |
191 | }
192 | else {
193 | const obj = {
194 | elemName: overlay.name,
195 | color: elements.color.value()
196 | };
197 | legendContainer = this.createLegend([obj]);
198 |
199 | legend.append(legendContainer);
200 | inputsContainerLeft.prepend(color);
201 | }
202 |
203 | color.addEventListener('input', (e) => {
204 | elements.color.func(e.target.value);
205 | legendContainer.firstChild.firstChild.style.backgroundColor = e.target.value
206 | });
207 | }
208 |
209 | if(elements.bringToFront){
210 | const front = L.DomUtil.create('button', 'btn fas fa-arrow-up');
211 | front.setAttribute('type', 'button');
212 | front.addEventListener('click', (e) => elements.bringToFront());
213 | inputsContainerRight.append(front);
214 | }
215 |
216 | if(elements.bringToBack){
217 | const back = L.DomUtil.create('button', 'btn fas fa-arrow-down');
218 | back.setAttribute('type', 'button');
219 | back.addEventListener('click', (e) => elements.bringToBack());
220 | inputsContainerRight.append(back);
221 | }
222 |
223 | return div
224 | },
225 |
226 | defineLayerElements(layer){
227 | let elements = {
228 | opacity: undefined,
229 | color:undefined,
230 | bringToFront: undefined,
231 | bringToBack: undefined ,
232 | legend:undefined,
233 | zoom: undefined
234 | }
235 |
236 | if(layer instanceof L.Marker){
237 | elements.opacity = {
238 | value: 1,
239 | func: (value) => layer.setOpacity(value)
240 | };
241 | elements.zoom = () => this._map.setView(layer.getLatLng(), this._map.getMaxZoom());
242 | }
243 | else if(layer instanceof L.Polygon){
244 | elements.opacity = {
245 | value: 0.2,
246 | func: (value) => layer.setStyle({fillOpacity:value})
247 | };
248 | elements.color = {
249 | value: () => {return layer.options.color || '#3388ff'},
250 | func: (value) => layer.setStyle({fillColor:value, color:value})
251 | };
252 | elements.legend = true;
253 | elements.bringToFront = () => layer.bringToFront();
254 | elements.bringToBack = () => layer.bringToBack();
255 | elements.zoom = () => this._map.fitBounds(layer.getBounds());
256 | }
257 | else if(layer instanceof L.GeoJSON){
258 | elements.opacity = {
259 | value: 0.2,
260 | func: (value) => layer.setStyle({fillOpacity:value, opacity: value})
261 | };
262 | elements.color = {
263 | value: () => {return layer.options.color || '#3388ff'},
264 | func: (value) => layer.setStyle({fillColor:value, color:value})
265 | };
266 | elements.legend = true;
267 | elements.bringToFront = () => layer.bringToFront();
268 | elements.bringToBack = () => layer.bringToBack();
269 | elements.zoom = () => this._map.fitBounds(layer.getBounds());
270 | }
271 |
272 | return elements
273 | },
274 |
275 | createLegend(arr){
276 |
277 | const container = L.DomUtil.create('div', 'ml-2');
278 | arr.forEach((elem) => {
279 | const div = document.createElement('div');
280 |
281 | const square = document.createElement('div');
282 | square.style.float = 'left';
283 | square.style.margin = '0.1rem';
284 | square.style.width = '10px';
285 | square.style.height = '10px';
286 | square.style.backgroundColor = elem.color;
287 | div.append(square);
288 |
289 | const txt = document.createElement('div');
290 | txt.innerText = elem.elemName;
291 | txt.style.fontSize = '10px';
292 | div.append(txt);
293 |
294 | container.append(div);
295 | });
296 |
297 | return container;
298 | },
299 |
300 | addOverlay(layer){
301 |
302 | if(!this.overlays) {
303 | this.overlays = [];
304 | this.toggle();
305 | }
306 |
307 | this.overlays.push(layer);
308 |
309 | const body = document.querySelector('.leaflet-controllable-legend-body');
310 | const overlaysContainer = body.firstChild;
311 | const index = overlaysContainer?.childNodes?.length || 0;
312 | const div = this.createChild(layer, index)
313 |
314 | overlaysContainer.append(div);
315 | },
316 |
317 | toggle(){
318 | this._container.childNodes.forEach(child => child.classList.toggle('hidden'));
319 | },
320 |
321 | evalLength(){
322 | if(this.overlays.length === 0) this.toggle();
323 | },
324 |
325 | findIndexByName(name){
326 | return this.overlays.findIndex(overlay => overlay.name === name)
327 | },
328 |
329 | validateNames(){
330 | const names = (this.overlays.map(overlay => overlay.name)).sort();
331 | names.reduce((pv, cv) => {
332 | if(pv !== cv ) return cv
333 | else throw new Error(`Overlays names must be unique. Repeated name: '${cv}'`)
334 | })
335 | }
336 | })
337 |
338 | L.multiControl = function(overlays, options){
339 | return new L.Control.multiControl(overlays, options);
340 | }
341 |
--------------------------------------------------------------------------------