/* Events.2.js - 10/20/2006 - Fixed bug in removeEvent, where all handlers were wiped out. TJB */ // ------------------------------------------------------------ // Requirements. // ------------------------------------------------------------ if (typeof dbg != "function") { var dbg = function () {}; }; // ------------------------------------------------------------ // Utilities. // ------------------------------------------------------------ function hitch (obj, meth) { return function () { return typeof meth == "function" ? meth.apply(obj, arguments) : obj[meth].apply(obj, arguments); }; }; // ------------------------------------------------------------ // Events // ------------------------------------------------------------ Events_class = function() { this.init(); }; Events_class.prototype.init = function() { this.dbgColor = "#006666"; this.debug = true; this.triggerElements = new Array(); // memory cleanup this.add({ element: window, eventType: 'unload', handler: this.clearTriggers, context: this }); }; Events_class.prototype.add = function (/* element, eventType, [object || function], [method] */) { // Consolidate arguments var element; var eventType; var handler; var delay = 0; var dataPackage; var trace = false; var context = false; var label = ""; if (arguments.length == 1) { element = arguments[0].element; eventType = arguments[0].type; handler = arguments[0].handler; delay = parseInt(arguments[0].delay) || 0; if (typeof element == "undefined" || typeof eventType == "undefined" || typeof handler == "undefined") { return false; }; context = typeof arguments[0].context != "undefined" ? arguments[0].context : false; dataPackage = typeof arguments[0].data != "undefined" ? arguments[0].data : {}; trace = (arguments[0].trace == true); label = typeof arguments[0].label != "undefined" ? arguments[0].label : ""; if (context) { // will execute "handler" as a method of object instance: "context" handler = hitch(context, handler); }; } else { element = arguments[0]; eventType = arguments[1]; // the third argument can be either a function pointer, or an object with a method reference if (typeof arguments[2] == "object" && typeof arguments[3] != "undefined") { // will execute "handler" as a method of object instance: "context" handler = hitch(context, handler); dataPackage = typeof arguments[4] == "undefined" ? {} : arguments[4]; } else if (typeof arguments[2] == "function") { // executes handler as method of window (free floating function) handler = arguments[2]; dataPackage = typeof arguments[3] == "undefined" ? {} : arguments[3]; } else { return false; }; }; // CREATE AND STORE EVENT HANDLER // This puts a function reference on an array associated with the element an eventType. Handlers // so stored will be fired by the trigger function in sequence, the handler will always have // an event object passed to it because the trigger handles cross-browser event parameter issues // closure variables: element, eventType, handler, dataPackage, trace, label var evtHandler = function(e) { if (trace) { dbg("Event Triggered", "", ""); dbg("Event Info", eventType, ""); dbg("Object Info", element.tagName + (element.id ? ": " + element.id : "")); dbg("Handler Label", label); }; handler(e, element, dataPackage); } this.storeHandler(element, eventType, evtHandler); // CREATE TRIGGER // The trigger will fire all handlers associated with a given element/eventType, the purpose of // the trigger is to encapsulate cross-browser parameter issues and to consolidate multiple // event references per element // re-reference "this" for the trigger closure var self = this; // create a closure to allow us to customize the event handler call var trigger = function () { // dbg("trigger firing on element: " + element); var e = arguments.length ? arguments[0] : window.event; self.runHandlers(e, element, eventType); }; // create a delay if requested if (delay) { trigger = this.createDelayedTrigger(element, eventType, trigger, delay); } return this.setTrigger(element, eventType, trigger); }; Events_class.prototype.setTrigger = function(element, eventType, fnTrigger) { if (!fnTrigger) { dbg("Events.setTrigger: no trigger function provided"); return false; }; // default the trigger storage spot element._triggers = element._triggers || new Object(); // wipe old trigger and old JS event (if any) this.removeTrigger(element, eventType); // store the new trigger function element._triggers[eventType] = fnTrigger; // element needs to be stored for memory cleanup at page unload this.registerTriggerElement(element); // Add trigger as the actual event that fires return this.addJSEvent(element, eventType, fnTrigger); }; // wipe the old trigger, if one existed Events_class.prototype.removeTrigger = function(element, eventType) { if (element._triggers && element._triggers[eventType]) { var fnTrigger = element._triggers[eventType] || null; this.removeJSEvent(element, eventType, fnTrigger); element._triggers[eventType] = null; }; } // memory cleanup function Events_class.prototype.clearTriggers = function(els) { var rElements = new Array(); if (els !== undefined) { if (els instanceof Array) { rElements = els; } else { rElements = new Array(els); }; } else { rElements = this.triggerElements || new Array(); }; for(var i=0, thisEl, eventType; thisEl=rElements[i]; i++) { if (thisEl._triggers !== undefined) { for(eventType in thisEl._triggers) { this.removeTrigger(thisEl, eventType); }; delete(thisEl._triggers); }; }; }; Events_class.prototype.registerTriggerElement = function(element) { var found = false; for(var i=0, elCount = this.triggerElements.length, thisEl; thisEl = this.triggerElements[i]; i++) { found = found || ( thisEl == element ); if (found) { return; }; }; if (!found) { this.triggerElements.push(element); }; }; // low-level event add/remove wrappers Events_class.prototype.removeJSEvent = function(element, eventType, fnHandler) { if (fnHandler !== undefined) { if (element.removeEventListener) { // firefox element.removeEventListener(eventType, fnHandler, false); result = true; } else if (element.detachEvent) { // ie result = element.detachEvent("on" + eventType, fnHandler); } return result } return false; }; Events_class.prototype.addJSEvent = function(element, eventType, fnHandler) { if (element && eventType && fnHandler) { if (element.addEventListener) { // firefox element.addEventListener(eventType, fnHandler, false); return true; } else if (element.attachEvent) { // ie return element.attachEvent("on" + eventType, fnHandler); }; } return false; }; // a handler is an individual operation assigned to an element/event Events_class.prototype.storeHandler = function(el, eventType, fnHandler) { // dbg("Events: store trigger", eventType, this.dbgColor); if (el && eventType && fnHandler) { // default storage stuff, the triggers object is an associative array // of all the assigned event types which are assigned to this element el._handlers = el._handlers || new Object(); el._handlers[eventType] = el._handlers[eventType] || new Array() var rHandlers = el._handlers[eventType]; // push the function reference onto the handlers array rHandlers.push(fnHandler); }; }; Events_class.prototype.runHandlers = function(e, element, eventType) { var rHandlers = element._handlers[eventType]; if (rHandlers && rHandlers instanceof Array) { for(var i=0, handlerCount = rHandlers.length; i < handlerCount; i++) { rHandlers[i](e); }; }; }; // REMOVE wipes all handlers tied to a given event/type Events_class.prototype.removeEvent = function(element, eventType) { this.removeTrigger(element, eventType); if (element._handlers && element._handlers[eventType]) { element._handlers[eventType] = new Array(); } }; Events_class.prototype.remove = Events_class.prototype.removeEvent; // Delayed event firing, creates a trigger which is actually a timer reset that calls // the originally defined trigger Events_class.prototype._delayedTriggers = new Object(); Events_class.prototype.createDelayedTrigger = function(el, eventType, trigger, delay) { // create an id that we can refer to with a string because that's the way setTimeout likes it var d = new Date(); var id = "delayed_event_" + this.getUniqueID(el) + "_" + eventType + "_" + d.getTime(); // store the trigger in a separate timer array this._delayedTriggers[id] = trigger; // create a timer object and the command it will fire var eventTimer = null; var cmd = "Events._delayedTriggers['" + id + "']()"; return function() { // closure variables: eventTimer, cmd, delay if (eventTimer) { clearTimeout(eventTimer); } eventTimer = setTimeout(cmd, delay); } }; Events_class.prototype.idCounter = 0; Events_class.prototype.getUniqueID = function(el) { if (el.id) return el.id; return this.idCounter++; }; // Events cancelling, halts execution of default event stuff // using passed event object Events_class.prototype.cancelEvent = function(event) { if (event) { if (event.stopPropagation) { event.stopPropagation(); } else { event.cancelBubble = true; } if (event.preventDefault) { event.preventDefault(); } else { event.returnValue = false; } }; return false; }; Events_class.prototype.cancel = Events_class.prototype.cancelEvent; // Determines object which fired event from event object reference // The second argument of every handler is now a direct reference to the assigned element, so I // do not see the value of this function anymore, but it exists here for backwards compatibility Events_class.prototype.getSrcElement = function(/* native js event object */) { var el = arguments[0].srcElement || arguments[0].currentTarget || window; try { /* Safari triggers events on text nodes */ if (el.nodeType == 3) { el = el.parentNode; }; } catch (e) { el = window; }; return el; }; // Not sure these really belong in here, preserved for backwards compatibility Events_class.prototype.disableTextSelect = function() { if (!document.onselectstart) { document.onselectstart = function() { return false; }; } }; Events_class.prototype.removeTextSelect = Events_class.prototype.disableTextSelect; // backward compat Events_class.prototype.enableTextSelect = function() { if (document.onselectstart) { document.onselectstart = null; } }; var Events = new Events_class();