disable boxZoom when control key active - leaflet

I am using the area select plugin. By default it responds to a ctrlKey box drag. And by default Leaflet boxZoom responds to a shiftKey box drag. All good so far. However a ctrlKey + shiftKey box drag triggers the Leaflet boxZoom and the area select plugin. I would like it to trigger just the area select plugin instead. Any suggestions?

If you have a look at Leaflet's source code for the BoxZoom map handler, you can see the line where it checks for a pressed shift key plus primary (""left"") mouse/pointer button to start the box zoom:
_onMouseDown: function (e) {
if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
And you want to change that to check for ctrlKey, so that the box zoom doesn't start if it's set to true, something like:
if (!e.shiftKey || e.ctrlKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
The question is how to do this without rewriting or breaking up everything. An approach is to monkey-patch that method from the BoxZoom handler's prototype while keeping a reference to the old one, e.g. something like:
var oldBoxZoomMouseDown = L.Map.BoxZoom.prototype._onMouseDown;
L.Map.BoxZoom.prototype._onMouseDown = function(ev) {
// Worry only about ctrlKey...
if (ev.ctrlKey) { return false; }
// ...and let the previous event handler worry about shift and primary button
oldBoxZoomMouseDown.call(this, ev);
}
Note that it'll work only when done before the map has been instantiated. There are other approaches, such as replacing the method of the BoxZoom instance after the map has been instantiated, and creating a subclass of the BoxZoom handler. Reading about javascript's prototypal inheritance and Leaflet's way of dealing with OOP is recommended at this point.

Related

Why doesn't marker.dragging.disable() work?

The following code receives an error on the lines for enabling and disabling the marker dragging ("Unable to get property 'disable' of undefined or null reference"). The markers show up on the map just fine and are draggable as the creation line indicates. Placing an alert in place of the enable line produces a proper object so I believe the marker is defined. Is there something I need to do to enable the IHandler interface? Or am I missing something else?
var marker = L.marker(L.latLng(lat,lon), {icon:myIcon, draggable:'true'})
.bindLabel(name, {noHide: true,direction: 'right'});
marker._myId = name;
if (mode === 0) {
marker.dragging.enable();
} else {
marker.dragging.disable();
}
I had a similar problem today (perhaps the same one) it was due to a bug in leaflet (see leaflet issue #2578) where changing the icon of a marker invalidates any drag handling set on that marker. This makes any calls to marker.dragging.disable() fail.
The fix hasn't made it into leaflets master at time of writing. A workaround is to change the icon after updating the draggable status if possible.
marker.dragging.disable();
marker.setIcon(marker_icon);
Use the following code to make an object draggable. Set elementToDrag to the object you wish to make draggable, which is in your case: "marker"
var draggable = new L.Draggable(elementToDrag);
draggable.enable();
To disable dragging, use the following code:
draggable.disable()
A class for making DOM elements draggable (including touch support).
Used internally for map and marker dragging. Only works for elements
that were positioned with DomUtil#setPosition
leaflet: Draggable
If you wish to only disable the drag option of a marker, then you can use the following code (where "marker" is the name of your marker object):
marker.dragging.disable();
marker.dragging.enable();
I haven't found an answer but my workaround was this:
var temp;
if (mode === 0) {
temp = true;
} else {
temp = false;
}
var marker = L.marker(L.latLng(lat,lon), {icon:myIcon, draggable:temp})
.bindLabel(name, {noHide: true,direction: 'right'});
marker._myId = name;
Fortunately I change my icon when it is draggable.

Mobile Safari: Disable scrolling pages "out of screen"

I want to block scrolling page "out of the iPhone screen" (when gray Safari's background behind the page border is visible). To do this, I'm cancelling touchmove event:
// Disables scrolling the page out of the screen.
function DisableTouchScrolling()
{
document.addEventListener("touchmove", function TouchHandler(e) { e.preventDefault(); }, true);
}
Unfortunately, this also disables mousemove event: when I tap on a button then move my finger out of it, then release the screen, the button's onclick event is triggered anyway.
I've tried mapping touch events on mouse events, as desribed here: http://ross.posterous.com/2008/08/19/iphone-touch-events-in-javascript/, but to no avail (the same behavior).
Any ideas?
From what I understand of your question, you've attempted to combine the code you've presented above with the code snippet provided by Ross Boucher on Posterous. Attempting to combine these two snippets back-to-back won't work, because in disabling touchmove, you've also disabled the shim that allows mousemove to work via his sample.
This question and its answers sketch out a workable solution to your problem. You should try these two snippets to see if they resolve your issue:
This snippet, which disables the old scrolling behavior:
elementYouWantToScroll.ontouchmove = function(e) {
e.stopPropagation();
};
Or this one, from the same:
document.ontouchmove = function(e) {
var target = e.currentTarget;
while(target) {
if(checkIfElementShouldScroll(target))
return;
target = target.parentNode;
}
e.preventDefault();
};
Then, drop in the code on Posterous:
function touchHandler(event)
{
var touches = event.changedTouches,
first = touches[0],
type = "";
switch(event.type)
{
case "touchstart": type = "mousedown"; break;
case "touchmove": type="mousemove"; break;
case "touchend": type="mouseup"; break;
default: return;
}
//initMouseEvent(type, canBubble, cancelable, view, clickCount,
// screenX, screenY, clientX, clientY, ctrlKey,
// altKey, shiftKey, metaKey, button, relatedTarget);
var simulatedEvent = document.createEvent("MouseEvent");
simulatedEvent.initMouseEvent(type, true, true, window, 1,
first.screenX, first.screenY,
first.clientX, first.clientY, false,
false, false, false, 0/*left*/, null);
first.target.dispatchEvent(simulatedEvent);
event.preventDefault();
}
And that should do it for you. If it doesn't, something else isn't working with Mobile Safari.
Unfortunately I haven't had the time to check out to above yet but was working on an identical problem and found that the nesting of elements in the DOM and which relation you apply it to affects the handler a lot (guess the above solves that, too - 'var target = e.currentTarget').
I used a slightly different approach (I'd love feedback on) by basically using a class "locked" that I assign to every element which (including all its children) i don't want the site to scroll when someone touchmoves on it.
E.g. in HTML:
<header class="locked">...</header>
<div id="content">...</div>
<footer class="locked"></div>
Then I have an event-listener running on that class (excuse my lazy jquery-selector):
$('.ubq_locked').on('touchmove', function(e) {
e.preventDefault();
});
This works pretty well for me on iOs and Android and at least gives me the control to not attach the listener to an element which I know causes problems. You do need to watch your z-index values by the way.
Plus I only attach the listener if it is a touch-device, e.g. like this:
function has_touch() {
var isTouchPad = (/hp-tablet/gi).test(navigator.appVersion);
return 'ontouchstart' in window && !isTouchPad;
}
This way non-touch devices will not be affected.
If you don't want to spam your HTML you could of course just write the selectors into an array and run through those ontouchmove, but I would expect that to be more costly in terms of performance (my knowledge there is limited though). Hope this can help.

can't unregister click event in openlayers

I am having issues with OpenLayers and unregistering the click events that are added to a layer. Basically, what I need to do is this:
When a user clicks on a marker, they get a bubble that has an "edit" link in it. The user clicks that and it creates a new layer on the map and then registers a click event to the map waiting for the user to click on the map. When they click somewhere on the map, it then moves the marker to where they clicked. This all works perfectly.
However, the issue is when the user clicks to edit the marker and then clicks on a button OUTSIDE OF THE MAP to cancel the action and NOT move the marker, the unregister of the click event doesn't work. They can still click on the map and it moves the marker.
Here is a sample of the code:
function move_marker(marker) {
lmLayer = new OpenLayers.Layer.Markers("Landmark Creation",{displayInLayerSwitcher: false});
map.addLayer(lmLayer);
map.events.register("click", lmLayer, function(evt){
var pixel = new OpenLayers.Pixel(evt.clientX,evt.clientY);
position = map.getLonLatFromPixel(pixel);
marker.lonlat = pixel;
marker.moveTo(pixel);
marker.draw();
lmLayer.redraw();
OpenLayers.Event.stop(evt);
});
}
function cancel_move() { // this function is triggered by a button outside of the map element
lmLayer = map.getLayersByName('Landmark Creation');
lmLayer[0].events.unregister("click");
map.events.unregister("click");
map.removeLayer(lmLayer[0]);
}
As you can see in the cancel function, I am getting the layer by the layer name, which according to the console.log it is finding at index 0. I added the unregister to the lmLayer in hopes that would help, but so far, no luck. Then on the map element I add the unregister call and then finally I remove that new layer because we don't want it interfering.
I'd appreciate some feedback on this. I'm losing my mind.
Thanks!
I think you need to tell OpenLayers which click event you want it to unregister:
var method = function() {
// Do stuff...
}
map.events.register('click', map, method);
map.events.unregister('click', map, method);
According to the OpenLayers.Events source it checks whether the scope and the method is present in the listener stack:
unregister: function (type, obj, func) {
if (obj == null) {
obj = this.object;
}
var listeners = this.listeners[type];
if (listeners != null) {
for (var i=0, len=listeners.length; i<len; i++) {
HERE --> if (listeners[i].obj == obj && listeners[i].func == func) { <-- HERE
listeners.splice(i, 1);
break;
}
}
}
},
I hope that works for you :)

Bing map AJAX Control V7 - How does one detect clicks but not drags?

I have a Bing map on my web page, and I want to detect when the user clicks in the window. However, I do not wish to detect when the user drags the map (this also generates a "click" event) . What is the best way to get only "pure" click events?
My solution ended up beeing a manual check if the click position was close to the position where the mouse was pushed down.
Microsoft.Maps.Events.addHandler(map, "click", clickHandler);
Microsoft.Maps.Events.addHandler(map, "mousedown", function(me) { lastMouseDownPoint = new Microsoft.Maps.Point(me.getX(), me.getY());});
function clickHandler(mouseEventArgs){
var point = new Microsoft.Maps.Point(mouseEventArgs.getX(), mouseEventArgs.getY());
//Drag detection
// Edited since the comma is incorrect, should be a plus as per pythagorean theorem
var dist = Math.sqrt(Math.pow(point.x-lastMouseDownPoint.x,2) + Math.pow(point.y-lastMouseDownPoint.y,2));
if(dist > 5) {
// We call this a drag
return;
}
// We have a "pure" click and can process it
}
Very simple:
Microsoft.Maps.Events.addHandler(map, 'click', onClick);
function onClick(e) {
if (e.mouseMoved === false && e.isPrimary === true) {
// Left click not being a drag
...
}
}
mouseMoved is true with a drag and drop and false otherwise.
The MouseEventArgs documentation http://msdn.microsoft.com/en-us/library/gg406731.aspx does not reference mouseMoved :/

What is the proper way in OpenLayers (OSM) to trigger a popup for a feature?

I have the feature ID, I can grab the marker layer on GeoRSS loadend, but I'm still not sure how to cause the popup to appear programmatically.
I'll create the popup on demand if that's necessary, but it seems as though I should be able to get the id of the marker as drawn on the map and call some event on that. I've tried using jQuery and calling the $(marker-id).click() event on the map elements, but that doesn't seem to be working. What am I missing?
Since I was asked for code, and since I presumed it to be boilerplate, here's where I am so far:
map = new OpenLayers.Map('myMap');
map.addLayer(new OpenLayers.Layer.OSM());
map.addLayer(new OpenLayers.Layer.GeoRSS(name,url));
//I've done some stuff as well in re: projections and centering and
//setting extents, but those really don't pertain to this question.
Elsewhere I've done a bit of jQuery templating and built me a nice list of all the points that are being shown on the map. I know how to do a callback from the layer loadend and get the layer object, I know how to retrieve my layer out of the map manually, I know how to iter over the layers collection and find my layer. So I can grab any of those details about the popup, but I still don't know how to go about using the built-in methods of the DOM or of this API to make it as easy as element.click() which is what I would prefer to do.
You don't have to click the feature to open a popup.
First you need a reference to the feature from the feature id. I would do that in the loadend event of the GeoRSS layer, using the markers property on the layer.
Assuming you have a reference to your feature, I would write a method which handles the automatic popup:
var popups = {}; // to be able to handle them later
function addPopup(feature) {
var text = getHtmlContent(feature); // handle the content in a separate function.
var popupId = evt.xy.x + "," + evt.xy.y;
var popup = popups[popupId];
if (!popup || !popup.map) {
popup = new OpenLayers.Popup.Anchored(
popupId,
feature.lonlat,
null,
" ",
null,
true,
function(evt) {
delete popups[this.id];
this.hide();
OpenLayers.Event.stop(evt);
}
);
popup.autoSize = true;
popup.useInlineStyles = false;
popups[popupId] = popup;
feature.layer.map.addPopup(popup, true);
}
popup.setContentHTML(popup.contentHTML + text);
popup.show();
}
fwiw I finally came back to this and did something entirely different, but his answer was a good one.
//I have a list of boxes that contain the information on the map (think google maps)
$('.paginatedItem').live('mouseenter', onFeatureSelected).live('mouseleave',onFeatureUnselected);
function onFeatureSelected(event) {
// I stuff the lookup attribute (I'm lazy) into a global
// a global, because there can be only one
hoveredItem = $(this).attr('lookup');
/* Do something here to indicate the onhover */
// find the layer pagination id
var feature = findFeatureById(hoveredItem);
if (feature) {
// use the pagination id to find the event, and then trigger the click for that event to show the popup
// also, pass a null event, since we don't necessarily have one.
feature.marker.events.listeners.click[0].func.call(feature, event)
}
}
function onFeatureUnselected(event) {
/* Do something here to indicate the onhover */
// find the layer pagination id
var feature = findFeatureById(hoveredItem);
if (feature) {
// use the pagination id to find the event, and then trigger the click for that event to show the popup
// also, pass a null event, since we don't necessarily have one.
feature.marker.events.listeners.click[0].func.call(feature, event)
}
/* Do something here to stop the indication of the onhover */
hoveredItem = null;
}
function findFeatureById(featureId) {
for (var key in map.layers) {
var layer = map.layers[key];
if (layer.hasOwnProperty('features')) {
for (var key1 in layer.features) {
var feature = layer.features[key1];
if (feature.hasOwnProperty('id') && feature.id == featureId) {
return feature;
}
}
}
}
return null;
}
also note that I keep map as a global so I don't have to reacquire it everytime I want to use it