Inside a jquery $.getJSON call, I create several markers and popups, then bind the popups to the markers:
$.each(points, function(index, p) {
var marker = L.marker(L.latLng(p.lat, p.lon), { title: p.text });
var popup = L.popup().setContent(p.text);
marker.bindPopup(popup);
}
I want all popups on the markers to be open after the map has loaded (and stay open, if possible). The Leaflet 1.0 documentation states: " Use Map.openPopup ... or use Map.addLayer to open as many as you want." (see leaflet - popup).
If possible, I would prefer using a "built in" solution to one where I have to fiddle around (like in SO - keep popups open). One advantage is that that approach can be generically used, for example to open all tooltips as well. And it would probably help me understand layers better.
I have tried several approaches, with LayerGroups and FeatureGroups, to no avail. How would you open a group of popups at once?
You can use a layerGroup containing all your popups. That way you have all the functionalities of group control for your popups.
I reproduced a basic example on JSfidle (https://jsfiddle.net/vaillant/f4zpr6us/) but you can imagine removing only certain popups from the layerGroup instead of removing all the popups at once. Below is the crucial part of the code:
var popup_layer = new L.layerGroup();
$.each(testData, function(index, p) {
var marker = L.marker(L.latLng(p.lat, p.lon));
marker.addTo(map);
popup = new L.popup({offset: new L.Point(0, -30)});
popup.setLatLng(L.latLng(p.lat, p.lon));
popup.setContent(p.text);
popup.openPopup();
popup_layer.addLayer(popup);
});
popup_layer.addTo(map);
Related
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';
}
I have a map with several moving markers on it and have been trying to remove each individual one when their respective animation ends. I am using Leaflet and Leaflet's plugin Leaflet.MovingMarker. I tried using methods isEnded() and end without much success.
With the latter method (end), the following code removes only but one marker (full code here):
marker.on('end', function() {
map.removeLayer(this);
});
With a click event instead of an end event, each marker is made easily removable, but then this is not what I am looking for:
marker.on('click', function() {
map.removeLayer(this);
});
With the method isEnded(), have not had any luck at all either (full code here):
var ended = marker.isEnded();
if (ended = true) {
console.log("this marker ended!");
map.removeLayer(this);
}
I wonder what I am missing here? Any hint appreciated!
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 });
Please check this screenshot to understand completely the problem:
My first question is:
How could I move the InfoWindow lower near the pin itself? I tried with css, javascript, etc and I cannot seem to find a "non hacky" way of doing it.
My second question is:
Is there any way to close an InfoWindow from my custom InfoWindow? I know I can do infoWindow.close() but I don't think I have the InfoWindow instance from a jquery event (it's a normal InfoWindow but with custom html in it).
Any help will be greatly welcome :)
Thanks and have a nice day!
From the API on pixelOffset:
The offset, in pixels, of the tip of the info window from the point on the map at whose geographical coordinates the info window is anchored
For the second question, you don't want to use jQuery events to open the infowindow, instead you can use the content property of the options to set your custom html, then do something like this:
var myInfoWindow = new google.maps.InfoWindow({ content: 'customHTML',
//some mode options
})
google.maps.event.addListener(marker, 'click', function (event) {
if (myInfoWindow) {
myInfoWindow.close()
myInfoWindow.setPosition(event.latLng);
myInfoWindow.open(map, marker);
}
})
The user has markers loaded depending on where they select their location. The markers are cleared when a new location is chosen. If I have an InfoWindow open when I clear the markers, it stays open even though the marker goes away. I want any open InfoWindow to close when the data is refreshed.
My implementation details:
I am using jquery-ui-map to enhance my Google Maps implementation with jQuery.
I attach a click event when creating the markers with the following code (FYI: I took out all the specific details and replaced them with generic ones).
$('#map-canvas').gmap('addMarker', {
'position' : new google.maps.LatLng(latitude, longitude),
'title' : myTitle,
'icon' : myImage,
'shadow' : myShadowImage
}).click(function() {
$('#map-canvas').gmap('openInfoWindow', {
'content' : displayContent(myObject)
}, this);
});
The option openInfoWindow does not return anything, so storing the InfoWindow object in an array to close it later does not look like a viable option.
Does anyone know how to achieve this using the jquery-ui-map library? I don't see a closeInfoWindow listed in the API documentation.
jquery-ui-map v.beta
var theInfoWindowObject = $('#map_canvas').gmap('get', 'iw');
theInfoWindowObject.close();
jquery-ui-map v rc 1
$('#map_canvas').gmap('closeInfoWindow');