Source: helper/events.js

// written by Dean Edwards, 2005
// with input from Tino Zijdel

// http://dean.edwards.name/weblog/2005/10/add-event/

/**
 * @module events
 */

/**
 * Add an event of a type to an element and call a handler
 * 
 * @param {object}   element the element
 * @param {string}   type    type of the event
 * @param {function} handler the function to call at the event
 */
function addEvent(element, type, handler) {

    // Eval for IE8 conditional
    var process = "state=false; /*@cc_on @if (@_win32) state=true; @end @*/ state";
    if (type == 'load' && eval(process) == true) {
        // Load Event for IE8
        addEvent(element, 'readystatechange', function () {
            // Loaded = online; complete = offline
            if (element.readyState == 'loaded' || element.readyState == 'complete') {
                handler();
            }
        });
        return;
    }

    // assign each event handler a unique ID
    if (!handler.$$guid) handler.$$guid = addEvent.guid++;
    // create a hash table of event types for the element
    if (!element.events) element.events = {};
    // create a hash table of event handlers for each element/event pair
    var handlers = element.events[type];
    if (!handlers) {
        handlers = element.events[type] = {};
        // store the existing event handler (if there is one)
        if (element["on" + type]) {
            handlers[0] = element["on" + type];
        }
    }
    // store the event handler in the hash table
    handlers[handler.$$guid] = handler;
    // assign a global event handler to do all the work
    element["on" + type] = handleEvent;
};
// a counter used to create unique IDs
addEvent.guid = 1;

/**
 * Remove an event from an element
 * @param {object} element the elment to remove the event from
 * @param {string} type    the event to remove
 * @param {function} handler the handler that was added
 */
function removeEvent(element, type, handler) {
    // delete the event handler from the hash table
    if (element.events && element.events[type]) {
        delete element.events[type][handler.$$guid];
    }
};

/**
 * Clear all events of a type from an element
 * @param {object} element the element to clear an event type from
 * @param {string} type    the event type
 */
function clearEvents(element, type) {
    if (element && element.events && element.events[type]) {
        element.events[type] = element.events[type][0] ? {
            // store old event if one existed
            0: element.events[type][0]
        } : {};
    }
}

/**
 * Handle an event if it is called
 * @param   {object}  [event=fixEvent(window.event,] handle the events
 * @returns {boolean} if it has been handled properly
 * @private
 */
function handleEvent(event) {
    var returnValue = true;
    // grab the event object (IE uses a global event object)
    event = event || fixEvent(window.event, this);
    // get a reference to the hash table of event handlers
    var handlers = this.events[event.type];
    // execute each event handler
    for (var i in handlers) {
        if (!handlers.hasOwnProperty(i)) continue;
        if (event.cancelBubble) break;
        if (handlers[i].call(this, event) === false) {
            returnValue = false;
        }
    }
    return returnValue;
};

/**
 * @private
 */
function fixEvent(event, _this) {
    // add W3C standard event methods
    event.preventDefault = fixEvent.preventDefault;
    event.stopPropagation = fixEvent.stopPropagation;
    // fix target
    event.target = event.srcElement;
    event.currentTarget = _this;
    // fix coords
    var base = (document.documentElement.scrollTop ? document.documentElement : document.body);
    event.pageX = (typeof event.pageX !== 'undefined') ? event.pageX : event.clientX + base.scrollLeft;
    event.pageY = (typeof event.pageY !== 'undefined') ? event.pageY : event.clientY + base.scrollTop;

    return event;
};
fixEvent.preventDefault = function () {
    this.returnValue = false;
};
fixEvent.stopPropagation = function () {
    this.cancelBubble = true;
};


/**
 * Pseudo event handler to be fired after the DOM was parsed or
 * on window load at last.
 *
 * @author based upon some code by Dean Edwards
 * @author Dean Edwards
 * @link   http://dean.edwards.name/weblog/2006/06/again/
 * @private
 */
window.fireoninit = function () {
    // quit if this function has already been called
    if (arguments.callee.done) return;
    // flag this function so we don't do the same thing twice
    arguments.callee.done = true;
    // kill the timer
    if (_timer) {
        clearInterval(_timer);
        _timer = null;
    }

    if (typeof window.oninit == 'function') {
        window.oninit();
    }
};

/***
 * Run the fireoninit function as soon as possible after
 * the DOM was loaded, using different methods for different
 * Browsers
 *
 * @author Dean Edwards
 * @link   http://dean.edwards.name/weblog/2006/06/again/
 */
// for Mozilla
if (document.addEventListener) {
    document.addEventListener("DOMContentLoaded", window.fireoninit, null);
}

// for Internet Explorer (using conditional comments)
/*@cc_on
  @if (@_win32)
  document.write("<scr" + "ipt id=\"__ie_init\" defer=\"true\" src=\"//:\"><\/script>");
  var script = document.getElementById("__ie_init");
  script.onreadystatechange = function() {
      if (this.readyState == "complete") {
          window.fireoninit(); // call the onload handler
      }
  };
  @end @*/

// for Safari
if (/WebKit/i.test(navigator.userAgent)) { // sniff
    var _timer = setInterval(function () {
        if (/loaded|complete/.test(document.readyState)) {
            window.fireoninit(); // call the onload handler
        }
    }, 10);
}

// for other browsers
window.onload = window.fireoninit;


/**
 * This is a pseudo Event that will be fired by the fireoninit
 * function above.
 *
 * Use addInitEvent to bind to this event!
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 * @see fireoninit()
 * @private
 */
window.oninit = function () {};

/**
 * Bind a function to the window.init pseudo event
 *
 * @author Simon Willison
 * @see http://simon.incutio.com/archive/2004/05/26/addLoadEvent
 * @param {function} the function to add to the init handler
 */
function addInitEvent(func) {
    var oldoninit = window.oninit;
    if (typeof window.oninit != 'function') {
        window.oninit = func;
    } else {
        window.oninit = function () {
            oldoninit();
            func();
        };
    }
};

/**
 * Bind variables to a function call creating a closure
 *
 * Use this to circumvent variable scope problems when creating closures
 * inside a loop
 *
 * @author  Adrian Lang <lang@cosmocode.de>
 * @link    http://www.cosmocode.de/en/blog/gohr/2009-10/15-javascript-fixing-the-closure-scope-in-loops
 * @param   functionref fnc - the function to be called
 * @param   mixed - any arguments to be passed to the function
 * @returns functionref
 */
function bind(fnc) {
    var args = Array.prototype.slice.call(arguments, 1);
    return function () {
        return fnc.apply(this, args);
    };
};

/**
 * @private
 */
function dispatchEvent(elem, eventName) {
    if (elem == null) {
        return;
    }

    debug("dispatching event " + eventName);
    if (elem.dispatchEvent) {
        // dispatch for firefox + others
        var evt = document.createEvent("HTMLEvents");
        evt.initEvent(eventName, true, true); // event
        // type,bubbling,cancelable
        return !elem.dispatchEvent(evt);
    } else if (document.createEventObject) {
        // dispatch for IE
        var evt = document.createEventObject();
        return (elem.fireEvent || function () {
            return false;
        })('on' + eventName, evt);
    }
};