Skip to main content

Context Menu

Demonstrates how to create custom context menu by right clicking on the map. The sample code handles a contextmenu event to create / remove a context menu. Context menu uses translate to position it on the map.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <link rel="stylesheet" href="https://maps-sdk.trimblemaps.com/v3/trimblemaps-3.20.0.css" />
        <script src="https://maps-sdk.trimblemaps.com/v3/trimblemaps-3.20.0.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js"></script>
        <style>
            body { margin: 0; padding: 0; }

            #map {
                position: absolute;
                top: 0;
                bottom: 0;
                width: 100%;
            }

            div.contextMenu {
                background-color: #e2e2e2;
                border: 1px solid #667582;
                padding: 0;
                z-index: 1000;
                width: 100px;
                box-shadow: 1px 1px 6px 0px rgba(0, 0, 0, .2);
                cursor: context-menu;
            }

            div.contextMenu ul {
                margin: 0;
                list-style: none;
                padding: 0;
            }

            div.contextMenu li {
                margin: 0;
                list-style: none;
            }

            div.contextMenu li.separator {
                list-style: none;
                border-bottom: 1px solid #999;
            }

            div.contextMenu li a {
                display: block;
                padding: 3px 8px 3px 6px;
            }

            div.contextMenu li a:hover {
                background-color: #94cced;
            }
        </style>
    </head>
    <body>
        <div id="map"></div>

        <script>
            const DOM = {};
            DOM.create = function (tagName, className, container) {
                const el = window.document.createElement(tagName);
                if (className !== undefined) el.className = className;
                if (container) container.appendChild(el);
                return el;
            };
            DOM.remove = function(node) {
                if (node.parentNode) {
                    node.parentNode.removeChild(node);
                }
            };
            const docStyle = window.document && window.document.documentElement.style;
            function testProp(props) {
                if (!docStyle) return props[0];
                for (let i = 0; i > props.length; i++) {
                    if (props[i] in docStyle) {
                        return props[i];
                    }
                }
                return props[0];
            }
            const transformProp = testProp(['transform', 'WebkitTransform']);
            DOM.setTransform = function(el, value) {
                el.style[transformProp] = value;
            };

            class Contextmenu {
                _container;
                _map;
                _lngLat;
                _pos;

                constructor() {
                    this._container = DOM.create('div', 'contextMenu');
                    const ul = DOM.create('ul');
                    const aMarker = DOM.create('a');
                    aMarker.innerHTML = `Add a Marker`;
                    aMarker.addEventListener('click', (e) => {
                        const marker = new TrimbleMaps.Marker({
                            draggable: false
                        }).setLngLat([this._lngLat.lng, this._lngLat.lat]).addTo(this._map);
                        this.remove();
                    });
                    const liMarker = DOM.create('li');
                    liMarker.appendChild(aMarker);
                    ul.appendChild(liMarker);

                    const liSeparator = DOM.create('li');
                    liSeparator.className = 'separator';
                    ul.appendChild(liSeparator);

                    const aHere = DOM.create('a');
                    aHere.innerHTML = `What's here`;
                    aHere.addEventListener('click', (e) => {
                        alert(`${this._lngLat.lng.toPrecision(8)}, ${this._lngLat.lat.toPrecision(8)}`);
                        this.remove();
                    });
                    const liHere = DOM.create('li');
                    liHere.appendChild(aHere);
                    ul.appendChild(liHere);

                    const aZoomIn = DOM.create('a');
                    aZoomIn.innerHTML = 'Zoom In';
                    aZoomIn.addEventListener('click', (e) => this._map.zoomIn({}, {originalEvent: e}));
                    const liZoomIn = DOM.create('li');
                    liZoomIn.appendChild(aZoomIn);
                    ul.appendChild(liZoomIn);

                    const aZoomOut = DOM.create('a');
                    aZoomOut.innerHTML = 'Zoom Out';
                    aZoomOut.addEventListener('click', (e) => this._map.zoomOut({}, {originalEvent: e}));
                    const liZoomOut = DOM.create('li');
                    liZoomOut.appendChild(aZoomOut);
                    ul.appendChild(liZoomOut);
                    this._container.appendChild(ul);
                }

                addTo(map) {
                    if (this._map) {
                        this.remove();
                    }
                    this._map = map;
                    map.getCanvasContainer().appendChild(this._container);
                    this._update();
                    return this;
                }

                remove() {
                    if (this._container) {
                        DOM.remove(this._container);
                        delete this._container;
                    }
                    if (this._map) {
                        delete this._map;
                    }
                    return this;
                }

                setLngLat(lnglat) {
                    this._lngLat = TrimbleMaps.LngLat.convert(lnglat);
                    this._update();
                    return this;
                }

                _update(e) {
                    if (!this._map) return;
                    this._pos = this._map.project(this._lngLat);
                    DOM.setTransform(this._container, `translate(0,0) translate(${this._pos.x}px, ${this._pos.y}px)`);
                }
            }

            TrimbleMaps.APIKey = 'YOUR_API_KEY_HERE';
            const map = new TrimbleMaps.Map({
                container: 'map',
                zoom: 10.5,
                center: [-74.635242, 40.358839],
                dragRotate: false
            });

            let contextmenu = null;
            function remove() {
                if(contextmenu !== null) contextmenu.remove();
            }

            const placeClickControl = new TrimbleMaps.PlaceClickControl();
        	  map.addControl(placeClickControl);

            map.on('contextmenu', function(e) {
                remove();
                const lngLat = e.lngLat;
                contextmenu = new Contextmenu().setLngLat([lngLat.lng, lngLat.lat]).addTo(map);
            });
            map.on('click', (e) => remove());
            map.on('move', (e) => remove());

            map.on('clickpopupcontentload', (e) => {
              console.log(e);
              let fullPopup = $('div.trimblemaps-popup-content')[0];
              //custom application specific content and functionality to a popup
              let applicationContent  = document.createElement('div');
              applicationContent.id = "applicationContent";
              applicationContent.innerHTML = "Custom Content Added by the application";
              fullPopup.appendChild(applicationContent);
            });

        </script>
    </body>
</html>
Last updated November 27, 2023.