PageRenderTime 157ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/nwevents-1.2.0.js

http://nwevents.googlecode.com/
JavaScript | 691 lines | 507 code | 42 blank | 142 comment | 185 complexity | 33ab1f66108165f08d86d02bc601386a MD5 | raw file
  1. /*
  2. * Copyright (C) 2005-2008 Diego Perini
  3. * All rights reserved.
  4. *
  5. * nwevents.js - Javascript Event Manager
  6. *
  7. * Author: Diego Perini <diego.perini at gmail com>
  8. * Version: 1.2.0
  9. * Created: 20051016
  10. * Release: 20080916
  11. *
  12. * License:
  13. * http://javascript.nwbox.com/NWEvents/MIT-LICENSE
  14. * Download:
  15. * http://javascript.nwbox.com/NWEvents/nwevents.js
  16. */
  17. window.NW || (window.NW = {});
  18. NW.Event = function() {
  19. var version = '1.2.0',
  20. // event collections
  21. Handlers = {},
  22. Delegates = {},
  23. Listeners = {},
  24. // event phases constants
  25. CAPTURING_PHASE = 1,
  26. AT_TARGET = 2,
  27. BUBBLING_PHASE = 3,
  28. // for simple delegation
  29. Patterns = {
  30. 'id': /#([^\.]+)/,
  31. 'tagName': /^([^#\.]+)/,
  32. 'className': /\.([^#]+)/,
  33. 'all': /^[\.\-\#\w\*]+$/
  34. },
  35. // use feature detection, currently FF3, Opera and IE
  36. hasActive = typeof document.activeElement != 'undefined',
  37. // fix IE event properties to comply with w3c standards
  38. fixEvent =
  39. function(element, event, capture) {
  40. // needed for DOM0 events
  41. event || (event = getContext(element).event);
  42. // bound element (listening the event)
  43. event.currentTarget = element;
  44. // fired element (triggering the event)
  45. event.target = event.srcElement || element;
  46. // add preventDefault and stopPropagation methods
  47. event.preventDefault = preventDefault;
  48. event.stopPropagation = stopPropagation;
  49. // bound and fired element are the same AT-TARGET
  50. event.eventPhase = capture && (event.target == element) ? CAPTURING_PHASE :
  51. (event.target == element ? AT_TARGET : BUBBLING_PHASE);
  52. // related element (routing of the event)
  53. event.relatedTarget =
  54. event[(event.target == event.fromElement ? 'to' : 'from') + 'Element'];
  55. // set time event was fixed
  56. event.timeStamp=+new Date();
  57. return event;
  58. },
  59. // prevent default action
  60. preventDefault =
  61. function() {
  62. this.returnValue = false;
  63. },
  64. // stop event propagation
  65. stopPropagation =
  66. function() {
  67. this.cancelBubble = true;
  68. },
  69. // get context window for element
  70. getContext =
  71. function(element) {
  72. return (element.ownerDocument || element.document || element).parentWindow || window;
  73. },
  74. // check collection for registered event,
  75. // match element, type, handler and capture
  76. isRegistered =
  77. function(array, element, type, handler, capture) {
  78. var i, l, found = false;
  79. if (array && array.elements) {
  80. for (i = 0, l = array.elements.length; l > i; i++) {
  81. if (array.elements[i] === element &&
  82. array.funcs[i] === handler &&
  83. array.parms[i] === capture) {
  84. found = i;
  85. break;
  86. }
  87. }
  88. }
  89. return found;
  90. },
  91. // get all listener of type for element
  92. getListeners =
  93. function(element, type, handler) {
  94. for (var i=0, r = []; Listeners[type].elements.length > i; i++) {
  95. if (Listeners[type].elements[i] === element && Listeners[type].funcs[i] === handler) {
  96. r.push(Listeners[type].funcs[i]);
  97. }
  98. }
  99. return r;
  100. },
  101. // handle listeners chain for event type
  102. handleListeners =
  103. function(event) {
  104. var i, l, elements, funcs, parms, result = true, type = event.type;
  105. if (!event.propagated && "|focus|blur|change|reset|submit|".indexOf(event.type) > -1) {
  106. if (event.preventDefault) {
  107. event.preventDefault();
  108. } else {
  109. event.returnValue = false;
  110. }
  111. return false;
  112. }
  113. if (Listeners[type] && Listeners[type].elements) {
  114. // make a copy of the Listeners[type] array
  115. // since it can be modified run time by the
  116. // events deleting themselves or adding new
  117. elements = Listeners[type].elements.slice();
  118. funcs = Listeners[type].funcs.slice();
  119. parms = Listeners[type].parms.slice();
  120. // process chain in fifo order
  121. for (i = 0, l = elements.length; l > i; i++) {
  122. // element match current target ?
  123. if (elements[i] === this
  124. && (
  125. (event.eventPhase == BUBBLING_PHASE && parms[i] === false) ||
  126. (event.eventPhase == CAPTURING_PHASE && parms[i] === true) ||
  127. !event.propagated
  128. )
  129. ) {
  130. // a synthetic event during the AT_TARGET phase ?
  131. if (event.propagated && event.target === this) {
  132. event.eventPhase = AT_TARGET;
  133. }
  134. // execute registered function in element scope
  135. if (funcs[i].call(this, event) === false) {
  136. result = false;
  137. break;
  138. }
  139. }
  140. }
  141. }
  142. return result;
  143. },
  144. // handle delegates chain for event type
  145. handleDelegates =
  146. function(event) {
  147. var i, l, elements, funcs, parms,
  148. result = true, type = event.type;
  149. if (Delegates[type] && Delegates[type].elements) {
  150. // make a copy of the Delegates[type] array
  151. // since it can be modified run time by the
  152. // events deleting themselves or adding new
  153. elements = Delegates[type].elements.slice();
  154. funcs = Delegates[type].funcs.slice();
  155. parms = Delegates[type].parms.slice();
  156. // process chain in fifo order
  157. for (i = 0, l = elements.length; l > i; i++) {
  158. // if event.target matches one of the registered elements and
  159. // if "this" element matches one of the registered delegates
  160. if (parms[i] === this && NW.Dom.match(event.target, elements[i])) {
  161. // execute registered function in element scope
  162. if (funcs[i].call(event.target, event) === false) {
  163. result = false;
  164. break;
  165. }
  166. }
  167. }
  168. }
  169. return result;
  170. },
  171. // create a synthetic event
  172. synthesize =
  173. function(element, type, capture) {
  174. return {
  175. type: type,
  176. target: element,
  177. bubbles: true,
  178. cancelable: true,
  179. currentTarget: element,
  180. relatedTarget: null,
  181. timeStamp: +new Date(),
  182. preventDefault: preventDefault,
  183. stopPropagation: stopPropagation,
  184. eventPhase: capture ? CAPTURING_PHASE : BUBBLING_PHASE
  185. };
  186. },
  187. // propagate events traversing the
  188. // ancestors path in both directions
  189. propagate =
  190. function(event) {
  191. var result = true, target = event.target;
  192. target['__' + event.type] = false;
  193. // remove the trampoline event
  194. NW.Event.removeHandler(target, event.type, arguments.callee, false);
  195. // execute the capturing phase
  196. result && (result = propagatePhase(target, event.type, true));
  197. // execute the bubbling phase
  198. result && (result = propagatePhase(target, event.type, false));
  199. // submit/reset events relayed to parent forms
  200. if (target.form) { target = target.form; }
  201. // execute existing native method if not overwritten by html
  202. result && /^\s*function\s+/.test(target[event.type] + '') && target[event.type]();
  203. return result;
  204. },
  205. // propagate event capturing or bubbling phase
  206. propagatePhase =
  207. function(element, type, capture) {
  208. var i, l,
  209. result = true,
  210. node = element, ancestors = [],
  211. event = synthesize(element, type, capture);
  212. // add synthetic flag
  213. event.propagated=true;
  214. // collect ancestors
  215. while(node.nodeType == 1) {
  216. ancestors.push(node);
  217. node = node.parentNode;
  218. }
  219. // capturing, reverse ancestors collection
  220. if (capture) ancestors.reverse();
  221. // execute registered handlers in fifo order
  222. for (i = 0, l = ancestors.length; l > i; i++) {
  223. // set currentTarget to current ancestor
  224. event.currentTarget = ancestors[i];
  225. // set eventPhase to the requested phase
  226. event.eventPhase = capture ? CAPTURING_PHASE : BUBBLING_PHASE;
  227. // execute listeners bound to this ancestor and set return value
  228. if (handleListeners.call(ancestors[i], event) === false || event.returnValue === false) {
  229. result = false;
  230. break;
  231. }
  232. }
  233. // remove synthetic flag
  234. delete event.propagated;
  235. return result;
  236. },
  237. // propagate activation events (W3 generic)
  238. propagateActivation =
  239. function(event) {
  240. var result = true, target = event.target;
  241. result && (result = propagatePhase(target, event.type, true));
  242. result && (result = propagatePhase(target, event.type, false));
  243. result || event.preventDefault();
  244. return result;
  245. },
  246. // propagate activation events (IE specific)
  247. propagateIEActivation =
  248. function(event) {
  249. var result = true, target = event.target;
  250. if (event.type == 'beforedeactivate') {
  251. result && (result = propagatePhase(target, 'blur', true));
  252. result && (result = propagatePhase(target, 'blur', false));
  253. }
  254. if (event.type == 'beforeactivate') {
  255. result && (result = propagatePhase(target, 'focus', true));
  256. result && (result = propagatePhase(target, 'focus', false));
  257. }
  258. result || (event.returnValue = false);
  259. return result;
  260. },
  261. // propagate form action events
  262. propagateFormAction =
  263. function(event) {
  264. var target = event.target, type = target.type, doc = getContext(target).document;
  265. // handle activeElement on context document
  266. if (target != doc.activeElement) {
  267. if ((!hasActive || window.opera) && target.nodeType == 1) {
  268. doc.activeElement = target;
  269. doc.focusElement = null;
  270. }
  271. }
  272. if (type) {
  273. // handle focusElement on context document
  274. if (target != doc.focusElement) {
  275. if ((!hasActive || window.opera)) {
  276. doc.focusElement = target;
  277. }
  278. }
  279. if (/file|text|password/.test(type) && event.keyCode == 13) {
  280. type = 'submit';
  281. target = target.form;
  282. } else if (/select-(one|multi)/.test(type)) {
  283. type = 'change';
  284. } else if (/reset|submit/.test(type)) {
  285. target = target.form;
  286. } else {
  287. return;
  288. }
  289. if (target && !target['__' + type]) {
  290. target['__' + type] = true;
  291. NW.Event.appendHandler(target, type, propagate, false);
  292. }
  293. }
  294. },
  295. // enable event propagation
  296. enablePropagation =
  297. function(win) {
  298. var doc = win.document;
  299. if (!doc.forcedPropagation) {
  300. doc.forcedPropagation = true;
  301. // deregistration on page unload
  302. NW.Event.appendHandler(win, 'beforeunload',
  303. function(event) {
  304. NW.Event.removeHandler(win, 'beforeunload', arguments.callee, false);
  305. disablePropagation(win);
  306. },false
  307. );
  308. // register capturing keydown and mousedown event handlers
  309. NW.Event.appendHandler(doc, 'keydown', propagateFormAction, true);
  310. NW.Event.appendHandler(doc, 'mousedown', propagateFormAction, true);
  311. if (doc.addEventListener) {
  312. // register capturing focus and blur event handlers
  313. NW.Event.appendHandler(doc, 'blur', propagateActivation, true);
  314. NW.Event.appendHandler(doc, 'focus', propagateActivation, true);
  315. } else if (doc.attachEvent) {
  316. // register emulated capturing focus and blur event handlers (for IE)
  317. NW.Event.appendHandler(doc, 'beforeactivate', propagateIEActivation, true);
  318. NW.Event.appendHandler(doc, 'beforedeactivate', propagateIEActivation, true);
  319. }
  320. }
  321. },
  322. // disable event propagation
  323. disablePropagation =
  324. function(win) {
  325. var doc = win.document;
  326. if (doc.forcedPropagation) {
  327. doc.forcedPropagation = false;
  328. // deregister capturing keydown and mousedown event handlers
  329. NW.Event.removeHandler(doc, 'keydown', propagateFormAction, true);
  330. NW.Event.removeHandler(doc, 'mousedown', propagateFormAction, true);
  331. if (doc.removeEventListener) {
  332. // deregister capturing focus and blur event handlers
  333. NW.Event.removeHandler(doc, 'blur', propagateActivation, true);
  334. NW.Event.removeHandler(doc, 'focus', propagateActivation, true);
  335. } else if (doc.detachEvent) {
  336. // deregister emulated capturing focus and blur event handlers (for IE)
  337. NW.Event.removeHandler(doc, 'beforeactivate', propagateIEActivation, true);
  338. NW.Event.removeHandler(doc, 'beforedeactivate', propagateIEActivation, true);
  339. }
  340. }
  341. };
  342. // inititalize the activeElement
  343. // to a known cross-browser state
  344. if (!hasActive || window.opera) {
  345. document.activeElement = document.documentElement;
  346. }
  347. return {
  348. // control the type of registration
  349. // for event listeners (DOM0 / DOM2)
  350. EVENTS_W3C: true,
  351. // block any further event processing
  352. stop:
  353. function(event) {
  354. if (event.preventDefault) {
  355. event.preventDefault();
  356. } else {
  357. event.returnValue = false;
  358. }
  359. if (event.stopPropagation) {
  360. event.stopPropagation();
  361. } else {
  362. event.cancelBubble = true;
  363. }
  364. return false;
  365. },
  366. // programatically dispatch native or custom events
  367. dispatch:
  368. function(element, type, capture) {
  369. var event, result, win = getContext(element), doc = win.document;
  370. if (element.fireEvent) {
  371. // IE event model
  372. event = doc.createEventObject();
  373. event.type = type;
  374. event.target = element;
  375. event.eventPhase = 0;
  376. event.currentTarget = element;
  377. event.cancelBubble= !!capture;
  378. event.returnValue= undefined;
  379. // dispatch event type to element
  380. result = element.fireEvent('on' + type, fixEvent(element, event, capture));
  381. } else {
  382. // W3C event model
  383. if (/mouse|click/.test(type)) {
  384. event = doc.createEvent('MouseEvents');
  385. event.initMouseEvent(type, true, true, win, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
  386. } else if (/key(down|press|out)/.test(type)) {
  387. event = doc.createEvent('KeyEvents');
  388. event.initKeyEvent(type, true, true, win, false, false, false, false, 0, 0);
  389. } else {
  390. event = doc.createEvent('HTMLEvents');
  391. event.initEvent(type, true, true);
  392. }
  393. // dispatch event type to element
  394. result = element.dispatchEvent(event);
  395. }
  396. return result;
  397. },
  398. // append an event handler
  399. appendHandler:
  400. function(element, type, handler, capture) {
  401. var key;
  402. Handlers[type] || (Handlers[type] = {
  403. elements: [],
  404. funcs: [],
  405. parms: [],
  406. wraps: []
  407. });
  408. // if handler is not already registered
  409. if ((key = isRegistered(Handlers[type], element, type, handler, capture || false)) === false) {
  410. // append handler to the chain
  411. Handlers[type].elements.push(element);
  412. Handlers[type].funcs.push(handler);
  413. Handlers[type].parms.push(capture);
  414. if (element.addEventListener && NW.Event.EVENTS_W3C) {
  415. // use DOM2 event registration
  416. element.addEventListener(type, handler, capture || false);
  417. } else if (element.attachEvent && NW.Event.EVENTS_W3C) {
  418. // append wrapper function to fix IE scope
  419. key = Handlers[type].wraps.push(
  420. function(event) {
  421. return handler.call(element, fixEvent(element, event, capture));
  422. }
  423. );
  424. // use MSIE event registration
  425. element.attachEvent('on' + type, Handlers[type].wraps[key - 1]);
  426. } else {
  427. // if first handler for this event type
  428. if (Handlers[type].elements.length == 0) {
  429. // save previous handler if existing
  430. if (typeof element['on' + type] == 'function') {
  431. Handlers[type].elements.push(element);
  432. Handlers[type].funcs.push(element['on' + type]);
  433. Handlers[type].parms.push(capture);
  434. }
  435. // use DOM0 event registration
  436. o['on' + type] =
  437. function(event) {
  438. return handler.call(this, fixEvent(this, event, capture));
  439. };
  440. }
  441. }
  442. }
  443. return this;
  444. },
  445. // remove an event handler
  446. removeHandler:
  447. function(element, type, handler, capture) {
  448. var key;
  449. // if handler is found to be registered
  450. if ((key = isRegistered(Handlers[type], element, type, handler, capture || false)) !== false) {
  451. // remove handler from the chain
  452. Handlers[type].elements.splice(key, 1);
  453. Handlers[type].funcs.splice(key, 1);
  454. Handlers[type].parms.splice(key, 1);
  455. if (element.removeEventListener && NW.Event.EVENTS_W3C) {
  456. // use DOM2 event deregistration
  457. element.removeEventListener(type, handler, capture || false);
  458. } else if (element.detachEvent && NW.Event.EVENTS_W3C) {
  459. // use MSIE event deregistration
  460. element.detachEvent('on' + type, Handlers[type].wraps[key]);
  461. // remove wrapper function from the chain
  462. Handlers[type].wraps.splice(key, 1);
  463. } else {
  464. // if last handler for this event type
  465. if (Handlers[type].elements.length == 1) {
  466. // use DOM0 event deregistration
  467. elements['on' + type] = Handlers[type].elements[0];
  468. // remove last handler from the chain
  469. Handlers[type].elements.splice(0, 1);
  470. Handlers[type].funcs.splice(0, 1);
  471. Handlers[type].parms.splice(0, 1);
  472. }
  473. }
  474. // if no more registered handlers of type
  475. if (Handlers[type].elements.length == 0) {
  476. // remove chain type from collection
  477. delete Handlers[type];
  478. }
  479. }
  480. return this;
  481. },
  482. // append an event listener
  483. appendListener:
  484. function(element, type, handler, capture) {
  485. var key, win = getContext(element);
  486. Listeners[type] || (Listeners[type] = {
  487. elements: [],
  488. funcs: [],
  489. parms: [],
  490. wraps: []
  491. });
  492. // if listener is not already registered
  493. if ((key = isRegistered(Listeners[type], element, type, handler, capture || false)) === false) {
  494. if (!win.document.forcedPropagation) {
  495. enablePropagation(win);
  496. }
  497. // append listener to the chain
  498. Listeners[type].elements.push(element);
  499. Listeners[type].funcs.push(handler);
  500. Listeners[type].parms.push(capture);
  501. if (getListeners(element, type, handler).length == 1) {
  502. NW.Event.appendHandler(element, type, handleListeners, capture || false);
  503. }
  504. }
  505. return this;
  506. },
  507. // remove an event listener
  508. removeListener:
  509. function(element, type, handler, capture) {
  510. var key;
  511. // if listener is found to be registered
  512. if ((key = isRegistered(Listeners[type], element, type, handler, capture || false)) !== false) {
  513. // remove listener from the chain
  514. Listeners[type].elements.splice(key, 1);
  515. Listeners[type].funcs.splice(key, 1);
  516. Listeners[type].parms.splice(key, 1);
  517. if (Listeners[type].elements.length == 0) {
  518. NW.Event.removeHandler(element, type, handleListeners, capture || false);
  519. delete Listeners[type];
  520. }
  521. }
  522. return this;
  523. },
  524. // append an event delegate
  525. appendDelegate:
  526. // with iframes pass a delegate parameter
  527. function(selector, type, handler, delegate) {
  528. var key;
  529. // "delegate" defaults to documentElement
  530. delegate = delegate || document.documentElement;
  531. Delegates[type] || (Delegates[type] = {
  532. elements: [],
  533. funcs: [],
  534. parms: []
  535. });
  536. // if delegate is not already registered
  537. if ((key = isRegistered(Delegates[type], selector, type, handler, delegate)) === false) {
  538. // append delegate to the chain
  539. Delegates[type].elements.push(selector);
  540. Delegates[type].funcs.push(handler);
  541. Delegates[type].parms.push(delegate);
  542. // if first delegate for this event type
  543. if (Delegates[type].elements.length == 1) {
  544. // append the real event lisyener for this chain
  545. NW.Event.appendListener(delegate, type, handleDelegates, true);
  546. }
  547. }
  548. return this;
  549. },
  550. // remove an event delegate
  551. removeDelegate:
  552. // with iframes pass a delegate parameter
  553. function(selector, type, handler, delegate) {
  554. var key;
  555. // "delegate" defaults to documentElement
  556. delegate = delegate || document.documentElement;
  557. // if delegate is found to be registered
  558. if ((key = isRegistered(Delegates[type], selector, type, handler, delegate)) !== false) {
  559. // remove delegate from the chain
  560. Delegates[type].elements.splice(key, 1);
  561. Delegates[type].funcs.splice(key, 1);
  562. Delegates[type].parms.splice(key, 1);
  563. // if last delegate for this event type
  564. if (Delegates[type].elements.length == 0) {
  565. delete Delegates[type];
  566. // remove the real event listener for this chain
  567. NW.Event.removeListener(delegate, type, handleDelegates, true);
  568. }
  569. }
  570. return this;
  571. }
  572. };
  573. }();
  574. // embedded NW.Dom.match() so basic event delegation works,
  575. // overwritten if loading the nwmatcher.js selector engine
  576. NW.Dom = function() {
  577. var Patterns = {
  578. id: /#([^\.]+)/,
  579. tagName: /^([^#\.]+)/,
  580. className: /\.([^#]+)/,
  581. all: /^[\.\-\#\w]+$/
  582. };
  583. return {
  584. // use a simple selector match or a full
  585. // CSS3 selector engine if it is available
  586. match:
  587. function(element, selector) {
  588. var j, id, doc, length, results, tagName, className, match, matched = false;
  589. doc = (element.ownerDocument || element.document || element);
  590. if (typeof selector == 'string') {
  591. if (typeof doc.querySelectorAll != 'undefined') {
  592. try {
  593. results = doc.querySelectorAll(selector);
  594. } catch(e) {
  595. results = [];
  596. }
  597. length = results.length;
  598. while (length--) {
  599. if (results[length] === element) {
  600. matched = true;
  601. break;
  602. }
  603. }
  604. } else if (selector.match(Patterns.all)) {
  605. // use a simple selector match (id, tag, class)
  606. match = selector.match(Patterns.tagName);
  607. tagName = match ? match[1] : '*';
  608. match = selector.match(Patterns.id);
  609. id = match ? match[1] : null;
  610. match = selector.match(Patterns.className);
  611. className = match ? match[1] : null;
  612. if ((!id || id == element.id) &&
  613. (!tagName || tagName == '*' || tagName == element.nodeName.toLowerCase()) &&
  614. (!className || (' ' + element.className + ' ').replace(/\s\s+/g,' ').indexOf(' ' + className + ' ') > -1)) {
  615. matched = true;
  616. }
  617. }
  618. } else {
  619. // a selector matcher element
  620. if (typeof selector == 'element') {
  621. // match on property/values
  622. for (j in selector) {
  623. if (j == 'className') {
  624. // handle special className matching
  625. if ((' ' + element.className + ' ').replace(/\s\s+/g,' ').indexOf(' ' + selector[j] + ' ') > -1) {
  626. matched = true;
  627. break;
  628. }
  629. } else if (j == 'nodeName' || j == 'tagName') {
  630. // handle upper/lower case tagName
  631. if (element.nodeName.toLowerCase() == selector[j].toLowerCase()) {
  632. matched = true;
  633. break;
  634. }
  635. } else {
  636. // handle matching other properties
  637. if (element[j] === selector[j]) {
  638. matched = true;
  639. break;
  640. }
  641. }
  642. }
  643. }
  644. }
  645. // boolean true/false
  646. return matched;
  647. }
  648. };
  649. }();