UNPKG

leaflet

Version:

JavaScript library for mobile-friendly interactive maps

483 lines (398 loc) 14.4 kB
/* * @class Popup * @inherits Layer * @aka L.Popup * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to * open popups while making sure that only one popup is open at one time * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want. * * @example * * If you want to just bind a popup to marker click and then open it, it's really easy: * * ```js * marker.bindPopup(popupContent).openPopup(); * ``` * Path overlays like polylines also have a `bindPopup` method. * Here's a more complicated way to open a popup on a map: * * ```js * var popup = L.popup() * .setLatLng(latlng) * .setContent('<p>Hello world!<br />This is a nice popup.</p>') * .openOn(map); * ``` */ /* @namespace Map * @section Interaction Options * @option closePopupOnClick: Boolean = true * Set it to `false` if you don't want popups to close when user clicks the map. */ L.Map.mergeOptions({ closePopupOnClick: true }); // @namespace Popup L.Popup = L.Layer.extend({ // @section // @aka Popup options options: { // @option maxWidth: Number = 300 // Max width of the popup, in pixels. maxWidth: 300, // @option minWidth: Number = 50 // Min width of the popup, in pixels. minWidth: 50, // @option maxHeight: Number = null // If set, creates a scrollable container of the given height // inside a popup if its content exceeds it. maxHeight: null, // @option autoPan: Boolean = true // Set it to `false` if you don't want the map to do panning animation // to fit the opened popup. autoPan: true, // @option autoPanPaddingTopLeft: Point = null // The margin between the popup and the top left corner of the map // view after autopanning was performed. autoPanPaddingTopLeft: null, // @option autoPanPaddingBottomRight: Point = null // The margin between the popup and the bottom right corner of the map // view after autopanning was performed. autoPanPaddingBottomRight: null, // @option autoPanPadding: Point = Point(5, 5) // Equivalent of setting both top left and bottom right autopan padding to the same value. autoPanPadding: [5, 5], // @option keepInView: Boolean = false // Set it to `true` if you want to prevent users from panning the popup // off of the screen while it is open. keepInView: false, // @option closeButton: Boolean = true // Controls the presence of a close button in the popup. closeButton: true, // @option offset: Point = Point(0, 7) // The offset of the popup position. Useful to control the anchor // of the popup when opening it on some overlays. offset: [0, 7], // @option autoClose: Boolean = true // Set it to `false` if you want to override the default behavior of // the popup closing when user clicks the map (set globally by // the Map's [closePopupOnClick](#map-closepopuponclick) option). autoClose: true, // @option zoomAnimation: Boolean = true // Whether to animate the popup on zoom. Disable it if you have // problems with Flash content inside popups. zoomAnimation: true, // @option className: String = '' // A custom CSS class name to assign to the popup. className: '', // @option pane: String = 'popupPane' // `Map pane` where the popup will be added. pane: 'popupPane' }, initialize: function (options, source) { L.setOptions(this, options); this._source = source; }, onAdd: function (map) { this._zoomAnimated = this._zoomAnimated && this.options.zoomAnimation; if (!this._container) { this._initLayout(); } if (map._fadeAnimated) { L.DomUtil.setOpacity(this._container, 0); } clearTimeout(this._removeTimeout); this.getPane().appendChild(this._container); this.update(); if (map._fadeAnimated) { L.DomUtil.setOpacity(this._container, 1); } // @namespace Map // @section Popup events // @event popupopen: PopupEvent // Fired when a popup is opened in the map map.fire('popupopen', {popup: this}); if (this._source) { // @namespace Layer // @section Popup events // @event popupopen: PopupEvent // Fired when a popup bound to this layer is opened this._source.fire('popupopen', {popup: this}, true); this._source.on('preclick', L.DomEvent.stopPropagation); } }, // @namespace Popup // @method openOn(map: Map): this // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`. openOn: function (map) { map.openPopup(this); return this; }, onRemove: function (map) { if (map._fadeAnimated) { L.DomUtil.setOpacity(this._container, 0); this._removeTimeout = setTimeout(L.bind(L.DomUtil.remove, L.DomUtil, this._container), 200); } else { L.DomUtil.remove(this._container); } // @namespace Map // @section Popup events // @event popupclose: PopupEvent // Fired when a popup in the map is closed map.fire('popupclose', {popup: this}); if (this._source) { // @namespace Layer // @section Popup events // @event popupclose: PopupEvent // Fired when a popup bound to this layer is closed // @namespace Popup this._source.fire('popupclose', {popup: this}, true); this._source.off('preclick', L.DomEvent.stopPropagation); } }, // @namespace Popup // @method getLatLng: LatLng // Returns the geographical point of popup. getLatLng: function () { return this._latlng; }, // @method setLatLng(latlng: LatLng): this // Sets the geographical point where the popup will open. setLatLng: function (latlng) { this._latlng = L.latLng(latlng); if (this._map) { this._updatePosition(); this._adjustPan(); } return this; }, // @method getContent: String|HTMLElement // Returns the content of the popup. getContent: function () { return this._content; }, // @method setContent(htmlContent: String|HTMLElement|Function): this // Sets the HTML content of the popup. If a function is passed the source layer will be passed to the function. The function should return a `String` or `HTMLElement` to be used in the popup. setContent: function (content) { this._content = content; this.update(); return this; }, // @method getElement: String|HTMLElement // Alias for [getContent()](#popup-getcontent) getElement: function () { return this._container; }, // @method update: null // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded. update: function () { if (!this._map) { return; } this._container.style.visibility = 'hidden'; this._updateContent(); this._updateLayout(); this._updatePosition(); this._container.style.visibility = ''; this._adjustPan(); }, getEvents: function () { var events = { zoom: this._updatePosition, viewreset: this._updatePosition }; if (this._zoomAnimated) { events.zoomanim = this._animateZoom; } if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) { events.preclick = this._close; } if (this.options.keepInView) { events.moveend = this._adjustPan; } return events; }, // @method isOpen: Boolean // Returns `true` when the popup is visible on the map. isOpen: function () { return !!this._map && this._map.hasLayer(this); }, // @method bringToFront: this // Brings this popup in front of other popups (in the same map pane). bringToFront: function () { if (this._map) { L.DomUtil.toFront(this._container); } return this; }, // @method bringToBack: this // Brings this popup to the back of other popups (in the same map pane). bringToBack: function () { if (this._map) { L.DomUtil.toBack(this._container); } return this; }, _close: function () { if (this._map) { this._map.closePopup(this); } }, _initLayout: function () { var prefix = 'leaflet-popup', container = this._container = L.DomUtil.create('div', prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide')); if (this.options.closeButton) { var closeButton = this._closeButton = L.DomUtil.create('a', prefix + '-close-button', container); closeButton.href = '#close'; closeButton.innerHTML = '&#215;'; L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this); } var wrapper = this._wrapper = L.DomUtil.create('div', prefix + '-content-wrapper', container); this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper); L.DomEvent .disableClickPropagation(wrapper) .disableScrollPropagation(this._contentNode) .on(wrapper, 'contextmenu', L.DomEvent.stopPropagation); this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container); this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer); }, _updateContent: function () { if (!this._content) { return; } var node = this._contentNode; var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content; if (typeof content === 'string') { node.innerHTML = content; } else { while (node.hasChildNodes()) { node.removeChild(node.firstChild); } node.appendChild(content); } this.fire('contentupdate'); }, _updateLayout: function () { var container = this._contentNode, style = container.style; style.width = ''; style.whiteSpace = 'nowrap'; var width = container.offsetWidth; width = Math.min(width, this.options.maxWidth); width = Math.max(width, this.options.minWidth); style.width = (width + 1) + 'px'; style.whiteSpace = ''; style.height = ''; var height = container.offsetHeight, maxHeight = this.options.maxHeight, scrolledClass = 'leaflet-popup-scrolled'; if (maxHeight && height > maxHeight) { style.height = maxHeight + 'px'; L.DomUtil.addClass(container, scrolledClass); } else { L.DomUtil.removeClass(container, scrolledClass); } this._containerWidth = this._container.offsetWidth; }, _updatePosition: function () { if (!this._map) { return; } var pos = this._map.latLngToLayerPoint(this._latlng), offset = L.point(this.options.offset); if (this._zoomAnimated) { L.DomUtil.setPosition(this._container, pos); } else { offset = offset.add(pos); } var bottom = this._containerBottom = -offset.y, left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x; // bottom position the popup in case the height of the popup changes (images loading etc) this._container.style.bottom = bottom + 'px'; this._container.style.left = left + 'px'; }, _animateZoom: function (e) { var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center); L.DomUtil.setPosition(this._container, pos); }, _adjustPan: function () { if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; } var map = this._map, containerHeight = this._container.offsetHeight, containerWidth = this._containerWidth, layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom); if (this._zoomAnimated) { layerPos._add(L.DomUtil.getPosition(this._container)); } var containerPos = map.layerPointToContainerPoint(layerPos), padding = L.point(this.options.autoPanPadding), paddingTL = L.point(this.options.autoPanPaddingTopLeft || padding), paddingBR = L.point(this.options.autoPanPaddingBottomRight || padding), size = map.getSize(), dx = 0, dy = 0; if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right dx = containerPos.x + containerWidth - size.x + paddingBR.x; } if (containerPos.x - dx - paddingTL.x < 0) { // left dx = containerPos.x - paddingTL.x; } if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom dy = containerPos.y + containerHeight - size.y + paddingBR.y; } if (containerPos.y - dy - paddingTL.y < 0) { // top dy = containerPos.y - paddingTL.y; } // @namespace Map // @section Popup events // @event autopanstart // Fired when the map starts autopanning when opening a popup. if (dx || dy) { map .fire('autopanstart') .panBy([dx, dy]); } }, _onCloseButtonClick: function (e) { this._close(); L.DomEvent.stop(e); } }); // @namespace Popup // @factory L.popup(options?: Popup options, source?: Layer) // Instantiates a Popup object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the popup with a reference to the Layer to which it refers. L.popup = function (options, source) { return new L.Popup(options, source); }; // @namespace Map // @section Methods for Layers and Controls L.Map.include({ // @method openPopup(popup: Popup): this // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability). // @alternative // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this // Creates a popup with the specified content and options and opens it in the given point on a map. openPopup: function (popup, latlng, options) { if (!(popup instanceof L.Popup)) { popup = new L.Popup(options).setContent(popup); } if (latlng) { popup.setLatLng(latlng); } if (this.hasLayer(popup)) { return this; } if (this._popup && this._popup.options.autoClose) { this.closePopup(); } this._popup = popup; return this.addLayer(popup); }, // @method closePopup(popup?: Popup): this // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one). closePopup: function (popup) { if (!popup || popup === this._popup) { popup = this._popup; this._popup = null; } if (popup) { this.removeLayer(popup); } return this; } });