3
Copyright 2012 Yahoo! Inc. All rights reserved.
4
Licensed under the BSD License.
5
http://yuilibrary.com/license/
7
YUI.add('event-move', function(Y) {
10
* Adds lower level support for "gesturemovestart", "gesturemove" and "gesturemoveend" events, which can be used to create drag/drop
11
* interactions which work across touch and mouse input devices. They correspond to "touchstart", "touchmove" and "touchend" on a touch input
12
* device, and "mousedown", "mousemove", "mouseup" on a mouse based input device.
14
* @module event-gestures
15
* @submodule event-move
18
var EVENT = ((Y.config.win && ("ontouchstart" in Y.config.win)) && !(Y.UA.chrome && Y.UA.chrome < 6)) ? {
32
GESTURE_MOVE = "gesture" + MOVE,
33
GESTURE_MOVE_END = GESTURE_MOVE + END,
34
GESTURE_MOVE_START = GESTURE_MOVE + START,
36
_MOVE_START_HANDLE = "_msh",
38
_MOVE_END_HANDLE = "_meh",
40
_DEL_MOVE_START_HANDLE = "_dmsh",
41
_DEL_MOVE_HANDLE = "_dmh",
42
_DEL_MOVE_END_HANDLE = "_dmeh",
48
MIN_DISTANCE = "minDistance",
49
PREVENT_DEFAULT = "preventDefault",
51
OWNER_DOCUMENT = "ownerDocument",
53
CURRENT_TARGET = "currentTarget",
56
NODE_TYPE = "nodeType",
58
_defArgsProcessor = function(se, args, delegate) {
59
var iConfig = (delegate) ? 4 : 3,
60
config = (args.length > iConfig) ? Y.merge(args.splice(iConfig,1)[0]) : {};
62
if (!(PREVENT_DEFAULT in config)) {
63
config[PREVENT_DEFAULT] = se.PREVENT_DEFAULT;
69
_getRoot = function(node, subscriber) {
70
return subscriber._extra.root || (node.get(NODE_TYPE) === 9) ? node : node.get(OWNER_DOCUMENT);
73
_normTouchFacade = function(touchFacade, touch, params) {
74
touchFacade.pageX = touch.pageX;
75
touchFacade.pageY = touch.pageY;
76
touchFacade.screenX = touch.screenX;
77
touchFacade.screenY = touch.screenY;
78
touchFacade.clientX = touch.clientX;
79
touchFacade.clientY = touch.clientY;
80
touchFacade[TARGET] = touchFacade[TARGET] || touch[TARGET];
81
touchFacade[CURRENT_TARGET] = touchFacade[CURRENT_TARGET] || touch[CURRENT_TARGET];
83
touchFacade[BUTTON] = (params && params[BUTTON]) || 1; // default to left (left as per vendors, not W3C which is 0)
86
_prevent = function(e, preventDefault) {
88
// preventDefault is a boolean or a function
89
if (!preventDefault.call || preventDefault(e)) {
95
define = Y.Event.define;
98
* Sets up a "gesturemovestart" event, that is fired on touch devices in response to a single finger "touchstart",
99
* and on mouse based devices in response to a "mousedown". The subscriber can specify the minimum time
100
* and distance thresholds which should be crossed before the "gesturemovestart" is fired and for the mouse,
101
* which button should initiate a "gesturemovestart". This event can also be listened for using node.delegate().
103
* <p>It is recommended that you use Y.bind to set up context and additional arguments for your event handler,
104
* however if you want to pass the context and arguments as additional signature arguments to on/delegate,
105
* you need to provide a null value for the configuration object, e.g: <code>node.on("gesturemovestart", fn, null, context, arg1, arg2, arg3)</code></p>
107
* @event gesturemovestart
109
* @param type {string} "gesturemovestart"
110
* @param fn {function} The method the event invokes. It receives the event facade of the underlying DOM event (mousedown or touchstart.touches[0]) which contains position co-ordinates.
111
* @param cfg {Object} Optional. An object which specifies:
114
* <dt>minDistance (defaults to 0)</dt>
115
* <dd>The minimum distance threshold which should be crossed before the gesturemovestart is fired</dd>
116
* <dt>minTime (defaults to 0)</dt>
117
* <dd>The minimum time threshold for which the finger/mouse should be help down before the gesturemovestart is fired</dd>
118
* <dt>button (no default)</dt>
119
* <dd>In the case of a mouse input device, if the event should only be fired for a specific mouse button.</dd>
120
* <dt>preventDefault (defaults to false)</dt>
121
* <dd>Can be set to true/false to prevent default behavior as soon as the touchstart or mousedown is received (that is before minTime or minDistance thresholds are crossed, and so before the gesturemovestart listener is notified) so that things like text selection and context popups (on touch devices) can be
122
* prevented. This property can also be set to a function, which returns true or false, based on the event facade passed to it (for example, DragDrop can determine if the target is a valid handle or not before preventing default).</dd>
125
* @return {EventHandle} the detach handle
128
define(GESTURE_MOVE_START, {
130
on: function (node, subscriber, ce) {
132
subscriber[_MOVE_START_HANDLE] = node.on(EVENT[START],
140
delegate : function(node, subscriber, ce, filter) {
144
subscriber[_DEL_MOVE_START_HANDLE] = node.delegate(EVENT[START],
146
se._onStart(e, node, subscriber, ce, true);
151
detachDelegate : function(node, subscriber, ce, filter) {
152
var handle = subscriber[_DEL_MOVE_START_HANDLE];
156
subscriber[_DEL_MOVE_START_HANDLE] = null;
160
detach: function (node, subscriber, ce) {
161
var startHandle = subscriber[_MOVE_START_HANDLE];
164
startHandle.detach();
165
subscriber[_MOVE_START_HANDLE] = null;
169
processArgs : function(args, delegate) {
170
var params = _defArgsProcessor(this, args, delegate);
172
if (!(MIN_TIME in params)) {
173
params[MIN_TIME] = this.MIN_TIME;
176
if (!(MIN_DISTANCE in params)) {
177
params[MIN_DISTANCE] = this.MIN_DISTANCE;
183
_onStart : function(e, node, subscriber, ce, delegate) {
186
node = e[CURRENT_TARGET];
189
var params = subscriber._extra,
191
minTime = params[MIN_TIME],
192
minDistance = params[MIN_DISTANCE],
193
button = params.button,
194
preventDefault = params[PREVENT_DEFAULT],
195
root = _getRoot(node, subscriber),
199
if (e.touches.length === 1) {
200
_normTouchFacade(e, e.touches[0], params);
205
fireStart = (button === undefined) || (button === e.button);
208
Y.log("gesturemovestart: params = button:" + button + ", minTime = " + minTime + ", minDistance = " + minDistance, "event-gestures");
212
_prevent(e, preventDefault);
214
if (minTime === 0 || minDistance === 0) {
215
Y.log("gesturemovestart: No minTime or minDistance. Firing immediately", "event-gestures");
216
this._start(e, node, ce, params);
220
startXY = [e.pageX, e.pageY];
224
Y.log("gesturemovestart: minTime specified. Setup timer.", "event-gestures");
225
Y.log("gesturemovestart: initialTime for minTime = " + new Date().getTime(), "event-gestures");
227
params._ht = Y.later(minTime, this, this._start, [e, node, ce, params]);
229
params._hme = root.on(EVENT[END], Y.bind(function() {
230
this._cancel(params);
234
if (minDistance > 0) {
236
Y.log("gesturemovestart: minDistance specified. Setup native mouse/touchmove listener to measure distance.", "event-gestures");
237
Y.log("gesturemovestart: initialXY for minDistance = " + startXY, "event-gestures");
239
params._hm = root.on(EVENT[MOVE], Y.bind(function(em) {
240
if (Math.abs(em.pageX - startXY[0]) > minDistance || Math.abs(em.pageY - startXY[1]) > minDistance) {
241
Y.log("gesturemovestart: minDistance hit.", "event-gestures");
242
this._start(e, node, ce, params);
250
_cancel : function(params) {
256
params._hme.detach();
265
_start : function(e, node, ce, params) {
268
this._cancel(params);
271
e.type = GESTURE_MOVE_START;
273
Y.log("gesturemovestart: Firing start: " + new Date().getTime(), "event-gestures");
275
node.setData(_MOVE_START, e);
281
PREVENT_DEFAULT : false
285
* Sets up a "gesturemove" event, that is fired on touch devices in response to a single finger "touchmove",
286
* and on mouse based devices in response to a "mousemove".
288
* <p>By default this event is only fired when the same node
289
* has received a "gesturemovestart" event. The subscriber can set standAlone to true, in the configuration properties,
290
* if they want to listen for this event without an initial "gesturemovestart".</p>
292
* <p>By default this event sets up it's internal "touchmove" and "mousemove" DOM listeners on the document element. The subscriber
293
* can set the root configuration property, to specify which node to attach DOM listeners to, if different from the document.</p>
295
* <p>This event can also be listened for using node.delegate().</p>
297
* <p>It is recommended that you use Y.bind to set up context and additional arguments for your event handler,
298
* however if you want to pass the context and arguments as additional signature arguments to on/delegate,
299
* you need to provide a null value for the configuration object, e.g: <code>node.on("gesturemove", fn, null, context, arg1, arg2, arg3)</code></p>
303
* @param type {string} "gesturemove"
304
* @param fn {function} The method the event invokes. It receives the event facade of the underlying DOM event (mousemove or touchmove.touches[0]) which contains position co-ordinates.
305
* @param cfg {Object} Optional. An object which specifies:
307
* <dt>standAlone (defaults to false)</dt>
308
* <dd>true, if the subscriber should be notified even if a "gesturemovestart" has not occured on the same node.</dd>
309
* <dt>root (defaults to document)</dt>
310
* <dd>The node to which the internal DOM listeners should be attached.</dd>
311
* <dt>preventDefault (defaults to false)</dt>
312
* <dd>Can be set to true/false to prevent default behavior as soon as the touchmove or mousemove is received. As with gesturemovestart, can also be set to function which returns true/false based on the event facade passed to it.</dd>
315
* @return {EventHandle} the detach handle
317
define(GESTURE_MOVE, {
319
on : function (node, subscriber, ce) {
321
var root = _getRoot(node, subscriber),
323
moveHandle = root.on(EVENT[MOVE],
330
subscriber[_MOVE_HANDLE] = moveHandle;
333
delegate : function(node, subscriber, ce, filter) {
337
subscriber[_DEL_MOVE_HANDLE] = node.delegate(EVENT[MOVE],
339
se._onMove(e, node, subscriber, ce, true);
344
detach : function (node, subscriber, ce) {
345
var moveHandle = subscriber[_MOVE_HANDLE];
349
subscriber[_MOVE_HANDLE] = null;
353
detachDelegate : function(node, subscriber, ce, filter) {
354
var handle = subscriber[_DEL_MOVE_HANDLE];
358
subscriber[_DEL_MOVE_HANDLE] = null;
363
processArgs : function(args, delegate) {
364
return _defArgsProcessor(this, args, delegate);
367
_onMove : function(e, node, subscriber, ce, delegate) {
370
node = e[CURRENT_TARGET];
373
var fireMove = subscriber._extra.standAlone || node.getData(_MOVE_START),
374
preventDefault = subscriber._extra.preventDefault;
376
Y.log("onMove initial fireMove check:" + fireMove,"event-gestures");
381
if (e.touches.length === 1) {
382
_normTouchFacade(e, e.touches[0]);
390
_prevent(e, preventDefault);
392
Y.log("onMove second fireMove check:" + fireMove,"event-gestures");
394
e.type = GESTURE_MOVE;
400
PREVENT_DEFAULT : false
404
* Sets up a "gesturemoveend" event, that is fired on touch devices in response to a single finger "touchend",
405
* and on mouse based devices in response to a "mouseup".
407
* <p>By default this event is only fired when the same node
408
* has received a "gesturemove" or "gesturemovestart" event. The subscriber can set standAlone to true, in the configuration properties,
409
* if they want to listen for this event without a preceding "gesturemovestart" or "gesturemove".</p>
411
* <p>By default this event sets up it's internal "touchend" and "mouseup" DOM listeners on the document element. The subscriber
412
* can set the root configuration property, to specify which node to attach DOM listeners to, if different from the document.</p>
414
* <p>This event can also be listened for using node.delegate().</p>
416
* <p>It is recommended that you use Y.bind to set up context and additional arguments for your event handler,
417
* however if you want to pass the context and arguments as additional signature arguments to on/delegate,
418
* you need to provide a null value for the configuration object, e.g: <code>node.on("gesturemoveend", fn, null, context, arg1, arg2, arg3)</code></p>
421
* @event gesturemoveend
423
* @param type {string} "gesturemoveend"
424
* @param fn {function} The method the event invokes. It receives the event facade of the underlying DOM event (mouseup or touchend.changedTouches[0]).
425
* @param cfg {Object} Optional. An object which specifies:
427
* <dt>standAlone (defaults to false)</dt>
428
* <dd>true, if the subscriber should be notified even if a "gesturemovestart" or "gesturemove" has not occured on the same node.</dd>
429
* <dt>root (defaults to document)</dt>
430
* <dd>The node to which the internal DOM listeners should be attached.</dd>
431
* <dt>preventDefault (defaults to false)</dt>
432
* <dd>Can be set to true/false to prevent default behavior as soon as the touchend or mouseup is received. As with gesturemovestart, can also be set to function which returns true/false based on the event facade passed to it.</dd>
435
* @return {EventHandle} the detach handle
437
define(GESTURE_MOVE_END, {
439
on : function (node, subscriber, ce) {
441
var root = _getRoot(node, subscriber),
443
endHandle = root.on(EVENT[END],
450
subscriber[_MOVE_END_HANDLE] = endHandle;
453
delegate : function(node, subscriber, ce, filter) {
457
subscriber[_DEL_MOVE_END_HANDLE] = node.delegate(EVENT[END],
459
se._onEnd(e, node, subscriber, ce, true);
464
detachDelegate : function(node, subscriber, ce, filter) {
465
var handle = subscriber[_DEL_MOVE_END_HANDLE];
469
subscriber[_DEL_MOVE_END_HANDLE] = null;
474
detach : function (node, subscriber, ce) {
475
var endHandle = subscriber[_MOVE_END_HANDLE];
479
subscriber[_MOVE_END_HANDLE] = null;
483
processArgs : function(args, delegate) {
484
return _defArgsProcessor(this, args, delegate);
487
_onEnd : function(e, node, subscriber, ce, delegate) {
490
node = e[CURRENT_TARGET];
493
var fireMoveEnd = subscriber._extra.standAlone || node.getData(_MOVE) || node.getData(_MOVE_START),
494
preventDefault = subscriber._extra.preventDefault;
498
if (e.changedTouches) {
499
if (e.changedTouches.length === 1) {
500
_normTouchFacade(e, e.changedTouches[0]);
508
_prevent(e, preventDefault);
510
e.type = GESTURE_MOVE_END;
513
node.clearData(_MOVE_START);
514
node.clearData(_MOVE);
519
PREVENT_DEFAULT : false
523
}, '3.5.1' ,{requires:['node-base','event-touch','event-synthetic']});