event.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  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. var pressedKeys = null;
  35. var ts = 0;
  36. exports.addListener = function(elem, type, callback) {
  37. if (elem.addEventListener) {
  38. return elem.addEventListener(type, callback, false);
  39. }
  40. if (elem.attachEvent) {
  41. var wrapper = function() {
  42. callback.call(elem, window.event);
  43. };
  44. callback._wrapper = wrapper;
  45. elem.attachEvent("on" + type, wrapper);
  46. }
  47. };
  48. exports.removeListener = function(elem, type, callback) {
  49. if (elem.removeEventListener) {
  50. return elem.removeEventListener(type, callback, false);
  51. }
  52. if (elem.detachEvent) {
  53. elem.detachEvent("on" + type, callback._wrapper || callback);
  54. }
  55. };
  56. /*
  57. * Prevents propagation and clobbers the default action of the passed event
  58. */
  59. exports.stopEvent = function(e) {
  60. exports.stopPropagation(e);
  61. exports.preventDefault(e);
  62. return false;
  63. };
  64. exports.stopPropagation = function(e) {
  65. if (e.stopPropagation)
  66. e.stopPropagation();
  67. else
  68. e.cancelBubble = true;
  69. };
  70. exports.preventDefault = function(e) {
  71. if (e.preventDefault)
  72. e.preventDefault();
  73. else
  74. e.returnValue = false;
  75. };
  76. /*
  77. * @return {Number} 0 for left button, 1 for middle button, 2 for right button
  78. */
  79. exports.getButton = function(e) {
  80. if (e.type == "dblclick")
  81. return 0;
  82. if (e.type == "contextmenu" || (useragent.isMac && (e.ctrlKey && !e.altKey && !e.shiftKey)))
  83. return 2;
  84. // DOM Event
  85. if (e.preventDefault) {
  86. return e.button;
  87. }
  88. // old IE
  89. else {
  90. return {1:0, 2:2, 4:1}[e.button];
  91. }
  92. };
  93. exports.capture = function(el, eventHandler, releaseCaptureHandler) {
  94. function onMouseUp(e) {
  95. eventHandler && eventHandler(e);
  96. releaseCaptureHandler && releaseCaptureHandler(e);
  97. exports.removeListener(document, "mousemove", eventHandler, true);
  98. exports.removeListener(document, "mouseup", onMouseUp, true);
  99. exports.removeListener(document, "dragstart", onMouseUp, true);
  100. }
  101. exports.addListener(document, "mousemove", eventHandler, true);
  102. exports.addListener(document, "mouseup", onMouseUp, true);
  103. exports.addListener(document, "dragstart", onMouseUp, true);
  104. return onMouseUp;
  105. };
  106. exports.addTouchMoveListener = function (el, callback) {
  107. if ("ontouchmove" in el) {
  108. var startx, starty;
  109. exports.addListener(el, "touchstart", function (e) {
  110. var touchObj = e.changedTouches[0];
  111. startx = touchObj.clientX;
  112. starty = touchObj.clientY;
  113. });
  114. exports.addListener(el, "touchmove", function (e) {
  115. var factor = 1,
  116. touchObj = e.changedTouches[0];
  117. e.wheelX = -(touchObj.clientX - startx) / factor;
  118. e.wheelY = -(touchObj.clientY - starty) / factor;
  119. startx = touchObj.clientX;
  120. starty = touchObj.clientY;
  121. callback(e);
  122. });
  123. }
  124. };
  125. exports.addMouseWheelListener = function(el, callback) {
  126. if ("onmousewheel" in el) {
  127. exports.addListener(el, "mousewheel", function(e) {
  128. var factor = 8;
  129. if (e.wheelDeltaX !== undefined) {
  130. e.wheelX = -e.wheelDeltaX / factor;
  131. e.wheelY = -e.wheelDeltaY / factor;
  132. } else {
  133. e.wheelX = 0;
  134. e.wheelY = -e.wheelDelta / factor;
  135. }
  136. callback(e);
  137. });
  138. } else if ("onwheel" in el) {
  139. exports.addListener(el, "wheel", function(e) {
  140. var factor = 0.35;
  141. switch (e.deltaMode) {
  142. case e.DOM_DELTA_PIXEL:
  143. e.wheelX = e.deltaX * factor || 0;
  144. e.wheelY = e.deltaY * factor || 0;
  145. break;
  146. case e.DOM_DELTA_LINE:
  147. case e.DOM_DELTA_PAGE:
  148. e.wheelX = (e.deltaX || 0) * 5;
  149. e.wheelY = (e.deltaY || 0) * 5;
  150. break;
  151. }
  152. callback(e);
  153. });
  154. } else {
  155. exports.addListener(el, "DOMMouseScroll", function(e) {
  156. if (e.axis && e.axis == e.HORIZONTAL_AXIS) {
  157. e.wheelX = (e.detail || 0) * 5;
  158. e.wheelY = 0;
  159. } else {
  160. e.wheelX = 0;
  161. e.wheelY = (e.detail || 0) * 5;
  162. }
  163. callback(e);
  164. });
  165. }
  166. };
  167. exports.addMultiMouseDownListener = function(el, timeouts, eventHandler, callbackName) {
  168. var clicks = 0;
  169. var startX, startY, timer;
  170. var eventNames = {
  171. 2: "dblclick",
  172. 3: "tripleclick",
  173. 4: "quadclick"
  174. };
  175. exports.addListener(el, "mousedown", function(e) {
  176. if (exports.getButton(e) !== 0) {
  177. clicks = 0;
  178. } else if (e.detail > 1) {
  179. clicks++;
  180. if (clicks > 4)
  181. clicks = 1;
  182. } else {
  183. clicks = 1;
  184. }
  185. if (useragent.isIE) {
  186. var isNewClick = Math.abs(e.clientX - startX) > 5 || Math.abs(e.clientY - startY) > 5;
  187. if (!timer || isNewClick)
  188. clicks = 1;
  189. if (timer)
  190. clearTimeout(timer);
  191. timer = setTimeout(function() {timer = null}, timeouts[clicks - 1] || 600);
  192. if (clicks == 1) {
  193. startX = e.clientX;
  194. startY = e.clientY;
  195. }
  196. }
  197. e._clicks = clicks;
  198. eventHandler[callbackName]("mousedown", e);
  199. if (clicks > 4)
  200. clicks = 0;
  201. else if (clicks > 1)
  202. return eventHandler[callbackName](eventNames[clicks], e);
  203. });
  204. if (useragent.isOldIE) {
  205. exports.addListener(el, "dblclick", function(e) {
  206. clicks = 2;
  207. if (timer)
  208. clearTimeout(timer);
  209. timer = setTimeout(function() {timer = null}, timeouts[clicks - 1] || 600);
  210. eventHandler[callbackName]("mousedown", e);
  211. eventHandler[callbackName](eventNames[clicks], e);
  212. });
  213. }
  214. };
  215. var getModifierHash = useragent.isMac && useragent.isOpera && !("KeyboardEvent" in window)
  216. ? function(e) {
  217. return 0 | (e.metaKey ? 1 : 0) | (e.altKey ? 2 : 0) | (e.shiftKey ? 4 : 0) | (e.ctrlKey ? 8 : 0);
  218. }
  219. : function(e) {
  220. return 0 | (e.ctrlKey ? 1 : 0) | (e.altKey ? 2 : 0) | (e.shiftKey ? 4 : 0) | (e.metaKey ? 8 : 0);
  221. };
  222. exports.getModifierString = function(e) {
  223. return keys.KEY_MODS[getModifierHash(e)];
  224. };
  225. function normalizeCommandKeys(callback, e, keyCode) {
  226. var hashId = getModifierHash(e);
  227. if (!useragent.isMac && pressedKeys) {
  228. if (pressedKeys.OSKey)
  229. hashId |= 8;
  230. if (pressedKeys.altGr) {
  231. if ((3 & hashId) != 3)
  232. pressedKeys.altGr = 0;
  233. else
  234. return;
  235. }
  236. if (keyCode === 18 || keyCode === 17) {
  237. var location = "location" in e ? e.location : e.keyLocation;
  238. if (keyCode === 17 && location === 1) {
  239. if (pressedKeys[keyCode] == 1)
  240. ts = e.timeStamp;
  241. } else if (keyCode === 18 && hashId === 3 && location === 2) {
  242. var dt = e.timeStamp - ts;
  243. if (dt < 50)
  244. pressedKeys.altGr = true;
  245. }
  246. }
  247. }
  248. if (keyCode in keys.MODIFIER_KEYS) {
  249. keyCode = -1;
  250. }
  251. // keyCode of right command is 93 on mac and 92 on windows.
  252. // keyCode of left command key is 91
  253. if (hashId & 8 && (keyCode >= 91 && keyCode <= 93)) {
  254. keyCode = -1;
  255. }
  256. if (!hashId && keyCode === 13) {
  257. var location = "location" in e ? e.location : e.keyLocation;
  258. if (location === 3) {
  259. callback(e, hashId, -keyCode);
  260. if (e.defaultPrevented)
  261. return;
  262. }
  263. }
  264. if (useragent.isChromeOS && hashId & 8) {
  265. callback(e, hashId, keyCode);
  266. if (e.defaultPrevented)
  267. return;
  268. else
  269. hashId &= ~8;
  270. }
  271. // If there is no hashId and the keyCode is not a function key, then
  272. // we don't call the callback as we don't handle a command key here
  273. // (it's a normal key/character input).
  274. if (!hashId && !(keyCode in keys.FUNCTION_KEYS) && !(keyCode in keys.PRINTABLE_KEYS)) {
  275. return false;
  276. }
  277. return callback(e, hashId, keyCode);
  278. }
  279. exports.addCommandKeyListener = function(el, callback) {
  280. var addListener = exports.addListener;
  281. if (useragent.isOldGecko || (useragent.isOpera && !("KeyboardEvent" in window))) {
  282. // Old versions of Gecko aka. Firefox < 4.0 didn't repeat the keydown
  283. // event if the user pressed the key for a longer time. Instead, the
  284. // keydown event was fired once and later on only the keypress event.
  285. // To emulate the 'right' keydown behavior, the keyCode of the initial
  286. // keyDown event is stored and in the following keypress events the
  287. // stores keyCode is used to emulate a keyDown event.
  288. var lastKeyDownKeyCode = null;
  289. addListener(el, "keydown", function(e) {
  290. lastKeyDownKeyCode = e.keyCode;
  291. });
  292. addListener(el, "keypress", function(e) {
  293. return normalizeCommandKeys(callback, e, lastKeyDownKeyCode);
  294. });
  295. } else {
  296. var lastDefaultPrevented = null;
  297. addListener(el, "keydown", function(e) {
  298. var keyCode = e.keyCode;
  299. pressedKeys[keyCode] = (pressedKeys[keyCode] || 0) + 1;
  300. if (keyCode == 91 || keyCode == 92) {
  301. pressedKeys.OSKey = true;
  302. } else if (pressedKeys.OSKey) {
  303. if (e.timeStamp - pressedKeys.lastT > 200 && pressedKeys.count == 1)
  304. resetPressedKeys();
  305. }
  306. if (pressedKeys[keyCode] == 1)
  307. pressedKeys.count++;
  308. // console.log(e.timeStamp - pressedKeys.lastT)
  309. pressedKeys.lastT = e.timeStamp;
  310. var result = normalizeCommandKeys(callback, e, keyCode);
  311. lastDefaultPrevented = e.defaultPrevented;
  312. return result;
  313. });
  314. addListener(el, "keypress", function(e) {
  315. if (lastDefaultPrevented && (e.ctrlKey || e.altKey || e.shiftKey || e.metaKey)) {
  316. exports.stopEvent(e);
  317. lastDefaultPrevented = null;
  318. }
  319. });
  320. addListener(el, "keyup", function(e) {
  321. var keyCode = e.keyCode;
  322. if (!pressedKeys[keyCode]) {
  323. // console.log("resetting", 1)
  324. resetPressedKeys();
  325. } else {
  326. pressedKeys.count = Math.max(pressedKeys.count - 1, 0);
  327. }
  328. if (keyCode == 91 || keyCode == 92) {
  329. pressedKeys.OSKey = false;
  330. }
  331. pressedKeys[keyCode] = null;
  332. });
  333. if (!pressedKeys) {
  334. resetPressedKeys();
  335. addListener(window, "focus", resetPressedKeys);
  336. }
  337. }
  338. };
  339. function resetPressedKeys() {
  340. // console.log("resetting")
  341. pressedKeys = Object.create(null);
  342. pressedKeys.count = 0;
  343. pressedKeys.lastT = 0;
  344. }
  345. if (typeof window == "object" && window.postMessage && !useragent.isOldIE) {
  346. var postMessageId = 1;
  347. exports.nextTick = function(callback, win) {
  348. win = win || window;
  349. var messageName = "zero-timeout-message-" + postMessageId;
  350. exports.addListener(win, "message", function listener(e) {
  351. if (e.data == messageName) {
  352. exports.stopPropagation(e);
  353. exports.removeListener(win, "message", listener);
  354. callback();
  355. }
  356. });
  357. win.postMessage(messageName, "*");
  358. };
  359. }
  360. exports.nextFrame = typeof window == "object" && (window.requestAnimationFrame
  361. || window.mozRequestAnimationFrame
  362. || window.webkitRequestAnimationFrame
  363. || window.msRequestAnimationFrame
  364. || window.oRequestAnimationFrame);
  365. if (exports.nextFrame)
  366. exports.nextFrame = exports.nextFrame.bind(window);
  367. else
  368. exports.nextFrame = function(callback) {
  369. setTimeout(callback, 17);
  370. };
  371. });