Why is it a Bad Idea to Listen for Internal DOM Events in YUI Widgets?

Continuing in the same vein as the previous post, I thought I’d highlight another basic, but interesting facet of YUI development: listening to events from widgets.

Let’s say you have a TabView, and you want to take some action each time the user changes tabs. (Let’s say in this case, just count the tab switches.) Naively, you might do something like:

var tabview = new Y.TabView().render('#tabview'),
numSwitches = 0;

tabview.get('boundingBox').on('click', function (ev) {
    numSwitches += 1;
    Y.one('#counter').setHTML(numSwitches);
});

However, a far better way to go is to listen for widget state changes directly — in this case, the TabView‘s selectionChange event:

var tabview = new Y.TabView().render('#tabview'),
numSwitches = 0;

tabview.after('selectionChange', function (ev) {
    numSwitches += 1;
    Y.one('#counter').setHTML(numSwitches);
});

There are three reasons why you would want to use the latter approach over the former.

First, repeated clicks on the same label do not indicate a tab switch.

Second, a tabview might supply additional ways to navigate to different tabs such as keystrokes or gestures, which a click listener would miss.

Third, it is poor practice to depend on the internal structure of a widget’s HTML. The HTML produced by a widget is a reflection of the data the widget represents, and the UI elements produced are a consequence of that data. Changes to the widget’s data can trigger the widget to refresh its internal HTML, which in turn can destroy any DOM event listeners you might have set. If you need to react to widget state changes, listen directly for those state change events, not the DOM events that might have triggered the state change.

Naturally, these principles apply to any library that provides widgets, not just YUI. If you’re building a widget or plugin with some other JS library, think about how you can use that library to directly indicate a state change, rather than forcing other developers to listen for DOM events.

To sum up: if you’re trying to keep track of widget state changes, listen for state changes, not DOM changes. The DOM within a widget is volatile, and DOM events don’t necessarily correspond 1-to-1 to the thing you are actually trying to track.