1 OSM.initializeContextMenu = function (map) {
2 const $contextMenu = $("#map-context-menu");
3 map.osm_contextmenu = new OSM.ContextMenu(map, $contextMenu);
5 const toggleMenuItem = ($element, enable) => {
6 $element.toggleClass("disabled", !enable)
7 .attr("aria-disabled", enable ? null : "true");
10 const updateContextMenuState = () => {
11 const zoom = map.getZoom();
12 toggleMenuItem($("#menu-action-add-note"), zoom >= 12);
13 toggleMenuItem($("#menu-action-query-features"), zoom >= 14);
16 const getDirectionsCoordinates = ($input) => {
17 const lat = $input.attr("data-lat");
18 const lon = $input.attr("data-lon");
19 if (lat && lon) return `${lat},${lon}`;
23 const latLngFromContext = () =>
24 L.latLng($contextMenu.data("lat"), $contextMenu.data("lng"));
26 const croppedLatLon = () =>
27 OSM.cropLocation(latLngFromContext(), map.getZoom());
29 const routeWithLatLon = (path, extraParams = {}) => {
30 const [lat, lon] = croppedLatLon();
31 OSM.router.route(`${path}?` + new URLSearchParams({ lat, lon, ...extraParams }));
34 const contextmenuItems = [
36 id: "menu-action-directions-from",
38 text: OSM.i18n.t("javascripts.context.directions_from"),
40 const params = new URLSearchParams({
41 from: croppedLatLon().join(","),
42 to: getDirectionsCoordinates($("#route_to"))
44 OSM.router.route(`/directions?${params}`);
48 id: "menu-action-directions-to",
50 text: OSM.i18n.t("javascripts.context.directions_to"),
52 const params = new URLSearchParams({
53 from: getDirectionsCoordinates($("#route_from")),
54 to: croppedLatLon().join(",")
56 OSM.router.route(`/directions?${params}`);
63 id: "menu-action-add-note",
65 text: OSM.i18n.t("javascripts.context.add_note"),
66 callback: () => routeWithLatLon("/note/new")
72 id: "menu-action-show-address",
74 text: OSM.i18n.t("javascripts.context.show_address"),
75 callback: () => routeWithLatLon("/search", { zoom: map.getZoom() })
78 id: "menu-action-query-features",
79 icon: "bi-question-circle",
80 text: OSM.i18n.t("javascripts.context.query_features"),
81 callback: () => routeWithLatLon("/query")
84 id: "menu-action-centre-map",
86 text: OSM.i18n.t("javascripts.context.centre_map"),
87 callback: () => map.panTo(latLngFromContext())
92 map.on("contextmenu", function (e) {
93 map.osm_contextmenu.show(e, contextmenuItems);
94 updateContextMenuState();
97 map.on("show-contextmenu", function (data) {
98 map.osm_contextmenu.show(data.event, data.items);
101 map.on("zoomend", updateContextMenuState);
105 constructor(map, $element) {
107 this._$element = $element;
108 this._popperInstance = null;
110 this._map.on("click movestart", this.hide, this);
111 $(document).on("click", (e) => {
112 if (!$(e.target).closest(this._$element).length) {
119 e.originalEvent.preventDefault();
120 e.originalEvent.stopPropagation();
123 this._$element.removeClass("d-none");
124 this._updatePopper(e);
126 this._$element.data("lat", e.latlng.lat);
127 this._$element.data("lng", e.latlng.lng);
131 this._$element.addClass("d-none");
132 if (this._popperInstance) {
133 this._popperInstance.destroy();
134 this._popperInstance = null;
139 const getVirtualReference = (x, y) => ({
140 getBoundingClientRect: () => ({
141 width: 0, height: 0, top: y, left: x, right: x, bottom: y
145 if (this._popperInstance) {
146 this._popperInstance.destroy();
147 this._popperInstance = null;
150 const virtualReference = getVirtualReference(
151 e.originalEvent.clientX,
152 e.originalEvent.clientY
155 this._popperInstance = Popper.createPopper(virtualReference, this._$element.find(".dropdown-menu")[0], {
156 placement: "bottom-start",
157 strategy: "absolute",
162 offset: [0, 0] // no offset, exactly aligned to placement corner
166 name: "preventOverflow",
167 options: { boundary: document.getElementById("map") }
172 fallbackPlacements: ["top-start", "bottom-end", "top-end"]
180 const $menuList = $("<ul>").addClass("dropdown-menu show shadow cm_dropdown_menu");
182 items.forEach((item) => {
183 const $menuItem = item.separator ?
184 this._createSeparator() :
185 this._createMenuItem(item);
186 $menuList.append($menuItem);
189 this._$element.empty().append($menuList);
192 _createMenuItem(item) {
193 const $icon = $("<i>").addClass(`bi ${item.icon}`);
194 const $label = $("<span>").text(item.text);
196 const $link = $("<a>")
197 .addClass("dropdown-item d-flex align-items-center gap-3")
198 .attr({ href: "#", id: item.id })
199 .append($icon, $label)
200 .on("click", (e) => {
206 return $("<li>").append($link);
210 return $("<li>").append($("<hr>").addClass("dropdown-divider"));
214 OSM.ContextMenu = ContextMenu;