NOBUG: Fixed file access permissions
[moodle.git] / lib / yuilib / 3.13.0 / event-move / event-move.js
bloba224acf80b47128314bbf29aa24a19168a8515fb
1 /*
2 YUI 3.13.0 (build 508226d)
3 Copyright 2013 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
8 YUI.add('event-move', function (Y, NAME) {
10 /**
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.
14  *
15  * <p>Documentation for the gesturemove triplet of events can be found on the <a href="../classes/YUI.html#event_gesturemove">YUI</a> global,
16  * along with the other supported events.</p>
18  @example
20      YUI().use('event-move', function (Y) {
21          Y.one('#myNode').on('gesturemovestart', function (e) {
22          });
23          Y.one('#myNode').on('gesturemove', function (e) {
24          });
25          Y.one('#myNode').on('gesturemoveend', function (e) {
26          });
27      });
29  * @module event-gestures
30  * @submodule event-move
31  */
34  var GESTURE_MAP = Y.Event._GESTURE_MAP,
35      EVENT = {
36          start: GESTURE_MAP.start,
37          end: GESTURE_MAP.end,
38          move: GESTURE_MAP.move
39      },
40     START = "start",
41     MOVE = "move",
42     END = "end",
44     GESTURE_MOVE = "gesture" + MOVE,
45     GESTURE_MOVE_END = GESTURE_MOVE + END,
46     GESTURE_MOVE_START = GESTURE_MOVE + START,
48     _MOVE_START_HANDLE = "_msh",
49     _MOVE_HANDLE = "_mh",
50     _MOVE_END_HANDLE = "_meh",
52     _DEL_MOVE_START_HANDLE = "_dmsh",
53     _DEL_MOVE_HANDLE = "_dmh",
54     _DEL_MOVE_END_HANDLE = "_dmeh",
56     _MOVE_START = "_ms",
57     _MOVE = "_m",
59     MIN_TIME = "minTime",
60     MIN_DISTANCE = "minDistance",
61     PREVENT_DEFAULT = "preventDefault",
62     BUTTON = "button",
63     OWNER_DOCUMENT = "ownerDocument",
65     CURRENT_TARGET = "currentTarget",
66     TARGET = "target",
68     NODE_TYPE = "nodeType",
69     SUPPORTS_POINTER = Y.config.win && ("msPointerEnabled" in Y.config.win.navigator),
70     MS_TOUCH_ACTION_COUNT = 'msTouchActionCount',
71     MS_INIT_TOUCH_ACTION = 'msInitTouchAction',
73     _defArgsProcessor = function(se, args, delegate) {
74         var iConfig = (delegate) ? 4 : 3,
75             config = (args.length > iConfig) ? Y.merge(args.splice(iConfig,1)[0]) : {};
77         if (!(PREVENT_DEFAULT in config)) {
78             config[PREVENT_DEFAULT] = se.PREVENT_DEFAULT;
79         }
81         return config;
82     },
84     _getRoot = function(node, subscriber) {
85         return subscriber._extra.root || (node.get(NODE_TYPE) === 9) ? node : node.get(OWNER_DOCUMENT);
86     },
88     //Checks to see if the node is the document, and if it is, returns the documentElement.
89     _checkDocumentElem = function(node) {
90         var elem = node.getDOMNode();
91         if (node.compareTo(Y.config.doc) && elem.documentElement) {
92             return elem.documentElement;
93         }
94         else {
95             return false;
96         }
97     },
99     _normTouchFacade = function(touchFacade, touch, params) {
100         touchFacade.pageX = touch.pageX;
101         touchFacade.pageY = touch.pageY;
102         touchFacade.screenX = touch.screenX;
103         touchFacade.screenY = touch.screenY;
104         touchFacade.clientX = touch.clientX;
105         touchFacade.clientY = touch.clientY;
106         touchFacade[TARGET] = touchFacade[TARGET] || touch[TARGET];
107         touchFacade[CURRENT_TARGET] = touchFacade[CURRENT_TARGET] || touch[CURRENT_TARGET];
109         touchFacade[BUTTON] = (params && params[BUTTON]) || 1; // default to left (left as per vendors, not W3C which is 0)
110     },
112     /*
113     In IE10 touch mode, gestures will not work properly unless the -ms-touch-action CSS property is set to something other than 'auto'. Read http://msdn.microsoft.com/en-us/library/windows/apps/hh767313.aspx for more info. To get around this, we set -ms-touch-action: none which is the same as e.preventDefault() on touch environments. This tells the browser to fire DOM events for all touch events, and not perform any default behavior.
115     The user can over-ride this by setting a more lenient -ms-touch-action property on a node (such as pan-x, pan-y, etc.) via CSS when subscribing to the 'gesturemovestart' event.
116     */
117     _setTouchActions = function (node) {
118         var elem = _checkDocumentElem(node) || node.getDOMNode(),
119             num = node.getData(MS_TOUCH_ACTION_COUNT);
121         //Checks to see if msTouchAction is supported.
122         if (SUPPORTS_POINTER) {
123             if (!num) {
124                 num = 0;
125                 node.setData(MS_INIT_TOUCH_ACTION, elem.style.msTouchAction);
126             }
127             elem.style.msTouchAction = Y.Event._DEFAULT_TOUCH_ACTION;
128             num++;
129             node.setData(MS_TOUCH_ACTION_COUNT, num);
130         }
131     },
133     /*
134     Resets the element's -ms-touch-action property back to the original value, This is called on detach() and detachDelegate().
135     */
136     _unsetTouchActions = function (node) {
137         var elem = _checkDocumentElem(node) || node.getDOMNode(),
138             num = node.getData(MS_TOUCH_ACTION_COUNT),
139             initTouchAction = node.getData(MS_INIT_TOUCH_ACTION);
141         if (SUPPORTS_POINTER) {
142             num--;
143             node.setData(MS_TOUCH_ACTION_COUNT, num);
144             if (num === 0 && elem.style.msTouchAction !== initTouchAction) {
145                 elem.style.msTouchAction = initTouchAction;
146             }
147         }
148     },
150     _prevent = function(e, preventDefault) {
151         if (preventDefault) {
152             // preventDefault is a boolean or a function
153             if (!preventDefault.call || preventDefault(e)) {
154                 e.preventDefault();
155             }
156         }
157     },
159     define = Y.Event.define;
160     Y.Event._DEFAULT_TOUCH_ACTION = 'none';
163  * Sets up a "gesturemovestart" event, that is fired on touch devices in response to a single finger "touchstart",
164  * and on mouse based devices in response to a "mousedown". The subscriber can specify the minimum time
165  * and distance thresholds which should be crossed before the "gesturemovestart" is fired and for the mouse,
166  * which button should initiate a "gesturemovestart". This event can also be listened for using node.delegate().
168  * <p>It is recommended that you use Y.bind to set up context and additional arguments for your event handler,
169  * however if you want to pass the context and arguments as additional signature arguments to on/delegate,
170  * 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>
172  * @event gesturemovestart
173  * @for YUI
174  * @param type {string} "gesturemovestart"
175  * @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.
176  * @param cfg {Object} Optional. An object which specifies:
178  * <dl>
179  * <dt>minDistance (defaults to 0)</dt>
180  * <dd>The minimum distance threshold which should be crossed before the gesturemovestart is fired</dd>
181  * <dt>minTime (defaults to 0)</dt>
182  * <dd>The minimum time threshold for which the finger/mouse should be help down before the gesturemovestart is fired</dd>
183  * <dt>button (no default)</dt>
184  * <dd>In the case of a mouse input device, if the event should only be fired for a specific mouse button.</dd>
185  * <dt>preventDefault (defaults to false)</dt>
186  * <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
187  * 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>
188  * </dl>
190  * @return {EventHandle} the detach handle
191  */
193 define(GESTURE_MOVE_START, {
195     on: function (node, subscriber, ce) {
197         //Set -ms-touch-action on IE10 and set preventDefault to true
198         _setTouchActions(node);
200         subscriber[_MOVE_START_HANDLE] = node.on(EVENT[START],
201             this._onStart,
202             this,
203             node,
204             subscriber,
205             ce);
206     },
208     delegate : function(node, subscriber, ce, filter) {
210         var se = this;
212         subscriber[_DEL_MOVE_START_HANDLE] = node.delegate(EVENT[START],
213             function(e) {
214                 se._onStart(e, node, subscriber, ce, true);
215             },
216             filter);
217     },
219     detachDelegate : function(node, subscriber, ce, filter) {
220         var handle = subscriber[_DEL_MOVE_START_HANDLE];
222         if (handle) {
223             handle.detach();
224             subscriber[_DEL_MOVE_START_HANDLE] = null;
225         }
227         _unsetTouchActions(node);
228     },
230     detach: function (node, subscriber, ce) {
231         var startHandle = subscriber[_MOVE_START_HANDLE];
233         if (startHandle) {
234             startHandle.detach();
235             subscriber[_MOVE_START_HANDLE] = null;
236         }
238         _unsetTouchActions(node);
239     },
241     processArgs : function(args, delegate) {
242         var params = _defArgsProcessor(this, args, delegate);
244         if (!(MIN_TIME in params)) {
245             params[MIN_TIME] = this.MIN_TIME;
246         }
248         if (!(MIN_DISTANCE in params)) {
249             params[MIN_DISTANCE] = this.MIN_DISTANCE;
250         }
252         return params;
253     },
255     _onStart : function(e, node, subscriber, ce, delegate) {
257         if (delegate) {
258             node = e[CURRENT_TARGET];
259         }
261         var params = subscriber._extra,
262             fireStart = true,
263             minTime = params[MIN_TIME],
264             minDistance = params[MIN_DISTANCE],
265             button = params.button,
266             preventDefault = params[PREVENT_DEFAULT],
267             root = _getRoot(node, subscriber),
268             startXY;
270         if (e.touches) {
271             if (e.touches.length === 1) {
272                 _normTouchFacade(e, e.touches[0], params);
273             } else {
274                 fireStart = false;
275             }
276         } else {
277             fireStart = (button === undefined) || (button === e.button);
278         }
281         if (fireStart) {
283             _prevent(e, preventDefault);
285             if (minTime === 0 || minDistance === 0) {
286                 this._start(e, node, ce, params);
288             } else {
290                 startXY = [e.pageX, e.pageY];
292                 if (minTime > 0) {
295                     params._ht = Y.later(minTime, this, this._start, [e, node, ce, params]);
297                     params._hme = root.on(EVENT[END], Y.bind(function() {
298                         this._cancel(params);
299                     }, this));
300                 }
302                 if (minDistance > 0) {
305                     params._hm = root.on(EVENT[MOVE], Y.bind(function(em) {
306                         if (Math.abs(em.pageX - startXY[0]) > minDistance || Math.abs(em.pageY - startXY[1]) > minDistance) {
307                             this._start(e, node, ce, params);
308                         }
309                     }, this));
310                 }
311             }
312         }
313     },
315     _cancel : function(params) {
316         if (params._ht) {
317             params._ht.cancel();
318             params._ht = null;
319         }
320         if (params._hme) {
321             params._hme.detach();
322             params._hme = null;
323         }
324         if (params._hm) {
325             params._hm.detach();
326             params._hm = null;
327         }
328     },
330     _start : function(e, node, ce, params) {
332         if (params) {
333             this._cancel(params);
334         }
336         e.type = GESTURE_MOVE_START;
339         node.setData(_MOVE_START, e);
340         ce.fire(e);
341     },
343     MIN_TIME : 0,
344     MIN_DISTANCE : 0,
345     PREVENT_DEFAULT : false
349  * Sets up a "gesturemove" event, that is fired on touch devices in response to a single finger "touchmove",
350  * and on mouse based devices in response to a "mousemove".
352  * <p>By default this event is only fired when the same node
353  * has received a "gesturemovestart" event. The subscriber can set standAlone to true, in the configuration properties,
354  * if they want to listen for this event without an initial "gesturemovestart".</p>
356  * <p>By default this event sets up it's internal "touchmove" and "mousemove" DOM listeners on the document element. The subscriber
357  * can set the root configuration property, to specify which node to attach DOM listeners to, if different from the document.</p>
359  * <p>This event can also be listened for using node.delegate().</p>
361  * <p>It is recommended that you use Y.bind to set up context and additional arguments for your event handler,
362  * however if you want to pass the context and arguments as additional signature arguments to on/delegate,
363  * 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>
365  * @event gesturemove
366  * @for YUI
367  * @param type {string} "gesturemove"
368  * @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.
369  * @param cfg {Object} Optional. An object which specifies:
370  * <dl>
371  * <dt>standAlone (defaults to false)</dt>
372  * <dd>true, if the subscriber should be notified even if a "gesturemovestart" has not occured on the same node.</dd>
373  * <dt>root (defaults to document)</dt>
374  * <dd>The node to which the internal DOM listeners should be attached.</dd>
375  * <dt>preventDefault (defaults to false)</dt>
376  * <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>
377  * </dl>
379  * @return {EventHandle} the detach handle
380  */
381 define(GESTURE_MOVE, {
383     on : function (node, subscriber, ce) {
385         _setTouchActions(node);
386         var root = _getRoot(node, subscriber, EVENT[MOVE]),
388             moveHandle = root.on(EVENT[MOVE],
389                 this._onMove,
390                 this,
391                 node,
392                 subscriber,
393                 ce);
395         subscriber[_MOVE_HANDLE] = moveHandle;
397     },
399     delegate : function(node, subscriber, ce, filter) {
401         var se = this;
403         subscriber[_DEL_MOVE_HANDLE] = node.delegate(EVENT[MOVE],
404             function(e) {
405                 se._onMove(e, node, subscriber, ce, true);
406             },
407             filter);
408     },
410     detach : function (node, subscriber, ce) {
411         var moveHandle = subscriber[_MOVE_HANDLE];
413         if (moveHandle) {
414             moveHandle.detach();
415             subscriber[_MOVE_HANDLE] = null;
416         }
418         _unsetTouchActions(node);
419     },
421     detachDelegate : function(node, subscriber, ce, filter) {
422         var handle = subscriber[_DEL_MOVE_HANDLE];
424         if (handle) {
425             handle.detach();
426             subscriber[_DEL_MOVE_HANDLE] = null;
427         }
429         _unsetTouchActions(node);
431     },
433     processArgs : function(args, delegate) {
434         return _defArgsProcessor(this, args, delegate);
435     },
437     _onMove : function(e, node, subscriber, ce, delegate) {
439         if (delegate) {
440             node = e[CURRENT_TARGET];
441         }
443         var fireMove = subscriber._extra.standAlone || node.getData(_MOVE_START),
444             preventDefault = subscriber._extra.preventDefault;
447         if (fireMove) {
449             if (e.touches) {
450                 if (e.touches.length === 1) {
451                     _normTouchFacade(e, e.touches[0]);
452                 } else {
453                     fireMove = false;
454                 }
455             }
457             if (fireMove) {
459                 _prevent(e, preventDefault);
462                 e.type = GESTURE_MOVE;
463                 ce.fire(e);
464             }
465         }
466     },
468     PREVENT_DEFAULT : false
472  * Sets up a "gesturemoveend" event, that is fired on touch devices in response to a single finger "touchend",
473  * and on mouse based devices in response to a "mouseup".
475  * <p>By default this event is only fired when the same node
476  * has received a "gesturemove" or "gesturemovestart" event. The subscriber can set standAlone to true, in the configuration properties,
477  * if they want to listen for this event without a preceding "gesturemovestart" or "gesturemove".</p>
479  * <p>By default this event sets up it's internal "touchend" and "mouseup" DOM listeners on the document element. The subscriber
480  * can set the root configuration property, to specify which node to attach DOM listeners to, if different from the document.</p>
482  * <p>This event can also be listened for using node.delegate().</p>
484  * <p>It is recommended that you use Y.bind to set up context and additional arguments for your event handler,
485  * however if you want to pass the context and arguments as additional signature arguments to on/delegate,
486  * 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>
489  * @event gesturemoveend
490  * @for YUI
491  * @param type {string} "gesturemoveend"
492  * @param fn {function} The method the event invokes. It receives the event facade of the underlying DOM event (mouseup or touchend.changedTouches[0]).
493  * @param cfg {Object} Optional. An object which specifies:
494  * <dl>
495  * <dt>standAlone (defaults to false)</dt>
496  * <dd>true, if the subscriber should be notified even if a "gesturemovestart" or "gesturemove" has not occured on the same node.</dd>
497  * <dt>root (defaults to document)</dt>
498  * <dd>The node to which the internal DOM listeners should be attached.</dd>
499  * <dt>preventDefault (defaults to false)</dt>
500  * <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>
501  * </dl>
503  * @return {EventHandle} the detach handle
504  */
505 define(GESTURE_MOVE_END, {
507     on : function (node, subscriber, ce) {
508         _setTouchActions(node);
509         var root = _getRoot(node, subscriber),
511             endHandle = root.on(EVENT[END],
512                 this._onEnd,
513                 this,
514                 node,
515                 subscriber,
516                 ce);
518         subscriber[_MOVE_END_HANDLE] = endHandle;
519     },
521     delegate : function(node, subscriber, ce, filter) {
523         var se = this;
525         subscriber[_DEL_MOVE_END_HANDLE] = node.delegate(EVENT[END],
526             function(e) {
527                 se._onEnd(e, node, subscriber, ce, true);
528             },
529             filter);
530     },
532     detachDelegate : function(node, subscriber, ce, filter) {
533         var handle = subscriber[_DEL_MOVE_END_HANDLE];
535         if (handle) {
536             handle.detach();
537             subscriber[_DEL_MOVE_END_HANDLE] = null;
538         }
540         _unsetTouchActions(node);
542     },
544     detach : function (node, subscriber, ce) {
545         var endHandle = subscriber[_MOVE_END_HANDLE];
547         if (endHandle) {
548             endHandle.detach();
549             subscriber[_MOVE_END_HANDLE] = null;
550         }
552         _unsetTouchActions(node);
553     },
555     processArgs : function(args, delegate) {
556         return _defArgsProcessor(this, args, delegate);
557     },
559     _onEnd : function(e, node, subscriber, ce, delegate) {
561         if (delegate) {
562             node = e[CURRENT_TARGET];
563         }
565         var fireMoveEnd = subscriber._extra.standAlone || node.getData(_MOVE) || node.getData(_MOVE_START),
566             preventDefault = subscriber._extra.preventDefault;
568         if (fireMoveEnd) {
570             if (e.changedTouches) {
571                 if (e.changedTouches.length === 1) {
572                     _normTouchFacade(e, e.changedTouches[0]);
573                 } else {
574                     fireMoveEnd = false;
575                 }
576             }
578             if (fireMoveEnd) {
580                 _prevent(e, preventDefault);
582                 e.type = GESTURE_MOVE_END;
583                 ce.fire(e);
585                 node.clearData(_MOVE_START);
586                 node.clearData(_MOVE);
587             }
588         }
589     },
591     PREVENT_DEFAULT : false
595 }, '3.13.0', {"requires": ["node-base", "event-touch", "event-synthetic"]});