/** * $Id: eventmanager.js 397 2008-04-22 20:17:57Z wingedfox $ * $HeadURL: https://svn.debugger.ru/repos/jslibs/BrowserExtensions/tags/BrowserExtensions.018/eventmanager.js $ * * EventManager (EM shorthand) is the class, written to manage event attach/detach/register and so on * * @modified $Date: 2008-04-23 00:17:57 +0400 (Срд, 23 Апр 2008) $ * @version $Rev: 397 $ * @license LGPL 2.1 or later * @author Ilya Lebedev * @depends helpers.js * * @class * @constructor EventManager */ var EM = new function () { var self = this; /** * Events pool * Element format: * { 'node' : {HTMLElement}, * ['rootEHCaller' : {Function}] * 'handler' : { * : [Function[, Function[, ...]]] * [, : [Function[, Function[, ...]]]] * } * } * * @type Array * @scope private */ var pool = []; /** * Unique ID counter, used to attach IDs to the objects * * @type Number * @scope private */ var UID = 0; /** * List of used keys, applied to different kinds of objects * * @type Object * @scope private */ var keys = { 'UEID' : '__eventManagerUniqueElementId' // ,'UHID' : '__eventManagerUniqueHandlerId' }; /************************************************************************** * PROTECTED METHODS ***************************************************************************/ /** * Method is being binded to any new event handler, then, when called, * does some unification between browser platforms and calls all binded * event handlers * * @param {Event} e event object * @scope protected */ var rootEventHandler = function (e) { unifyEvent(e); var id = null ,hid = null ,el = e.target ,fe = true ,res = true; if (!e.currentTarget || !(id = e.currentTarget[keys.UEID]) || !(hid = pool[id].handler[e.type])) return; try { for (var i=0, hL=hid.length; i=0; i--) { if (pool[i] && (el=(pid = pool[i]).node)) { for (var z in pid.handler) { if (!pid.handler.hasOwnProperty(z)) continue; try { if (el.removeEventListener) { el.removeEventListener(z, pid.rootEHCaller?pid.rootEHCaller:rootEventHandler, false); } else if (el.detachEvent) { el.detachEvent('on'+z, pid.rootEHCaller?pid.rootEHCaller:rootEventHandler); } } catch (e) {} pid.handler[z].length = 0; } } el = pid.node = null; } if (window.removeEventListener) { window.removeEventListener(z, arguments.callee, false); } else { window.detachEvent('on'+z, arguments.callee); } }; /************************************************************************** * PRIVATE METHODS ***************************************************************************/ /** * Makes an event clone, it does not dereference objects in the event properties * * @param {Event} e event handler * @return {Object} cloned event * @scope private */ var unifyEvent = function (e) { var i=self.EU.length ,cur,cur1,k,init while (i--) { cur = self.EU[i]; if (cur[0].test(e.type)) { k=cur[1].length; init = null; while (k--) { cur1 = cur[1][k]; if ('init' == cur1[0]) init = cur1[1] else if (!e[cur1[0]]) e[cur1[0]] = cur1[1]; } if (init) init.call(e); } } if (!e.target && e.type != 'unload') e.target = e.srcElement; return e; }; /** * Returns UEID property for the specified element, creates it, if asked * * @param {Object} el element to find UEID on * @param {Boolean} f optional flag to force UEID creation, if not exists * @retutn {Number} UEID, if > 0 * @scope private */ var getUEID = function (el, f) { return el[keys.UEID] || (f && (el[keys.UEID] = ++UID)); }; /************************************************************************** * PUBLIC METHODS ***************************************************************************/ /** * Adds the event listener to the queue * * @param {Object} el element to attach event handler to * @param {String} et event name to attach event handler to (without 'on' prefix) * @param {Function} h event handler * @return {Boolean} success state * @scope public */ self.addEventListener = function (el, et, h) { if (!el || !isFunction(h)) return false; // if (!el.addEventListener && !el.attachEvent) return false; /* * unique identifier is used to keep an eye on the element */ var id = getUEID(el, true) ,pid = null ,hid = null; /* * prepare pool object, if needed */ if (!pool[id]) { pool[id] = { 'node' : el ,'handler' : {} } }; pid = pool[id]; /* * prepare handlers storage in the pool object, if needed */ if (!pid.handler.hasOwnProperty(et)) { pid.handler[et] = []; /* * if we a here, this means that we have not connected to a node yet * note, we've already made a check for the required methods existense */ if (el.addEventListener) { el.addEventListener(et, rootEventHandler, false); } else if (el.attachEvent) { /* * this workaround is used to avoid IE's lack of currentTarget property */ pid.rootEHCaller = function(e) { e.currentTarget = pid.node;//pool[id].node; var res = rootEventHandler(e); e.currentTarget = null; return res; }; el.attachEvent('on'+et, pid.rootEHCaller); } }; hid = pid.handler[et]; /* * finally, attach handler, if it was not attached before */ if (hid.indexOf(h)==-1) { hid[hid.length] = h; return true; } return false; }; /** * Removes the event listener from the queue * * @param {Object} el element to attach event handler to * @param {String} et event name to attach event handler to (without 'on' prefix) * @param {Function} h event handler * @return {Boolean} success state * @scope public */ self.removeEventListener = function (el,et,h) { if (!el || !isFunction(h)) return false; var id = getUEID(el) ,pid = pool[id] ,eid = null; if (pid && (eid = pid.handler[et])) { /* * we've found an event handler */ eid.splice(eid.indexOf(h),1); if (0 == eid.length) { delete pid.handler[et]; /* * remove the actual listener */ if (el.removeEventListener) { el.removeEventListener(et, pid.rootEHCaller?pid.rootEHCaller:rootEventHandler, false); } else if (el.detachEvent) { el.detachEvent('on'+et, pid.rootEHCaller?pid.rootEHCaller:rootEventHandler); } } return true; } return false; }; /** * Dispatch custom events on the specified element * * @param {Object} e event object itself * @return {Boolean} cancelled status * @scope public */ self.dispatchEvent = function (e) { var res = rootEventHandler(e); return res; }; /** * Registers new event handler for any object * It's a good idea to register events on the object instances, really * * @param {Object} o object to register new event on * @param {String} n bindable event name * @param {Boolean} b optional bubbling allowed flag * @param {Function} d optional default action function * @return {EMEvent} object, allowing to invoke events * @scope public * @see EM.EventTarget */ self.registerEvent = function (o, n, b, d) { var id = getUEID(o,true); if (!pool[id]) { pool[id] = {'node' : o, 'handler' : []}; } else { pool[id].node = o; } return new EM.EventTarget(o, n, b, d); }; /** * Performs object initialization * */ var __construct = function() { /* * for IE, to dereference event handlers and remove memory leaks */ if (window.attachEvent && !window.addEventListener) { window.attachEvent('onunload',unloadEventHandler); } }; __construct(); }; /****************************************************************************** * STATIC METHODS ******************************************************************************/ EM.preventDefaultAction = function(e) { e.preventDefault(); } EM.stopPropagationAction = function(e) { e.stopPropagation(); } /****************************************************************************** * SUPPLEMENTARY CLASSES ******************************************************************************/ /** * EventTarget base class * Used to create self-containing event object * * @class EM.EventTarget * @constructor * @param {String} name event name * @param {Null, Object} obj event execution context (this), window if null passed * @param {Boolean} bubble flag allowing event to bubble across element.parentNode * @param {Function} def default action for the event */ EM.EventTarget = function (obj, name, bubble, def) { var self = this; /** * Indicates possible bubbling, by default bubbling is not allowed * * @type Boolean * @default false * @scope private */ var canBubble = !!bubble; /** * Stores function, performing default action for the event * * @type Function * @scope private */ var defaultAction = isFunction(def)?def:null; /************************************************************************** * PRIVATE METHODS ***************************************************************************/ /************************************************************************** * PROTECTED METHODS ***************************************************************************/ /** * Used to trigger created event on the supplied object or on the 'obj' constructor param * * * @param {Object} el optional element to trigger event on (.target property in the event object) * @param {Object} event data */ self.trigger = function (el, data) { if (!(arguments.length-1) && el!=obj) { data = el; el = null; } if (!el) el = obj; var e = {} ,res = true ,undef = true ,tmp = null for (var i in data) { if (data.hasOwnProperty(i)) e[i] = data[i]; } /* * set defaults */ canBubble = !!bubble; defaultAction = def; /* * start the go */ do { e.preventDefault = preventDefault; e.stopPropagation = stopPropagation; e.target = el; e.currentTarget = el; e.type = name; tmp = EM.dispatchEvent(e); undef &= (isUndefined(tmp)) res &= !(false===tmp); } while ((el = el.parentNode) && canBubble); /* * try to execute the default action */ if (isFunction(defaultAction) && res && !undef) { defaultAction(e); } return (defaultAction && res && !undef); }; /** * Prevents default event action * * @scope protected */ var preventDefault = function () { defaultAction = null; }; /** * Stops bubbling * * @scope protected */ var stopPropagation = function () { canBubble = false; }; }; /** * Namespace for event unification routines * * @type Array * @scope protected */ EM.EU = [ [/./ , [ /** * Prevents event from calling default event handler * * @scope protected */ ['preventDefault', function() { this.returnValue = false; } ] /** * Prevents event from futher bubbling * * @scope protected */ ,['stopPropagation', function() { this.cancelBubble = true; } ] ]] ,[/^mouse(over|out|down|up)/ , [ /** * Used to detect left or right button pressed. * Due to some browser inconsistense, middle button is ignored * * @return {Number} 1 - left button, 2 - right button * @scope protected */ ['getButton', function () { return this.button==2?2:1 } ] ,['EM_MB_LEFT', '1'] ,['EM_MB_RIGHT', '2'] ]] ,[/^key(down|up|press)/ , [ /** * Used to return browser-independend keycode * * @return {Number} fixed key code * @scope protected */ ['getKeyCode', function () { switch (this.keyCode) { case 189: return 109; case 187: return 61; case 186: return 59; default: return this.keyCode; } } ] /** * Calculates 'repeat' property for the key events * * @return {Number} 0 means no repeating keystroke detected * @scope protected */ ,['getRepeat', function () { return arguments.callee.repeat } ] ,['init', function () { var ac = this.getRepeat if ('keyup' == this.type) { ac.repeat = 0; ac.keyCode = 0; } else if ('keydown' == this.type) { ac.repeat = ac.keyCode==this.keyCode; ac.keyCode=this.keyCode; } } ] ]] ]; /* * register core event handler, domload * it's called right on the document initialization, before images complete load */ (function (){ var evt = EM.registerEvent(window,'domload') ,executed = false ,clearEvents = function() { //For IE EM.removeEventListener(document, 'propertychange', handlers.ie); //For Mozilla EM.removeEventListener(document, 'DOMContentLoaded', handlers.mz); //For someone else EM.removeEventListener(window, 'load', handlers.mz); } ,handlers = { 'ie' : function(e) { if (window.event.propertyName == 'activeElement' && !executed) { evt.trigger(window); clearEvents(); executed = true; } } ,'mz' : function (e) {if(!executed)evt.trigger(window); executed=true;} }; //For IE EM.addEventListener(document, 'propertychange', handlers.ie); //For Mozilla EM.addEventListener(document,'DOMContentLoaded', handlers.mz); //For Safari and Opera if(/WebKit|Khtml/i.test(navigator.userAgent)||(window.opera&&parseInt(window.opera.version())<9))(function(){/loaded|complete/.test(document.readyState)?(evt.trigger(window),executed=true):setTimeout(arguments.callee,100)})(); //For someone else EM.addEventListener(window, 'load', handlers.mz); })();