How to close all popups programmatically in mapbox gl? - javascript

So, I know that we have Marker.togglePopup() in Mapbox GL API.
But can we close all popups programmatically?

Here is an example: https://jsfiddle.net/kmandov/eozdazdr/
Click the buttons at the top right to open/close the popup.
Given you have a popup and a marker:
var popup = new mapboxgl.Popup({offset:[0, -30]})
.setText('Construction on the Washington Monument began in 1848.');
new mapboxgl.Marker(el, {offset:[-25, -25]})
.setLngLat(monument)
.setPopup(popup)
.addTo(map);
You can close the popup by calling:
popup.remove();
or you can open it by calling:
popup.addTo(map);
As you can see in the Marker source, togglePopup uses these two methods internally:
togglePopup() {
var popup = this._popup;
if (!popup) return;
else if (popup.isOpen()) popup.remove();
else popup.addTo(this._map);
}

The accepted answer didn't apply to my use case (I wasn't using a Marker). I was able to come up with a different solution by utilizing the mapbox's built-in event workflow. Hopefully this helps someone else.
Mapbox allows you to listen to events on the map (and manually trigger them). The documentation doesn't mention it, but you can use custom events.
Given you have a popup:
// Create popup and add it to the map
const popup = new mapboxgl.Popup({ offset: 37, anchor: 'bottom' }).setDOMContent('<h5>Hello</h5>').setLngLat(feature.geometry.coordinates).addTo(map);
// Add a custom event listener to the map
map.on('closeAllPopups', () => {
popup.remove();
});
When you want to close all popups, fire the event:
map.fire('closeAllPopups');

Mapbox automatically uses the class .mapboxgl-popup for the popup. You can also add additional classes with options.className.
So, if you have jQuery available, just do:
$('.mapboxgl-popup').remove();
Or plain javascript:
const popup = document.getElementsByClassName('mapboxgl-popup');
if ( popup.length ) {
popup[0].remove();
}
I'm pretty sure you can assume there's only ever one popup open. The default behavior seems to be that if one is open and a second item is clicked, the first popup is removed from the DOM completely when the second one opens. If your application allows multiple open popups somehow, you will need to loop through and remove each with plain js, instead of using the first item only.

I know it's an old question but I recently worked on Mapbox. As of mapbox-gl v2.3, mapboxgl.Popup has a closeOnClick property where if set to true, the popup disappears when you click away from the popup.
let popup = new mapboxgl.Popup({
anchor: "top",
closeOnClick: true,
});
map.on(
"click",
"location",
(e) => {
map.getCanvas().style.cursor = "pointer";
let coordinates = e.features[0].geometry.coordinates.slice();
popup
.setLngLat(coordinates)
.setHTML("what I want to display")
.addTo(map);
}
);
Alternatively, you can show the popup on "mouseenter" instead of on "click" and add a "mouseleave" event to remove the popup:
map.on(
"mouseleave",
"location",
() => {
map.getCanvas().style.cursor = "";
popup.remove();
}
);

wrap your popup with React.StrictMode and closeOnClick={false}.
const [popup, setPopup] = useState<boolean>(false);
...
{popup && (
<React.StrictMode>
<Popup longitude={52.001126260374704} latitude={34.906269171550214}
anchor="bottom"
onClose={() => {
setPopup(false)
}}
closeOnClick={false}
>
You are here
</Popup>
</React.StrictMode>
)}

Related

How to extend Leaflet Icon Class to add data-open attribute to marker HTML?

I'm trying to trigger some functionality based on the click of a marker on a GeoJSON layer in Leaflet. The eventual functionality I'm trying to implement is a flyout, or scroll out type modal populated from the individual feature's JSON attributes. Essentially, I'm trying to implement the functionality in this Tutsplus Tutorial with dynamic feature content based on the marker click.
I THINK I've figured out most of the pieces I need, but I'm struggling with how to add a data attribute, specifically data-open, to the individual marker. Building on an earlier question of mine I've realized it's not enough to just update a DOM element's CSS, but rather my app should be implementing changes based on data attributes to fully get the functionality I want.
From this question I know that this should be done by extending the L.Icon class that Leaflet provides, but the answer is a bit too terse for my current JS skills. I apologize for this effectively being a "ELI5" of a previously asked question, but I'm not sure where the options and slug come into function. I think they're implied by the question, rather than the answer I'm citing and being set on the marker itself.
Here's a simplified version of the the click handler on my markers, which grabs and zooms to location, gets feature info, and populates that info to a div. The zoom functionality works, as does extracting and placing the feature info, but I'm struggling with how to connect the functionality to trigger the modal and place the div with the feature info over the map.
function zoomToFeature(e) {
var latLngs = [e.target.getLatLng()];
var markerBounds = L.latLngBounds(latLngs);
var street = e.target.feature.properties.str_addr;
document.getElementById('street').textContent = street;
mymap.fitBounds(markerBounds);
//where the modal trigger should be
document.getElementById('infoBox').classList.add('is-visible');
}
Here are the event listeners taken from the linked tutorial, which are currently not firing, but I have them working in a standalone implementation:
const openEls = document.querySelectorAll("[data-open]");
const closeEls = document.querySelectorAll("[data-close]");
const isVisible = "is-visible";
//this is the event I want to trigger on marker click
for (const el of openEls) {
el.addEventListener("click", function() {
const modalId = this.dataset.open;
console.log(this);
document.getElementById(modalId).classList.add(isVisible);
});
}
for (const el of closeEls) {
el.addEventListener("click", function() {
this.parentElement.parentElement.parentElement.classList.remove(isVisible);
});
}
document.addEventListener("click", e => {
if (e.target == document.querySelector(".modal.is-visible")) {
document.querySelector(".modal.is-visible").classList.remove(isVisible);
}
});
So, where I'm trying to get is that when my markers are clicked, the trigger the modal to appear over the map. So, I think I'm missing connecting the marker click event with the event that triggers the modal. I think what's missing is adding the data attribute to the markers, or some way chain the events without the data attributes. As there's no direct way to add an attribute to the markers, I try to add slug option on my circle markers:
var circleMarkerOptions = {
radius: 2,
weight: 1,
opacity: 1,
fillOpacity: 0.8,
slug: 'open',
}
and If I read the previously asked question's answer correctly, than extending the Icon Class this way should add a data-open attribute.
L.Icon.DataMarkup = L.Icon.extend({
_setIconStyles: function(img, name) {
L.Icon.prototype._setIconStyles.call(this, img, name);
if (options.slug) {
img.dataset.slug = options.slug;
}
}
});
A stripped down version of my code is here (thanks #ghybs). My full implementation pulls the markers from a PostGIS table. It's a bit hard to see in the Plunker, but this code adds my class to my modal, but doesn't trigger the functionality. It does trigger the visibility if the class is manually updated to modal.is-visible, but the current implementation which renders modal is-visbile doesn't, which I think is because the CSS is interpreted on page load(?) and not in response to the update via the dev tools, while the concatenated css class matches extactly(?). When I do trigger the modal via the dev tools, the close modal listeners don't seem to work, so I'm also missing that piece of the puzzle.
So, it's a work-around to setting the data attribute, but I realized I was shoe-horning a solution where it wasn't needed. Assuming someone ends up with the same mental block. Appropriate listeners on the modal close button and another function passed to the existing marker click listener produce the desired functionality.
const closeM = document.querySelector(".close-modal");
closeM.addEventListener("click", closeMe);
var modal = document.getElementById('infoBox');
and
function modalAction(){
modal.style.display = 'block';
}
function closeMe(){
modal.style.display = 'none';
}

Empty ghost Popup when dynamically rendering popup in react-leaflet

I'm dynamically rendering a marker popup in react-leaflet via a series of nested marker elements: DonutMarker, BoundsDriftMarker, and DriftMarker. The Popup is created in DonutMarker (GitHub) and passed to BoundsDriftMarker as a prop:
export default function DonutMarker(props: DonutMarkerProps) {
...
return (
<BoundsDriftMarker
...
popup={
<Popup>
...
test
</Popup>
}
showPopup={props.showPopup}
/>
);
}
then from BoundsDriftMarker (GitHub) it's dynamically rendered as a child to DriftMarker:
export default function BoundsDriftMarker({position, bounds, icon, duration, popup, showPopup}: BoundsDriftMarkerProps) {
...
return (<DriftMarker
...
>
...
{showPopup && popup}
</DriftMarker>)
}
This is what it looks like currently when showPopup == true, along with the React browser plugin correctly showing the Popup element under the marker:
However, when I switch showPopup == false after this, I get an empty popup even though the browser plugin shows no popup:
Are the nested marker components causing this issue, or is there some other problem?
try Seth Lutske answer in
react-leaflet: Clear marker cluster before rendering new markers
He wraps markers in MarkerClusterGroup, and change the key every time he need to refresh markers in map. Works for me too
<MarkerClusterGroup
key={uuidv4()}
spiderfyDistanceMultiplier={1}
showCoverageOnHover={true}
>

How to show modal window on marker click?

I am using leafletjs to build a web map and trying to figure out how to show a modal window when a marker is clicked (instead of the default popup method).
Here's my setup:
var myAirports = L.geoJson(myData, {
pointToLayer: function(latlng){
..snip..
},
onEachFeature: function(feature,layer){
$('#myModalOne').modal(options);
}
});
myAirports.addTo(map);
My HTML is like so:
<div id="myModalOne">....</div>
<div id="myModalTwo">....</div>
Lets say my data has a featurecollection with a key of 'name' (i.e., 'name': 'Bush Airport') for each feature. Would I just add a switch statement to my onEachFeature function?
Just need a little guidance,thanks.
Note: I am using Bootstrap for the modal windows
If I understand you correctly, you don't need to set the pointToLayer option which is useful if you want to display something else than a marker.
What you need is to catch the click event on the markers and display a modal window. There is no popup by default.
var myAirports = L.geoJson(myData, {
onEachFeature: function(feature,layer){
layer.on('click', function(e){
$('#myModal'+feature.properties.name).modal(options);
// or whatever that opens the right modal window
});
}
});
myAirports.addTo(map);

Opening a leaflet popup on a layerGroup

I am trying to draw country shapes on a leaflet map using L.GeoJSON(data).addTo(map). I then want to bind a popup to the click event of that country shape...
new L.GeoJSON(data, {
onEachFeature: function(feature, layer) {
layer['on']('click', popupFunction);
}
}).addTo(this.map);
popupFunction = function(event) {
var layer = event.target;
// Open the 'add' popup and get our content node
var bound = layer.bindPopup(
"<div>Hello World!</div>"
).openPopup();
// Ugly hack to get the HTML content node for the popup
// because I need to do things with it
var contentNode = $(bound['_popup']['_contentNode']);
}
Now this works fine when the data is a single polygon, because then the layer attribute passed to the onEachFeature function is just that: a layer.
However if the data is a multipolygon (i.e. the US) this stops working because the "layer" is now a layerGroup (it has a _layers) attribute and therefore has no _popup attribute and so I can't get the _contentNode for the popup.
It seems like this should be quite a common thing, wanting a popup on a layerGroup. Why does it have no _popup attribute?
short answer: layergroup does not support popup
plan B:
you should consider using FeatureGroup, it extends LayerGroup and has the bindPopup method and this is an example
L.featureGroup([marker1, marker2, polyline])
.bindPopup('Hello world!')
.on('click', function() { alert('Clicked on a group!'); })
.addTo(map);
You cannot bind a L.Popup to anything else than a L.Layer because the popup will some coordinates to anchor on.
For a L.Marker it will be the position (L.Latlng), for the L.Polygon it will be the center (look at the code to see how it is calculated).
As for the other cases (like yours), you can open a popup but you will have to decide where the popup opens:
var popup = L.popup()
.setLatLng(latlng)
.setContent('<p>Hello world!<br />This is a nice popup.</p>')
.openOn(map);
First of all, you should be able to bind popUp in similar way to Using GeoJSON with Leaflet tutorial. Something like this:
var geoJsonLayer = L.geoJson(data, {
onEachFeature: function(feature, layer) {
layer.bindPopup('<div>Hello World!</div>');
}
}).addTo(map);
How to further process your popUps depends on your usecase. Maybe this can be enough for you:
geoJsonLayer.eachLayer(function(layer) {
var popUp = layer._popup;
// process popUp, maybe with popUp.setContent("something");
});
Hope this helps..
I used this code to open all popups in a layer group:
markers.eachLayer(marker => marker.openPopup());
While dealing with layer groups, you might want to consider passing { autoClose: false, closeOnClick: false } options while binding popup, so popups won't get closed while opening new popup, or if user clicks on the map:
marker.bindPopup(item.name, { autoClose: false, closeOnClick: false });

Google Maps infowindow closeclick event (function parameter)

My web application uses the following popup-overlay plugin to show profile content in a popup on the screen:
http://vast-eng.github.io/jquery-popup-overlay/
My problem is related to a Google Map - marker infowindow that I use within the popup window.
When I try to close the infowindow, the popup disappears as well.
This is wrong! I don't immediately see why it is doing this.
If I don't find a solution, I could disable or hide the X, but I prefer not to do this.
The "where" section in the following link shows you the problem:
http://www.zwoop.be/dev/#list/bars/1
Edit
The event parameter returns "undefined" for the following event listener:
google.maps.event.addListener(self.marker.infowindow, "closeclick", function(e)
{
console.log(e);
});
Thanks
Edit2
Here is a quick fiddle that illustrates the problem:
http://jsfiddle.net/462HF/
The blur function in the popup overlay plugin is getting called when you click the 'X' in the infoWindow on the google map, that is why the entire popup is closing. Below is the code in the jquery.popupoverlay.js file:
if (options.blur) {
blurhandler = function (e) {
if (!$(e.target).parents().andSelf().is('#' + el.id)) {
methods.hide(el);
}
};
}
The 'X' is an image, and its parents()only go back to the InfoWindow div, but not the popup div. Therefore, it will call methods.hide(el) and close the popup.
There is probably a better way of determining if it was clicked, but changing it to the following works. It just checks the target's img source:
if (options.blur) {
blurhandler = function (e) {
if (!$(e.target).parents().andSelf().is('#' + el.id) ) {
if(!($(e.target).attr('src') === 'http://maps.gstatic.com/mapfiles/api-3/images/mapcnt3.png'))
methods.hide(el);
}
};
}

Categories

Resources