Base.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. define("dojox/gesture/Base", [
  2. "dojo/_base/kernel",
  3. "dojo/_base/declare",
  4. "dojo/_base/array",
  5. "dojo/_base/lang",
  6. "dojo/dom",
  7. "dojo/on",
  8. "dojo/touch",
  9. "dojo/has",
  10. "../main"
  11. ], function(kernel, declare, array, lang, dom, on, touch, has, dojox){
  12. // module:
  13. // dojox/gesture/Base
  14. // summary:
  15. // This module provides an abstract parental class for various gesture implementations.
  16. /*=====
  17. dojox.gesture.Base = {
  18. // summary:
  19. // An abstract parental class for various gesture implementations.
  20. //
  21. // It's mainly responsible for:
  22. //
  23. // 1. Binding on() listening handlers for supported gesture events.
  24. //
  25. // 2. Monitoring underneath events and process different phases - 'press'|'move'|'release'|'cancel'.
  26. //
  27. // 3. Firing and bubbling gesture events with on() API.
  28. //
  29. // A gesture implementation only needs to extend this class and overwrite appropriate phase handlers:
  30. //
  31. // - press()|move()|release()|cancel for recognizing and firing gestures
  32. //
  33. // example:
  34. // 1. A typical gesture implementation.
  35. //
  36. // Suppose we have dojox/gesture/a which provides 3 gesture events:"a", "a.x", "a.y" to be used as:
  37. // | dojo.connect(node, dojox.gesture.a, function(e){});
  38. // | dojo.connect(node, dojox.gesture.a.x, function(e){});
  39. // | dojo.connect(node, dojox.gesture.a.y, function(e){});
  40. //
  41. // The definition of the gesture "a" may look like:
  42. // | define([..., "./Base"], function(..., Base){
  43. // | var clz = declare(Base, {
  44. // | defaultEvent: "a",
  45. // |
  46. // | subEvents: ["x", "y"],
  47. // |
  48. // | press: function(data, e){
  49. // | this.fire(node, {type: "a.x", ...});
  50. // | },
  51. // | move: function(data, e){
  52. // | this.fire(node, {type: "a.y", ...});
  53. // | },
  54. // | release: function(data, e){
  55. // | this.fire(node, {type: "a", ...});
  56. // | },
  57. // | cancel: function(data, e){
  58. // | // clean up
  59. // | }
  60. // | });
  61. // |
  62. // | // in order to have a default instance for handy use
  63. // | dojox.gesture.a = new clz();
  64. // |
  65. // | // so that we can create new instances like
  66. // | // var mine = new dojox.gesture.a.A({...})
  67. // | dojox.gesture.a.A = clz;
  68. // |
  69. // | return dojox.gesture.a;
  70. // | });
  71. //
  72. // 2. A gesture can be used in the following ways(taking dojox.gestre.tap for example):
  73. //
  74. // A. Used with dojo.connect()
  75. // | dojo.connect(node, dojox.gesture.tap, function(e){});
  76. // | dojo.connect(node, dojox.gesture.tap.hold, function(e){});
  77. // | dojo.connect(node, dojox.gesture.tap.doubletap, function(e){});
  78. //
  79. // B. Used with dojo.on
  80. // | define(["dojo/on", "dojox/gesture/tap"], function(on, tap){
  81. // | on(node, tap, function(e){});
  82. // | on(node, tap.hold, function(e){});
  83. // | on(node, tap.doubletap, function(e){});
  84. //
  85. // C. Used with dojox.gesture.tap directly
  86. // | dojox.gesture.tap(node, function(e){});
  87. // | dojox.gesture.tap.hold(node, function(e){});
  88. // | dojox.gesture.tap.doubletap(node, function(e){});
  89. //
  90. // Though there is always a default gesture instance after being required, e.g
  91. // | require(["dojox/gesture/tap"], function(){...});
  92. //
  93. // It's possible to create a new one with different parameter setting:
  94. // | var myTap = new dojox.gesture.tap.Tap({holdThreshold: 300});
  95. // | dojo.connect(node, myTap, function(e){});
  96. // | dojo.connect(node, myTap.hold, function(e){});
  97. // | dojo.connect(node, myTap.doubletap, function(e){});
  98. //
  99. // Please refer to dojox/gesture/ for more gesture usages
  100. };
  101. =====*/
  102. kernel.experimental("dojox.gesture.Base");
  103. lang.getObject("gesture", true, dojox);
  104. // Declare an internal anonymous class which will only be exported by module return value
  105. return declare(/*===== "dojox.gesture.Base", =====*/null, {
  106. // defaultEvent: [readonly] String
  107. // Default event e.g. 'tap' is a default event of dojox.gesture.tap
  108. defaultEvent: " ",
  109. // subEvents: [readonly] Array
  110. // A list of sub events e.g ['hold', 'doubletap'],
  111. // used by being combined with defaultEvent like 'tap.hold', 'tap.doubletap' etc.
  112. subEvents: [],
  113. // touchOnly: boolean
  114. // Whether the gesture is touch-device only
  115. touchOnly : false,
  116. // _elements: Array
  117. // List of elements that wraps target node and gesture data
  118. _elements: null,
  119. /*=====
  120. // _lock: Dom
  121. // The dom node whose descendants are all locked for processing
  122. _lock: null,
  123. // _events: [readonly] Array
  124. // The complete list of supported gesture events with full name space
  125. // e.g ['tap', 'tap.hold', 'tap.doubletap']
  126. _events: null,
  127. =====*/
  128. constructor: function(args){
  129. lang.mixin(this, args);
  130. this.init();
  131. },
  132. init: function(){
  133. // summary:
  134. // Initialization works
  135. this._elements = [];
  136. if(!has("touch") && this.touchOnly){
  137. console.warn("Gestures:[", this.defaultEvent, "] is only supported on touch devices!");
  138. return;
  139. }
  140. // bind on() handlers for various events
  141. var evt = this.defaultEvent;
  142. this.call = this._handle(evt);
  143. this._events = [evt];
  144. array.forEach(this.subEvents, function(subEvt){
  145. this[subEvt] = this._handle(evt + '.' + subEvt);
  146. this._events.push(evt + '.' + subEvt);
  147. }, this);
  148. },
  149. _handle: function(/*String*/eventType){
  150. // summary:
  151. // Bind listen handler for the given gesture event(e.g. 'tap', 'tap.hold' etc.)
  152. // the returned handle will be used internally by dojo/on
  153. var self = this;
  154. //called by dojo/on
  155. return function(node, listener){
  156. // normalize, arguments might be (null, node, listener)
  157. var a = arguments;
  158. if(a.length > 2){
  159. node = a[1];
  160. listener = a[2];
  161. }
  162. var isNode = node && (node.nodeType || node.attachEvent || node.addEventListener);
  163. if(!isNode){
  164. return on(node, eventType, listener);
  165. }else{
  166. var onHandle = self._add(node, eventType, listener);
  167. // FIXME - users are supposed to explicitly call either
  168. // disconnect(signal) or signal.remove() to release resources
  169. var signal = {
  170. remove: function(){
  171. onHandle.remove();
  172. self._remove(node, eventType);
  173. }
  174. };
  175. return signal;
  176. }
  177. }; // dojo/on handle
  178. },
  179. _add: function(/*Dom*/node, /*String*/type, /*function*/listener){
  180. // summary:
  181. // Bind dojo/on handlers for both gesture event(e.g 'tab.hold')
  182. // and underneath 'press'|'move'|'release' events
  183. var element = this._getGestureElement(node);
  184. if(!element){
  185. // the first time listening to the node
  186. element = {
  187. target: node,
  188. data: {},
  189. handles: {}
  190. };
  191. var _press = lang.hitch(this, "_process", element, "press");
  192. var _move = lang.hitch(this, "_process", element, "move");
  193. var _release = lang.hitch(this, "_process", element, "release");
  194. var _cancel = lang.hitch(this, "_process", element, "cancel");
  195. var handles = element.handles;
  196. if(this.touchOnly){
  197. handles.press = on(node, 'touchstart', _press);
  198. handles.move = on(node, 'touchmove', _move);
  199. handles.release = on(node, 'touchend', _release);
  200. handles.cancel = on(node, 'touchcancel', _cancel);
  201. }else{
  202. handles.press = touch.press(node, _press);
  203. handles.move = touch.move(node, _move);
  204. handles.release = touch.release(node, _release);
  205. handles.cancel = touch.cancel(node, _cancel);
  206. }
  207. this._elements.push(element);
  208. }
  209. // track num of listeners for the gesture event - type
  210. // so that we can release element if no more gestures being monitored
  211. element.handles[type] = !element.handles[type] ? 1 : ++element.handles[type];
  212. return on(node, type, listener); //handle
  213. },
  214. _getGestureElement: function(/*Dom*/node){
  215. // summary:
  216. // Obtain a gesture element for the give node
  217. var i = 0, element;
  218. for(; i < this._elements.length; i++){
  219. element = this._elements[i];
  220. if(element.target === node){
  221. return element;
  222. }
  223. }
  224. },
  225. _process: function(element, phase, e){
  226. // summary:
  227. // Process and dispatch to appropriate phase handlers.
  228. // Also provides the machinery for managing gesture bubbling.
  229. // description:
  230. // 1. e._locking is used to make sure only the most inner node
  231. // will be processed for the same gesture, suppose we have:
  232. // | on(inner, dojox.gesture.tap, func1);
  233. // | on(outer, dojox.gesture.tap, func2);
  234. // only the inner node will be processed by tap gesture, once matched,
  235. // the 'tap' event will be bubbled up from inner to outer, dojo.StopEvent(e)
  236. // can be used at any level to stop the 'tap' event.
  237. //
  238. // 2. Once a node starts being processed, all it's descendant nodes will be locked.
  239. // The same gesture won't be processed on its descendant nodes until the lock is released.
  240. // element: Object
  241. // Gesture element
  242. // phase: String
  243. // Phase of a gesture to be processed, might be 'press'|'move'|'release'|'cancel'
  244. // e: Event
  245. // Native event
  246. e._locking = e._locking || {};
  247. if(e._locking[this.defaultEvent] || this.isLocked(e.currentTarget)){
  248. return;
  249. }
  250. // invoking gesture.press()|move()|release()|cancel()
  251. e.preventDefault();
  252. e._locking[this.defaultEvent] = true;
  253. this[phase](element.data, e);
  254. },
  255. press: function(data, e){
  256. // summary:
  257. // Process the 'press' phase of a gesture
  258. },
  259. move: function(data, e){
  260. // summary:
  261. // Process the 'move' phase of a gesture
  262. },
  263. release: function(data, e){
  264. // summary:
  265. // Process the 'release' phase of a gesture
  266. },
  267. cancel: function(data, e){
  268. // summary:
  269. // Process the 'cancel' phase of a gesture
  270. },
  271. fire: function(node, event){
  272. // summary:
  273. // Fire a gesture event and invoke registered listeners
  274. // a simulated GestureEvent will also be sent along
  275. // node: DomNode
  276. // Target node to fire the gesture
  277. // event: Object
  278. // An object containing specific gesture info e.g {type: 'tap.hold'|'swipe.left'), ...}
  279. // all these properties will be put into a simulated GestureEvent when fired.
  280. // Note - Default properties in a native Event won't be overwritten, see on.emit() for more details.
  281. if(!node || !event){
  282. return;
  283. }
  284. event.bubbles = true;
  285. event.cancelable = true;
  286. on.emit(node, event.type, event);
  287. },
  288. _remove: function(/*Dom*/node, /*String*/type){
  289. // summary:
  290. // Check and remove underneath handlers if node
  291. // is not being listened for 'this' gesture anymore,
  292. // this happens when user removed all previous on() handlers.
  293. var element = this._getGestureElement(node);
  294. if(!element || !element.handles){ return; }
  295. element.handles[type]--;
  296. var handles = element.handles;
  297. if(!array.some(this._events, function(evt){
  298. return handles[evt] > 0;
  299. })){
  300. // clean up if node is not being listened anymore
  301. this._cleanHandles(handles);
  302. var i = array.indexOf(this._elements, element);
  303. if(i >= 0){
  304. this._elements.splice(i, 1);
  305. }
  306. }
  307. },
  308. _cleanHandles: function(/*Object*/handles){
  309. // summary:
  310. // Clean up on handles
  311. for(var x in handles){
  312. //remove handles for "press"|"move"|"release"|"cancel"
  313. if(handles[x].remove){
  314. handles[x].remove();
  315. }
  316. delete handles[x];
  317. }
  318. },
  319. lock: function(/*Dom*/node){
  320. // summary:
  321. // Lock all descendants of the node.
  322. // tags:
  323. // protected
  324. this._lock = node;
  325. },
  326. unLock: function(){
  327. // summary:
  328. // Release the lock
  329. // tags:
  330. // protected
  331. this._lock = null;
  332. },
  333. isLocked: function(node){
  334. // summary:
  335. // Check if the node is locked, isLocked(node) means
  336. // whether it's a descendant of the currently locked node.
  337. // tags:
  338. // protected
  339. if(!this._lock || !node){
  340. return false;
  341. }
  342. return this._lock !== node && dom.isDescendant(node, this._lock);
  343. },
  344. destroy: function(){
  345. // summary:
  346. // Release all handlers and resources
  347. array.forEach(this._elements, function(element){
  348. this._cleanHandles(element.handles);
  349. }, this);
  350. this._elements = null;
  351. }
  352. });
  353. });