I'm trying to write a custom event handler for CTRL + click in Leaflet. My problem is that the click location given by the map is different from the click location in the event handler, e.g. LatLng(51.49174, -0.11639) from the map click becomes LatLng(51.50938, -0.126) in the handler. The click locations match exactly if the map is the only thing on the page. Adding some other div elements above the map (like an <h1> title) makes the clicks not match. Panning the map also makes the click locations not match.
I'm wondering if I attached my L.DomEvent.on() correctly. Following the Leaflet Handlers tutorial, my code looks like
L.CtrlClickHandler = L.Handler.extend({
addHooks: function() {
L.DomEvent.on(document, 'click', this._captureClick, this);
},
removeHooks: function() {
L.DomEvent.off(document, 'click', this._captureClick, this);
},
_captureClick: function(event) {
if (event.ctrlKey) {
console.log('control click registered at layer '
+ map.layerPointToLatLng(new L.point(event.layerX, event.layerY)));
}
}
});
// add this to all maps
L.Map.addInitHook('addHandler', 'ctrlClick', L.CtrlClickHandler);
Here's a live example on JSFiddle.
I'm using Leaflet 0.7.7 due to some other dependencies in my code. Upgrading to Leaflet 1.0.1 makes it match better (e.g., LatLng(51.49868, -0.1018) vs. LatLng(51.4987, -0.1018)) but the two locations still are not exactly the same.
Am I attaching the L.DomEvent to the correct thing? Should that be attached to the map div somehow, as opposed to document?
Edit: Thanks to #AlexParij for the suggestion. I realized that panning the map also makes the clicks not match, with or without div elements above the map. This happens for Leaflet 1.0.1 as well as 0.7.7. I've tried every combination I can think of, combining different event locations (event.layerX, event.pageX, event.clientX, event.offsetX, event.screenX, and event.x) with projection methods layerPointToLatLng and unproject but none of them match the map click. Now I'm really confused... Fiddle with these different options and Leaflet 1.0.1: https://jsfiddle.net/c4tkyewz/
TL; DR: use map.mouseEventToLatLng() in a custom handler.
#AlexParij was correct; I was not using the correct definition of the layer points and container points. Inside the handler, event is different from Leaflet's internal mouse event (where the location is available from e.latlng).
I looked through Leaflet's core to find the answer. Getting the location from event requires taking the Mouse Event -> Container Point -> Layer Point -> latLng. Thankfully, the Leaflet developers already programmed a nice function for this: mouseEventToLatLng().
/*
* This is a custom handler to check if someone has control clicked
* the map and print the location of the click
*/
L.CtrlClickHandler = L.Handler.extend({
addHooks: function() {
L.DomEvent.on(document, 'click', this._captureClick, this);
},
removeHooks: function() {
L.DomEvent.off(document, 'click', this._captureClick, this);
},
_captureClick: function(event) {
if (event.ctrlKey) {
// translate mouse event to lat/lng (note: `mouseEventToLatLng()`
// calls Leaflet's `mouseEventToContainerPoint()` followed by
// `containerPointToLayerPoint()` and finally `layerPointToLatLng()`)
var latlng = map.mouseEventToLatLng(event);
console.log('Handler detected CTRL + click at ' + latlng);
}
}
});
// add this to all maps
L.Map.addInitHook('addHandler', 'ctrlClick', L.CtrlClickHandler);
Live example with Leaflet 1.0.1: https://jsfiddle.net/c4tkyewz/1/
Also tested with Leaflet 0.7.7.
As a bonus, to access the CTRL key directly from Leaflet's native handling of the click event map.on('click', function(e) {});, use e.originalEvent.ctrlKey.
Related
I have been trying to close an info bubble when the map only is clicked, but I can't achieve it.
I have tried the following
this.map.addEventListener("tap", this.handleMapClick);
private handleMapClick(evt: Event) {
this.clearOpenInformationBubble();
}
However, the tap event is triggered even when maps objects are clicked meaning that the info bubble remains closed when I click a marker.
I have also tried to add a 'blur' event listener to the bubble element, but that doesn't seem to work
const bubble = new H.ui.InfoBubble(evt.target.getGeometry(), {
content: ...
});
this.ui.addBubble(bubble);
bubble.getElement().focus()
bubble.getElement().addEventListener('blur', evt => {
this.clearOpenInformationBubble();
})
I was wondering if there is a way to listen for an event triggered by a map ONLY tap.
Here's a similar implementation in Google maps.
google.maps.event.addDomListener(map, "click", function() {
alert('Map clicked')
});
I think I'd try the answer here. In this answer, they wanted to close any other open infobubbles first. I believe if you remove the event listener on your group, keep the one on your map, and always remove open infobubbles you would be good. Then add logic to see if the event target value has data and show an infobubble.
This assumes your doing infobubbles based on markers having a data object associated with them.
Edit:
I was able to get this to work - again assuming your use case is markers to infobubbles.
map.addEventListener('tap', evt => {
ui.getBubbles().forEach(bub => ui.removeBubble(bub));
if(!evt.target.getData) return;
// for all objects that it contains
var bubble = new H.ui.InfoBubble(evt.target.getGeometry(), {
// read custom data
content: evt.target.getData()
});
// show info bubble
ui.addBubble(bubble);
});
You can check the event target in the callback function.
If it is the map, then only you close the InfoBubble:
this.map.addEventListener("tap", this.handleMapClick);
private handleMapClick(evt: Event) {
if (evt.target === this.map) {
this.clearOpenInformationBubble();
}
}
```
I need to handle a zoom event in Open Layers 3.
The following is my code:
map_object = new ol.Map({
target: 'map',
controls: controls_list,
interactions: interactions_list,
overlays: [overlay],
layers: [OSM_raster, WFS_layer],
view: view
});
map_object.on("Zoom", function() {
console.log('Zooming...');
});
This code runs with no errors and shows a map, but there is no output to the console, suggesting this function isn't firing.
I have also tried:
map_object.on("drag", function() {
console.log('Dragging...');
});
And this too does nothing.
Any help as to how to handle map control events in OL3 would be much appreciated (particularly zooming!). Note I have tried 'zoom' as well as 'Zoom' for the type field of the on method.
Just to add to this, you can check variations of events available with 'propertychange', from what I am seeing, there is no explicit .on ('zoom', ...) but rather you can access 'resolution' and other properties as mentioned in previous comments:
map.getView().on('propertychange', function(e) {
switch (e.key) {
case 'resolution':
console.log(e.oldValue);
break;
}
});
try with moveend event. (see https://openlayers.org/en/latest/apidoc/module-ol_MapEvent-MapEvent.html#event:moveend).
As mentioned by tonio, the way to listen on zoom change, which is called resolution change in openlayers terminology, is with
map.getView().on('change:resolution', (event) => {
console.log(event);
});
I find this is better (more succinct, less cruft) than listening on the general propertychange and verifying manually if the change concerns resolution.
This fires rapidly when using the mouse button so throttling it might be a good idea before launching any computation that waits for it to change.
Documentation for View
You can manage the moveend event...
We will need a global variable to alocate map’s view zoom level. I’ve
named it as currentZoomLevel.
There is available a moveend event. Let’s use it, and add a zoom level
check function..
In case of there’s a new zoom level, we trigger a zoomend event to
DOM’s document.
Finally we will need to add zoomend listener to the document element.
var = currentZoomLevel;
map.on('moveend', checknewzoom);
function checknewzoom(evt)
{
var newZoomLevel = map.getView().getZoom();
if (newZoomLevel != currentZoomLevel)
{
currentZoomLevel = newZoomLevel;
$(document).trigger("zoomend", zoomend_event);
}
}
$(document).on('zoomend', function () {
console.log("Zoom");
//Your code here
});
Source
On this website:
http://www.crunchpanorama.com/
you use the google maps zoom control slider to zoom and recluster markers. I want to capture this event as well and perform actions accordingly.
Problem is when reading google docs, all I could find is the zoom_changed event of map. However, this event is not only called when changing zoom using slider, but also when clicking marker (which zooms into marker). So zoom_changed will not help me:
google.maps.event.addListener(map, 'zoom_changed', function () {
...
I want to be able to target the zoom change on the control slider specifically. How can I go about this?
Maybe there's a workaround similar to this:
var self = this;
google.maps.event.addListener(marker, 'click', function() {
// Set a boolean variable to true to indicate a marker/cluster has been clicked
self._markerClicked = true;
});
google.maps.event.addListener(map, 'zoom_changed', function() {
// Check the boolean variable and run your code if it's false
if ( !self._markerClicked ) {
// Take action here
} else {
// Reset variable back to false
self._markerClicked = false;
}
});
Ran into this recently. This is the best solution I have found if you want to capture when the map default zoom buttons are clicked. This assumes that the zoom buttons are the only ones in the div map container.
$("#id-container-name").on("click", "button", whateverfunction());
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/
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 = $('TestLink').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.
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: TestLink<script> $(".speciallink").on("click", function(){alert("hello from inside the popup")});</script>';
marker.bindPopup(popup_content);
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!');});
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');
});
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);
}
}`
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 .
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/
I want to know when a Google Maps zoom_changed event is fired specifically by a user interaction with the +/- zoom buttons. If I use a general event listener for zoom_changed, I can't tell if it is a user-generated event or a zoom change caused by something like fitBounds(). Looking for the best way to do this.
I've tried the following things, none of which seem to work:
1) Looked for event information on zoom_changed. There appears to be none.
2) Add listeners for mouseover and mouseout that let me set a flag to see if the user is in the map bounds, and check the flag on zoom_changed. This doesn't work because the map does not consider the zoom buttons as part of the map frame (in other words, hovering over zoom buttons triggers the mouseout event).
3) Add a normal (non-gMap) listener to the zoom buttons. However, I can't find a definitive CSS selector that will allow me to grab just the buttons.
4) Looked for a function in the gMaps API that would let me do something like getZoomElements(), and then I could set listeners using that.
The weird thing is I can clearly do what I want if I add a custom control to the map. It seems very odd that they would force me to do that instead of having a hook into the default zoom controls.
I wouldn't just hook in to the +/- buttons (or buttons on your own custom control for that matter). The user can change the zoom on the map with the mouse wheel, or by double-clicking on the map. Plus, you'd be relying on implementation detail rather than documented API, which is a major no-no.
This really means the only reliable, documented way to detect a change in zoom is to listen to the zoom_changed event of the Map.
If your event handler can't determine whether the event came from user action or an API call, there's two approaches:
Set a flag before calling an API function so that you know you can ignore this change.
Can you re-architect your app such that it does not matter whether a zoom change came from code or the user?
I solved this issue by creating Custom Zoom buttons for my Map.
Here is the code from my project:
Edit: Removed unnecessary and self explanatory common code
your zoomControl function:
function zoomControl(map, div) {
var controlDiv = div,
control = this;
// Set styles to your own pa DIV
controlDiv.style.padding = '5px';
// Set CSS for the zoom in div.
var zoomIncrease = document.createElement('div');
zoomIncrease.title = 'Click to zoom in';
// etc.
// Set CSS for the zoom in interior.
var zoomIncreaseText = document.createElement('div');
// Use custom image
zoomIncreaseText.innerHTML = '<strong><img src="./images/plusBut.png" width="30px" height="30px"/></strong>';
zoomIncrease.appendChild(zoomIncreaseText);
// Set CSS for the zoom out div, in asimilar way
var zoomDecreaseText = document.createElement('div');
// .. Code .. Similar to above
// Set CSS for the zoom out interior.
// .. Code ..
// Setup the click event listener for Zoom Increase:
google.maps.event.addDomListener(zoomIncrease, 'click', function() {
zoom = MainMap.getZoom();
MainMap.setZoom(zoom+1);
// Other Code parts
});
// Setup the click event listener for Zoom decrease:
google.maps.event.addDomListener(zoomDecrease, 'click', function() {
zoom = MainMap.getZoom();
MainMap.setZoom(zoom-1);
});
}
your initialize function:
function initializeMap() {
var latlng = new google.maps.LatLng(38.6, -98);
var options = {
zoom : 5,
center : latlng,
mapTypeId : google.maps.MapTypeId.ROADMAP,
// Other Options
};
MainMap = new google.maps.Map(document.getElementById("google-map-canvas"),
options);
// The main part - Create your own Custom Zoom Buttons
// Create the DIV to hold the control and call the zoomControl()
var zoomControlDiv = document.createElement('div'),
zoomLevelControl = new zoomControl(MainMap, zoomControlDiv);
zoomControlDiv.index = 1;
MainMap.controls[google.maps.ControlPosition.RIGHT_TOP].push(zoomControlDiv);
}
Hope it helps
var zoomFlag = "user"; // always assume it's user unless otherwise
// some method changing the zoom through API
zoomFlag = "api";
map.setZoom(map.getZoom() - 1);
zoomFlag = "user";
// google maps event handler
zoom_changed: function() {
if (zoomFlag === "user") {
// user zoom
}
}
I was looking to do the same thing. I was hoping to find that there was a way built into the Google Maps API, but at a minimum, you should be able to store the starting zoom level as a variable when you initialize the map. Then, compare the result of getZoom() to it to know whether it was a zoom in or a zoom out.
For example:
map = new google.maps.Map(document.getElementById('map_canvas'), { zoom: 11 });
var previous_zoom = 11;
google.maps.event.addListener(map,'zoom_changed',function(){
if(map.getZoom() > previous_zoom) {
alert('You just zoomed in.');
}
previous_zoom = map.getZoom();
}
I would suggest using a custom control for zoom in/out and then using event listeners on the custom congrol:
http://goo.gl/u8gKC
You can easily hide the default zoom control:
http://goo.gl/N5HIE
(zoomControl to be specific)