event.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. /* ***** BEGIN LICENSE BLOCK *****
  2. * Distributed under the BSD license:
  3. *
  4. * Copyright (c) 2010, Ajax.org B.V.
  5. * All rights reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions are met:
  9. * * Redistributions of source code must retain the above copyright
  10. * notice, this list of conditions and the following disclaimer.
  11. * * Redistributions in binary form must reproduce the above copyright
  12. * notice, this list of conditions and the following disclaimer in the
  13. * documentation and/or other materials provided with the distribution.
  14. * * Neither the name of Ajax.org B.V. nor the
  15. * names of its contributors may be used to endorse or promote products
  16. * derived from this software without specific prior written permission.
  17. *
  18. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  19. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  20. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21. * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
  22. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  23. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  24. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  25. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  27. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. *
  29. * ***** END LICENSE BLOCK ***** */
  30. define(function(require, exports, module) {
  31. "use strict";
  32. var keys = require("./keys");
  33. var useragent = require("./useragent");
  34. exports.addListener = function(elem, type, callback) {
  35. if (elem.addEventListener) {
  36. return elem.addEventListener(type, callback, false);
  37. }
  38. if (elem.attachEvent) {
  39. var wrapper = function() {
  40. callback.call(elem, window.event);
  41. };
  42. callback._wrapper = wrapper;
  43. elem.attachEvent("on" + type, wrapper);
  44. }
  45. };
  46. exports.removeListener = function(elem, type, callback) {
  47. if (elem.removeEventListener) {
  48. return elem.removeEventListener(type, callback, false);
  49. }
  50. if (elem.detachEvent) {
  51. elem.detachEvent("on" + type, callback._wrapper || callback);
  52. }
  53. };
  54. /*
  55. * Prevents propagation and clobbers the default action of the passed event
  56. */
  57. exports.stopEvent = function(e) {
  58. exports.stopPropagation(e);
  59. exports.preventDefault(e);
  60. return false;
  61. };
  62. exports.stopPropagation = function(e) {
  63. if (e.stopPropagation)
  64. e.stopPropagation();
  65. else
  66. e.cancelBubble = true;
  67. };
  68. exports.preventDefault = function(e) {
  69. if (e.preventDefault)
  70. e.preventDefault();
  71. else
  72. e.returnValue = false;
  73. };
  74. /*
  75. * @return {Number} 0 for left button, 1 for middle button, 2 for right button
  76. */
  77. exports.getButton = function(e) {
  78. if (e.type == "dblclick")
  79. return 0;
  80. if (e.type == "contextmenu" || (useragent.isMac && (e.ctrlKey && !e.altKey && !e.shiftKey)))
  81. return 2;
  82. // DOM Event
  83. if (e.preventDefault) {
  84. return e.button;
  85. }
  86. // old IE
  87. else {
  88. return {1:0, 2:2, 4:1}[e.button];
  89. }
  90. };
  91. exports.capture = function(el, eventHandler, releaseCaptureHandler) {
  92. function onMouseUp(e) {
  93. eventHandler && eventHandler(e);
  94. releaseCaptureHandler && releaseCaptureHandler(e);
  95. exports.removeListener(document, "mousemove", eventHandler, true);
  96. exports.removeListener(document, "mouseup", onMouseUp, true);
  97. exports.removeListener(document, "dragstart", onMouseUp, true);
  98. }
  99. exports.addListener(document, "mousemove", eventHandler, true);
  100. exports.addListener(document, "mouseup", onMouseUp, true);
  101. exports.addListener(document, "dragstart", onMouseUp, true);
  102. return onMouseUp;
  103. };
  104. exports.addTouchMoveListener = function (el, callback) {
  105. if ("ontouchmove" in el) {
  106. var startx, starty;
  107. exports.addListener(el, "touchstart", function (e) {
  108. var touchObj = e.changedTouches[0];
  109. startx = touchObj.clientX;
  110. starty = touchObj.clientY;
  111. });
  112. exports.addListener(el, "touchmove", function (e) {
  113. var factor = 1,
  114. touchObj = e.changedTouches[0];
  115. e.wheelX = -(touchObj.clientX - startx) / factor;
  116. e.wheelY = -(touchObj.clientY - starty) / factor;
  117. startx = touchObj.clientX;
  118. starty = touchObj.clientY;
  119. callback(e);
  120. });
  121. }
  122. };
  123. exports.addMouseWheelListener = function(el, callback) {
  124. if ("onmousewheel" in el) {
  125. exports.addListener(el, "mousewheel", function(e) {
  126. var factor = 8;
  127. if (e.wheelDeltaX !== undefined) {
  128. e.wheelX = -e.wheelDeltaX / factor;
  129. e.wheelY = -e.wheelDeltaY / factor;
  130. } else {
  131. e.wheelX = 0;
  132. e.wheelY = -e.wheelDelta / factor;
  133. }
  134. callback(e);
  135. });
  136. } else if ("onwheel" in el) {
  137. exports.addListener(el, "wheel", function(e) {
  138. var factor = 0.35;
  139. switch (e.deltaMode) {
  140. case e.DOM_DELTA_PIXEL:
  141. e.wheelX = e.deltaX * factor || 0;
  142. e.wheelY = e.deltaY * factor || 0;
  143. break;
  144. case e.DOM_DELTA_LINE:
  145. case e.DOM_DELTA_PAGE:
  146. e.wheelX = (e.deltaX || 0) * 5;
  147. e.wheelY = (e.deltaY || 0) * 5;
  148. break;
  149. }
  150. callback(e);
  151. });
  152. } else {
  153. exports.addListener(el, "DOMMouseScroll", function(e) {
  154. if (e.axis && e.axis == e.HORIZONTAL_AXIS) {
  155. e.wheelX = (e.detail || 0) * 5;
  156. e.wheelY = 0;
  157. } else {
  158. e.wheelX = 0;
  159. e.wheelY = (e.detail || 0) * 5;
  160. }
  161. callback(e);
  162. });
  163. }
  164. };
  165. exports.addMultiMouseDownListener = function(el, timeouts, eventHandler, callbackName) {
  166. var clicks = 0;
  167. var startX, startY, timer;
  168. var eventNames = {
  169. 2: "dblclick",
  170. 3: "tripleclick",
  171. 4: "quadclick"
  172. };
  173. exports.addListener(el, "mousedown", function(e) {
  174. if (exports.getButton(e) !== 0) {
  175. clicks = 0;
  176. } else if (e.detail > 1) {
  177. clicks++;
  178. if (clicks > 4)
  179. clicks = 1;
  180. } else {
  181. clicks = 1;
  182. }
  183. if (useragent.isIE) {
  184. var isNewClick = Math.abs(e.clientX - startX) > 5 || Math.abs(e.clientY - startY) > 5;
  185. if (!timer || isNewClick)
  186. clicks = 1;
  187. if (timer)
  188. clearTimeout(timer);
  189. timer = setTimeout(function() {timer = null}, timeouts[clicks - 1] || 600);
  190. if (clicks == 1) {
  191. startX = e.clientX;
  192. startY = e.clientY;
  193. }
  194. }
  195. e._clicks = clicks;
  196. eventHandler[callbackName]("mousedown", e);
  197. if (clicks > 4)
  198. clicks = 0;
  199. else if (clicks > 1)
  200. return eventHandler[callbackName](eventNames[clicks], e);
  201. });
  202. if (useragent.isOldIE) {
  203. exports.addListener(el, "dblclick", function(e) {
  204. clicks = 2;
  205. if (timer)
  206. clearTimeout(timer);
  207. timer = setTimeout(function() {timer = null}, timeouts[clicks - 1] || 600);
  208. eventHandler[callbackName]("mousedown", e);
  209. eventHandler[callbackName](eventNames[clicks], e);
  210. });
  211. }
  212. };
  213. var getModifierHash = useragent.isMac && useragent.isOpera && !("KeyboardEvent" in window)
  214. ? function(e) {
  215. return 0 | (e.metaKey ? 1 : 0) | (e.altKey ? 2 : 0) | (e.shiftKey ? 4 : 0) | (e.ctrlKey ? 8 : 0);
  216. }
  217. : function(e) {
  218. return 0 | (e.ctrlKey ? 1 : 0) | (e.altKey ? 2 : 0) | (e.shiftKey ? 4 : 0) | (e.metaKey ? 8 : 0);
  219. };
  220. exports.getModifierString = function(e) {
  221. return keys.KEY_MODS[getModifierHash(e)];
  222. };
  223. function normalizeCommandKeys(callback, e, keyCode) {
  224. var hashId = getModifierHash(e);
  225. if (!useragent.isMac && pressedKeys) {
  226. if (pressedKeys[91] || pressedKeys[92])
  227. hashId |= 8;
  228. if (pressedKeys.altGr) {
  229. if ((3 & hashId) != 3)
  230. pressedKeys.altGr = 0;
  231. else
  232. return;
  233. }
  234. if (keyCode === 18 || keyCode === 17) {
  235. var location = "location" in e ? e.location : e.keyLocation;
  236. if (keyCode === 17 && location === 1) {
  237. if (pressedKeys[keyCode] == 1)
  238. ts = e.timeStamp;
  239. } else if (keyCode === 18 && hashId === 3 && location === 2) {
  240. var dt = e.timeStamp - ts;
  241. if (dt < 50)
  242. pressedKeys.altGr = true;
  243. }
  244. }
  245. }
  246. if (keyCode in keys.MODIFIER_KEYS) {
  247. keyCode = -1;
  248. }
  249. if (hashId & 8 && (keyCode === 91 || keyCode === 93)) {
  250. keyCode = -1;
  251. }
  252. if (!hashId && keyCode === 13) {
  253. var location = "location" in e ? e.location : e.keyLocation;
  254. if (location === 3) {
  255. callback(e, hashId, -keyCode);
  256. if (e.defaultPrevented)
  257. return;
  258. }
  259. }
  260. if (useragent.isChromeOS && hashId & 8) {
  261. callback(e, hashId, keyCode);
  262. if (e.defaultPrevented)
  263. return;
  264. else
  265. hashId &= ~8;
  266. }
  267. // If there is no hashId and the keyCode is not a function key, then
  268. // we don't call the callback as we don't handle a command key here
  269. // (it's a normal key/character input).
  270. if (!hashId && !(keyCode in keys.FUNCTION_KEYS) && !(keyCode in keys.PRINTABLE_KEYS)) {
  271. return false;
  272. }
  273. return callback(e, hashId, keyCode);
  274. }
  275. var pressedKeys = null;
  276. var ts = 0;
  277. exports.addCommandKeyListener = function(el, callback) {
  278. var addListener = exports.addListener;
  279. if (useragent.isOldGecko || (useragent.isOpera && !("KeyboardEvent" in window))) {
  280. // Old versions of Gecko aka. Firefox < 4.0 didn't repeat the keydown
  281. // event if the user pressed the key for a longer time. Instead, the
  282. // keydown event was fired once and later on only the keypress event.
  283. // To emulate the 'right' keydown behavior, the keyCode of the initial
  284. // keyDown event is stored and in the following keypress events the
  285. // stores keyCode is used to emulate a keyDown event.
  286. var lastKeyDownKeyCode = null;
  287. addListener(el, "keydown", function(e) {
  288. lastKeyDownKeyCode = e.keyCode;
  289. });
  290. addListener(el, "keypress", function(e) {
  291. return normalizeCommandKeys(callback, e, lastKeyDownKeyCode);
  292. });
  293. } else {
  294. var lastDefaultPrevented = null;
  295. addListener(el, "keydown", function(e) {
  296. pressedKeys[e.keyCode] = (pressedKeys[e.keyCode] || 0) + 1;
  297. var result = normalizeCommandKeys(callback, e, e.keyCode);
  298. lastDefaultPrevented = e.defaultPrevented;
  299. return result;
  300. });
  301. addListener(el, "keypress", function(e) {
  302. if (lastDefaultPrevented && (e.ctrlKey || e.altKey || e.shiftKey || e.metaKey)) {
  303. exports.stopEvent(e);
  304. lastDefaultPrevented = null;
  305. }
  306. });
  307. addListener(el, "keyup", function(e) {
  308. pressedKeys[e.keyCode] = null;
  309. });
  310. if (!pressedKeys) {
  311. resetPressedKeys();
  312. addListener(window, "focus", resetPressedKeys);
  313. }
  314. }
  315. };
  316. function resetPressedKeys(e) {
  317. pressedKeys = Object.create(null);
  318. }
  319. if (typeof window == "object" && window.postMessage && !useragent.isOldIE) {
  320. var postMessageId = 1;
  321. exports.nextTick = function(callback, win) {
  322. win = win || window;
  323. var messageName = "zero-timeout-message-" + postMessageId;
  324. exports.addListener(win, "message", function listener(e) {
  325. if (e.data == messageName) {
  326. exports.stopPropagation(e);
  327. exports.removeListener(win, "message", listener);
  328. callback();
  329. }
  330. });
  331. win.postMessage(messageName, "*");
  332. };
  333. }
  334. exports.nextFrame = typeof window == "object" && (window.requestAnimationFrame
  335. || window.mozRequestAnimationFrame
  336. || window.webkitRequestAnimationFrame
  337. || window.msRequestAnimationFrame
  338. || window.oRequestAnimationFrame);
  339. if (exports.nextFrame)
  340. exports.nextFrame = exports.nextFrame.bind(window);
  341. else
  342. exports.nextFrame = function(callback) {
  343. setTimeout(callback, 17);
  344. };
  345. });