UNPKG

leaflet

Version:

JavaScript library for mobile-friendly interactive maps

335 lines (275 loc) 10.4 kB
/* * @class Popup * @inherits DivOverlay * @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 Popup L.Popup = L.DivOverlay.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 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 className: String = '' // A custom CSS class name to assign to the popup. className: '' }, // @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; }, onAdd: function (map) { L.DivOverlay.prototype.onAdd.call(this, map); // @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); // For non-path layers, we toggle the popup when clicking // again the layer, so prevent the map to reopen it. if (!(this._source instanceof L.Path)) { this._source.on('preclick', L.DomEvent.stopPropagation); } } }, onRemove: function (map) { L.DivOverlay.prototype.onRemove.call(this, map); // @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 this._source.fire('popupclose', {popup: this}, true); if (!(this._source instanceof L.Path)) { this._source.off('preclick', L.DomEvent.stopPropagation); } } }, getEvents: function () { var events = L.DivOverlay.prototype.getEvents.call(this); 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; }, _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-animated'); 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); }, _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; }, _animateZoom: function (e) { var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center), anchor = this._getAnchor(); L.DomUtil.setPosition(this._container, pos.add(anchor)); }, _adjustPan: function () { if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; } var map = this._map, marginBottom = parseInt(L.DomUtil.getStyle(this._container, 'marginBottom'), 10) || 0, containerHeight = this._container.offsetHeight + marginBottom, containerWidth = this._containerWidth, layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom); 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: Event // 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); }, _getAnchor: function () { // Where should we anchor the popup on the source layer? return L.point(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]); } }); // @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 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 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; } });