[OpenLayers-Commits] r11572 - in sandbox/tschaub/click:
lib/OpenLayers/Handler tests/Handler
commits-20090109 at openlayers.org
commits-20090109 at openlayers.org
Sun Feb 27 20:57:52 EST 2011
Author: tschaub
Date: 2011-02-27 17:57:51 -0800 (Sun, 27 Feb 2011)
New Revision: 11572
Modified:
sandbox/tschaub/click/lib/OpenLayers/Handler/Click.js
sandbox/tschaub/click/tests/Handler/Click.html
Log:
Applying patch for #3133.
Modified: sandbox/tschaub/click/lib/OpenLayers/Handler/Click.js
===================================================================
--- sandbox/tschaub/click/lib/OpenLayers/Handler/Click.js 2011-02-28 01:54:21 UTC (rev 11571)
+++ sandbox/tschaub/click/lib/OpenLayers/Handler/Click.js 2011-02-28 01:57:51 UTC (rev 11572)
@@ -51,13 +51,21 @@
* constructed.
*/
pixelTolerance: 0,
-
+
/**
+ * APIProperty: dblclickTolerance
+ * {Number} Maximum distance in pixels between clicks for a sequence of
+ * events to be considered a double click. Default is 10. If the
+ * distance between two clicks is greater than this value, a double-
+ * click will not be fired.
+ */
+ dblclickTolerance: 10,
+
+ /**
* APIProperty: stopSingle
* {Boolean} Stop other listeners from being notified of clicks. Default
- * is false. If true, any click listeners registered before this one
- * will not be notified of *any* click event (associated with double
- * or single clicks).
+ * is false. If true, any listeners registered before this one for
+ * click or rightclick events will not be notified.
*/
stopSingle: false,
@@ -82,6 +90,13 @@
* {Number} The id of the timeout waiting to clear the <delayedCall>.
*/
timerId: null,
+
+ /**
+ * Property: touch
+ * {Boolean} When a touchstart event is fired, touch will be true and all
+ * mouse related listeners will do nothing.
+ */
+ touch: false,
/**
* Property: down
@@ -92,15 +107,22 @@
*/
down: null,
- /**
+ /**
* Property: last
* {Object} Object that store relevant information about the last
- * touchmove. Its 'xy' OpenLayers.Pixel property gives
+ * mousemove or touchmove. Its 'xy' OpenLayers.Pixel property gives
* the average location of the mouse/touch event. Its 'touches'
* property records clientX/clientY of each touches.
*/
last: null,
+ /**
+ * Property: first
+ * {Object} When waiting for double clicks, this object will store
+ * information about the first click in a two click sequence.
+ */
+ first: null,
+
/**
* Property: rightclickTimerId
* {Number} The id of the right mouse timeout waiting to clear the
@@ -126,35 +148,60 @@
*/
initialize: function(control, callbacks, options) {
OpenLayers.Handler.prototype.initialize.apply(this, arguments);
- // optionally register for mousedown
- if(this.pixelTolerance != null) {
- this.mousedown = function(evt) {
- this.down = this.getEventInfo(evt);
- return true;
- };
- }
},
/**
- * Method: mousedown
- * Handle mousedown. Only registered as a listener if pixelTolerance is
- * a non-zero value at construction.
+ * Method: touchstart
+ * Handle touchstart.
*
* Returns:
* {Boolean} Continue propagating this event.
*/
- mousedown: null,
+ touchstart: function(evt) {
+ this.touch = true;
+ this.down = this.getEventInfo(evt);
+ this.last = this.getEventInfo(evt);
+ return true;
+ },
+
+ /**
+ * Method: touchmove
+ * Store position of last move, because touchend event can have
+ * an empty "touches" property.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ touchmove: function(evt) {
+ this.last = this.getEventInfo(evt);
+ return true;
+ },
/**
- * Method: touchstart
- * Handle touchstart.
+ * Method: touchend
+ * Correctly set event xy property, and add lastTouches to have
+ * touches property from last touchstart or touchmove
+ */
+ touchend: function(evt) {
+ // touchend doesn't come with a pixel position or touches
+ evt.xy = this.last.xy;
+ evt.lastTouches = this.last.touches;
+ this.handleSingle(evt);
+ return true;
+ },
+
+ /**
+ * Method: mousedown
+ * Handle mousedown.
*
* Returns:
* {Boolean} Continue propagating this event.
*/
- touchstart: function(evt) {
- this.down = this.getEventInfo(evt);
- this.last = null;
+ mousedown: function(evt) {
+ if (!this.touch) {
+ this.down = this.getEventInfo(evt);
+ this.last = this.getEventInfo(evt);
+ }
return true;
},
@@ -171,8 +218,7 @@
// Collect right mouse clicks from the mouseup
// IE - ignores the second right click in mousedown so using
// mouseup instead
- if(this.checkModifiers(evt) &&
- this.control.handleRightClicks &&
+ if (this.checkModifiers(evt) && this.control.handleRightClicks &&
OpenLayers.Event.isRightClick(evt)) {
propagate = this.rightclick(evt);
}
@@ -194,7 +240,7 @@
if(this.rightclickTimerId != null) {
//Second click received before timeout this must be
// a double click
- this.clearTimer();
+ this.clearTimer();
this.callback('dblrightclick', [evt]);
return !this.stopDouble;
} else {
@@ -227,93 +273,97 @@
if (evt) {
this.callback('rightclick', [evt]);
}
- return !this.stopSingle;
},
/**
+ * Method: click
+ * Handle click events from the browser. This is registered as a listener
+ * for click events and should not be called from other events in this
+ * handler.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ click: function(evt) {
+ if (!this.touch) {
+ if (!this.last) {
+ this.last = this.getEventInfo(evt);
+ }
+ this.handleSingle(evt);
+ }
+ return !this.stopSingle;
+ },
+
+ /**
* Method: dblclick
* Handle dblclick. For a dblclick, we get two clicks in some browsers
* (FF) and one in others (IE). So we need to always register for
- * dblclick to properly handle single clicks.
+ * dblclick to properly handle single clicks. This method is registered
+ * as a listener for the dblclick browser event. It should *not* be
+ * called by other methods in this handler.
*
* Returns:
* {Boolean} Continue propagating this event.
*/
dblclick: function(evt) {
- // for touch devices trigger dblclick only for
- // "one finger" touch
- var last = this.down || this.last;
- if (this.passesTolerance(evt) &&
- (!last || !last.touches || last.touches.length == 1)) {
- if(this["double"]) {
- this.callback('dblclick', [evt]);
- }
- this.clearTimer();
+ if (!this.touch) {
+ this.handleDouble(evt);
}
return !this.stopDouble;
},
- /**
- * Method: touchmove
- * Store position of last move, because touchend event can have
- * an empty "touches" property.
+ /**
+ * Method: handleDouble
+ * Handle double-click sequence.
*/
- touchmove: function(evt) {
- this.last = this.getEventInfo(evt);
- },
-
- /**
- * Method: touchend
- * Correctly set event xy property, and add lastTouches to have
- * touches property from last touchstart or touchmove
- */
- touchend: function(evt) {
- var last = this.last || this.down;
- if (!evt || !last) {
- return false;
+ handleDouble: function(evt) {
+ if (this["double"] && this.passesDblclickTolerance(evt)) {
+ this.callback("dblclick", [evt]);
}
- evt.xy = last.xy;
- evt.lastTouches = last.touches;
- return evt.xy ? this.click(evt) : false;
},
-
- /**
- * Method: click
- * Handle click.
- *
- * Returns:
- * {Boolean} Continue propagating this event.
+
+ /**
+ * Method: handleSingle
+ * Handle single click sequence.
*/
- click: function(evt) {
- // Sencha Touch emulates click events, see ticket 3079 for more info
- if (this.down && this.down.touches && evt.type === "click") {
- return !this.stopSingle;
- }
- if(this.passesTolerance(evt)) {
- if(this.timerId != null) {
+ handleSingle: function(evt) {
+ if (this.passesTolerance(evt)) {
+ if (this.timerId != null) {
// already received a click
- var last = this.down || this.last;
- if (last && last.touches && last.touches.length > 0) {
- // touch device - we may trigger dblclick
- this.dblclick(evt);
- } else {
+ if (this.last.touches && this.last.touches.length === 1) {
+ // touch device, no dblclick event - this may be a double
+ this.handleDouble(evt);
+ }
+ // if we're not in a touch environment we clear the click timer
+ // if we've got a second touch, we'll get two touchend events
+ if (!this.last.touches || this.last.touches.length !== 2) {
this.clearTimer();
}
} else {
+ // remember the first click info so we can compare to the second
+ this.first = this.getEventInfo(evt);
// set the timer, send evt only if single is true
//use a clone of the event object because it will no longer
//be a valid event object in IE in the timer callback
var clickEvent = this.single ?
OpenLayers.Util.extend({}, evt) : null;
- this.timerId = window.setTimeout(
- OpenLayers.Function.bind(this.delayedCall, this, clickEvent),
- this.delay
- );
+ this.queuePotentialClick(clickEvent);
}
}
- return !this.stopSingle;
},
+ /**
+ * Method: queuePotentialClick
+ * This method is separated out largely to make testing easier (so we
+ * don't have to override window.setTimeout)
+ */
+ queuePotentialClick: function(evt) {
+ this.timerId = window.setTimeout(
+ OpenLayers.Function.bind(this.delayedCall, this, evt),
+ this.delay
+ );
+ },
+
/**
* Method: passesTolerance
* Determine whether the event is within the optional pixel tolerance. Note
@@ -327,28 +377,38 @@
*/
passesTolerance: function(evt) {
var passes = true;
- if(this.pixelTolerance != null && this.down && this.down.xy) {
- var dpx = Math.sqrt(
- Math.pow(this.down.xy.x - evt.xy.x, 2) +
- Math.pow(this.down.xy.y - evt.xy.y, 2)
- );
- if(dpx > this.pixelTolerance) {
- passes = false;
- }
+ if (this.pixelTolerance != null && this.down && this.down.xy) {
+ passes = this.pixelTolerance >= this.down.xy.distanceTo(evt.xy);
}
return passes;
},
+
+ /**
+ * Method: passesDblclickTolerance
+ * Determine whether the event is within the optional double-cick pixel
+ * tolerance.
+ *
+ * Returns:
+ * {Boolean} The click is within the double-click pixel tolerance.
+ */
+ passesDblclickTolerance: function(evt) {
+ var passes = true;
+ if (this.down && this.first) {
+ passes = this.down.xy.distanceTo(this.first.xy) <= this.dblclickTolerance;
+ }
+ return passes;
+ },
/**
* Method: clearTimer
* Clear the timer and set <timerId> to null.
*/
clearTimer: function() {
- if(this.timerId != null) {
+ if (this.timerId != null) {
window.clearTimeout(this.timerId);
this.timerId = null;
}
- if(this.rightclickTimerId != null) {
+ if (this.rightclickTimerId != null) {
window.clearTimeout(this.rightclickTimerId);
this.rightclickTimerId = null;
}
@@ -361,8 +421,8 @@
*/
delayedCall: function(evt) {
this.timerId = null;
- if(evt) {
- this.callback('click', [evt]);
+ if (evt) {
+ this.callback("click", [evt]);
}
},
@@ -407,7 +467,7 @@
if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
this.clearTimer();
this.down = null;
- this.last = null;
+ this.first = null;
deactivated = true;
}
return deactivated;
Modified: sandbox/tschaub/click/tests/Handler/Click.html
===================================================================
--- sandbox/tschaub/click/tests/Handler/Click.html 2011-02-28 01:54:21 UTC (rev 11571)
+++ sandbox/tschaub/click/tests/Handler/Click.html 2011-02-28 01:57:51 UTC (rev 11572)
@@ -102,12 +102,16 @@
var callbackMap;
function callbackSetup(log, options) {
- callbackMap = new OpenLayers.Map('map', {controls: []});
+ callbackMap = new OpenLayers.Map({
+ div: "map",
+ controls: [], // no controls here because these tests use a custom setTimeout and we only want setTimeout calls from a single handler
+ layers: [new OpenLayers.Layer(null, {isBaseLayer: true})],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 1
+ });
+ var control = new OpenLayers.Control();
+ callbackMap.addControl(control);
- var control = {
- map: callbackMap
- };
-
var callbacks = {
"click": function(evt) {
log.push({callback: "click", evt: evt});
@@ -119,6 +123,7 @@
var handler = new OpenLayers.Handler.Click(control, callbacks, options);
handler.activate();
+
var timers = {};
window._setTimeout = window.setTimeout;
window.setTimeout = function(func, delay) {
@@ -270,13 +275,140 @@
handler.map.events.triggerEvent("mouseup", up);
handler.map.events.triggerEvent("click", up);
-
t.eq(log.length, 0, "nothing logged - event outside tolerance");
callbackTeardown();
}
+ function test_callbacks_within_dblclickTolerance(t) {
+ t.plan(6);
+
+ var log = [];
+ var handler = callbackSetup(log, {single: false, "double": true, dblclickTolerance: 8});
+
+ var first = {
+ xy: px(0, 0)
+ };
+ var second = {
+ xy: px(0, 5)
+ };
+
+ handler.map.events.triggerEvent("mousedown", first);
+ handler.map.events.triggerEvent("mouseup", first);
+ handler.map.events.triggerEvent("click", first);
+ t.eq(log.length, 1, "one item logged");
+ t.eq(log[0] && log[0].method, "setTimeout", "setTimeout called");
+
+ handler.map.events.triggerEvent("mousedown", second);
+ handler.map.events.triggerEvent("mouseup", second);
+ handler.map.events.triggerEvent("click", second);
+ t.eq(log.length, 2, "two events logged");
+ t.eq(log[1] && log[1].method, "clearTimeout", "clearTimeout called");
+
+ handler.map.events.triggerEvent("dblclick", second);
+ t.eq(log.length, 3, "three items logged");
+ t.eq(log[2] && log[2].callback, "dblclick", "dblclick callback called");
+
+ callbackTeardown();
+ }
+
+ function test_callbacks_outside_dblclickTolerance(t) {
+ t.plan(5);
+
+ var log = [];
+ // default dblclickTolerance is 10
+ var handler = callbackSetup(log, {single: false, "double": true});
+
+ var first = {
+ xy: px(0, 0)
+ };
+ var second = {
+ xy: px(10.5, 0)
+ };
+
+ handler.map.events.triggerEvent("mousedown", first);
+ handler.map.events.triggerEvent("mouseup", first);
+ handler.map.events.triggerEvent("click", first);
+ t.eq(log.length, 1, "one item logged");
+ t.eq(log[0] && log[0].method, "setTimeout", "setTimeout called");
+
+ handler.map.events.triggerEvent("mousedown", second);
+ handler.map.events.triggerEvent("mouseup", second);
+ handler.map.events.triggerEvent("click", second);
+ t.eq(log.length, 2, "two items logged");
+ t.eq(log[1] && log[1].method, "clearTimeout", "clearTimeout called");
+
+ handler.map.events.triggerEvent("dblclick", second);
+ t.eq(log.length, 2, "still two items logged - dblclick callback is not called");
+
+ callbackTeardown();
+ }
+
+ function test_callbacks_multitouch_single(t) {
+
+ t.plan(2);
+
+ var log = [];
+
+ var callbacks = {
+ click: function(evt) {
+ log.push({callback: "click", type: evt.type});
+ },
+ dblclick: function(evt) {
+ log.push({callback: "dblclick", type: evt.type});
+ }
+ };
+
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer(null, {isBaseLayer: true});
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 1);
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler.Click(
+ control, callbacks,
+ {"double": true, single: true, pixelTolerance: 2}
+ );
+
+ // we override here so we don't have to wait for the timeout
+ handler.queuePotentialClick = function(evt) {
+ log.push({potential: true, evt: evt});
+ OpenLayers.Handler.Click.prototype.queuePotentialClick.call(this, evt);
+ }
+
+ handler.activate();
+
+ function handle(o) {
+ var touches = [];
+ if (("x0" in o) && ("y0" in o)) {
+ touches.push({
+ clientX: o.x0, clientY: o.y0
+ });
+ }
+ if (("x1" in o) && ("y1" in o)) {
+ touches.push({
+ clientX: o.x1, clientY: o.y1
+ });
+ }
+ handler.map.events.handleBrowserEvent({
+ type: o.type, touches: touches
+ });
+ }
+
+ // a typical multitouch sequence goes like this:
+ // touchstart, touchstart, touchend, touchend
+ handle({type: "touchstart", x0: 10, y0: 10});
+ handle({type: "touchstart", x0: 10, y0: 10, x1: 30, y1: 15});
+ handle({type: "touchend"});
+ handle({type: "touchend"});
+
+ t.eq(log.length, 1, "one item logged");
+ t.eq(log[0] && log[0].potential, true, "click in queue - no dblclick called");
+
+ map.destroy();
+ }
+
function test_Handler_Click_deactivate(t) {
t.plan(4);
var control = {
@@ -395,84 +527,125 @@
});
}
- function test_touch_ignoresimulatedclick(t) {
- t.plan(2);
+ function test_touch_within_dblclickTolerance(t) {
+ t.plan(4);
- // set up
-
var log;
- var map = new OpenLayers.Map('map');
- var control = {map: map};
-
var callbacks = {
- 'dblclick': function(e) {
- log.dblclick = {x: e.xy.x, y: e.xy.y,
- lastTouches: e.lastTouches};
+ click: function(evt) {
+ log.push({callback: "click", type: evt.type});
+ },
+ dblclick: function(evt) {
+ log.push({callback: "dblclick", type: evt.type});
}
};
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer(null, {isBaseLayer: true});
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 1);
+ var control = new OpenLayers.Control();
+ map.addControl(control);
var handler = new OpenLayers.Handler.Click(
- control, callbacks,
- {'double': true, pixelTolerance: null});
+ control, callbacks,
+ {"double": true, single: true, pixelTolerance: 2}
+ );
+ handler.activate();
+
+ function handle(type, x, y) {
+ map.events.handleBrowserEvent({
+ type: type,
+ touches: [
+ {clientX: x, clientY: y}
+ ]
+ });
+ }
// test
+ log = [];
+ // sequence of two clicks on a touch device
+ // click 1
+ handle("touchstart", 10, 10);
+ handle("touchend", 11, 10);
+ handle("mousemove", 11, 10);
+ handle("mousedown", 10, 10);
+ handle("mouseup", 11, 10);
+ handle("click", 11, 10);
+ // click 2
+ handle("touchstart", 12, 10);
+ handle("touchend", 12, 10);
+ handle("mousedown", 12, 10);
+ handle("mouseup", 12, 10);
+ handle("click", 12, 10);
- log = {};
- handler.touchstart({xy: px(1, 1), touches: ["foo"]});
- handler.touchend({});
- handler.touchstart({xy: px(1, 1), touches: ["foo"]});
- handler.touchend({type: "click"});
+ t.eq(log.length, 1, "one callback called");
+ t.eq(log[0] && log[0].callback, "dblclick", "click callback called");
+ t.eq(log[0] && log[0].type, "touchend", "click callback called with touchend event");
+ t.ok(!handler.timerId, "handler doesn't have a timerId waiting for click")
- t.eq(!!handler.down.touches, true, "Handler down touches property should be truthy");
-
- t.ok(log.dblclick == undefined, "dblclick callback not called with simulated click");
-
// tear down
map.destroy();
}
- function test_touch_dblclick(t) {
- t.plan(5);
+ function test_touch_outside_dblclickTolerance(t) {
+ t.plan(2);
- // set up
-
var log;
- var map = new OpenLayers.Map('map');
- var control = {map: map};
-
var callbacks = {
- 'click': function(e) {
- log.click = {x: e.xy.x, y: e.xy.y,
- lastTouches: e.lastTouches};
+ click: function(evt) {
+ log.push({callback: "click", type: evt.type});
},
- 'dblclick': function(e) {
- log.dblclick = {x: e.xy.x, y: e.xy.y,
- lastTouches: e.lastTouches};
+ dblclick: function(evt) {
+ log.push({callback: "dblclick", type: evt.type});
}
};
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer(null, {isBaseLayer: true});
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 1);
+ var control = new OpenLayers.Control();
+ map.addControl(control);
var handler = new OpenLayers.Handler.Click(
- control, callbacks,
- {'double': true, pixelTolerance: null});
+ control, callbacks,
+ {"double": true, single: true, pixelTolerance: 2, dblclickTolerance: 8}
+ );
+ handler.activate();
+
+ function handle(type, x, y) {
+ var touches = [];
+ if (x !== undefined && y !== undefined) {
+ touches.push({
+ clientX: x, clientY: y
+ });
+ }
+ map.events.handleBrowserEvent({
+ type: type, touches: touches
+ });
+ }
// test
+ log = [];
+ // sequence of two clicks on a touch device
+ // click 1
+ handle("touchstart", 10, 10);
+ handle("touchend");
+ handle("mousemove", 11, 10);
+ handle("mousedown", 10, 10);
+ handle("mouseup", 11, 10);
+ handle("click", 11, 10);
+ // click 2
+ handle("touchstart", 20, 10);
+ handle("touchend");
+ handle("mousedown", 20, 10);
+ handle("mouseup", 20, 10);
+ handle("click", 20, 10);
- log = {};
- handler.touchstart({xy: px(1, 1), touches: [{clientX:0, clientY:10}]});
- handler.touchend({});
- handler.touchstart({xy: px(1, 1), touches: [{clientX:0, clientY:10}]});
- handler.touchend({});
+ t.eq(log.length, 0, "no callbacks called");
+ t.ok(!handler.timerId, "handler doesn't have a timerId waiting for click")
- t.eq(log.click, undefined, "click callback not called");
- t.ok(log.dblclick != undefined, "dblclick callback called");
- if(log.dblclick != undefined) {
- t.eq(log.dblclick.x, 1, "evt.xy.x as expected");
- t.eq(log.dblclick.y, 1, "evt.xy.y as expected");
- t.ok(log.dblclick.lastTouches, "evt.lastTouches on evt");
- }
-
// tear down
map.destroy();
}
More information about the Commits
mailing list