42

I have a leaflet map up and running. It overlays a series of polygons (via GeoJSON) on the map and attaches popups to each polygon. Each of the popups display information about that polygon.

I'd like to have inside the popup a link that, when clicked, runs a javascript function that pulls further smaller polygons via AJAX and shows them.

I can't get the script to catch a click on the link via the normal jQuery/Javascript click events. Here's what I mean by normal (the following doesn't work):

$('a .smallPolygonLink').click(function(e){
  console.log("One of the many Small Polygon Links was clicked");
});

The bindPopup part is as follows. It runs on each polygon when made and it pops up correctly on clicking on a polygon. It does show the link, just won't run the above code on click.

var popupContent = "Basic Information..." + '<a class="smallPolygonLink" href="#">Click here to see the smaller polygons</a>';
layer.bindPopup(popupContent);

Here's a JSFiddle illustrating the example, though in a far simpler form. http://jsfiddle.net/2XfVc/4/

Josh
  • 3,385
  • 5
  • 23
  • 45

7 Answers7

60

The link element inside the popup is being dynamically generated from your markup each time the popup is opened. That means the link doesn't exist when you're trying to bind the handler to it.

The ideal approach here would be to use on to delegate event handling to the popup element or an ancestor of it. Unfortunately, the popup prevents event propagation, which is why delegating event handling to any static elements outside the popup won't work.

What you can do is preconstruct the link, attach the handler, and then pass it to the bindPopup method.

var link = $('<a href="#" class="speciallink">TestLink</a>').click(function() {
    alert("test");
})[0];
marker.bindPopup(link);

Here is a demonstration: http://jsfiddle.net/2XfVc/7/

In general, to insert any sort of complex markup with multiple event handlers, use the folowing approach:

// Create an element to hold all your text and markup
var container = $('<div />');

// Delegate all event handling for the container itself and its contents to the container
container.on('click', '.smallPolygonLink', function() {
    ...
});

// Insert whatever you want into the container, using whichever approach you prefer
container.html("This is a link: <a href='#' class='smallPolygonLink'>Click me</a>.");
container.append($('<span class="bold">').text(" :)"))

// Insert the container into the popup
marker.bindPopup(container[0]);

Here is a demo: http://jsfiddle.net/8Lnt4/

See this Git issue for more on event propagation in leaflet popups.

Asad Saeeduddin
  • 46,193
  • 6
  • 90
  • 139
  • How would I modify this to add text or content that is NOT clickable? E.g. Name: xxxx, Age: xxxx, Click Link for more details. With only the word "Link" being clickable. Also, what does the [0] part do? I can't find any reference in the jquery click api. – Josh Dec 06 '12 at 04:51
  • 3
    @Josh Just add other elements along with the clickable one. Here is a demonstration: http://jsfiddle.net/2XfVc/8/ The `[0]` part simply gets the DOM node from the jQuery collection, since leaflet accepts standard DOM nodes but not jQuery collections. – Asad Saeeduddin Dec 06 '12 at 08:56
  • Awesome. I changed it to `.html()` instead of `.text()` so that some of the html formatting would show up, but otherwise that was perfect. Thanks. – Josh Dec 07 '12 at 06:41
29

While the Popup content wrapper prevents event propagation, events within the popup inner Markup propagate just fine. You can add events to popup elements when they are displayed on the map (and have become part of the DOM). Just watch for leaflet event popupopen.

var map = L.map('map').setView([51.505, 10], 7); //for example

//the .on() here is part of leaflet
map.on('popupopen', function() {  
  $('a .smallPolygonLink').click(function(e){
    console.log("One of the many Small Polygon Links was clicked");
  });
});

http://jsfiddle.net/tJGQ7/2/

This works like a charm for me. If your popup does not have a 'a .smallPolygonLink' the above code does nothing. This code runs on every startup of a popup. However you don't have to worry that it attaches more than one handler to an element, since when the popup closes, the DOM nodes get thrown away.

There is a much more general way to do this. However, it involves eval(). Use at your own risk. But when AJAXloading partial pages that contain JS you run the same risks, so for your edification I present "executing JS inside your leaflet popups":

map.on('popupopen', function(){
    var cont = document.getElementsByClassName('leaflet-popup-content')[0];    
    var lst = cont.getElementsByTagName('script');
    for (var i=0; i<lst.length;i++) {
       eval(lst[i].innerText)
    }
});

demo: http://jsfiddle.net/tJGQ7/4/

Now you can write:

var popup_content = 'Testing the Link: <a href="#" class="speciallink">TestLink</a><script> $(".speciallink").on("click", function(){alert("hello from inside the popup")});</script>';

marker.bindPopup(popup_content);
semiomant
  • 584
  • 6
  • 12
  • Definitely the best answer here - no need for work-arounds :-) – Erwin Wessels Jun 22 '13 at 10:58
  • yep, should be the best answer here. nice solution! – Yunwei.W Aug 12 '13 at 15:59
  • 1
    The problem with this is that you're re-attaching the event handler every time the popup is opened, which is inefficient now, and unworkable as soon as you look at inserting more complex structures with multiple handlers. The ideal approach here would be to delegate event handling to the popup element or an ancestor thereof, which is what I originally suggested in my answer. I discovered however that leaflet disrupts event propagation, which meant `on` wouldn't work. The second best approach is to bind any and all event handling functionality exactly *once* to a container element and insert. – Asad Saeeduddin Sep 24 '13 at 20:31
  • I deal with very complex ajax-loaded popup contents, in my experience there is no computing cycle or memory bottleneck that would justify to speak of inefficiency here. In contrast, with my method i separate content and functionality that belongs together into one unit. Your workaround jquery trick makes the the container object a big bag of functionality to save some milliseconds of execution time. I quote: "Premature optimization is the root of all evil". – semiomant Sep 25 '13 at 09:36
  • This answer works... but will attach multiple handlers to the popup.. see answer by @AsadSaeeduddin for more accurate solution. – Rosdi Kasim Jun 07 '18 at 05:03
  • are you saying "since when the popup closes, the DOM nodes get thrown away." is no longer true? When I worked on this years ago, I checked for multiple attachment and it didnt happen for the reason cited. – semiomant Jun 07 '18 at 09:42
5

That's what I find on the mapbox offical website: Create a click event in a marker popup with Mapbox.js and jQuery. The comment explains why we say $('#map') instead of $('#mybutton').

var marker = L.marker([43.6475, -79.3838], {
  icon: L.mapbox.marker.icon({
    'marker-color': '#9c89cc'
  })
})
.bindPopup('<button class="trigger">Say hi</button>')
.addTo(map);
//The HTML we put in bindPopup doesn't exist yet, so we can't just say
//$('#mybutton'). Instead, we listen for click events on the map element which will bubble up from the tooltip, once it's created and someone clicks on it.

$('#map').on('click', '.trigger', function() {
alert('Hello from Toronto!');});
Kiki Yang
  • 81
  • 1
  • 4
4

I came across this problem, tried the solution above. But it didn't worked for me. Found the following pretty basic jquery solution.

// add your marker to the map
var my_marker = new L.marker([51.2323, 4.1231], {icon: my_icon});
var popup = L.popup().setContent('<a class="click" href="#">click</a>');
my_marker.addTo(map).bindPopup(popup);

// later on
jQuery("body").on('click','a.click', function(e){
  e.preventDefault();
  alert('clicked');
});
jivanrij
  • 157
  • 2
  • 12
4

You can check inner properties of popup object, including _wrapper etc.

map.on('popupopen', _bindPopupClick);
map.on('popupclose', _unbindPopupClick);

var _bindPopupClick = function (e) {
    if (e.popup) {
        e.popup._wrapper.addEventListener('click', _bindPopupClickHandler);
    }
};
var _unbindPopupClick = function (e) {
    if (e.popup) {
        e.popup._wrapper.removeEventListener('click', _bindPopupClickHandler);
    }
}`
SerzN1
  • 1,814
  • 23
  • 15
  • This vanilla solution works like a charm when using Vue2 and you don't want to use jquery. Thanks! – Daantje Jun 26 '18 at 11:28
1

You can use jQuery to select the canvas element, but you'd have to use its own methods within the canvas. A decent start would be https://developer.mozilla.org/en/canvas_tutorial .

wandarkaf
  • 1,839
  • 20
  • 30
0

mapbox JavaScript library has an event:

bindPopup('<button class="trigger">Say hi</button>');
addTo(map);

$('#map').on('click', '.trigger', function() {
    alert('Hello from Toronto!');
});

https://www.mapbox.com/mapbox.js/example/v1.0.0/clicks-in-popups/

ppovoski
  • 4,553
  • 5
  • 22
  • 28
  • don't dump code, explain it. a good explanation is important to understand why your proposed solution will solve OP's problem. – Wasi Ahmad Dec 26 '16 at 00:19