diff options
Diffstat (limited to 'js/dojo/dojox/gesture/Base.js')
| -rw-r--r-- | js/dojo/dojox/gesture/Base.js | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/js/dojo/dojox/gesture/Base.js b/js/dojo/dojox/gesture/Base.js new file mode 100644 index 0000000..555e978 --- /dev/null +++ b/js/dojo/dojox/gesture/Base.js @@ -0,0 +1,372 @@ +//>>built +define("dojox/gesture/Base", [ + "dojo/_base/kernel", + "dojo/_base/declare", + "dojo/_base/array", + "dojo/_base/lang", + "dojo/dom", + "dojo/on", + "dojo/touch", + "dojo/has", + "../main" +], function(kernel, declare, array, lang, dom, on, touch, has, dojox){ + // module: + // dojox/gesture/Base + // summary: + // This module provides an abstract parental class for various gesture implementations. + +/*===== + dojox.gesture.Base = { + // summary: + // An abstract parental class for various gesture implementations. + // + // It's mainly responsible for: + // + // 1. Binding on() listening handlers for supported gesture events. + // + // 2. Monitoring underneath events and process different phases - 'press'|'move'|'release'|'cancel'. + // + // 3. Firing and bubbling gesture events with on() API. + // + // A gesture implementation only needs to extend this class and overwrite appropriate phase handlers: + // + // - press()|move()|release()|cancel for recognizing and firing gestures + // + // example: + // 1. A typical gesture implementation. + // + // Suppose we have dojox/gesture/a which provides 3 gesture events:"a", "a.x", "a.y" to be used as: + // | dojo.connect(node, dojox.gesture.a, function(e){}); + // | dojo.connect(node, dojox.gesture.a.x, function(e){}); + // | dojo.connect(node, dojox.gesture.a.y, function(e){}); + // + // The definition of the gesture "a" may look like: + // | define([..., "./Base"], function(..., Base){ + // | var clz = declare(Base, { + // | defaultEvent: "a", + // | + // | subEvents: ["x", "y"], + // | + // | press: function(data, e){ + // | this.fire(node, {type: "a.x", ...}); + // | }, + // | move: function(data, e){ + // | this.fire(node, {type: "a.y", ...}); + // | }, + // | release: function(data, e){ + // | this.fire(node, {type: "a", ...}); + // | }, + // | cancel: function(data, e){ + // | // clean up + // | } + // | }); + // | + // | // in order to have a default instance for handy use + // | dojox.gesture.a = new clz(); + // | + // | // so that we can create new instances like + // | // var mine = new dojox.gesture.a.A({...}) + // | dojox.gesture.a.A = clz; + // | + // | return dojox.gesture.a; + // | }); + // + // 2. A gesture can be used in the following ways(taking dojox.gestre.tap for example): + // + // A. Used with dojo.connect() + // | dojo.connect(node, dojox.gesture.tap, function(e){}); + // | dojo.connect(node, dojox.gesture.tap.hold, function(e){}); + // | dojo.connect(node, dojox.gesture.tap.doubletap, function(e){}); + // + // B. Used with dojo.on + // | define(["dojo/on", "dojox/gesture/tap"], function(on, tap){ + // | on(node, tap, function(e){}); + // | on(node, tap.hold, function(e){}); + // | on(node, tap.doubletap, function(e){}); + // + // C. Used with dojox.gesture.tap directly + // | dojox.gesture.tap(node, function(e){}); + // | dojox.gesture.tap.hold(node, function(e){}); + // | dojox.gesture.tap.doubletap(node, function(e){}); + // + // Though there is always a default gesture instance after being required, e.g + // | require(["dojox/gesture/tap"], function(){...}); + // + // It's possible to create a new one with different parameter setting: + // | var myTap = new dojox.gesture.tap.Tap({holdThreshold: 300}); + // | dojo.connect(node, myTap, function(e){}); + // | dojo.connect(node, myTap.hold, function(e){}); + // | dojo.connect(node, myTap.doubletap, function(e){}); + // + // Please refer to dojox/gesture/ for more gesture usages + }; +=====*/ + kernel.experimental("dojox.gesture.Base"); + + lang.getObject("gesture", true, dojox); + + // Declare an internal anonymous class which will only be exported by module return value + return declare(/*===== "dojox.gesture.Base", =====*/null, { + + // defaultEvent: [readonly] String + // Default event e.g. 'tap' is a default event of dojox.gesture.tap + defaultEvent: " ", + + // subEvents: [readonly] Array + // A list of sub events e.g ['hold', 'doubletap'], + // used by being combined with defaultEvent like 'tap.hold', 'tap.doubletap' etc. + subEvents: [], + + // touchOnly: boolean + // Whether the gesture is touch-device only + touchOnly : false, + + // _elements: Array + // List of elements that wraps target node and gesture data + _elements: null, + + /*===== + // _lock: Dom + // The dom node whose descendants are all locked for processing + _lock: null, + + // _events: [readonly] Array + // The complete list of supported gesture events with full name space + // e.g ['tap', 'tap.hold', 'tap.doubletap'] + _events: null, + =====*/ + + constructor: function(args){ + lang.mixin(this, args); + this.init(); + }, + init: function(){ + // summary: + // Initialization works + this._elements = []; + + if(!has("touch") && this.touchOnly){ + console.warn("Gestures:[", this.defaultEvent, "] is only supported on touch devices!"); + return; + } + + // bind on() handlers for various events + var evt = this.defaultEvent; + this.call = this._handle(evt); + + this._events = [evt]; + array.forEach(this.subEvents, function(subEvt){ + this[subEvt] = this._handle(evt + '.' + subEvt); + this._events.push(evt + '.' + subEvt); + }, this); + }, + _handle: function(/*String*/eventType){ + // summary: + // Bind listen handler for the given gesture event(e.g. 'tap', 'tap.hold' etc.) + // the returned handle will be used internally by dojo/on + var self = this; + //called by dojo/on + return function(node, listener){ + // normalize, arguments might be (null, node, listener) + var a = arguments; + if(a.length > 2){ + node = a[1]; + listener = a[2]; + } + var isNode = node && (node.nodeType || node.attachEvent || node.addEventListener); + if(!isNode){ + return on(node, eventType, listener); + }else{ + var onHandle = self._add(node, eventType, listener); + // FIXME - users are supposed to explicitly call either + // disconnect(signal) or signal.remove() to release resources + var signal = { + remove: function(){ + onHandle.remove(); + self._remove(node, eventType); + } + }; + return signal; + } + }; // dojo/on handle + }, + _add: function(/*Dom*/node, /*String*/type, /*function*/listener){ + // summary: + // Bind dojo/on handlers for both gesture event(e.g 'tab.hold') + // and underneath 'press'|'move'|'release' events + var element = this._getGestureElement(node); + if(!element){ + // the first time listening to the node + element = { + target: node, + data: {}, + handles: {} + }; + + var _press = lang.hitch(this, "_process", element, "press"); + var _move = lang.hitch(this, "_process", element, "move"); + var _release = lang.hitch(this, "_process", element, "release"); + var _cancel = lang.hitch(this, "_process", element, "cancel"); + + var handles = element.handles; + if(this.touchOnly){ + handles.press = on(node, 'touchstart', _press); + handles.move = on(node, 'touchmove', _move); + handles.release = on(node, 'touchend', _release); + handles.cancel = on(node, 'touchcancel', _cancel); + }else{ + handles.press = touch.press(node, _press); + handles.move = touch.move(node, _move); + handles.release = touch.release(node, _release); + handles.cancel = touch.cancel(node, _cancel); + } + this._elements.push(element); + } + // track num of listeners for the gesture event - type + // so that we can release element if no more gestures being monitored + element.handles[type] = !element.handles[type] ? 1 : ++element.handles[type]; + + return on(node, type, listener); //handle + }, + _getGestureElement: function(/*Dom*/node){ + // summary: + // Obtain a gesture element for the give node + var i = 0, element; + for(; i < this._elements.length; i++){ + element = this._elements[i]; + if(element.target === node){ + return element; + } + } + }, + _process: function(element, phase, e){ + // summary: + // Process and dispatch to appropriate phase handlers. + // Also provides the machinery for managing gesture bubbling. + // description: + // 1. e._locking is used to make sure only the most inner node + // will be processed for the same gesture, suppose we have: + // | on(inner, dojox.gesture.tap, func1); + // | on(outer, dojox.gesture.tap, func2); + // only the inner node will be processed by tap gesture, once matched, + // the 'tap' event will be bubbled up from inner to outer, dojo.StopEvent(e) + // can be used at any level to stop the 'tap' event. + // + // 2. Once a node starts being processed, all it's descendant nodes will be locked. + // The same gesture won't be processed on its descendant nodes until the lock is released. + // element: Object + // Gesture element + // phase: String + // Phase of a gesture to be processed, might be 'press'|'move'|'release'|'cancel' + // e: Event + // Native event + e._locking = e._locking || {}; + if(e._locking[this.defaultEvent] || this.isLocked(e.currentTarget)){ + return; + } + // invoking gesture.press()|move()|release()|cancel() + e.preventDefault(); + e._locking[this.defaultEvent] = true; + this[phase](element.data, e); + }, + press: function(data, e){ + // summary: + // Process the 'press' phase of a gesture + }, + move: function(data, e){ + // summary: + // Process the 'move' phase of a gesture + }, + release: function(data, e){ + // summary: + // Process the 'release' phase of a gesture + }, + cancel: function(data, e){ + // summary: + // Process the 'cancel' phase of a gesture + }, + fire: function(node, event){ + // summary: + // Fire a gesture event and invoke registered listeners + // a simulated GestureEvent will also be sent along + // node: DomNode + // Target node to fire the gesture + // event: Object + // An object containing specific gesture info e.g {type: 'tap.hold'|'swipe.left'), ...} + // all these properties will be put into a simulated GestureEvent when fired. + // Note - Default properties in a native Event won't be overwritten, see on.emit() for more details. + if(!node || !event){ + return; + } + event.bubbles = true; + event.cancelable = true; + on.emit(node, event.type, event); + }, + _remove: function(/*Dom*/node, /*String*/type){ + // summary: + // Check and remove underneath handlers if node + // is not being listened for 'this' gesture anymore, + // this happens when user removed all previous on() handlers. + var element = this._getGestureElement(node); + if(!element || !element.handles){ return; } + + element.handles[type]--; + + var handles = element.handles; + if(!array.some(this._events, function(evt){ + return handles[evt] > 0; + })){ + // clean up if node is not being listened anymore + this._cleanHandles(handles); + var i = array.indexOf(this._elements, element); + if(i >= 0){ + this._elements.splice(i, 1); + } + } + }, + _cleanHandles: function(/*Object*/handles){ + // summary: + // Clean up on handles + for(var x in handles){ + //remove handles for "press"|"move"|"release"|"cancel" + if(handles[x].remove){ + handles[x].remove(); + } + delete handles[x]; + } + }, + lock: function(/*Dom*/node){ + // summary: + // Lock all descendants of the node. + // tags: + // protected + this._lock = node; + }, + unLock: function(){ + // summary: + // Release the lock + // tags: + // protected + this._lock = null; + }, + isLocked: function(node){ + // summary: + // Check if the node is locked, isLocked(node) means + // whether it's a descendant of the currently locked node. + // tags: + // protected + if(!this._lock || !node){ + return false; + } + return this._lock !== node && dom.isDescendant(node, this._lock); + }, + destroy: function(){ + // summary: + // Release all handlers and resources + array.forEach(this._elements, function(element){ + this._cleanHandles(element.handles); + }, this); + this._elements = null; + } + }); +});
\ No newline at end of file |
