From 40100537ca6f5ccc4a6956924999833c1703527c Mon Sep 17 00:00:00 2001 From: GoodZon Date: Wed, 23 Feb 2022 21:23:03 +0200 Subject: [PATCH 1/9] Add Bootstrap 5 Theme support, remove standart css files --- README.md | 4 ++- bower.json | 6 ++-- dist/leaflet.contextmenu.css | 54 -------------------------------- dist/leaflet.contextmenu.js | 27 ++++++++-------- dist/leaflet.contextmenu.min.css | 1 - dist/leaflet.contextmenu.min.js | 4 +-- examples/angular.html | 9 +++--- examples/index.html | 11 ++++--- package-lock.json | 52 ++++++++++++++++++++++++++++++ package.json | 16 +++++----- src/Map.ContextMenu.js | 25 ++++++++------- src/copyright.js | 2 +- 12 files changed, 106 insertions(+), 105 deletions(-) delete mode 100644 dist/leaflet.contextmenu.css delete mode 100644 dist/leaflet.contextmenu.min.css create mode 100644 package-lock.json diff --git a/README.md b/README.md index 7eca7e8..d7950c1 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,9 @@ [![npm](https://img.shields.io/npm/v/leaflet-contextmenu.svg)](https://www.npmjs.com/package/leaflet-contextmenu) [![Bower](https://img.shields.io/bower/v/leaflet.contextmenu.svg)](https://libraries.io/bower/Leaflet.contextmenu) -A context menu for Leaflet. See the [demo](http://aratcliffe.github.io/Leaflet.contextmenu/examples/index.html). +A context menu for Leaflet. See the [demo](http://gunyakov.github.io/Leaflet.contextmenu/examples/index.html). + +## Updated to use Bootstrap dropdown CSS style Now supporting Leaflet 1.0 diff --git a/bower.json b/bower.json index 7d1b08d..8276496 100644 --- a/bower.json +++ b/bower.json @@ -1,8 +1,8 @@ { "name": "leaflet.contextmenu", - "homepage": "https://github.com/aratcliffe/Leaflet.contextmenu", - "description": "A context menu for Leaflet", - "main": ["dist/leaflet.contextmenu.js", "dist/leaflet.contextmenu.css"], + "homepage": "https://github.com/gunyakov/Leaflet.contextmenu", + "description": "A context menu for Leaflet with Bootstrap CSS and Bootstrap Icons support", + "main": ["dist/leaflet.contextmenu.js"], "keywords": [ "gis" ], diff --git a/dist/leaflet.contextmenu.css b/dist/leaflet.contextmenu.css deleted file mode 100644 index 0b5e2de..0000000 --- a/dist/leaflet.contextmenu.css +++ /dev/null @@ -1,54 +0,0 @@ -.leaflet-contextmenu { - display: none; - box-shadow: 0 1px 7px rgba(0,0,0,0.4); - -webkit-border-radius: 4px; - border-radius: 4px; - padding: 4px 0; - background-color: #fff; - cursor: default; - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; -} - -.leaflet-contextmenu a.leaflet-contextmenu-item { - display: block; - color: #222; - font-size: 12px; - line-height: 20px; - text-decoration: none; - padding: 0 12px; - border-top: 1px solid transparent; - border-bottom: 1px solid transparent; - cursor: default; - outline: none; -} - -.leaflet-contextmenu a.leaflet-contextmenu-item-disabled { - opacity: 0.5; -} - -.leaflet-contextmenu a.leaflet-contextmenu-item.over { - background-color: #f4f4f4; - border-top: 1px solid #f0f0f0; - border-bottom: 1px solid #f0f0f0; -} - -.leaflet-contextmenu a.leaflet-contextmenu-item-disabled.over { - background-color: inherit; - border-top: 1px solid transparent; - border-bottom: 1px solid transparent; -} - -.leaflet-contextmenu-icon { - margin: 2px 8px 0 0; - width: 16px; - height: 16px; - float: left; - border: 0; -} - -.leaflet-contextmenu-separator { - border-bottom: 1px solid #ccc; - margin: 5px 0; -} diff --git a/dist/leaflet.contextmenu.js b/dist/leaflet.contextmenu.js index 489da87..4b58bae 100644 --- a/dist/leaflet.contextmenu.js +++ b/dist/leaflet.contextmenu.js @@ -1,7 +1,7 @@ /* Leaflet.contextmenu, a context menu for Leaflet. (c) 2015, Adam Ratcliffe, GeoSmart Maps Limited - + (c) 2021, Oleg Gunyakov, Maptorium Tile Downloader @preserve */ @@ -31,7 +31,7 @@ L.Map.ContextMenu = L.Handler.extend({ _touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart', statics: { - BASE_CLS: 'leaflet-contextmenu' + BASE_CLS: 'dropdown-menu' }, initialize: function (map) { @@ -180,7 +180,7 @@ L.Map.ContextMenu = L.Handler.extend({ setDisabled: function (item, disabled) { var container = this._container, - itemCls = L.Map.ContextMenu.BASE_CLS + '-item'; + itemCls = 'dropdown-item'; if (!isNaN(item)) { item = container.children[item]; @@ -188,13 +188,13 @@ L.Map.ContextMenu = L.Handler.extend({ if (item && L.DomUtil.hasClass(item, itemCls)) { if (disabled) { - L.DomUtil.addClass(item, itemCls + '-disabled'); + L.DomUtil.addClass(item, 'disabled'); this._map.fire('contextmenu.disableitem', { contextmenu: this, el: item }); } else { - L.DomUtil.removeClass(item, itemCls + '-disabled'); + L.DomUtil.removeClass(item, 'disabled'); this._map.fire('contextmenu.enableitem', { contextmenu: this, el: item @@ -222,21 +222,22 @@ L.Map.ContextMenu = L.Handler.extend({ return this._createSeparator(container, index); } - var itemCls = L.Map.ContextMenu.BASE_CLS + '-item', - cls = options.disabled ? (itemCls + ' ' + itemCls + '-disabled') : itemCls, - el = this._insertElementAt('a', cls, container, index), + var itemCls = 'dropdown-item', + cls = options.disabled ? (itemCls + ' disabled') : itemCls, + cls = options.class ? (itemCls + ' ' + options.class) : itemCls, + el = this._insertElementAt('button', cls, container, index), callback = this._createEventHandler(el, options.callback, options.context, options.hideOnSelect), icon = this._getIcon(options), iconCls = this._getIconCls(options), html = ''; if (icon) { - html = ''; + html = ''; } else if (iconCls) { - html = ''; + html = ''; } - el.innerHTML = html + options.text; + el.innerHTML = html + " " + options.text; el.href = '#'; L.DomEvent @@ -299,7 +300,7 @@ L.Map.ContextMenu = L.Handler.extend({ }, _createSeparator: function (container, index) { - var el = this._insertElementAt('div', L.Map.ContextMenu.BASE_CLS + '-separator', container, index); + var el = this._insertElementAt('div', 'dropdown-divider', container, index); return { id: L.Util.stamp(el), @@ -310,7 +311,7 @@ L.Map.ContextMenu = L.Handler.extend({ _createEventHandler: function (el, func, context, hideOnSelect) { var me = this, map = this._map, - disabledCls = L.Map.ContextMenu.BASE_CLS + '-item-disabled', + disabledCls = 'disabled', hideOnSelect = (hideOnSelect !== undefined) ? hideOnSelect : true; return function (e) { diff --git a/dist/leaflet.contextmenu.min.css b/dist/leaflet.contextmenu.min.css deleted file mode 100644 index ef6c6a0..0000000 --- a/dist/leaflet.contextmenu.min.css +++ /dev/null @@ -1 +0,0 @@ -.leaflet-contextmenu{display:none;box-shadow:0 1px 7px rgba(0,0,0,0.4);-webkit-border-radius:4px;border-radius:4px;padding:4px 0;background-color:#fff;cursor:default;-webkit-user-select:none;-moz-user-select:none;user-select:none}.leaflet-contextmenu a.leaflet-contextmenu-item{display:block;color:#222;font-size:12px;line-height:20px;text-decoration:none;padding:0 12px;border-top:1px solid transparent;border-bottom:1px solid transparent;cursor:default;outline:0}.leaflet-contextmenu a.leaflet-contextmenu-item-disabled{opacity:.5}.leaflet-contextmenu a.leaflet-contextmenu-item.over{background-color:#f4f4f4;border-top:1px solid #f0f0f0;border-bottom:1px solid #f0f0f0}.leaflet-contextmenu a.leaflet-contextmenu-item-disabled.over{background-color:inherit;border-top:1px solid transparent;border-bottom:1px solid transparent}.leaflet-contextmenu-icon{margin:2px 8px 0 0;width:16px;height:16px;float:left;border:0}.leaflet-contextmenu-separator{border-bottom:1px solid #ccc;margin:5px 0} diff --git a/dist/leaflet.contextmenu.min.js b/dist/leaflet.contextmenu.min.js index 80a8870..0c15199 100644 --- a/dist/leaflet.contextmenu.min.js +++ b/dist/leaflet.contextmenu.min.js @@ -1,7 +1,7 @@ /* Leaflet.contextmenu, a context menu for Leaflet. (c) 2015, Adam Ratcliffe, GeoSmart Maps Limited - + (c) 2021, Oleg Gunyakov, Maptorium Tile Downloader @preserve */ -(function(t){var e;if(typeof define==="function"&&define.amd){define(["leaflet"],t)}else if(typeof module==="object"&&typeof module.exports==="object"){e=require("leaflet");module.exports=t(e)}else{if(typeof window.L==="undefined"){throw new Error("Leaflet must be loaded first")}t(window.L)}})(function(t){t.Map.mergeOptions({contextmenuItems:[]});t.Map.ContextMenu=t.Handler.extend({_touchstart:t.Browser.msPointer?"MSPointerDown":t.Browser.pointer?"pointerdown":"touchstart",statics:{BASE_CLS:"leaflet-contextmenu"},initialize:function(e){t.Handler.prototype.initialize.call(this,e);this._items=[];this._visible=false;var n=this._container=t.DomUtil.create("div",t.Map.ContextMenu.BASE_CLS,e._container);n.style.zIndex=1e4;n.style.position="absolute";if(e.options.contextmenuWidth){n.style.width=e.options.contextmenuWidth+"px"}this._createItems();t.DomEvent.on(n,"click",t.DomEvent.stop).on(n,"mousedown",t.DomEvent.stop).on(n,"dblclick",t.DomEvent.stop).on(n,"contextmenu",t.DomEvent.stop)},addHooks:function(){var e=this._map.getContainer();t.DomEvent.on(e,"mouseleave",this._hide,this).on(document,"keydown",this._onKeyDown,this);if(t.Browser.touch){t.DomEvent.on(document,this._touchstart,this._hide,this)}this._map.on({contextmenu:this._show,mousedown:this._hide,zoomstart:this._hide},this)},removeHooks:function(){var e=this._map.getContainer();t.DomEvent.off(e,"mouseleave",this._hide,this).off(document,"keydown",this._onKeyDown,this);if(t.Browser.touch){t.DomEvent.off(document,this._touchstart,this._hide,this)}this._map.off({contextmenu:this._show,mousedown:this._hide,zoomstart:this._hide},this)},showAt:function(e,n){if(e instanceof t.LatLng){e=this._map.latLngToContainerPoint(e)}this._showAtPoint(e,n)},hide:function(){this._hide()},addItem:function(t){return this.insertItem(t)},insertItem:function(t,e){e=e!==undefined?e:this._items.length;var n=this._createItem(this._container,t,e);this._items.push(n);this._sizeChanged=true;this._map.fire("contextmenu.additem",{contextmenu:this,el:n.el,index:e});return n.el},removeItem:function(e){var n=this._container;if(!isNaN(e)){e=n.children[e]}if(e){this._removeItem(t.Util.stamp(e));this._sizeChanged=true;this._map.fire("contextmenu.removeitem",{contextmenu:this,el:e});return e}return null},removeAllItems:function(){var e=this._container.children,n;while(e.length){n=e[0];this._removeItem(t.Util.stamp(n))}return e},hideAllItems:function(){var t,e,n;for(e=0,n=this._items.length;e'}else if(m){u=''}h.innerHTML=u+n.text;h.href="#";t.DomEvent.on(h,"mouseover",this._onItemMouseOver,this).on(h,"mouseout",this._onItemMouseOut,this).on(h,"mousedown",t.DomEvent.stopPropagation).on(h,"click",r);if(t.Browser.touch){t.DomEvent.on(h,this._touchstart,t.DomEvent.stopPropagation)}if(!t.Browser.pointer){t.DomEvent.on(h,"click",this._onItemMouseOut,this)}return{id:t.Util.stamp(h),el:h,callback:r}},_removeItem:function(e){var n,i,o,s,h;for(o=0,s=this._items.length;on.x){i.style.left="auto";i.style.right=Math.min(Math.max(n.x-e.x,0),n.x-o.x-1)+"px"}else{i.style.left=Math.max(e.x,0)+"px";i.style.right="auto"}if(e.y+o.y>n.y){i.style.top="auto";i.style.bottom=Math.min(Math.max(n.y-e.y,0),n.y-o.y-1)+"px"}else{i.style.top=Math.max(e.y,0)+"px";i.style.bottom="auto"}},_getElementSize:function(t){var e=this._size,n=t.style.display;if(!e||this._sizeChanged){e={};t.style.left="-999999px";t.style.right="auto";t.style.display="block";e.x=t.offsetWidth;e.y=t.offsetHeight;t.style.left="auto";t.style.display=n;this._sizeChanged=false}return e},_onKeyDown:function(t){var e=t.keyCode;if(e===27){this._hide()}},_onItemMouseOver:function(e){t.DomUtil.addClass(e.target||e.srcElement,"over")},_onItemMouseOut:function(e){t.DomUtil.removeClass(e.target||e.srcElement,"over")}});t.Map.addInitHook("addHandler","contextmenu",t.Map.ContextMenu);t.Mixin.ContextMenu={bindContextMenu:function(e){t.setOptions(this,e);this._initContextMenu();return this},unbindContextMenu:function(){this.off("contextmenu",this._showContextMenu,this);return this},addContextMenuItem:function(t){this.options.contextmenuItems.push(t)},removeContextMenuItemWithIndex:function(t){var e=[];for(var n=0;n'}else if(a){m=''}s.innerHTML=m+" "+e.text;s.href="#";d.DomEvent.on(s,"mouseover",this._onItemMouseOver,this).on(s,"mouseout",this._onItemMouseOut,this).on(s,"mousedown",d.DomEvent.stopPropagation).on(s,"click",r);if(d.Browser.touch){d.DomEvent.on(s,this._touchstart,d.DomEvent.stopPropagation)}if(!d.Browser.pointer){d.DomEvent.on(s,"click",this._onItemMouseOut,this)}return{id:d.Util.stamp(s),el:s,callback:r}},_removeItem:function(t){var e,n,i,o,s;for(i=0,o=this._items.length;ie.x){n.style.left="auto";n.style.right=Math.min(Math.max(e.x-t.x,0),e.x-i.x-1)+"px"}else{n.style.left=Math.max(t.x,0)+"px";n.style.right="auto"}if(t.y+i.y>e.y){n.style.top="auto";n.style.bottom=Math.min(Math.max(e.y-t.y,0),e.y-i.y-1)+"px"}else{n.style.top=Math.max(t.y,0)+"px";n.style.bottom="auto"}},_getElementSize:function(t){var e=this._size,n=t.style.display;if(!e||this._sizeChanged){e={};t.style.left="-999999px";t.style.right="auto";t.style.display="block";e.x=t.offsetWidth;e.y=t.offsetHeight;t.style.left="auto";t.style.display=n;this._sizeChanged=false}return e},_onKeyDown:function(t){var e=t.keyCode;if(e===27){this._hide()}},_onItemMouseOver:function(t){d.DomUtil.addClass(t.target||t.srcElement,"over")},_onItemMouseOut:function(t){d.DomUtil.removeClass(t.target||t.srcElement,"over")}});d.Map.addInitHook("addHandler","contextmenu",d.Map.ContextMenu);d.Mixin.ContextMenu={bindContextMenu:function(t){d.setOptions(this,t);this._initContextMenu();return this},unbindContextMenu:function(){this.off("contextmenu",this._showContextMenu,this);return this},addContextMenuItem:function(t){this.options.contextmenuItems.push(t)},removeContextMenuItemWithIndex:function(t){var e=[];for(var n=0;n Angular Leaflet Context Menu - - - + + @@ -33,9 +32,9 @@ function showCoordinates (e) { alert(e.latlng); - } + } - + diff --git a/examples/index.html b/examples/index.html index ecd29f6..70f74cc 100644 --- a/examples/index.html +++ b/examples/index.html @@ -2,12 +2,13 @@ Leaflet Context Menu - - + + +
- + + + diff --git a/package-lock.json b/package-lock.json index eac8657..07fe043 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "leaflet-contextmenu-bootstrap", - "version": "1.5.4", + "version": "1.5.6", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "leaflet-contextmenu-bootstrap", - "version": "1.5.4", + "version": "1.5.6", "license": "MIT", "devDependencies": { "uglify-js": "^3.15.1", @@ -14,9 +14,9 @@ } }, "node_modules/uglify-js": { - "version": "3.15.1", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.15.1.tgz", - "integrity": "sha512-FAGKF12fWdkpvNJZENacOH0e/83eG6JyVQyanIJaBXCN1J11TUQv1T1/z8S+Z0CG0ZPk1nPcreF/c7lrTd0TEQ==", + "version": "3.16.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.16.3.tgz", + "integrity": "sha512-uVbFqx9vvLhQg0iBaau9Z75AxWJ8tqM9AV890dIZCLApF4rTcyHwmAvLeEdYRs+BzYWu8Iw81F79ah0EfTXbaw==", "dev": true, "bin": { "uglifyjs": "bin/uglifyjs" @@ -28,7 +28,7 @@ "node_modules/uglifycss": { "version": "0.0.25", "resolved": "https://registry.npmjs.org/uglifycss/-/uglifycss-0.0.25.tgz", - "integrity": "sha1-vqcr9JeerO8TowLPR7LRrz80QZc=", + "integrity": "sha512-MPfsrz1Cek1uby3nnPbCBjfcu4q4HxdLnWzYPShO+mHYjEO+C1PDAHH4Q7rZLImv3UF5LXOHCdoz4g32+nb5xw==", "dev": true, "bin": { "uglifycss": "uglifycss" @@ -37,15 +37,15 @@ }, "dependencies": { "uglify-js": { - "version": "3.15.1", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.15.1.tgz", - "integrity": "sha512-FAGKF12fWdkpvNJZENacOH0e/83eG6JyVQyanIJaBXCN1J11TUQv1T1/z8S+Z0CG0ZPk1nPcreF/c7lrTd0TEQ==", + "version": "3.16.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.16.3.tgz", + "integrity": "sha512-uVbFqx9vvLhQg0iBaau9Z75AxWJ8tqM9AV890dIZCLApF4rTcyHwmAvLeEdYRs+BzYWu8Iw81F79ah0EfTXbaw==", "dev": true }, "uglifycss": { "version": "0.0.25", "resolved": "https://registry.npmjs.org/uglifycss/-/uglifycss-0.0.25.tgz", - "integrity": "sha1-vqcr9JeerO8TowLPR7LRrz80QZc=", + "integrity": "sha512-MPfsrz1Cek1uby3nnPbCBjfcu4q4HxdLnWzYPShO+mHYjEO+C1PDAHH4Q7rZLImv3UF5LXOHCdoz4g32+nb5xw==", "dev": true } } diff --git a/package.json b/package.json index 867d52e..1075ad0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "leaflet-contextmenu-bootstrap", - "version": "1.5.6", + "version": "2.0.0", "description": "A context menu for Leaflet", "main": "dist/leaflet.contextmenu.js", "directories": { diff --git a/src/Map.ContextMenu.js b/src/Map.ContextMenu.js index 31f5145..c40144c 100644 --- a/src/Map.ContextMenu.js +++ b/src/Map.ContextMenu.js @@ -6,37 +6,45 @@ L.Map.ContextMenu = L.Handler.extend({ _touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart', statics: { - BASE_CLS: 'dropdown-menu' + CLS_CONTAINER: 'dropdown-menu', + CLS_DISABLED: 'disabled', + CLS_TOGGLE: 'dropdown-toggle arrow-none', + CLS_ELEMENT: 'dropdown', + CLS_INNER: 'dropdown-item', + CONTAINER: 'ul', + ELEMENT: 'li', + INNER: 'a', + ZINDEX: 10000 }, initialize: function (map) { L.Handler.prototype.initialize.call(this, map); this._items = []; + + this._idCounter = 0; this._visible = false; - var container = this._container = L.DomUtil.create('div', L.Map.ContextMenu.BASE_CLS, map._container); - container.style.zIndex = 10000; - container.style.position = 'absolute'; + this._container = this._initContainer(map._container); if (map.options.contextmenuWidth) { - container.style.width = map.options.contextmenuWidth + 'px'; + this._container.style.width = map.options.contextmenuWidth + 'px'; } this._createItems(); L.DomEvent - .on(container, 'click', L.DomEvent.stop) - .on(container, 'mousedown', L.DomEvent.stop) - .on(container, 'dblclick', L.DomEvent.stop) - .on(container, 'contextmenu', L.DomEvent.stop); + .on(this._container, 'click', L.DomEvent.stop) + .on(this._container, 'mousedown', L.DomEvent.stop) + .on(this._container, 'dblclick', L.DomEvent.stop) + .on(this._container, 'contextmenu', L.DomEvent.stop); }, addHooks: function () { var container = this._map.getContainer(); L.DomEvent - .on(container, 'mouseleave', this._hide, this) + //.on(container, 'mouseleave', this._hide, this) .on(document, 'keydown', this._onKeyDown, this); if (L.Browser.touch) { @@ -182,13 +190,21 @@ L.Map.ContextMenu = L.Handler.extend({ return this._visible; }, - _createItems: function () { - var itemOptions = this._map.options.contextmenuItems, - item, + _initContainer: function(_container, subMenu = false) { + var container = L.DomUtil.create(L.Map.ContextMenu.CONTAINER, L.Map.ContextMenu.CLS_CONTAINER, _container); + container.style.zIndex = L.Map.ContextMenu.ZINDEX; + //if(!subMenu) { + container.style.position = 'absolute'; + //} + return container; + }, + + _createItems: function (itemOptions = this._map.options.contextmenuItems, container = this._container) { + let item, i, l; for (i = 0, l = itemOptions.length; i < l; i++) { - this._items.push(this._createItem(this._container, itemOptions[i])); + this._items.push(this._createItem(container, itemOptions[i])); } }, @@ -197,30 +213,65 @@ L.Map.ContextMenu = L.Handler.extend({ return this._createSeparator(container, index); } - var itemCls = 'dropdown-item', - cls = options.disabled ? (itemCls + ' disabled') : itemCls, - cls = options.class ? (itemCls + ' ' + options.class) : itemCls, - el = this._insertElementAt('button', cls, container, index), - callback = this._createEventHandler(el, options.callback, options.context, options.hideOnSelect), - icon = this._getIcon(options), - iconCls = this._getIconCls(options), - html = ''; + let subMenu = false; + if(options.contextmenuItems) { + subMenu = true; + } + + let itemCls = L.Map.ContextMenu.CLS_ELEMENT; + let cls = options.disabled ? (itemCls + ' ' + L.Map.ContextMenu.CLS_DISABLED) : itemCls; + cls = options.class ? (itemCls + ' ' + options.class) : itemCls; + let el = this._insertElementAt(L.Map.ContextMenu.ELEMENT, cls, container, index); + this._idCounter++; + + let callback = false; + if(!subMenu) { + callback = this._createEventHandler(el, options.callback, options.context, options.hideOnSelect); + } + let icon = this._getIcon(options); + let iconCls = this._getIconCls(options); + + let inner = L.DomUtil.create(L.Map.ContextMenu.INNER, L.Map.ContextMenu.CLS_INNER, el); + inner.href = '#'; + //inner.setAttribute('id', 'ctxMenu' + this._idCounter); + inner.setAttribute("role", "button"); + inner.setAttribute("data-toggle", "dropdown"); + inner.setAttribute("aria-haspopup", "true"); + inner.setAttribute("aria-expanded", "false"); + + if(iconCls) { + L.DomUtil.create("i", iconCls, inner); + } if (icon) { - html = ''; - } else if (iconCls) { - html = ''; + let iconContainer = L.DomUtil.create("img", '', inner); + iconContainer.src = icon; } - el.innerHTML = html + " " + options.text + ""; - el.href = '#'; + let textContainer = L.DomUtil.create("span", '', inner); + textContainer.innerHTML = " " + options.text; + + if(subMenu) { + //L.DomUtil.create("div", "arrow-down", inner); + L.DomUtil.addClass(inner, L.Map.ContextMenu.CLS_TOGGLE); + if(options.contextmenuItems) { + let subContainer = this._initContainer(el, true); + //subContainer.setAttribute("aria-labelledby", 'ctxMenu' + this._idCounter); + this._createItems(options.contextmenuItems, subContainer); + } + } - L.DomEvent - .on(el, 'mouseover', this._onItemMouseOver, this) - .on(el, 'mouseout', this._onItemMouseOut, this) + if(!subMenu) { + L.DomEvent + //.on(el, 'mouseover', this._onItemMouseOver, this) .on(el, 'mousedown', L.DomEvent.stopPropagation) + //.on(el, 'mouseout', this._onItemMouseOut, this) .on(el, 'click', callback); - + } + else { + L.DomEvent.on(el, 'click', this._showSubMenu, this); + //L.DomEvent.on(el, 'mouseout', this._hideSubMenu, this); + } if (L.Browser.touch) { L.DomEvent.on(el, this._touchstart, L.DomEvent.stopPropagation); } @@ -237,6 +288,31 @@ L.Map.ContextMenu = L.Handler.extend({ }; }, + _showSubMenu: function(target) { + let container = target.target.closest(".dropdown-menu"); + let allSubMenu = container.querySelectorAll(".show"); + for(i = 0; i < allSubMenu.length; i++) { + L.DomUtil.removeClass(allSubMenu[i], "show"); + } + let subContainer = target.target.closest("li"); + subContainer = subContainer.querySelector(".dropdown-menu"); + if(subContainer) { + subContainer.style.top = "0px"; + subContainer.style.left = container.offsetWidth + "px"; + if(L.DomUtil.hasClass(subContainer, "show")) { + //L.DomUtil.removeClass(subContainer, "show"); + } + else { + L.DomUtil.addClass(subContainer, "show"); + } + } + }, + + _hideSubMenu: function(target) { + let subContainer = target.target.closest("li"); + subContainer = subContainer.querySelector(".dropdown-menu"); + L.DomUtil.removeClass(subContainer, "show"); + }, _removeItem: function (id) { var item, el, @@ -341,7 +417,11 @@ L.Map.ContextMenu = L.Handler.extend({ }, _show: function (e) { - this._showAtPoint(e.containerPoint, e); + var subContainer = document.querySelectorAll(".show"); + for (var i = 0; i < subContainer.length; i++) { + L.DomUtil.removeClass(subContainer[i], "show"); + } + this._showAtPoint(e.containerPoint, e); }, _showAtPoint: function (pt, data) { @@ -447,11 +527,11 @@ L.Map.ContextMenu = L.Handler.extend({ }, _onItemMouseOver: function (e) { - L.DomUtil.addClass(e.target || e.srcElement, 'over'); + //L.DomUtil.addClass(e.target || e.srcElement, 'over'); }, _onItemMouseOut: function (e) { - L.DomUtil.removeClass(e.target || e.srcElement, 'over'); + //L.DomUtil.removeClass(e.target || e.srcElement, 'over'); } }); diff --git a/src/Mixin.ContextMenu.js b/src/Mixin.ContextMenu.js index 4411b98..ae8a7e0 100644 --- a/src/Mixin.ContextMenu.js +++ b/src/Mixin.ContextMenu.js @@ -53,18 +53,24 @@ L.Mixin.ContextMenu = { if (!this.options.contextmenuInheritItems) { this._map.contextmenu.hideAllItems(); } - - for (i = 0, l = this.options.contextmenuItems.length; i < l; i++) { - itemOptions = this.options.contextmenuItems[i]; - this._items.push(this._map.contextmenu.insertItem(itemOptions, itemOptions.index)); - } + this._subMenu(this.options.contextmenuItems); this._map.once('contextmenu.hide', this._hideContextMenu, this); - + var subContainer = document.querySelectorAll(".show"); + for (var i = 0; i < subContainer.length; i++) { + L.DomUtil.removeClass(subContainer[i], "show"); + } this._map.contextmenu.showAt(pt, data); } }, + _subMenu: function(contextmenuItems) { + for (i = 0, l = contextmenuItems.length; i < l; i++) { + itemOptions = contextmenuItems[i]; + this._items.push(this._map.contextmenu.insertItem(itemOptions, itemOptions.index)); + } + }, + _hideContextMenu: function () { var i, l; From 4c359c0b88895011de6f378acfa3d5e883eb8387 Mon Sep 17 00:00:00 2001 From: gunyakov Date: Sat, 6 Aug 2022 14:23:32 +0300 Subject: [PATCH 7/9] V2.0.0. Multilevel bootstrap dropdowns --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5c28cfc..016a2e3 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ A context menu for Leaflet based on Bootstrap dropdowns. See the [demo](http://g ##Find bug. When activate map menu together with polygon menu, callback functions dont receive relatedTarget object. +##Multi level bootstrap dropdowns supported + Full compatible with Leaflet 1.7.* ## Usage From 768454c9e203e36ab4c502b86a6c8048c78aba10 Mon Sep 17 00:00:00 2001 From: gunyakov Date: Tue, 9 Aug 2022 16:30:40 +0300 Subject: [PATCH 8/9] relatedTarget bug --- dist/leaflet.contextmenu.js | 6 ++++-- dist/leaflet.contextmenu.min.js | 2 +- examples/index.html | 2 ++ package.json | 2 +- src/Map.ContextMenu.js | 6 ++++-- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/dist/leaflet.contextmenu.js b/dist/leaflet.contextmenu.js index 405e908..e26e99e 100755 --- a/dist/leaflet.contextmenu.js +++ b/dist/leaflet.contextmenu.js @@ -52,6 +52,8 @@ L.Map.ContextMenu = L.Handler.extend({ this._container = this._initContainer(map._container); + this._relatedTarget = false; + if (map.options.contextmenuWidth) { this._container.style.width = map.options.contextmenuWidth + 'px'; } @@ -399,7 +401,7 @@ L.Map.ContextMenu = L.Handler.extend({ containerPoint = me._showLocation.containerPoint, layerPoint = map.containerPointToLayerPoint(containerPoint), latlng = map.layerPointToLatLng(layerPoint), - relatedTarget = me._showLocation.relatedTarget, + relatedTarget = me._relatedTarget, data = { containerPoint: containerPoint, layerPoint: layerPoint, @@ -459,7 +461,7 @@ L.Map.ContextMenu = L.Handler.extend({ }; if (data && data.relatedTarget){ - this._showLocation.relatedTarget = data.relatedTarget; + this._relatedTarget = data.relatedTarget; } this._setPosition(pt); diff --git a/dist/leaflet.contextmenu.min.js b/dist/leaflet.contextmenu.min.js index 5684fa3..6ac8b05 100644 --- a/dist/leaflet.contextmenu.min.js +++ b/dist/leaflet.contextmenu.min.js @@ -4,4 +4,4 @@ (c) 2021-2022, Oleg Gunyakov, Maptorium Tile Downloader @preserve */ -(function(t){var e;if(typeof define==="function"&&define.amd){define(["leaflet"],t)}else if(typeof module==="object"&&typeof module.exports==="object"){e=require("leaflet");module.exports=t(e)}else{if(typeof window.L==="undefined"){throw new Error("Leaflet must be loaded first")}t(window.L)}})(function(d){d.Map.mergeOptions({contextmenuItems:[]});d.Map.ContextMenu=d.Handler.extend({_touchstart:d.Browser.msPointer?"MSPointerDown":d.Browser.pointer?"pointerdown":"touchstart",statics:{CLS_CONTAINER:"dropdown-menu",CLS_DISABLED:"disabled",CLS_TOGGLE:"dropdown-toggle arrow-none",CLS_ELEMENT:"dropdown",CLS_INNER:"dropdown-item",CONTAINER:"ul",ELEMENT:"li",INNER:"a",ZINDEX:1e4},initialize:function(t){d.Handler.prototype.initialize.call(this,t);this._items=[];this._idCounter=0;this._visible=false;this._container=this._initContainer(t._container);if(t.options.contextmenuWidth){this._container.style.width=t.options.contextmenuWidth+"px"}this._createItems();d.DomEvent.on(this._container,"click",d.DomEvent.stop).on(this._container,"mousedown",d.DomEvent.stop).on(this._container,"dblclick",d.DomEvent.stop).on(this._container,"contextmenu",d.DomEvent.stop)},addHooks:function(){var t=this._map.getContainer();d.DomEvent.on(document,"keydown",this._onKeyDown,this);if(d.Browser.touch){d.DomEvent.on(document,this._touchstart,this._hide,this)}this._map.on({contextmenu:this._show,mousedown:this._hide,zoomstart:this._hide},this)},removeHooks:function(){var t=this._map.getContainer();d.DomEvent.off(t,"mouseleave",this._hide,this).off(document,"keydown",this._onKeyDown,this);if(d.Browser.touch){d.DomEvent.off(document,this._touchstart,this._hide,this)}this._map.off({contextmenu:this._show,mousedown:this._hide,zoomstart:this._hide},this)},showAt:function(t,e){if(t instanceof d.LatLng){t=this._map.latLngToContainerPoint(t)}this._showAtPoint(t,e)},hide:function(){this._hide()},addItem:function(t){return this.insertItem(t)},insertItem:function(t,e){e=e!==undefined?e:this._items.length;var n=this._createItem(this._container,t,e);this._items.push(n);this._sizeChanged=true;this._map.fire("contextmenu.additem",{contextmenu:this,el:n.el,index:e});return n.el},removeItem:function(t){var e=this._container;if(!isNaN(t)){t=e.children[t]}if(t){this._removeItem(d.Util.stamp(t));this._sizeChanged=true;this._map.fire("contextmenu.removeitem",{contextmenu:this,el:t});return t}return null},removeAllItems:function(){var t=this._container.children,e;while(t.length){e=t[0];this._removeItem(d.Util.stamp(e))}return t},hideAllItems:function(){var t,e,n;for(e=0,n=this._items.length;ee.x){n.style.left="auto";n.style.right=Math.min(Math.max(e.x-t.x,0),e.x-i.x-1)+"px"}else{n.style.left=Math.max(t.x,0)+"px";n.style.right="auto"}if(t.y+i.y>e.y){n.style.top="auto";n.style.bottom=Math.min(Math.max(e.y-t.y,0),e.y-i.y-1)+"px"}else{n.style.top=Math.max(t.y,0)+"px";n.style.bottom="auto"}},_getElementSize:function(t){var e=this._size,n=t.style.display;if(!e||this._sizeChanged){e={};t.style.left="-999999px";t.style.right="auto";t.style.display="block";e.x=t.offsetWidth;e.y=t.offsetHeight;t.style.left="auto";t.style.display=n;this._sizeChanged=false}return e},_onKeyDown:function(t){var e=t.keyCode;if(e===27){this._hide()}},_onItemMouseOver:function(t){},_onItemMouseOut:function(t){}});d.Map.addInitHook("addHandler","contextmenu",d.Map.ContextMenu);d.Mixin.ContextMenu={bindContextMenu:function(t){d.setOptions(this,t);this._initContextMenu();return this},unbindContextMenu:function(){this.off("contextmenu",this._showContextMenu,this);return this},addContextMenuItem:function(t){this.options.contextmenuItems.push(t)},removeContextMenuItemWithIndex:function(t){var e=[];for(var n=0;ne.x){n.style.left="auto";n.style.right=Math.min(Math.max(e.x-t.x,0),e.x-i.x-1)+"px"}else{n.style.left=Math.max(t.x,0)+"px";n.style.right="auto"}if(t.y+i.y>e.y){n.style.top="auto";n.style.bottom=Math.min(Math.max(e.y-t.y,0),e.y-i.y-1)+"px"}else{n.style.top=Math.max(t.y,0)+"px";n.style.bottom="auto"}},_getElementSize:function(t){var e=this._size,n=t.style.display;if(!e||this._sizeChanged){e={};t.style.left="-999999px";t.style.right="auto";t.style.display="block";e.x=t.offsetWidth;e.y=t.offsetHeight;t.style.left="auto";t.style.display=n;this._sizeChanged=false}return e},_onKeyDown:function(t){var e=t.keyCode;if(e===27){this._hide()}},_onItemMouseOver:function(t){},_onItemMouseOut:function(t){}});d.Map.addInitHook("addHandler","contextmenu",d.Map.ContextMenu);d.Mixin.ContextMenu={bindContextMenu:function(t){d.setOptions(this,t);this._initContextMenu();return this},unbindContextMenu:function(){this.off("contextmenu",this._showContextMenu,this);return this},addContextMenuItem:function(t){this.options.contextmenuItems.push(t)},removeContextMenuItemWithIndex:function(t){var e=[];for(var n=0;n Date: Tue, 9 Aug 2022 22:39:56 +0300 Subject: [PATCH 9/9] Add data to send in callback function --- README.md | 1 + dist/leaflet.contextmenu.js | 7 ++++--- dist/leaflet.contextmenu.min.js | 2 +- examples/index.html | 10 ++++++++-- package.json | 2 +- src/Map.ContextMenu.js | 7 ++++--- 6 files changed, 19 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 016a2e3..f9d7bd2 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,7 @@ L.marker(ll, { | retinaIconCls | String | `undefined` | A CSS class which sets the background image for a retina version of the icon (exclusive of the `retinaIcon` option). | callback | Function | `undefined` | A callback function to be invoked when the menu item is clicked. The callback is passed an object with properties identifying the location the menu was opened at: `latlng`, `layerPoint` and `containerPoint`. | context | Object | The map | The scope the callback will be executed in. +| data| Any | `undefined` | Data whitch be sended to callback function. | disabled | Bool | `false` | If `true` the menu item will initially be in a disabled state and will not respond to click events. | separator | Bool | `undefined` | If `true` a separator will be created instead of a menu item. | hideOnSelect | Bool | `true` | If `true` the context menu will be automatically hidden when a menu item is selected diff --git a/dist/leaflet.contextmenu.js b/dist/leaflet.contextmenu.js index e26e99e..a90357d 100755 --- a/dist/leaflet.contextmenu.js +++ b/dist/leaflet.contextmenu.js @@ -253,7 +253,7 @@ L.Map.ContextMenu = L.Handler.extend({ let callback = false; if(!subMenu) { - callback = this._createEventHandler(el, options.callback, options.context, options.hideOnSelect); + callback = this._createEventHandler(el, options.callback, options.context, options.hideOnSelect, options.data); } let icon = this._getIcon(options); let iconCls = this._getIconCls(options); @@ -386,7 +386,7 @@ L.Map.ContextMenu = L.Handler.extend({ }; }, - _createEventHandler: function (el, func, context, hideOnSelect) { + _createEventHandler: function (el, func, context, hideOnSelect, itemData) { var me = this, map = this._map, disabledCls = 'disabled', @@ -406,7 +406,8 @@ L.Map.ContextMenu = L.Handler.extend({ containerPoint: containerPoint, layerPoint: layerPoint, latlng: latlng, - relatedTarget: relatedTarget + relatedTarget: relatedTarget, + data: itemData }; if (hideOnSelect) { diff --git a/dist/leaflet.contextmenu.min.js b/dist/leaflet.contextmenu.min.js index 6ac8b05..47c610d 100644 --- a/dist/leaflet.contextmenu.min.js +++ b/dist/leaflet.contextmenu.min.js @@ -4,4 +4,4 @@ (c) 2021-2022, Oleg Gunyakov, Maptorium Tile Downloader @preserve */ -(function(t){var e;if(typeof define==="function"&&define.amd){define(["leaflet"],t)}else if(typeof module==="object"&&typeof module.exports==="object"){e=require("leaflet");module.exports=t(e)}else{if(typeof window.L==="undefined"){throw new Error("Leaflet must be loaded first")}t(window.L)}})(function(d){d.Map.mergeOptions({contextmenuItems:[]});d.Map.ContextMenu=d.Handler.extend({_touchstart:d.Browser.msPointer?"MSPointerDown":d.Browser.pointer?"pointerdown":"touchstart",statics:{CLS_CONTAINER:"dropdown-menu",CLS_DISABLED:"disabled",CLS_TOGGLE:"dropdown-toggle arrow-none",CLS_ELEMENT:"dropdown",CLS_INNER:"dropdown-item",CONTAINER:"ul",ELEMENT:"li",INNER:"a",ZINDEX:1e4},initialize:function(t){d.Handler.prototype.initialize.call(this,t);this._items=[];this._idCounter=0;this._visible=false;this._container=this._initContainer(t._container);this._relatedTarget=false;if(t.options.contextmenuWidth){this._container.style.width=t.options.contextmenuWidth+"px"}this._createItems();d.DomEvent.on(this._container,"click",d.DomEvent.stop).on(this._container,"mousedown",d.DomEvent.stop).on(this._container,"dblclick",d.DomEvent.stop).on(this._container,"contextmenu",d.DomEvent.stop)},addHooks:function(){var t=this._map.getContainer();d.DomEvent.on(document,"keydown",this._onKeyDown,this);if(d.Browser.touch){d.DomEvent.on(document,this._touchstart,this._hide,this)}this._map.on({contextmenu:this._show,mousedown:this._hide,zoomstart:this._hide},this)},removeHooks:function(){var t=this._map.getContainer();d.DomEvent.off(t,"mouseleave",this._hide,this).off(document,"keydown",this._onKeyDown,this);if(d.Browser.touch){d.DomEvent.off(document,this._touchstart,this._hide,this)}this._map.off({contextmenu:this._show,mousedown:this._hide,zoomstart:this._hide},this)},showAt:function(t,e){if(t instanceof d.LatLng){t=this._map.latLngToContainerPoint(t)}this._showAtPoint(t,e)},hide:function(){this._hide()},addItem:function(t){return this.insertItem(t)},insertItem:function(t,e){e=e!==undefined?e:this._items.length;var n=this._createItem(this._container,t,e);this._items.push(n);this._sizeChanged=true;this._map.fire("contextmenu.additem",{contextmenu:this,el:n.el,index:e});return n.el},removeItem:function(t){var e=this._container;if(!isNaN(t)){t=e.children[t]}if(t){this._removeItem(d.Util.stamp(t));this._sizeChanged=true;this._map.fire("contextmenu.removeitem",{contextmenu:this,el:t});return t}return null},removeAllItems:function(){var t=this._container.children,e;while(t.length){e=t[0];this._removeItem(d.Util.stamp(e))}return t},hideAllItems:function(){var t,e,n;for(e=0,n=this._items.length;ee.x){n.style.left="auto";n.style.right=Math.min(Math.max(e.x-t.x,0),e.x-i.x-1)+"px"}else{n.style.left=Math.max(t.x,0)+"px";n.style.right="auto"}if(t.y+i.y>e.y){n.style.top="auto";n.style.bottom=Math.min(Math.max(e.y-t.y,0),e.y-i.y-1)+"px"}else{n.style.top=Math.max(t.y,0)+"px";n.style.bottom="auto"}},_getElementSize:function(t){var e=this._size,n=t.style.display;if(!e||this._sizeChanged){e={};t.style.left="-999999px";t.style.right="auto";t.style.display="block";e.x=t.offsetWidth;e.y=t.offsetHeight;t.style.left="auto";t.style.display=n;this._sizeChanged=false}return e},_onKeyDown:function(t){var e=t.keyCode;if(e===27){this._hide()}},_onItemMouseOver:function(t){},_onItemMouseOut:function(t){}});d.Map.addInitHook("addHandler","contextmenu",d.Map.ContextMenu);d.Mixin.ContextMenu={bindContextMenu:function(t){d.setOptions(this,t);this._initContextMenu();return this},unbindContextMenu:function(){this.off("contextmenu",this._showContextMenu,this);return this},addContextMenuItem:function(t){this.options.contextmenuItems.push(t)},removeContextMenuItemWithIndex:function(t){var e=[];for(var n=0;ne.x){n.style.left="auto";n.style.right=Math.min(Math.max(e.x-t.x,0),e.x-i.x-1)+"px"}else{n.style.left=Math.max(t.x,0)+"px";n.style.right="auto"}if(t.y+i.y>e.y){n.style.top="auto";n.style.bottom=Math.min(Math.max(e.y-t.y,0),e.y-i.y-1)+"px"}else{n.style.top=Math.max(t.y,0)+"px";n.style.bottom="auto"}},_getElementSize:function(t){var e=this._size,n=t.style.display;if(!e||this._sizeChanged){e={};t.style.left="-999999px";t.style.right="auto";t.style.display="block";e.x=t.offsetWidth;e.y=t.offsetHeight;t.style.left="auto";t.style.display=n;this._sizeChanged=false}return e},_onKeyDown:function(t){var e=t.keyCode;if(e===27){this._hide()}},_onItemMouseOver:function(t){},_onItemMouseOut:function(t){}});f.Map.addInitHook("addHandler","contextmenu",f.Map.ContextMenu);f.Mixin.ContextMenu={bindContextMenu:function(t){f.setOptions(this,t);this._initContextMenu();return this},unbindContextMenu:function(){this.off("contextmenu",this._showContextMenu,this);return this},addContextMenuItem:function(t){this.options.contextmenuItems.push(t)},removeContextMenuItemWithIndex:function(t){var e=[];for(var n=0;n