manager.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. var STOP = 1;
  2. var FORCED_STOP = 2;
  3. /**
  4. * Manager
  5. * @param {HTMLElement} element
  6. * @param {Object} [options]
  7. * @constructor
  8. */
  9. function Manager(element, options) {
  10. this.options = assign({}, Hammer.defaults, options || {});
  11. this.options.inputTarget = this.options.inputTarget || element;
  12. this.handlers = {};
  13. this.session = {};
  14. this.recognizers = [];
  15. this.oldCssProps = {};
  16. this.element = element;
  17. this.input = createInputInstance(this);
  18. this.touchAction = new TouchAction(this, this.options.touchAction);
  19. toggleCssProps(this, true);
  20. each(this.options.recognizers, function(item) {
  21. var recognizer = this.add(new (item[0])(item[1]));
  22. item[2] && recognizer.recognizeWith(item[2]);
  23. item[3] && recognizer.requireFailure(item[3]);
  24. }, this);
  25. }
  26. Manager.prototype = {
  27. /**
  28. * set options
  29. * @param {Object} options
  30. * @returns {Manager}
  31. */
  32. set: function(options) {
  33. assign(this.options, options);
  34. // Options that need a little more setup
  35. if (options.touchAction) {
  36. this.touchAction.update();
  37. }
  38. if (options.inputTarget) {
  39. // Clean up existing event listeners and reinitialize
  40. this.input.destroy();
  41. this.input.target = options.inputTarget;
  42. this.input.init();
  43. }
  44. return this;
  45. },
  46. /**
  47. * stop recognizing for this session.
  48. * This session will be discarded, when a new [input]start event is fired.
  49. * When forced, the recognizer cycle is stopped immediately.
  50. * @param {Boolean} [force]
  51. */
  52. stop: function(force) {
  53. this.session.stopped = force ? FORCED_STOP : STOP;
  54. },
  55. /**
  56. * run the recognizers!
  57. * called by the inputHandler function on every movement of the pointers (touches)
  58. * it walks through all the recognizers and tries to detect the gesture that is being made
  59. * @param {Object} inputData
  60. */
  61. recognize: function(inputData) {
  62. var session = this.session;
  63. if (session.stopped) {
  64. return;
  65. }
  66. // run the touch-action polyfill
  67. this.touchAction.preventDefaults(inputData);
  68. var recognizer;
  69. var recognizers = this.recognizers;
  70. // this holds the recognizer that is being recognized.
  71. // so the recognizer's state needs to be BEGAN, CHANGED, ENDED or RECOGNIZED
  72. // if no recognizer is detecting a thing, it is set to `null`
  73. var curRecognizer = session.curRecognizer;
  74. // reset when the last recognizer is recognized
  75. // or when we're in a new session
  76. if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)) {
  77. curRecognizer = session.curRecognizer = null;
  78. }
  79. var i = 0;
  80. while (i < recognizers.length) {
  81. recognizer = recognizers[i];
  82. // find out if we are allowed try to recognize the input for this one.
  83. // 1. allow if the session is NOT forced stopped (see the .stop() method)
  84. // 2. allow if we still haven't recognized a gesture in this session, or the this recognizer is the one
  85. // that is being recognized.
  86. // 3. allow if the recognizer is allowed to run simultaneous with the current recognized recognizer.
  87. // this can be setup with the `recognizeWith()` method on the recognizer.
  88. if (session.stopped !== FORCED_STOP && ( // 1
  89. !curRecognizer || recognizer == curRecognizer || // 2
  90. recognizer.canRecognizeWith(curRecognizer))) { // 3
  91. recognizer.recognize(inputData);
  92. } else {
  93. recognizer.reset();
  94. }
  95. // if the recognizer has been recognizing the input as a valid gesture, we want to store this one as the
  96. // current active recognizer. but only if we don't already have an active recognizer
  97. if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)) {
  98. curRecognizer = session.curRecognizer = recognizer;
  99. }
  100. i++;
  101. }
  102. },
  103. /**
  104. * get a recognizer by its event name.
  105. * @param {Recognizer|String} recognizer
  106. * @returns {Recognizer|Null}
  107. */
  108. get: function(recognizer) {
  109. if (recognizer instanceof Recognizer) {
  110. return recognizer;
  111. }
  112. var recognizers = this.recognizers;
  113. for (var i = 0; i < recognizers.length; i++) {
  114. if (recognizers[i].options.event == recognizer) {
  115. return recognizers[i];
  116. }
  117. }
  118. return null;
  119. },
  120. /**
  121. * add a recognizer to the manager
  122. * existing recognizers with the same event name will be removed
  123. * @param {Recognizer} recognizer
  124. * @returns {Recognizer|Manager}
  125. */
  126. add: function(recognizer) {
  127. if (invokeArrayArg(recognizer, 'add', this)) {
  128. return this;
  129. }
  130. // remove existing
  131. var existing = this.get(recognizer.options.event);
  132. if (existing) {
  133. this.remove(existing);
  134. }
  135. this.recognizers.push(recognizer);
  136. recognizer.manager = this;
  137. this.touchAction.update();
  138. return recognizer;
  139. },
  140. /**
  141. * remove a recognizer by name or instance
  142. * @param {Recognizer|String} recognizer
  143. * @returns {Manager}
  144. */
  145. remove: function(recognizer) {
  146. if (invokeArrayArg(recognizer, 'remove', this)) {
  147. return this;
  148. }
  149. recognizer = this.get(recognizer);
  150. // let's make sure this recognizer exists
  151. if (recognizer) {
  152. var recognizers = this.recognizers;
  153. var index = inArray(recognizers, recognizer);
  154. if (index !== -1) {
  155. recognizers.splice(index, 1);
  156. this.touchAction.update();
  157. }
  158. }
  159. return this;
  160. },
  161. /**
  162. * bind event
  163. * @param {String} events
  164. * @param {Function} handler
  165. * @returns {EventEmitter} this
  166. */
  167. on: function(events, handler) {
  168. if (events === undefined) {
  169. return;
  170. }
  171. if (handler === undefined) {
  172. return;
  173. }
  174. var handlers = this.handlers;
  175. each(splitStr(events), function(event) {
  176. handlers[event] = handlers[event] || [];
  177. handlers[event].push(handler);
  178. });
  179. return this;
  180. },
  181. /**
  182. * unbind event, leave emit blank to remove all handlers
  183. * @param {String} events
  184. * @param {Function} [handler]
  185. * @returns {EventEmitter} this
  186. */
  187. off: function(events, handler) {
  188. if (events === undefined) {
  189. return;
  190. }
  191. var handlers = this.handlers;
  192. each(splitStr(events), function(event) {
  193. if (!handler) {
  194. delete handlers[event];
  195. } else {
  196. handlers[event] && handlers[event].splice(inArray(handlers[event], handler), 1);
  197. }
  198. });
  199. return this;
  200. },
  201. /**
  202. * emit event to the listeners
  203. * @param {String} event
  204. * @param {Object} data
  205. */
  206. emit: function(event, data) {
  207. // we also want to trigger dom events
  208. if (this.options.domEvents) {
  209. triggerDomEvent(event, data);
  210. }
  211. // no handlers, so skip it all
  212. var handlers = this.handlers[event] && this.handlers[event].slice();
  213. if (!handlers || !handlers.length) {
  214. return;
  215. }
  216. data.type = event;
  217. data.preventDefault = function() {
  218. data.srcEvent.preventDefault();
  219. };
  220. var i = 0;
  221. while (i < handlers.length) {
  222. handlers[i](data);
  223. i++;
  224. }
  225. },
  226. /**
  227. * destroy the manager and unbinds all events
  228. * it doesn't unbind dom events, that is the user own responsibility
  229. */
  230. destroy: function() {
  231. this.element && toggleCssProps(this, false);
  232. this.handlers = {};
  233. this.session = {};
  234. this.input.destroy();
  235. this.element = null;
  236. }
  237. };
  238. /**
  239. * add/remove the css properties as defined in manager.options.cssProps
  240. * @param {Manager} manager
  241. * @param {Boolean} add
  242. */
  243. function toggleCssProps(manager, add) {
  244. var element = manager.element;
  245. if (!element.style) {
  246. return;
  247. }
  248. var prop;
  249. each(manager.options.cssProps, function(value, name) {
  250. prop = prefixed(element.style, name);
  251. if (add) {
  252. manager.oldCssProps[prop] = element.style[prop];
  253. element.style[prop] = value;
  254. } else {
  255. element.style[prop] = manager.oldCssProps[prop] || '';
  256. }
  257. });
  258. if (!add) {
  259. manager.oldCssProps = {};
  260. }
  261. }
  262. /**
  263. * trigger dom event
  264. * @param {String} event
  265. * @param {Object} data
  266. */
  267. function triggerDomEvent(event, data) {
  268. var gestureEvent = document.createEvent('Event');
  269. gestureEvent.initEvent(event, true, true);
  270. gestureEvent.gesture = data;
  271. data.target.dispatchEvent(gestureEvent);
  272. }