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']});