leaflet.contextMenu.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. /*
  2. Leaflet.contextmenu, a context menu for Leaflet.
  3. (c) 2015, Adam Ratcliffe, GeoSmart Maps Limited
  4. @preserve
  5. */
  6. /*(function(factory) {
  7. // Packaging/modules magic dance
  8. var L;
  9. if (typeof define === 'function' && define.amd) {
  10. // AMD
  11. define(['leaflet'], factory);
  12. } else if (typeof module !== 'undefined') {
  13. // Node/CommonJS
  14. L = require('leaflet');
  15. module.exports = factory(L);
  16. } else {
  17. // Browser globals
  18. if (typeof window.L === 'undefined') {
  19. throw new Error('Leaflet must be loaded first');
  20. }
  21. factory(window.L);
  22. }
  23. })*///(function(L) {
  24. L.Map.mergeOptions({
  25. contextmenuItems: []
  26. });
  27. L.Map.ContextMenu = L.Handler.extend({
  28. _touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart',
  29. statics: {
  30. BASE_CLS: 'leaflet-contextmenu'
  31. },
  32. runCallFunc:[],
  33. initialize: function (map) {
  34. L.Handler.prototype.initialize.call(this, map);
  35. this._items = [];
  36. this._visible = false;
  37. var container = this._container = L.DomUtil.create('div', L.Map.ContextMenu.BASE_CLS, map._container);
  38. container.style.zIndex = 10000;
  39. container.style.position = 'absolute';
  40. if (map.options.contextmenuWidth) {
  41. container.style.width = map.options.contextmenuWidth + 'px';
  42. }
  43. this._createItems();
  44. L.DomEvent
  45. .on(container, 'click', L.DomEvent.stop)
  46. .on(container, 'mousedown', L.DomEvent.stop)
  47. .on(container, 'dblclick', L.DomEvent.stop)
  48. .on(container, 'contextmenu', L.DomEvent.stop);
  49. },
  50. addHooks: function () {
  51. var container = this._map.getContainer();
  52. L.DomEvent
  53. .on(container, 'mouseleave', this._hide, this)
  54. .on(document, 'keydown', this._onKeyDown, this);
  55. if (L.Browser.touch) {
  56. L.DomEvent.on(document, this._touchstart, this._hide, this);
  57. }
  58. this._map.on({
  59. contextmenu: this._show,
  60. mousedown: this._hide,
  61. movestart: this._hide,
  62. zoomstart: this._hide
  63. }, this);
  64. },
  65. removeHooks: function () {
  66. var container = this._map.getContainer();
  67. L.DomEvent
  68. .off(container, 'mouseleave', this._hide, this)
  69. .off(document, 'keydown', this._onKeyDown, this);
  70. if (L.Browser.touch) {
  71. L.DomEvent.off(document, this._touchstart, this._hide, this);
  72. }
  73. this._map.off({
  74. contextmenu: this._show,
  75. mousedown: this._hide,
  76. movestart: this._hide,
  77. zoomstart: this._hide
  78. }, this);
  79. },
  80. showAt: function (point, data) {
  81. if (point instanceof L.LatLng) {
  82. point = this._map.latLngToContainerPoint(point);
  83. }
  84. this._showAtPoint(point, data);
  85. },
  86. hide: function () {
  87. this._hide();
  88. },
  89. addItem: function (options) {
  90. return this.insertItem(options);
  91. },
  92. insertItem: function (options, index) {
  93. index = index !== undefined ? index: this._items.length;
  94. var item = this._createItem(this._container, options, index);
  95. this._items.push(item);
  96. this._sizeChanged = true;
  97. this._map.fire('contextmenu.additem', {
  98. contextmenu: this,
  99. el: item.el,
  100. index: index
  101. });
  102. return item.el;
  103. },
  104. removeItem: function (item) {
  105. var container = this._container;
  106. if (!isNaN(item)) {
  107. item = container.children[item];
  108. }
  109. if (item) {
  110. this._removeItem(L.Util.stamp(item));
  111. this._sizeChanged = true;
  112. this._map.fire('contextmenu.removeitem', {
  113. contextmenu: this,
  114. el: item
  115. });
  116. }
  117. },
  118. removeAllItems: function () {
  119. var item;
  120. while (this._container.children.length) {
  121. item = this._container.children[0];
  122. this._removeItem(L.Util.stamp(item));
  123. }
  124. },
  125. hideAllItems: function () {
  126. var item, i, l;
  127. for (i = 0, l = this._items.length; i < l; i++) {
  128. item = this._items[i];
  129. item.el.style.display = 'none';
  130. }
  131. },
  132. showAllItems: function () {
  133. var item, i, l;
  134. for (i = 0, l = this._items.length; i < l; i++) {
  135. item = this._items[i];
  136. item.el.style.display = '';
  137. }
  138. },
  139. setDisabled: function (item, disabled) {
  140. var container = this._container,
  141. itemCls = L.Map.ContextMenu.BASE_CLS + '-item';
  142. if (!isNaN(item)) {
  143. item = container.children[item];
  144. }
  145. if (item && L.DomUtil.hasClass(item, itemCls)) {
  146. if (disabled) {
  147. L.DomUtil.addClass(item, itemCls + '-disabled');
  148. this._map.fire('contextmenu.disableitem', {
  149. contextmenu: this,
  150. el: item
  151. });
  152. } else {
  153. L.DomUtil.removeClass(item, itemCls + '-disabled');
  154. this._map.fire('contextmenu.enableitem', {
  155. contextmenu: this,
  156. el: item
  157. });
  158. }
  159. }
  160. },
  161. isVisible: function () {
  162. return this._visible;
  163. },
  164. _createItems: function () {
  165. var itemOptions = this._map.options.contextmenuItems,
  166. item,
  167. i, l;
  168. for (i = 0, l = itemOptions.length; i < l; i++) {
  169. this._items.push(this._createItem(this._container, itemOptions[i]));
  170. }
  171. },
  172. _createItem: function (container, options, index) {
  173. if (options.separator || options === '-') {
  174. return this._createSeparator(container, index);
  175. }
  176. var itemCls = L.Map.ContextMenu.BASE_CLS + '-item',
  177. cls = options.disabled ? (itemCls + ' ' + itemCls + '-disabled') : itemCls;
  178. if(options.hasOwnProperty('className')){
  179. cls = cls + ' ' + options.className;
  180. }
  181. var el = this._insertElementAt('a', cls, container, index),
  182. callback = this._createEventHandler(el, options.callback, options.context, options.hideOnSelect),
  183. html = '';
  184. if(options.hasOwnProperty('runCall')){
  185. this.runCallFunc.push(options.runCall);
  186. }
  187. if (options.icon) {
  188. html = '<img class="' + L.Map.ContextMenu.BASE_CLS + '-icon" src="' + options.icon + '"/>';
  189. } else if (options.iconCls) {
  190. html = '<span class="' + L.Map.ContextMenu.BASE_CLS + '-icon ' + options.iconCls + '"></span>';
  191. }
  192. if(typeof options.text === 'object'){
  193. el.innerHTML = html;
  194. el.appendChild(options.text);
  195. }else {
  196. el.innerHTML = html + options.text;
  197. }
  198. //el.innerHTML = html + options.text;
  199. el.href = '#';
  200. L.DomEvent
  201. .on(el, 'mouseover', this._onItemMouseOver, this)
  202. .on(el, 'mouseout', this._onItemMouseOut, this)
  203. .on(el, 'mousedown', L.DomEvent.stopPropagation)
  204. .on(el, 'click', callback);
  205. if (L.Browser.touch) {
  206. L.DomEvent.on(el, this._touchstart, L.DomEvent.stopPropagation);
  207. }
  208. return {
  209. id: L.Util.stamp(el),
  210. el: el,
  211. callback: callback
  212. };
  213. },
  214. _removeItem: function (id) {
  215. var item,
  216. el,
  217. i, l, callback;
  218. for (i = 0, l = this._items.length; i < l; i++) {
  219. item = this._items[i];
  220. if (item.id === id) {
  221. el = item.el;
  222. callback = item.callback;
  223. if (callback) {
  224. L.DomEvent
  225. .off(el, 'mouseover', this._onItemMouseOver, this)
  226. .off(el, 'mouseover', this._onItemMouseOut, this)
  227. .off(el, 'mousedown', L.DomEvent.stopPropagation)
  228. .off(el, 'click', callback);
  229. if (L.Browser.touch) {
  230. L.DomEvent.off(el, this._touchstart, L.DomEvent.stopPropagation);
  231. }
  232. }
  233. this._container.removeChild(el);
  234. this._items.splice(i, 1);
  235. return item;
  236. }
  237. }
  238. return null;
  239. },
  240. _createSeparator: function (container, index) {
  241. var el = this._insertElementAt('div', L.Map.ContextMenu.BASE_CLS + '-separator', container, index);
  242. return {
  243. id: L.Util.stamp(el),
  244. el: el
  245. };
  246. },
  247. _createEventHandler: function (el, func, context, hideOnSelect) {
  248. var me = this,
  249. map = this._map,
  250. disabledCls = L.Map.ContextMenu.BASE_CLS + '-item-disabled',
  251. hideOnSelect = (hideOnSelect !== undefined) ? hideOnSelect : true;
  252. return function (e) {
  253. if (L.DomUtil.hasClass(el, disabledCls)) {
  254. return;
  255. }
  256. if (hideOnSelect) {
  257. me._hide();
  258. }
  259. if (func) {
  260. func.call(context || map, me._showLocation);
  261. }
  262. me._map.fire('contextmenu:select', {
  263. contextmenu: me,
  264. el: el
  265. });
  266. };
  267. },
  268. _insertElementAt: function (tagName, className, container, index) {
  269. var refEl,
  270. el = document.createElement(tagName);
  271. el.className = className;
  272. if (index !== undefined) {
  273. refEl = container.children[index];
  274. }
  275. if (refEl) {
  276. container.insertBefore(el, refEl);
  277. } else {
  278. container.appendChild(el);
  279. }
  280. return el;
  281. },
  282. _show: function (e) {
  283. var _this = this;
  284. //this._showAtPoint(e.containerPoint, e);
  285. for(var i = 0, l=this.runCallFunc.length; i<l; i++){
  286. this.runCallFunc[i](e);
  287. }
  288. _this.tempE = e;
  289. setTimeout(function(){
  290. _this._showAtPoint(_this.tempE.containerPoint);
  291. },100);
  292. if(_this.circleMarker){
  293. _this._map.removeLayer(_this.circleMarker);
  294. }
  295. _this.circleMarker = L.circleMarker(e.latlng, { fillColor: "#cb0000",fillOpacity:1,weight:1, color:"#fff", radius: 3, opacity:1 }).addTo(this._map);
  296. },
  297. _showAtPoint: function (pt, data) {
  298. if (this._items.length) {
  299. var map = this._map,
  300. layerPoint = map.containerPointToLayerPoint(pt),
  301. latlng = map.layerPointToLatLng(layerPoint),
  302. event = L.extend(data || {}, {contextmenu: this});
  303. this._showLocation = {
  304. latlng: latlng,
  305. layerPoint: layerPoint,
  306. containerPoint: pt
  307. };
  308. if(data && data.relatedTarget){
  309. this._showLocation.relatedTarget = data.relatedTarget;
  310. }
  311. this._setPosition(pt);
  312. if (!this._visible) {
  313. this._container.style.display = 'block';
  314. this._visible = true;
  315. } else {
  316. this._setPosition(pt);
  317. }
  318. this._map.fire('contextmenu.show', event);
  319. }
  320. },
  321. _hide: function () {
  322. if (this._visible) {
  323. this._visible = false;
  324. this._container.style.display = 'none';
  325. this._map.fire('contextmenu.hide', {contextmenu: this});
  326. this._map.removeLayer(this.circleMarker);
  327. }
  328. },
  329. _setPosition: function (pt) {
  330. var mapSize = this._map.getSize(),
  331. container = this._container,
  332. containerSize = this._getElementSize(container),
  333. anchor;
  334. if (this._map.options.contextmenuAnchor) {
  335. anchor = L.point(this._map.options.contextmenuAnchor);
  336. pt = pt.add(anchor);
  337. }
  338. container._leaflet_pos = pt;
  339. if (pt.x + containerSize.x > mapSize.x) {
  340. container.style.left = 'auto';
  341. container.style.right = Math.max(mapSize.x - pt.x, 0) + 'px';
  342. } else {
  343. container.style.left = Math.max(pt.x, 0) + 'px';
  344. container.style.right = 'auto';
  345. }
  346. if (pt.y + containerSize.y > mapSize.y) {
  347. container.style.top = 'auto';
  348. container.style.bottom = Math.max(mapSize.y - pt.y, 0) + 'px';
  349. } else {
  350. container.style.top = Math.max(pt.y, 0) + 'px';
  351. container.style.bottom = 'auto';
  352. }
  353. },
  354. _getElementSize: function (el) {
  355. var size = this._size,
  356. initialDisplay = el.style.display;
  357. if (!size || this._sizeChanged) {
  358. size = {};
  359. el.style.left = '-999999px';
  360. el.style.right = 'auto';
  361. el.style.display = 'block';
  362. size.x = el.offsetWidth;
  363. size.y = el.offsetHeight;
  364. el.style.left = 'auto';
  365. el.style.display = initialDisplay;
  366. this._sizeChanged = false;
  367. }
  368. return size;
  369. },
  370. _onKeyDown: function (e) {
  371. var key = e.keyCode;
  372. // If ESC pressed and context menu is visible hide it
  373. if (key === 27) {
  374. this._hide();
  375. }
  376. },
  377. _onItemMouseOver: function (e) {
  378. L.DomUtil.addClass(e.target || e.srcElement, 'over');
  379. },
  380. _onItemMouseOut: function (e) {
  381. L.DomUtil.removeClass(e.target || e.srcElement, 'over');
  382. }
  383. });
  384. L.Map.addInitHook('addHandler', 'contextmenu', L.Map.ContextMenu);
  385. L.Mixin.ContextMenu = {
  386. bindContextMenu: function (options) {
  387. L.setOptions(this, options);
  388. this._initContextMenu();
  389. return this;
  390. },
  391. unbindContextMenu: function (){
  392. this.off('contextmenu', this._showContextMenu, this);
  393. return this;
  394. },
  395. addContextMenuItem: function (item) {
  396. this.options.contextmenuItems.push(item);
  397. },
  398. removeContextMenuItemWithIndex: function (index) {
  399. var items = [];
  400. for (var i = 0; i < this.options.contextmenuItems.length; i++) {
  401. if(this.options.contextmenuItems[i].index == index){
  402. items.push(i);
  403. }
  404. }
  405. var elem = items.pop();
  406. while (elem !== undefined) {
  407. this.options.contextmenuItems.splice(elem,1);
  408. elem = items.pop();
  409. }
  410. },
  411. replaceConextMenuItem: function (item) {
  412. this.removeContextMenuItemWithIndex(item.index);
  413. this.addContextMenuItem(item);
  414. },
  415. _initContextMenu: function () {
  416. this._items = [];
  417. this.on('contextmenu', this._showContextMenu, this);
  418. },
  419. _showContextMenu: function (e) {
  420. var itemOptions,
  421. data, pt, i, l;
  422. if (this._map.contextmenu) {
  423. data = L.extend({relatedTarget: this}, e)
  424. pt = this._map.mouseEventToContainerPoint(e.originalEvent);
  425. if (!this.options.contextmenuInheritItems) {
  426. this._map.contextmenu.hideAllItems();
  427. }
  428. for (i = 0, l = this.options.contextmenuItems.length; i < l; i++) {
  429. itemOptions = this.options.contextmenuItems[i];
  430. this._items.push(this._map.contextmenu.insertItem(itemOptions, itemOptions.index));
  431. }
  432. this._map.once('contextmenu.hide', this._hideContextMenu, this);
  433. this._map.contextmenu.showAt(pt, data);
  434. }
  435. },
  436. _hideContextMenu: function () {
  437. var i, l;
  438. for (i = 0, l = this._items.length; i < l; i++) {
  439. this._map.contextmenu.removeItem(this._items[i]);
  440. }
  441. this._items.length = 0;
  442. if (!this.options.contextmenuInheritItems) {
  443. this._map.contextmenu.showAllItems();
  444. }
  445. }
  446. };
  447. var classes = [L.Marker, L.Path],
  448. defaultOptions = {
  449. contextmenu: false,
  450. contextmenuItems: [],
  451. contextmenuInheritItems: true
  452. },
  453. cls, i, l;
  454. for (i = 0, l = classes.length; i < l; i++) {
  455. cls = classes[i];
  456. // L.Class should probably provide an empty options hash, as it does not test
  457. // for it here and add if needed
  458. if (!cls.prototype.options) {
  459. cls.prototype.options = defaultOptions;
  460. } else {
  461. cls.mergeOptions(defaultOptions);
  462. }
  463. cls.addInitHook(function () {
  464. if (this.options.contextmenu) {
  465. this._initContextMenu();
  466. }
  467. });
  468. cls.include(L.Mixin.ContextMenu);
  469. }
  470. /* return L.Map.ContextMenu;
  471. });*/