A large enterprise sized project I work on uses YUI library extensively, and events are a huge part of the rich front end we’re developing. What you start to learn quickly about UI events, is that dependencies between different events start to get very complicated very fast. When A depends on B, but B needs to wait for C and D and E to finish, but E needs to wait for F to finish, you have a complex situation on your hands. Up until recently we were able to rely mostly on just using CustomEvents in YUI to handle this for us.
Where that starts to break down is when you have one event, A, that is dependent on multiple other events, B, C, D. You now have to manually keep track of what has fired, and make sure you don’t fire that A until B, C and D have all fired. With very little code, here is a simple class that can be used in conjunction with YIU CustomEvents. It will handle the ‘book-keeping’ of firing what you want when all the events you have designated fire.
EventMediator = {
addActivationRecord : function(record) {
record = record || {};
var that = this;
for(var idx in record.events) {
var eventRecord = record.events[idx];
if(eventRecord.event !== null) {
eventRecord.fired = false;
eventRecord.event.subscribe(function(scopedEvent) {
return function(e) {
scopedEvent.fired = true;
that.fireActivation(record);
}
}(eventRecord));
}
}
},
fireActivation : function(record) {
var fired = true;
for(var idx in record.events) {
if(record.events[idx].fired === false) {
fired = false;
break;
}
}
if(fired === true) {
record.activate.call(record.scope);
}
}
};
Using the EventMediator would look something like the following:
EventMediator.addActivationRecord({
events : [
{ event : myObj.someYUICusomEvent },
{ event : myObj.anotherYUICusomEvent }
],
activate : myObj1.myActivationCallback,
scope : myObj1
});
It’s pretty straightforward I think. You call addActivationRecord, passing in an array of objects, whose ‘event’ property points to a YUI CustomEvent. You also provide an activate callback method, and give it a scope in which to call the method. For my purposes so far this has worked pretty well, although I’m sure it could be built up to be much more robust. Hope it helps someone out!
Javascript getFirstDescendantBy()
Update – Jan 22, 2009
A new native YUI function is in the works, and does this job better
Recently I was working on optimizing some javascript, and found a slow area that was trying to find the first focusable input in a certain area of the page, and it was taking anywhere from 100 – 500 milliseconds, depending on the size of the DOM tree in that element. After digging into it, I noticed it was using the YAHOO.util.dom.getElementsBy() method, which basically had to walk through the whole DOM tree in this case, testing each node against the boolean method passed in. After calling that, it would then return the first, if any, element that getElementsBy returned. Obviously this was a bad approach, as after you find the first match, there is no need to go further.
I did a little research, and saw that this had come up in a thread on the YUI group. I ended up writing a small method to fill in this functionality I wanted out of YUI, called getFirstDescendantBy(rootEl, method). Below is the code, the function takes a root element, or string id of that element, and then a function to test each element against that has the element being tested as the only input. This function passed in should return a boolean, and is similar to the way the YUI dom function getElementsBy works. Hopefully this will help out some people in similar situations.
function getFirstDescendantBy(rootEl, method) { var root = typeof rootEl === 'string' ? document.getElementById(rootEl) : rootEl; var firstDescendant = null; var children = root.childNodes; for(var idx in children) { var child = children[idx]; if(child.nodeType === 1) { if(method(child)) { firstDescendant = child; break; }else if(child.childNodes.length > 0) { var recursiveResult = getFirstDescendantBy(child, method); if(recursiveResult !== null) { firstDescendant = recursiveResult; break; } } } } return firstDescendant; }