MDL-32843 import YUI 3.5.1
[moodle.git] / lib / yui / 3.5.1 / build / event-move / event-move.js
blob03344b3c5d6a0fe0c0466d25e781a304e93227c8
1 /*
2 YUI 3.5.1 (build 22)
3 Copyright 2012 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
7 YUI.add('event-move', function(Y) {
9 /**
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.
13  *
14  * @module event-gestures
15  * @submodule event-move
16  */
18 var EVENT = ((Y.config.win && ("ontouchstart" in Y.config.win)) && !(Y.UA.chrome && Y.UA.chrome < 6)) ? {
19         start: "touchstart",
20         move: "touchmove",
21         end: "touchend"
22     } : {
23         start: "mousedown",
24         move: "mousemove",
25         end: "mouseup"
26     },
28     START = "start",
29     MOVE = "move",
30     END = "end",
32     GESTURE_MOVE = "gesture" + MOVE,
33     GESTURE_MOVE_END = GESTURE_MOVE + END,
34     GESTURE_MOVE_START = GESTURE_MOVE + START,
36     _MOVE_START_HANDLE = "_msh",
37     _MOVE_HANDLE = "_mh",
38     _MOVE_END_HANDLE = "_meh",
40     _DEL_MOVE_START_HANDLE = "_dmsh",
41     _DEL_MOVE_HANDLE = "_dmh",
42     _DEL_MOVE_END_HANDLE = "_dmeh",
44     _MOVE_START = "_ms",
45     _MOVE = "_m",
47     MIN_TIME = "minTime",
48     MIN_DISTANCE = "minDistance",
49     PREVENT_DEFAULT = "preventDefault",
50     BUTTON = "button",
51     OWNER_DOCUMENT = "ownerDocument",
53     CURRENT_TARGET = "currentTarget",
54     TARGET = "target",
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;
64         }
66         return config;
67     },
69     _getRoot = function(node, subscriber) {
70         return subscriber._extra.root || (node.get(NODE_TYPE) === 9) ? node : node.get(OWNER_DOCUMENT);
71     },
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)
84     },
86     _prevent = function(e, preventDefault) {
87         if (preventDefault) {
88             // preventDefault is a boolean or a function
89             if (!preventDefault.call || preventDefault(e)) {
90                 e.preventDefault();
91             }
92         }
93     },
95     define = Y.Event.define;
97 /**
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().
102  * 
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
108  * @for YUI
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:
113  * <dl>
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>
123  * </dl>
125  * @return {EventHandle} the detach handle
126  */
128 define(GESTURE_MOVE_START, {
130     on: function (node, subscriber, ce) {
132         subscriber[_MOVE_START_HANDLE] = node.on(EVENT[START], 
133             this._onStart,
134             this,
135             node,
136             subscriber,
137             ce);
138     },
140     delegate : function(node, subscriber, ce, filter) {
142         var se = this;
144         subscriber[_DEL_MOVE_START_HANDLE] = node.delegate(EVENT[START],
145             function(e) {
146                 se._onStart(e, node, subscriber, ce, true);
147             },
148             filter);
149     },
151     detachDelegate : function(node, subscriber, ce, filter) {
152         var handle = subscriber[_DEL_MOVE_START_HANDLE];
154         if (handle) {
155             handle.detach();
156             subscriber[_DEL_MOVE_START_HANDLE] = null;
157         }
158     },
160     detach: function (node, subscriber, ce) {
161         var startHandle = subscriber[_MOVE_START_HANDLE];
163         if (startHandle) {
164             startHandle.detach();
165             subscriber[_MOVE_START_HANDLE] = null;
166         }
167     },
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;
174         }
176         if (!(MIN_DISTANCE in params)) {
177             params[MIN_DISTANCE] = this.MIN_DISTANCE;
178         }
180         return params;
181     },
183     _onStart : function(e, node, subscriber, ce, delegate) {
185         if (delegate) {
186             node = e[CURRENT_TARGET];
187         }
189         var params = subscriber._extra,
190             fireStart = true,
191             minTime = params[MIN_TIME],
192             minDistance = params[MIN_DISTANCE],
193             button = params.button,
194             preventDefault = params[PREVENT_DEFAULT],
195             root = _getRoot(node, subscriber),
196             startXY;
198         if (e.touches) {
199             if (e.touches.length === 1) {
200                 _normTouchFacade(e, e.touches[0], params);
201             } else {
202                 fireStart = false;
203             }
204         } else {
205             fireStart = (button === undefined) || (button === e.button);
206         }
209         if (fireStart) {
211             _prevent(e, preventDefault);
213             if (minTime === 0 || minDistance === 0) {
214                 this._start(e, node, ce, params);
216             } else {
218                 startXY = [e.pageX, e.pageY];
220                 if (minTime > 0) {
223                     params._ht = Y.later(minTime, this, this._start, [e, node, ce, params]);
225                     params._hme = root.on(EVENT[END], Y.bind(function() {
226                         this._cancel(params);
227                     }, this));
228                 }
230                 if (minDistance > 0) {
233                     params._hm = root.on(EVENT[MOVE], Y.bind(function(em) {
234                         if (Math.abs(em.pageX - startXY[0]) > minDistance || Math.abs(em.pageY - startXY[1]) > minDistance) {
235                             this._start(e, node, ce, params);
236                         }
237                     }, this));
238                 }                        
239             }
240         }
241     },
243     _cancel : function(params) {
244         if (params._ht) {
245             params._ht.cancel();
246             params._ht = null;
247         }
248         if (params._hme) {
249             params._hme.detach();
250             params._hme = null;
251         }
252         if (params._hm) {
253             params._hm.detach();
254             params._hm = null;
255         }
256     },
258     _start : function(e, node, ce, params) {
260         if (params) {
261             this._cancel(params);
262         }
264         e.type = GESTURE_MOVE_START;
267         node.setData(_MOVE_START, e);
268         ce.fire(e);
269     },
271     MIN_TIME : 0,
272     MIN_DISTANCE : 0,
273     PREVENT_DEFAULT : false
277  * Sets up a "gesturemove" event, that is fired on touch devices in response to a single finger "touchmove",
278  * and on mouse based devices in response to a "mousemove".
279  * 
280  * <p>By default this event is only fired when the same node
281  * has received a "gesturemovestart" event. The subscriber can set standAlone to true, in the configuration properties,
282  * if they want to listen for this event without an initial "gesturemovestart".</p>
283  * 
284  * <p>By default this event sets up it's internal "touchmove" and "mousemove" DOM listeners on the document element. The subscriber
285  * can set the root configuration property, to specify which node to attach DOM listeners to, if different from the document.</p> 
287  * <p>This event can also be listened for using node.delegate().</p>
289  * <p>It is recommended that you use Y.bind to set up context and additional arguments for your event handler,
290  * however if you want to pass the context and arguments as additional signature arguments to on/delegate, 
291  * 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>
293  * @event gesturemove
294  * @for YUI
295  * @param type {string} "gesturemove"
296  * @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.
297  * @param cfg {Object} Optional. An object which specifies:
298  * <dl>
299  * <dt>standAlone (defaults to false)</dt>
300  * <dd>true, if the subscriber should be notified even if a "gesturemovestart" has not occured on the same node.</dd>
301  * <dt>root (defaults to document)</dt>
302  * <dd>The node to which the internal DOM listeners should be attached.</dd>
303  * <dt>preventDefault (defaults to false)</dt>
304  * <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>
305  * </dl>
307  * @return {EventHandle} the detach handle
308  */
309 define(GESTURE_MOVE, {
311     on : function (node, subscriber, ce) {
313         var root = _getRoot(node, subscriber),
315             moveHandle = root.on(EVENT[MOVE], 
316                 this._onMove,
317                 this,
318                 node,
319                 subscriber,
320                 ce);
322         subscriber[_MOVE_HANDLE] = moveHandle;
323     },
325     delegate : function(node, subscriber, ce, filter) {
327         var se = this;
329         subscriber[_DEL_MOVE_HANDLE] = node.delegate(EVENT[MOVE],
330             function(e) {
331                 se._onMove(e, node, subscriber, ce, true);
332             },
333             filter);
334     },
336     detach : function (node, subscriber, ce) {
337         var moveHandle = subscriber[_MOVE_HANDLE];
339         if (moveHandle) {
340             moveHandle.detach();
341             subscriber[_MOVE_HANDLE] = null;
342         }
343     },
344     
345     detachDelegate : function(node, subscriber, ce, filter) {
346         var handle = subscriber[_DEL_MOVE_HANDLE];
348         if (handle) {
349             handle.detach();
350             subscriber[_DEL_MOVE_HANDLE] = null;
351         }
353     },
355     processArgs : function(args, delegate) {
356         return _defArgsProcessor(this, args, delegate);
357     },
359     _onMove : function(e, node, subscriber, ce, delegate) {
361         if (delegate) {
362             node = e[CURRENT_TARGET];
363         }
365         var fireMove = subscriber._extra.standAlone || node.getData(_MOVE_START),
366             preventDefault = subscriber._extra.preventDefault;
369         if (fireMove) {
371             if (e.touches) {
372                 if (e.touches.length === 1) {
373                     _normTouchFacade(e, e.touches[0]);                    
374                 } else {
375                     fireMove = false;
376                 }
377             }
379             if (fireMove) {
381                 _prevent(e, preventDefault);
384                 e.type = GESTURE_MOVE;
385                 ce.fire(e);
386             }
387         }
388     },
389     
390     PREVENT_DEFAULT : false
394  * Sets up a "gesturemoveend" event, that is fired on touch devices in response to a single finger "touchend",
395  * and on mouse based devices in response to a "mouseup".
396  * 
397  * <p>By default this event is only fired when the same node
398  * has received a "gesturemove" or "gesturemovestart" event. The subscriber can set standAlone to true, in the configuration properties,
399  * if they want to listen for this event without a preceding "gesturemovestart" or "gesturemove".</p>
401  * <p>By default this event sets up it's internal "touchend" and "mouseup" DOM listeners on the document element. The subscriber
402  * can set the root configuration property, to specify which node to attach DOM listeners to, if different from the document.</p> 
404  * <p>This event can also be listened for using node.delegate().</p>
406  * <p>It is recommended that you use Y.bind to set up context and additional arguments for your event handler,
407  * however if you want to pass the context and arguments as additional signature arguments to on/delegate, 
408  * 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>
411  * @event gesturemoveend
412  * @for YUI
413  * @param type {string} "gesturemoveend"
414  * @param fn {function} The method the event invokes. It receives the event facade of the underlying DOM event (mouseup or touchend.changedTouches[0]).
415  * @param cfg {Object} Optional. An object which specifies:
416  * <dl>
417  * <dt>standAlone (defaults to false)</dt>
418  * <dd>true, if the subscriber should be notified even if a "gesturemovestart" or "gesturemove" has not occured on the same node.</dd>
419  * <dt>root (defaults to document)</dt>
420  * <dd>The node to which the internal DOM listeners should be attached.</dd>
421  * <dt>preventDefault (defaults to false)</dt>
422  * <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>
423  * </dl>
425  * @return {EventHandle} the detach handle
426  */
427 define(GESTURE_MOVE_END, {
429     on : function (node, subscriber, ce) {
431         var root = _getRoot(node, subscriber),
433             endHandle = root.on(EVENT[END], 
434                 this._onEnd, 
435                 this,
436                 node,
437                 subscriber, 
438                 ce);
440         subscriber[_MOVE_END_HANDLE] = endHandle;
441     },
443     delegate : function(node, subscriber, ce, filter) {
445         var se = this;
447         subscriber[_DEL_MOVE_END_HANDLE] = node.delegate(EVENT[END],
448             function(e) {
449                 se._onEnd(e, node, subscriber, ce, true);
450             },
451             filter);
452     },
454     detachDelegate : function(node, subscriber, ce, filter) {
455         var handle = subscriber[_DEL_MOVE_END_HANDLE];
457         if (handle) {
458             handle.detach();
459             subscriber[_DEL_MOVE_END_HANDLE] = null;
460         }
462     },
464     detach : function (node, subscriber, ce) {
465         var endHandle = subscriber[_MOVE_END_HANDLE];
466     
467         if (endHandle) {
468             endHandle.detach();
469             subscriber[_MOVE_END_HANDLE] = null;
470         }
471     },
473     processArgs : function(args, delegate) {
474         return _defArgsProcessor(this, args, delegate);
475     },
477     _onEnd : function(e, node, subscriber, ce, delegate) {
479         if (delegate) {
480             node = e[CURRENT_TARGET];
481         }
483         var fireMoveEnd = subscriber._extra.standAlone || node.getData(_MOVE) || node.getData(_MOVE_START),
484             preventDefault = subscriber._extra.preventDefault;
486         if (fireMoveEnd) {
488             if (e.changedTouches) {
489                 if (e.changedTouches.length === 1) {
490                     _normTouchFacade(e, e.changedTouches[0]);                    
491                 } else {
492                     fireMoveEnd = false;
493                 }
494             }
496             if (fireMoveEnd) {
498                 _prevent(e, preventDefault);
500                 e.type = GESTURE_MOVE_END;
501                 ce.fire(e);
503                 node.clearData(_MOVE_START);
504                 node.clearData(_MOVE);
505             }
506         }
507     },
509     PREVENT_DEFAULT : false
513 }, '3.5.1' ,{requires:['node-base','event-touch','event-synthetic']});