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