2 Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
8 YUI.add('event-move', function(Y) {
11 * Adds lower level support for "gesturemovestart", "gesturemove" and "gesturemoveend" events, which can be used to create drag/drop
12 * interactions which work across touch and mouse input devices. They correspond to "touchstart", "touchmove" and "touchend" on a touch input
13 * device, and "mousedown", "mousemove", "mouseup" on a mouse based input device.
15 * @module event-gestures
16 * @submodule event-move
19 var EVENT = ("ontouchstart" in Y.config.win && !Y.UA.chrome) ? {
33 GESTURE_MOVE = "gesture" + MOVE,
34 GESTURE_MOVE_END = GESTURE_MOVE + END,
35 GESTURE_MOVE_START = GESTURE_MOVE + START,
37 _MOVE_START_HANDLE = "_msh",
39 _MOVE_END_HANDLE = "_meh",
41 _DEL_MOVE_START_HANDLE = "_dmsh",
42 _DEL_MOVE_HANDLE = "_dmh",
43 _DEL_MOVE_END_HANDLE = "_dmeh",
49 MIN_DISTANCE = "minDistance",
50 PREVENT_DEFAULT = "preventDefault",
52 OWNER_DOCUMENT = "ownerDocument",
54 CURRENT_TARGET = "currentTarget",
57 NODE_TYPE = "nodeType",
59 _defArgsProcessor = function(se, args, delegate) {
60 var iConfig = (delegate) ? 4 : 3,
61 config = (args.length > iConfig) ? Y.merge(args.splice(iConfig,1)[0]) : {};
63 if (!(PREVENT_DEFAULT in config)) {
64 config[PREVENT_DEFAULT] = se.PREVENT_DEFAULT;
70 _getRoot = function(node, subscriber) {
71 return subscriber._extra.root || (node.get(NODE_TYPE) === 9) ? node : node.get(OWNER_DOCUMENT);
74 _normTouchFacade = function(touchFacade, touch, params) {
75 touchFacade.pageX = touch.pageX;
76 touchFacade.pageY = touch.pageY;
77 touchFacade.screenX = touch.screenX;
78 touchFacade.screenY = touch.screenY;
79 touchFacade.clientX = touch.clientX;
80 touchFacade.clientY = touch.clientY;
81 touchFacade[TARGET] = touchFacade[TARGET] || touch[TARGET];
82 touchFacade[CURRENT_TARGET] = touchFacade[CURRENT_TARGET] || touch[CURRENT_TARGET];
84 touchFacade[BUTTON] = (params && params[BUTTON]) || 1; // default to left (left as per vendors, not W3C which is 0)
87 _prevent = function(e, preventDefault) {
89 // preventDefault is a boolean or a function
90 if (!preventDefault.call || preventDefault(e)) {
96 define = Y.Event.define;
99 * Sets up a "gesturemovestart" event, that is fired on touch devices in response to a single finger "touchstart",
100 * and on mouse based devices in response to a "mousedown". The subscriber can specify the minimum time
101 * and distance thresholds which should be crossed before the "gesturemovestart" is fired and for the mouse,
102 * which button should initiate a "gesturemovestart". This event can also be listened for using node.delegate().
104 * <p>It is recommended that you use Y.bind to set up context and additional arguments for your event handler,
105 * however if you want to pass the context and arguments as additional signature arguments to on/delegate,
106 * 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>
108 * @event gesturemovestart
110 * @param type {string} "gesturemovestart"
111 * @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.
112 * @param cfg {Object} Optional. An object which specifies:
115 * <dt>minDistance (defaults to 0)</dt>
116 * <dd>The minimum distance threshold which should be crossed before the gesturemovestart is fired</dd>
117 * <dt>minTime (defaults to 0)</dt>
118 * <dd>The minimum time threshold for which the finger/mouse should be help down before the gesturemovestart is fired</dd>
119 * <dt>button (no default)</dt>
120 * <dd>In the case of a mouse input device, if the event should only be fired for a specific mouse button.</dd>
121 * <dt>preventDefault (defaults to false)</dt>
122 * <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
123 * 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>
126 * @return {EventHandle} the detach handle
129 define(GESTURE_MOVE_START, {
131 on: function (node, subscriber, ce) {
133 subscriber[_MOVE_START_HANDLE] = node.on(EVENT[START],
141 delegate : function(node, subscriber, ce, filter) {
145 subscriber[_DEL_MOVE_START_HANDLE] = node.delegate(EVENT[START],
147 se._onStart(e, node, subscriber, ce, true);
152 detachDelegate : function(node, subscriber, ce, filter) {
153 var handle = subscriber[_DEL_MOVE_START_HANDLE];
157 subscriber[_DEL_MOVE_START_HANDLE] = null;
161 detach: function (node, subscriber, ce) {
162 var startHandle = subscriber[_MOVE_START_HANDLE];
165 startHandle.detach();
166 subscriber[_MOVE_START_HANDLE] = null;
170 processArgs : function(args, delegate) {
171 var params = _defArgsProcessor(this, args, delegate);
173 if (!(MIN_TIME in params)) {
174 params[MIN_TIME] = this.MIN_TIME;
177 if (!(MIN_DISTANCE in params)) {
178 params[MIN_DISTANCE] = this.MIN_DISTANCE;
184 _onStart : function(e, node, subscriber, ce, delegate) {
187 node = e[CURRENT_TARGET];
190 var params = subscriber._extra,
192 minTime = params[MIN_TIME],
193 minDistance = params[MIN_DISTANCE],
194 button = params.button,
195 preventDefault = params[PREVENT_DEFAULT],
196 root = _getRoot(node, subscriber),
200 if (e.touches.length === 1) {
201 _normTouchFacade(e, e.touches[0], params);
206 fireStart = (button === undefined) || (button === e.button);
212 _prevent(e, preventDefault);
214 if (minTime === 0 || minDistance === 0) {
215 this._start(e, node, ce, params);
219 startXY = [e.pageX, e.pageY];
224 params._ht = Y.later(minTime, this, this._start, [e, node, ce, params]);
226 params._hme = root.on(EVENT[END], Y.bind(function() {
227 this._cancel(params);
231 if (minDistance > 0) {
234 params._hm = root.on(EVENT[MOVE], Y.bind(function(em) {
235 if (Math.abs(em.pageX - startXY[0]) > minDistance || Math.abs(em.pageY - startXY[1]) > minDistance) {
236 this._start(e, node, ce, params);
244 _cancel : function(params) {
250 params._hme.detach();
259 _start : function(e, node, ce, params) {
262 this._cancel(params);
265 e.type = GESTURE_MOVE_START;
268 node.setData(_MOVE_START, e);
274 PREVENT_DEFAULT : false
278 * Sets up a "gesturemove" event, that is fired on touch devices in response to a single finger "touchmove",
279 * and on mouse based devices in response to a "mousemove".
281 * <p>By default this event is only fired when the same node
282 * has received a "gesturemovestart" event. The subscriber can set standAlone to true, in the configuration properties,
283 * if they want to listen for this event without an initial "gesturemovestart".</p>
285 * <p>By default this event sets up it's internal "touchmove" and "mousemove" DOM listeners on the document element. The subscriber
286 * can set the root configuration property, to specify which node to attach DOM listeners to, if different from the document.</p>
288 * <p>This event can also be listened for using node.delegate().</p>
290 * <p>It is recommended that you use Y.bind to set up context and additional arguments for your event handler,
291 * however if you want to pass the context and arguments as additional signature arguments to on/delegate,
292 * 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>
296 * @param type {string} "gesturemove"
297 * @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.
298 * @param cfg {Object} Optional. An object which specifies:
300 * <dt>standAlone (defaults to false)</dt>
301 * <dd>true, if the subscriber should be notified even if a "gesturemovestart" has not occured on the same node.</dd>
302 * <dt>root (defaults to document)</dt>
303 * <dd>The node to which the internal DOM listeners should be attached.</dd>
304 * <dt>preventDefault (defaults to false)</dt>
305 * <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>
308 * @return {EventHandle} the detach handle
310 define(GESTURE_MOVE, {
312 on : function (node, subscriber, ce) {
314 var root = _getRoot(node, subscriber),
316 moveHandle = root.on(EVENT[MOVE],
323 subscriber[_MOVE_HANDLE] = moveHandle;
326 delegate : function(node, subscriber, ce, filter) {
330 subscriber[_DEL_MOVE_HANDLE] = node.delegate(EVENT[MOVE],
332 se._onMove(e, node, subscriber, ce, true);
337 detach : function (node, subscriber, ce) {
338 var moveHandle = subscriber[_MOVE_HANDLE];
342 subscriber[_MOVE_HANDLE] = null;
346 detachDelegate : function(node, subscriber, ce, filter) {
347 var handle = subscriber[_DEL_MOVE_HANDLE];
351 subscriber[_DEL_MOVE_HANDLE] = null;
356 processArgs : function(args, delegate) {
357 return _defArgsProcessor(this, args, delegate);
360 _onMove : function(e, node, subscriber, ce, delegate) {
363 node = e[CURRENT_TARGET];
366 var fireMove = subscriber._extra.standAlone || node.getData(_MOVE_START),
367 preventDefault = subscriber._extra.preventDefault;
373 if (e.touches.length === 1) {
374 _normTouchFacade(e, e.touches[0]);
382 _prevent(e, preventDefault);
385 e.type = GESTURE_MOVE;
391 PREVENT_DEFAULT : false
395 * Sets up a "gesturemoveend" event, that is fired on touch devices in response to a single finger "touchend",
396 * and on mouse based devices in response to a "mouseup".
398 * <p>By default this event is only fired when the same node
399 * has received a "gesturemove" or "gesturemovestart" event. The subscriber can set standAlone to true, in the configuration properties,
400 * if they want to listen for this event without a preceding "gesturemovestart" or "gesturemove".</p>
402 * <p>By default this event sets up it's internal "touchend" and "mouseup" DOM listeners on the document element. The subscriber
403 * can set the root configuration property, to specify which node to attach DOM listeners to, if different from the document.</p>
405 * <p>This event can also be listened for using node.delegate().</p>
407 * <p>It is recommended that you use Y.bind to set up context and additional arguments for your event handler,
408 * however if you want to pass the context and arguments as additional signature arguments to on/delegate,
409 * 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>
412 * @event gesturemoveend
414 * @param type {string} "gesturemoveend"
415 * @param fn {function} The method the event invokes. It receives the event facade of the underlying DOM event (mouseup or touchend.changedTouches[0]).
416 * @param cfg {Object} Optional. An object which specifies:
418 * <dt>standAlone (defaults to false)</dt>
419 * <dd>true, if the subscriber should be notified even if a "gesturemovestart" or "gesturemove" has not occured on the same node.</dd>
420 * <dt>root (defaults to document)</dt>
421 * <dd>The node to which the internal DOM listeners should be attached.</dd>
422 * <dt>preventDefault (defaults to false)</dt>
423 * <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>
426 * @return {EventHandle} the detach handle
428 define(GESTURE_MOVE_END, {
430 on : function (node, subscriber, ce) {
432 var root = _getRoot(node, subscriber),
434 endHandle = root.on(EVENT[END],
441 subscriber[_MOVE_END_HANDLE] = endHandle;
444 delegate : function(node, subscriber, ce, filter) {
448 subscriber[_DEL_MOVE_END_HANDLE] = node.delegate(EVENT[END],
450 se._onEnd(e, node, subscriber, ce, true);
455 detachDelegate : function(node, subscriber, ce, filter) {
456 var handle = subscriber[_DEL_MOVE_END_HANDLE];
460 subscriber[_DEL_MOVE_END_HANDLE] = null;
465 detach : function (node, subscriber, ce) {
466 var endHandle = subscriber[_MOVE_END_HANDLE];
470 subscriber[_MOVE_END_HANDLE] = null;
474 processArgs : function(args, delegate) {
475 return _defArgsProcessor(this, args, delegate);
478 _onEnd : function(e, node, subscriber, ce, delegate) {
481 node = e[CURRENT_TARGET];
484 var fireMoveEnd = subscriber._extra.standAlone || node.getData(_MOVE) || node.getData(_MOVE_START),
485 preventDefault = subscriber._extra.preventDefault;
489 if (e.changedTouches) {
490 if (e.changedTouches.length === 1) {
491 _normTouchFacade(e, e.changedTouches[0]);
499 _prevent(e, preventDefault);
501 e.type = GESTURE_MOVE_END;
504 node.clearData(_MOVE_START);
505 node.clearData(_MOVE);
510 PREVENT_DEFAULT : false
514 }, '3.3.0' ,{requires:['node-base','event-touch','event-synthetic']});