event.js 11 KB

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