Event Package Final
In case you have not been following the discussion so far, here are links to the other articles:
Event Package on 11/07/07
Event Package Part 2 on 10/24//07
Event Package Part 1 on 10/18/07
X-Browser Event Handling on 09/28/07
Today, I will be walking through the Event.js, discussing the parts not previously covered. Let’s start by looking at the caching function:
Example 1: Cache Event Function
var cacheEvent = function(el, eType, fn, opt) { // ensure that opt in an object var o = opt || {}; o.capture = (o.capture || false); // wrap the function so we can control the scope and return the optional second object var wrapFn = function(e) { return fn.call(o.scope || window, e, o.data); }; // cache the listener so we can try to automatically unload var evt = [el, eType, fn, wrapFn, o]; ecache[ecache.length] = evt; return evt; };
This Function will be called any time an event is added using the ‘add’ method and accepts the same parameters as the ‘add’ method. The first part of the Function, ensures that the options exist and that the capture value is set. Capture is a required for the actual attachment of the event, but since it is false 99% of the time, we are making it optional. The ‘wrapFn’ internal Function is used to set the scope of, and pass through the data option into, the callback Function. We then create an array of the important values for the cache and push it onto the stack.
We return the cache object so that the ‘add’ Function can use ‘wrapFn’ as its callback and apply the value of capture. This creates an interesting problem when removing events using the old ‘remove’ Function, as we have attached the the ‘wrapFn’ as the callback, but code outside of this package, does not have access to the wrapping Function. That is why we use a private ‘remove’ Function, so that we can locate ‘wrapFn’ from the cache. The public ‘remove’ Function does just that:
Example 2: Public Remove Function
remove: function(el, eType, fn, fl) { var rs = that.getListeners(el, eType); Core.batch(rs, function(r) { if ((r.fn) == (fn)) { update(r); } }); },
The public remove Function first gets all the cached objects for a given element that match the event type, using the ‘getListeners’ method (discussed later). We then iterate on the results and compare the paremeter ‘fn’ with the cached non-wrapped Function. When this is true, we actually remove the event, otherwise. The update Function handles this removal:
Example 3: Removing Events and Cached Elements
var update = function(r) { remove(r.el, r.type, r.wfn, r.opt.capture); var arr = []; if (0 < r.index) { arr = ecache.slice(0, r.index); } arr.concat(ecache.slice(r.index + 1)); ecache = arr; };
First we actually remove the event with the internal ‘remove’ Fucntion (notice that the wrapped Function is used). The rest of the method slices the cached event object that we are removing out of the stack.
In order to quickly and effectively find the events attached on an Element, both internally and externally, we created the public ‘getListeners’ method. I model the data architecture after a similar Function in YUI, as I could not think of a more elegant way to return matching cache objects.
Example 4: getListeners Function
getListeners: function(el, eType) { var results = []; Core.batch(ecache, function(l, i) { if (l && l[EL] === el && (! eType || eType === l[TYPE]) ) { results.push({ el: l[EL], type: l[TYPE], fn: l[FN], wfn: l[WFN], opt: l[OPT], index: i }); } }); return results; },
We simply iterate through the cache and create special result objects each time the element, and event type when provided, match that of a cached event object. Because the results objects can be passed externally, where the position name variables (’EL’, ‘FN’, ‘TYPE’, ‘WFN’, and ‘OPT’) do not exist, we instead create objects where each member is the lowercase of the constant and is set to the comparable value. The index is added, so the event object can later be removed from the cache.
I added two other Functions that I found to be extremely useful. One is the ‘removeEvents’ Function that removes all events from a given element by leveraging the existing caching infrastructure from event removal. And a ‘removeAll’ Function that removes all events ever assigned using the ‘add’ method. This method is attached to the window unload event and helps prevent memory leaks (especially in IE6) where the memory reference to the dom element that triggers the event is not cleared, because the event is still pointing to the dom element.
Lastly, I removed as spaces from the Event name constants:
Example 5: Event Name Constants
BLUR: ‘blur’, CLICK: ‘click’, DOUBLECLICK: ‘dblclick’, KEYDOWN: ‘keydown’, KEYPRESS: ‘keypress’, KEYUP: ‘keyup’, LOAD: ‘load’, MOUSEDOWN: ‘mousedown’, MOUSEMOVE: ‘mousemove’, MOUSEOVER: ‘mouseover’, MOUSEOUT: ‘mouseout’, MOUSEUP: ‘mouseup’, SUBMIT: ’submit’, UNLOAD: ‘unload’,
I did this because there was a conflict between several events, such as the KEYUP event and the KEY_UP that actual keyboard key ‘up arrow’.
Many of the Function names have become shorter as well. I simply found that I knew when using the event package that ‘add’ meant that I was adding a listener, so I did not need the names to be longer.
Please let me know any feedback you have, especially if you have suggestions for improvement.

Nice package! I’ve been developing a similar event package for an application I’m developing. One thing you might want to try is resolving the events as soon as they are triggered (as seen in the jQuery source).
Add a new method ‘resolveEvent’ and call it from the wrapped function in the add event method. The purpose would be to resolve any cross browser discrepancies.
First map the event to the window object - window.event, and then begin resolving the event properites, such as target:
event.target = event.target || event.srcElement;
that way there is no need to call getTarget(), simply call event.target
Go to the jQuery source and look for the fix method in the event namespace for all the fixes
Comment by Rozzy — November 25, 2007 @ 7:34 pm
rozzy, great comment. I chose not to resolve the source, because I use getTarget less than 20% of the time. More often than not, I find myself passing through the object that I need to interact with. The operation time is nominal, so you aren’t slowing much down by finding the target on each event.
Comment by admin — November 28, 2007 @ 10:23 am