webui-aria2/js/libs/dojox/gesture/Base.js.uncompressed.js
2012-05-01 19:52:07 +08:00

372 lines
12 KiB
JavaScript

//>>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;
}
});
});