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.

Posted in Web

Why Does YUI Have a Y.one() Method?

When I was working on the YUI 3 Cookbook, one of the things I really enjoyed was being able to pick the brains of the YUI team and get a straight-from-the-horse’s-mouth account of YUI’s architecture and design. With the release of the cookbook, I thought it would be fun to look back through the manuscript and pick out some short topics that might be of interest to new and intermediate YUI users.

One basic thing you might be wondering about is… why does YUI have two idioms for reaching into the DOM?

YUI’s first method, Y.all(), retrieves a Y.NodeList instance:

var errorDivs = Y.all('div.error');

If the selector fails to match any elements, Y.all() returns an empty Y.NodeList.

By contrast, the second method, Y.one(), retrieves a single Y.Node instance:

var errorDiv = Y.one('div.error');

If the selector matches multiple nodes, Y.one() returns the first match (as if you were using the :first jQuery pseudo-selector). If the selector fails to match any elements, Y.one() returns null.

So what’s up with this? Why bother having two methods, and why do they behave so differently? Why not just have querySelectorAll()-like behavior and be done with it?

The answer is that it’s helpful to know ahead of time whether you will receive a single node or a collection. This in turn is why the two methods return different values when the selector fails to find a match. Since Y.one() returns null, it is great for simple node existence checks:

if (Y.one('#myDiv')) {
    ...
}

As for Y.all(), the fact that it always returns an empty collection makes it always safe for doing bulk operations. For instance, the following works just fine even if there are no error divs in the document:

Y.all('div.error').remove();

Some libraries rely on a single abstraction (reaching into the DOM always returns a collection), but the choice of Y.all() and Y.one() — or, if you’re thinking in native terms, querySelectorAll() and querySelector() — ultimately enables you to write cleaner code. The cost is having an extra API method to think about. Fortunately, YUI’s methods have short, distinct names, so it’s pretty easy to remember which one does what.

Posted in Web