/**
* Add a popup to the map
* @public
* @param {bemap.Popup} popup
* @param {object} options
* @return {bemap.LeafletMap} this
*/
bemap.LeafletMap.prototype.addPopup = function(popup, options) {
if (popup !== null && bemap.inheritsof(popup, bemap.Popup)) {
popup.native = L.popup({
autoPan: true
});
if (popup.map === null) {
popup.map = this;
}
popup.native.setContent(popup.getInformation());
if (!this.native.popups) {
this.native.popups = [];
}
this.native.popups.push(popup.native);
// Track the MODEL as well, keyed on the map (same shape as MapLibre's
// `_bemapPopups`). `native.popups` holds L.Popup instances, so there is no
// route from it back to the bemap.Popup objects — which is why clearPopup()
// could close the natives but left every model claiming isVisible() === true,
// still holding `map` and a detached native.
if (!this._bemapPopups) this._bemapPopups = [];
if (this._bemapPopups.indexOf(popup) === -1) this._bemapPopups.push(popup);
// Parity with OL (bemap-map-ol-popup.js) and MapLibre: a popup is opened
// ONLY when it has a coordinate to sit at and has not been asked to stay
// hidden. This engine used to call addTo() unconditionally, which meant
// `visible:false` opened anyway, and a popup created without a coordinate
// threw a TypeError on `getCoordinate().getLatLonArray()`.
//
// Safe for existing consumers: `bemap.Popup` defaults `visible` to TRUE
// (bemap-popup.js), so anyone who never passes the option is unaffected —
// only an explicit `visible:false` behaves differently, which is the fix.
if (popup.coordinate !== undefined && popup.visible !== false) {
popup.native.setLatLng(popup.getCoordinate().getLatLonArray());
popup.native.addTo(this.native);
popup.visible = true;
} else {
// Cannot be placed (or was asked to stay closed): keep the model honest so
// isVisible() does not lie. Same resolution as the other two engines.
popup.visible = false;
}
}
return this;
};
/**
* Remove a popup from the map.
* @public
* @param {bemap.Popup} popup the popup to remove from the map.
* @return {bemap.LeafletMap} this;
*/
bemap.LeafletMap.prototype.removePopup = function(popup) {
if (popup !== null && bemap.inheritsof(popup, bemap.Popup)) {
if (popup.native) {
this.native.removeLayer(popup.native);
// Drop the tracking entry too. It was never spliced, so every
// add/remove cycle leaked an L.Popup plus its detached DOM subtree, and
// clearPopup() then walked stale natives.
var list = this.native.popups;
if (list) {
var at = list.indexOf(popup.native);
if (at !== -1) {
list.splice(at, 1);
}
}
popup.native = null;
}
popup.visible = false;
popup.map = null;
}
// Drop the model entry too, whether or not it still had a native, so a
// repeated remove cannot leave a stale reference behind.
if (popup && this._bemapPopups) {
var mAt = this._bemapPopups.indexOf(popup);
if (mAt !== -1) this._bemapPopups.splice(mAt, 1);
}
return this;
};
/**
* Remove all the popups from the map.
* @return {bemap.LeafletMap} this;
*/
bemap.LeafletMap.prototype.clearPopup = function() {
var native = this.native;
// Go through removePopup() for every tracked MODEL, so each bemap.Popup ends
// up in the same state a single removePopup() leaves it in: visible = false,
// map = null, native released, tracking entries dropped. Closing only the
// native layers (what this used to do) left every model reporting
// isVisible() === true while holding a detached L.Popup — the same stale-state
// bug that was fixed on MapLibre, and it is fixed the same way here.
var owned = this._bemapPopups ? this._bemapPopups.slice() : [];
for (var m = 0; m < owned.length; m++) {
this.removePopup(owned[m]);
}
this._bemapPopups = [];
// Then sweep any native left in `native.popups` that had no model behind it —
// a popup added before this tracking existed, or one pushed there directly.
// Copy first: forEach + splice on the same array skips every other element.
if (native.popups && native.popups.length !== 0) {
var pending = native.popups.slice();
for (var i = 0; i < pending.length; i++) {
native.removeLayer(pending[i]);
}
native.popups.length = 0;
}
// `return this` used to sit INSIDE the `if`, so calling clearPopup() on a map
// with no popups returned undefined and broke the documented chaining
// contract — `map.clearPopup().addPopup(p)` threw on a fresh map.
return this;
};
/**
* Show or hide a popup.
*
* Added in the BEMAP-1898 audit. LeafletMap had no implementation, so
* `popup.show()` / `popup.hide()` / `popup.setVisible()` (bemap-popup.js) fell
* through to the base-class no-op (bemap-map.js) and did nothing at all: on
* Leaflet there was NO way to close a popup, and `isVisible()` kept reporting
* whatever the constructor was given. OL and MapLibre both implement it.
* @public
* @param {bemap.Popup} popup
* @param {boolean} visible
* @return {bemap.LeafletMap} this
*/
bemap.LeafletMap.prototype.setVisiblePopup = function(popup, visible) {
if (popup !== null && bemap.inheritsof(popup, bemap.Popup) && popup.native) {
if (visible && popup.coordinate !== undefined) {
popup.visible = true;
popup.native.setLatLng(popup.getCoordinate().getLatLonArray());
popup.native.addTo(this.native);
} else {
// Includes "asked to show but has no coordinate": it cannot be placed, so
// report it as hidden rather than let isVisible() claim otherwise.
popup.visible = false;
this.native.closePopup(popup.native);
}
}
return this;
};
/**
* Set the coordinate of the popup.
* @param {bemap.Popup} popup the popup of wich to set the coordinate.
* @param {bemap.Coordinate} coordinate the new coordinate.
* @param {object} options Options.
* @param {bemap.Layer} options.panningMap enable the map panning animation. move map from the current position to the popup anchor at the center of map.
* @return {bemap.OlMap} this.
*/
bemap.LeafletMap.prototype.setCoordinatePopup = function(popup, coordinate, options) {
if (popup !== null && bemap.inheritsof(popup, bemap.Popup)) {
popup.coordinate = coordinate;
if (popup.native !== null) {
popup.native.setLatLng(popup.getCoordinate().getLatLonArray());
}
}
return this;
};