Merge branch 'contextmenu'
authorTom Hughes <tom@compton.nu>
Sat, 18 Feb 2017 16:20:20 +0000 (16:20 +0000)
committerTom Hughes <tom@compton.nu>
Sat, 18 Feb 2017 16:20:20 +0000 (16:20 +0000)
Vendorfile
app/assets/javascripts/index.js
app/assets/javascripts/index/contextmenu.js [new file with mode: 0644]
app/assets/javascripts/index/new_note.js
app/assets/stylesheets/leaflet-all.scss
config/locales/en.yml
vendor/assets/leaflet/leaflet.contextmenu.css [new file with mode: 0644]
vendor/assets/leaflet/leaflet.contextmenu.js [new file with mode: 0644]

index 361f2c84361173bc6948a7c3e661a6a10e44bc8b..304be9e15de2ae9d92a8d237cb7281e36e3a0068 100644 (file)
@@ -20,6 +20,11 @@ folder 'vendor/assets' do
       file "images/#{image}", "https://unpkg.com/leaflet@1.0.3/dist/images/#{image}"
     end
 
+    from 'git://github.com/aratcliffe/Leaflet.contextmenu.git', :tag => 'v1.2.1' do
+      file 'leaflet.contextmenu.js', 'dist/leaflet.contextmenu.js'
+      file 'leaflet.contextmenu.css', 'dist/leaflet.contextmenu.css'
+    end
+
     from 'git://github.com/kajic/leaflet-locationfilter.git' do
       file 'leaflet.locationfilter.css', 'src/locationfilter.css'
       file 'leaflet.locationfilter.js', 'src/locationfilter.js'
index 9d7122e4da70013021b29f7801a5b5620c496a7e..1ba2fbbdef5cc719b5799813aa03bce0cfd50870 100644 (file)
@@ -7,6 +7,8 @@
 //= require leaflet.share
 //= require leaflet.polyline
 //= require leaflet.query
+//= require leaflet.contextmenu
+//= require index/contextmenu
 //= require index/search
 //= require index/browse
 //= require index/export
@@ -77,7 +79,9 @@ $(document).ready(function () {
 
   var map = new L.OSM.Map("map", {
     zoomControl: false,
-    layerControl: false
+    layerControl: false,
+    contextmenu: true,
+    contextmenuWidth: 140
   });
 
   map.attributionControl.setPrefix('');
@@ -147,6 +151,8 @@ $(document).ready(function () {
   L.control.scale()
     .addTo(map);
 
+  OSM.initializeContextMenu(map);
+
   if (OSM.STATUS !== 'api_offline' && OSM.STATUS !== 'database_offline') {
     OSM.initializeNotes(map);
     if (params.layers.indexOf(map.noteLayer.options.code) >= 0) {
diff --git a/app/assets/javascripts/index/contextmenu.js b/app/assets/javascripts/index/contextmenu.js
new file mode 100644 (file)
index 0000000..1e7251e
--- /dev/null
@@ -0,0 +1,86 @@
+OSM.initializeContextMenu = function (map) {
+  map.contextmenu.addItem({
+    text: I18n.t("javascripts.context.directions_from"),
+    callback: function directionsFromHere(e) {
+      var precision = OSM.zoomPrecision(map.getZoom()),
+          latlng = e.latlng.wrap(),
+          lat = latlng.lat.toFixed(precision),
+          lng = latlng.lng.toFixed(precision);
+
+      OSM.router.route("/directions?" + querystring.stringify({
+        route: lat + "," + lng + ";" + $("#route_to").val()
+      }));
+    }
+  });
+
+  map.contextmenu.addItem({
+    text: I18n.t("javascripts.context.directions_to"),
+    callback: function directionsToHere(e) {
+      var precision = OSM.zoomPrecision(map.getZoom()),
+          latlng = e.latlng.wrap(),
+          lat = latlng.lat.toFixed(precision),
+          lng = latlng.lng.toFixed(precision);
+
+      OSM.router.route("/directions?" + querystring.stringify({
+        route: $("#route_from").val() + ";" + lat + "," + lng
+      }));
+    }
+  });
+
+  map.contextmenu.addItem({
+    text: I18n.t("javascripts.context.add_note"),
+    callback: function addNoteHere(e) {
+      var precision = OSM.zoomPrecision(map.getZoom()),
+          latlng = e.latlng.wrap(),
+          lat = latlng.lat.toFixed(precision),
+          lng = latlng.lng.toFixed(precision);
+
+      OSM.router.route("/note/new?lat=" + lat + "&lon=" + lng);
+    }
+  });
+
+  map.contextmenu.addItem({
+    text: I18n.t("javascripts.context.show_address"),
+    callback: function describeLocation(e) {
+      var precision = OSM.zoomPrecision(map.getZoom()),
+          latlng = e.latlng.wrap(),
+          lat = latlng.lat.toFixed(precision),
+          lng = latlng.lng.toFixed(precision);
+
+      OSM.router.route("/search?query=" + encodeURIComponent(lat + "," + lng));
+    }
+  });
+
+  map.contextmenu.addItem({
+    text: I18n.t("javascripts.context.query_features"),
+    callback: function queryFeatures(e) {
+      var precision = OSM.zoomPrecision(map.getZoom()),
+          latlng = e.latlng.wrap(),
+          lat = latlng.lat.toFixed(precision),
+          lng = latlng.lng.toFixed(precision);
+
+      OSM.router.route("/query?lat=" + lat + "&lon=" + lng);
+    }
+  });
+
+  map.contextmenu.addItem({
+    text: I18n.t("javascripts.context.centre_map"),
+    callback: function centreMap(e) {
+      map.panTo(e.latlng);
+    }
+  });
+
+  map.on("mousedown", function (e) {
+    if (e.shiftKey) map.contextmenu.disable();
+  }).on("mouseup", function () {
+    map.contextmenu.enable();
+  });
+
+  var updateMenu = function updateMenu () {
+    map.contextmenu.setDisabled(2, map.getZoom() < 12);
+    map.contextmenu.setDisabled(4, map.getZoom() < 14);
+  };
+
+  map.on("zoomend", updateMenu);
+  updateMenu();
+};
index 397daa637f4f7c2397b584a45339cda5a15d6231..53697e65b584d20686d3ee9bf3d200878fb9fac2 100644 (file)
@@ -77,7 +77,9 @@ OSM.NewNote = function(map) {
   }
 
   page.pushstate = page.popstate = function (path) {
-    OSM.loadSidebarContent(path, page.load);
+    OSM.loadSidebarContent(path, function () {
+      page.load(path);
+    });
   };
 
   function newHalo(loc, a) {
@@ -97,7 +99,7 @@ OSM.NewNote = function(map) {
     }
   }
 
-  page.load = function () {
+  page.load = function (path) {
     if (addNoteButton.hasClass("disabled")) return;
     if (addNoteButton.hasClass("active")) return;
 
@@ -105,12 +107,34 @@ OSM.NewNote = function(map) {
 
     map.addLayer(noteLayer);
 
-    var mapSize = map.getSize();
-    var markerPosition;
+    var params = querystring.parse(path.substring(path.indexOf('?') + 1));
+    var markerLatlng;
+
+    if (params.lat && params.lon) {
+      markerLatlng = L.latLng(params.lat, params.lon);
+
+      var markerPosition = map.latLngToContainerPoint(markerLatlng),
+          mapSize = map.getSize(),
+          panBy = L.point(0, 0);
+
+      if (markerPosition.x < 50) {
+        panBy.x = markerPosition.x - 50;
+      } else if (markerPosition.x > mapSize.x - 50) {
+        panBy.x = 50 - mapSize.x + markerPosition.x;
+      }
 
-    markerPosition = [mapSize.x / 2, mapSize.y / 2];
+      if (markerPosition.y < 50) {
+        panBy.y = markerPosition.y - 50;
+      } else if (markerPosition.y > mapSize.y - 50) {
+        panBy.y = 50 - mapSize.y + markerPosition.y;
+      }
+
+      map.panBy(panBy);
+    } else {
+      markerLatlng = map.getCenter();
+    }
 
-    newNote = L.marker(map.containerPointToLatLng(markerPosition), {
+    newNote = L.marker(markerLatlng, {
       icon: noteIcons["new"],
       opacity: 0.9,
       draggable: true
index 10ad2607a5a4c5bbbc56b6df50892edff384ac54..82312e5c2312ae3bc174e0606a6f3f10c2f555d9 100644 (file)
@@ -1,6 +1,7 @@
 /*
  *= require leaflet
  *= require leaflet.locationfilter
+ *= require leaflet.contextmenu
  */
 
 /* Override to serve images through the asset pipeline. */
index 1867a433f2dfc9ba70febed84d94996c6b592523..b2ebffd61a273f7d4d329886ca0c07b8b6a34272 100644 (file)
@@ -2305,6 +2305,13 @@ en:
       nothing_found: No features found
       error: "Error contacting %{server}: %{error}"
       timeout: "Timeout contacting %{server}"
+    context:
+      directions_from: Directions from here
+      directions_to: Directions to here
+      add_note: Add a note here
+      show_address: Show address
+      query_features: Query features
+      centre_map: Centre map here
   redaction:
     edit:
       description: "Description"
diff --git a/vendor/assets/leaflet/leaflet.contextmenu.css b/vendor/assets/leaflet/leaflet.contextmenu.css
new file mode 100644 (file)
index 0000000..0b5e2de
--- /dev/null
@@ -0,0 +1,54 @@
+.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/vendor/assets/leaflet/leaflet.contextmenu.js b/vendor/assets/leaflet/leaflet.contextmenu.js
new file mode 100644 (file)
index 0000000..a9b011d
--- /dev/null
@@ -0,0 +1,580 @@
+/*
+       Leaflet.contextmenu, a context menu for Leaflet.
+       (c) 2015, Adam Ratcliffe, GeoSmart Maps Limited
+
+       @preserve
+*/
+
+(function(factory) {
+       // Packaging/modules magic dance
+       var L;
+       if (typeof define === 'function' && define.amd) {
+               // AMD
+               define(['leaflet'], factory);
+       } else if (typeof module === 'object' && typeof module.exports === 'object') {
+               // Node/CommonJS
+               L = require('leaflet');
+               module.exports = factory(L);
+       } else {
+               // Browser globals
+               if (typeof window.L === 'undefined') {
+                       throw new Error('Leaflet must be loaded first');
+               }
+               factory(window.L);
+       }
+})(function(L) {
+L.Map.mergeOptions({
+    contextmenuItems: []
+});
+
+L.Map.ContextMenu = L.Handler.extend({
+    _touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart',
+    
+    statics: {
+        BASE_CLS: 'leaflet-contextmenu'
+    },
+    
+    initialize: function (map) {
+        L.Handler.prototype.initialize.call(this, map);
+        
+        this._items = [];
+        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';
+
+        if (map.options.contextmenuWidth) {
+            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);
+    },
+
+    addHooks: function () {
+        var container = this._map.getContainer();
+
+        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,
+            movestart: 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);
+
+        if (L.Browser.touch) {
+            L.DomEvent.off(document, this._touchstart, this._hide, this);
+        }
+
+        this._map.off({
+            contextmenu: this._show,
+            mousedown: this._hide,
+            movestart: this._hide,
+            zoomstart: this._hide
+        }, this);
+    },
+
+    showAt: function (point, data) {
+        if (point instanceof L.LatLng) {
+            point = this._map.latLngToContainerPoint(point);
+        }
+        this._showAtPoint(point, data);
+    },
+
+    hide: function () {
+        this._hide();
+    },
+
+    addItem: function (options) {
+        return this.insertItem(options);
+    },
+
+    insertItem: function (options, index) {
+        index = index !== undefined ? index: this._items.length;
+
+        var item = this._createItem(this._container, options, index);
+
+        this._items.push(item);
+
+        this._sizeChanged = true;
+
+        this._map.fire('contextmenu.additem', {
+            contextmenu: this,
+            el: item.el,
+            index: index
+        });
+
+        return item.el;
+    },
+
+    removeItem: function (item) {
+        var container = this._container;
+
+        if (!isNaN(item)) {
+            item = container.children[item];
+        }
+
+        if (item) {
+            this._removeItem(L.Util.stamp(item));
+
+            this._sizeChanged = true;
+
+            this._map.fire('contextmenu.removeitem', {
+                contextmenu: this,
+                el: item
+            });
+        }
+    },
+
+    removeAllItems: function () {
+        var item;
+
+        while (this._container.children.length) {
+            item = this._container.children[0];
+            this._removeItem(L.Util.stamp(item));
+        }
+    },
+
+    hideAllItems: function () {
+        var item, i, l;
+
+        for (i = 0, l = this._items.length; i < l; i++) {
+            item = this._items[i];
+            item.el.style.display = 'none';
+        }
+    },
+
+    showAllItems: function () {
+        var item, i, l;
+
+        for (i = 0, l = this._items.length; i < l; i++) {
+            item = this._items[i];
+            item.el.style.display = '';
+        }
+    },
+
+    setDisabled: function (item, disabled) {
+        var container = this._container,
+        itemCls = L.Map.ContextMenu.BASE_CLS + '-item';
+
+        if (!isNaN(item)) {
+            item = container.children[item];
+        }
+
+        if (item && L.DomUtil.hasClass(item, itemCls)) {
+            if (disabled) {
+                L.DomUtil.addClass(item, itemCls + '-disabled');
+                this._map.fire('contextmenu.disableitem', {
+                    contextmenu: this,
+                    el: item
+                });
+            } else {
+                L.DomUtil.removeClass(item, itemCls + '-disabled');
+                this._map.fire('contextmenu.enableitem', {
+                    contextmenu: this,
+                    el: item
+                });
+            }
+        }
+    },
+
+    isVisible: function () {
+        return this._visible;
+    },
+
+    _createItems: function () {
+        var itemOptions = this._map.options.contextmenuItems,
+            item,
+            i, l;
+
+        for (i = 0, l = itemOptions.length; i < l; i++) {
+            this._items.push(this._createItem(this._container, itemOptions[i]));
+        }
+    },
+
+    _createItem: function (container, options, index) {
+        if (options.separator || options === '-') {
+            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),
+            callback = this._createEventHandler(el, options.callback, options.context, options.hideOnSelect),
+            icon = this._getIcon(options),
+            iconCls = this._getIconCls(options),
+            html = '';
+
+        if (icon) {
+            html = '<img class="' + L.Map.ContextMenu.BASE_CLS + '-icon" src="' + icon + '"/>';
+        } else if (iconCls) {
+            html = '<span class="' + L.Map.ContextMenu.BASE_CLS + '-icon ' + iconCls + '"></span>';
+        }
+
+        el.innerHTML = html + options.text;
+        el.href = '#';
+
+        L.DomEvent
+            .on(el, 'mouseover', this._onItemMouseOver, this)
+            .on(el, 'mouseout', this._onItemMouseOut, this)
+            .on(el, 'mousedown', L.DomEvent.stopPropagation)
+            .on(el, 'click', callback);
+
+        if (L.Browser.touch) {
+            L.DomEvent.on(el, this._touchstart, L.DomEvent.stopPropagation);
+        }
+
+        // Devices without a mouse fire "mouseover" on tap, but never â€śmouseout"
+        if (!L.Browser.pointer) {
+            L.DomEvent.on(el, 'click', this._onItemMouseOut, this);
+        }
+
+        return {
+            id: L.Util.stamp(el),
+            el: el,
+            callback: callback
+        };
+    },
+
+    _removeItem: function (id) {
+        var item,
+            el,
+            i, l, callback;
+
+        for (i = 0, l = this._items.length; i < l; i++) {
+            item = this._items[i];
+
+            if (item.id === id) {
+                el = item.el;
+                callback = item.callback;
+
+                if (callback) {
+                    L.DomEvent
+                        .off(el, 'mouseover', this._onItemMouseOver, this)
+                        .off(el, 'mouseover', this._onItemMouseOut, this)
+                        .off(el, 'mousedown', L.DomEvent.stopPropagation)
+                        .off(el, 'click', callback);
+
+                    if (L.Browser.touch) {
+                        L.DomEvent.off(el, this._touchstart, L.DomEvent.stopPropagation);
+                    }
+
+                    if (!L.Browser.pointer) {
+                        L.DomEvent.on(el, 'click', this._onItemMouseOut, this);
+                    }
+                }
+
+                this._container.removeChild(el);
+                this._items.splice(i, 1);
+
+                return item;
+            }
+        }
+        return null;
+    },
+
+    _createSeparator: function (container, index) {
+        var el = this._insertElementAt('div', L.Map.ContextMenu.BASE_CLS + '-separator', container, index);
+
+        return {
+            id: L.Util.stamp(el),
+            el: el
+        };
+    },
+
+    _createEventHandler: function (el, func, context, hideOnSelect) {
+        var me = this,
+            map = this._map,
+            disabledCls = L.Map.ContextMenu.BASE_CLS + '-item-disabled',
+            hideOnSelect = (hideOnSelect !== undefined) ? hideOnSelect : true;
+
+        return function (e) {
+            if (L.DomUtil.hasClass(el, disabledCls)) {
+                return;
+            }
+
+            if (hideOnSelect) {
+                me._hide();
+            }
+
+            if (func) {
+                func.call(context || map, me._showLocation);
+            }
+
+            me._map.fire('contextmenu:select', {
+                contextmenu: me,
+                el: el
+            });
+        };
+    },
+
+    _insertElementAt: function (tagName, className, container, index) {
+        var refEl,
+            el = document.createElement(tagName);
+
+        el.className = className;
+
+        if (index !== undefined) {
+            refEl = container.children[index];
+        }
+
+        if (refEl) {
+            container.insertBefore(el, refEl);
+        } else {
+            container.appendChild(el);
+        }
+
+        return el;
+    },
+
+    _show: function (e) {
+        this._showAtPoint(e.containerPoint, e);
+    },
+
+    _showAtPoint: function (pt, data) {
+        if (this._items.length) {
+            var map = this._map,
+            layerPoint = map.containerPointToLayerPoint(pt),
+            latlng = map.layerPointToLatLng(layerPoint),
+            event = L.extend(data || {}, {contextmenu: this});
+
+            this._showLocation = {
+                latlng: latlng,
+                layerPoint: layerPoint,
+                containerPoint: pt
+            };
+
+            if (data && data.relatedTarget){
+                this._showLocation.relatedTarget = data.relatedTarget;
+            }
+
+            this._setPosition(pt);
+
+            if (!this._visible) {
+                this._container.style.display = 'block';
+                this._visible = true;
+            }
+
+            this._map.fire('contextmenu.show', event);
+        }
+    },
+
+    _hide: function () {
+        if (this._visible) {
+            this._visible = false;
+            this._container.style.display = 'none';
+            this._map.fire('contextmenu.hide', {contextmenu: this});
+        }
+    },
+
+    _getIcon: function (options) {
+        return L.Browser.retina && options.retinaIcon || options.icon;
+    },
+
+    _getIconCls: function (options) {
+        return L.Browser.retina && options.retinaIconCls || options.iconCls;
+    },
+
+    _setPosition: function (pt) {
+        var mapSize = this._map.getSize(),
+            container = this._container,
+            containerSize = this._getElementSize(container),
+            anchor;
+
+        if (this._map.options.contextmenuAnchor) {
+            anchor = L.point(this._map.options.contextmenuAnchor);
+            pt = pt.add(anchor);
+        }
+
+        container._leaflet_pos = pt;
+
+        if (pt.x + containerSize.x > mapSize.x) {
+            container.style.left = 'auto';
+            container.style.right = Math.min(Math.max(mapSize.x - pt.x, 0), mapSize.x - containerSize.x - 1) + 'px';
+        } else {
+            container.style.left = Math.max(pt.x, 0) + 'px';
+            container.style.right = 'auto';
+        }
+
+        if (pt.y + containerSize.y > mapSize.y) {
+            container.style.top = 'auto';
+            container.style.bottom = Math.min(Math.max(mapSize.y - pt.y, 0), mapSize.y - containerSize.y - 1) + 'px';
+        } else {
+            container.style.top = Math.max(pt.y, 0) + 'px';
+            container.style.bottom = 'auto';
+        }
+    },
+
+    _getElementSize: function (el) {
+        var size = this._size,
+            initialDisplay = el.style.display;
+
+        if (!size || this._sizeChanged) {
+            size = {};
+
+            el.style.left = '-999999px';
+            el.style.right = 'auto';
+            el.style.display = 'block';
+
+            size.x = el.offsetWidth;
+            size.y = el.offsetHeight;
+
+            el.style.left = 'auto';
+            el.style.display = initialDisplay;
+
+            this._sizeChanged = false;
+        }
+
+        return size;
+    },
+
+    _onKeyDown: function (e) {
+        var key = e.keyCode;
+
+        // If ESC pressed and context menu is visible hide it
+        if (key === 27) {
+            this._hide();
+        }
+    },
+
+    _onItemMouseOver: function (e) {
+        L.DomUtil.addClass(e.target || e.srcElement, 'over');
+    },
+
+    _onItemMouseOut: function (e) {
+        L.DomUtil.removeClass(e.target || e.srcElement, 'over');
+    }
+});
+
+L.Map.addInitHook('addHandler', 'contextmenu', L.Map.ContextMenu);
+L.Mixin.ContextMenu = {
+    bindContextMenu: function (options) {
+        L.setOptions(this, options);
+        this._initContextMenu();
+
+        return this;
+    },
+
+    unbindContextMenu: function (){
+        this.off('contextmenu', this._showContextMenu, this);
+
+        return this;
+    },
+
+    addContextMenuItem: function (item) {
+            this.options.contextmenuItems.push(item);
+    },
+
+    removeContextMenuItemWithIndex: function (index) {
+        var items = [];
+        for (var i = 0; i < this.options.contextmenuItems.length; i++) {
+            if (this.options.contextmenuItems[i].index == index){
+                items.push(i);
+            }
+        }
+        var elem = items.pop();
+        while (elem !== undefined) {
+            this.options.contextmenuItems.splice(elem,1);
+            elem = items.pop();
+        }
+    },
+
+    replaceContextMenuItem: function (item) {
+        this.removeContextMenuItemWithIndex(item.index);
+        this.addContextMenuItem(item);
+    },
+
+    _initContextMenu: function () {
+        this._items = [];
+
+        this.on('contextmenu', this._showContextMenu, this);
+    },
+
+    _showContextMenu: function (e) {
+        var itemOptions,
+            data, pt, i, l;
+
+        if (this._map.contextmenu) {
+            data = L.extend({relatedTarget: this}, e);
+
+            pt = this._map.mouseEventToContainerPoint(e.originalEvent);
+
+            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._map.once('contextmenu.hide', this._hideContextMenu, this);
+
+            this._map.contextmenu.showAt(pt, data);
+        }
+    },
+
+    _hideContextMenu: function () {
+        var i, l;
+
+        for (i = 0, l = this._items.length; i < l; i++) {
+            this._map.contextmenu.removeItem(this._items[i]);
+        }
+        this._items.length = 0;
+
+        if (!this.options.contextmenuInheritItems) {
+            this._map.contextmenu.showAllItems();
+        }
+    }
+};
+
+var classes = [L.Marker, L.Path],
+    defaultOptions = {
+        contextmenu: false,
+        contextmenuItems: [],
+        contextmenuInheritItems: true
+    },
+    cls, i, l;
+
+for (i = 0, l = classes.length; i < l; i++) {
+    cls = classes[i];
+
+    // L.Class should probably provide an empty options hash, as it does not test
+    // for it here and add if needed
+    if (!cls.prototype.options) {
+        cls.prototype.options = defaultOptions;
+    } else {
+        cls.mergeOptions(defaultOptions);
+    }
+
+    cls.addInitHook(function () {
+        if (this.options.contextmenu) {
+            this._initContextMenu();
+        }
+    });
+
+    cls.include(L.Mixin.ContextMenu);
+}
+return L.Map.ContextMenu;
+});