
DOM manipulation in YUI
The YUI Dom utility enhances your DOM toolkit in several important ways, giving you more power and more control. First of all, it adds a whole range of new methods for obtaining DOM nodes such as:
.getAncestorByTagName()
.getAncestorByClassName()
.getChildren()
.getElementsByClassName()
These give you much more flexibility than the standard DOM node retrieval methods. As if these additions weren't enough in themselves, the YUI Dom utility also gives you a range of methods for defining your own parameters with which to obtain elements. These include:
.getAncestorBy()
.getChildrenBy()
.getElementsBy()
.getFirstChildBy()
.getLastChildBy()
.getNextSiblingBy()
.getPreviousSiblingBy()
In some of these methods that would return an array of elements, as you are likely to loop through that array, you can provide instead a function that will be executed on each of them. These methods, then, not only locate the nodes but do the looping for you. The function that actually does the looping is also available to you: batch()
.
If you ever tried positioning elements in your code you probably had to figure out what would work with each browser and in what conditions, whether to use, for example, the CSS top
attribute or the DOM's scrollTop, offsetTop
, or clientTop
. The YUI provides .getRegion(), .getX(), .getY()
, and .getXY()
. You can also change these positional properties as well using .setX(), .setY()
, and .setXY()
and they all simply work as expected.
Another important point is that you don't need to worry about whether these methods should be called on the special document node or on standard nodes. All of the methods defined by the classes making up the Dom utility are used in conjunction with the library namespace YAHOO.util.Dom
prefixed to the method name.
If you look through the W3C Level 1 Core Specification, which defines most of the basic DOM manipulation and traversal methods, you'll notice that an insertAfter
method does not exist. The YUI Dom utility kindly adds this method to our toolset, giving you more flexibility when adding new content to your pages, as we saw in our first YUI Dom example.
Although a specification exists for working with stylesheets using the traditional DOM, support for class names is limited and is sometimes not implemented by browsers. The YUI rectifies this lack of support by providing several methods that allow you to work closely with class names, which we'll see later.
Many DOMs make light work
Traditionally, the DOM is sometimes regarded as a bit of a pain to work with, but all of that ends when using the Dom utility. A lot of the other utilities and controls make use of the functionality that it provides, so it is usually part of any library implementation you create.
The Dom utility has a few classes and is relatively small compared to some of the other utility files. Except for the Region
class and Point
, which is a subclass of it, they are static, that is, you don't need to create instances of them. They provide browser normalization functions that help to iron out the differences between DOM implementations among some of the more common browsers, and are made up mostly of convenience methods. Let's look at them in more detail.
The Dom class
The first class is YAHOO.util.Dom
. All of its members are static methods, you don't need to create an instance to use them and there are no properties at all. The methods available deal mainly with element positioning, setting CSS classes and styles, and getting elements by a range of different means, although there are also a couple of useful methods used to obtain the viewport width and height, and other information like that.
The Dom utility allows you to use the shorthand .get()
method to get an element using a reference to it, such as its id
, but you can also get elements based on class name, or by using the .getElementsBy()
method to create a custom test, such as by attribute. This method takes three arguments: the first is a reference to a function that will receive a reference to each object and returns true
if it is part of the set you are looking for, the second is optional and is used to pre-select some elements by tag name, and the third (also optional) is where to start the search.
Dom.getElementsBy( // we are assuming our habitual shortcut function(el) { return el.type == "text"; }, "input", "container", function(el) { Dom.setStyle(el,"background-color","red"); } );
In this code segment, which could easily be inserted into the sandbox of the previous example, we are using .getElementsBy()
to look for input elements of type text
starting from the element with an id
of container
. Instead of storing the resulting array for further use, we add a function that will do what we want on those elements that match the previous conditions: we set their background to red.
If you're ever using a utility that assigns default id
attributes to elements dynamically, and you wonder where these id
attributes come from, the answer is the .generateId()
method found here in the Dom class. It generates either a single id
or array of id
attributes for either an element or array of elements passed into it as an argument. It can also generate a prefix for the id
if this is passed as a second, optional parameter.
Using the Dom class
Let's put some of the available methods from the Dom class to work in a basic page. In your text editor, begin with our standard template and make sure to include yahoo-dom-events.js
, and then create our sandbox with the usual shortcuts. Instead of showing the whole code at once, I'll show a little bit of HTML and the code that manipulates it so, we'll start with checking the view port dimensions:
<div class="box" id="info"> <h2 class="header">Page Information</h2> <p><span id="portWidth"> The current viewport width in pixels is: </span></p> <p><span id="portHeight"> The current viewport height in pixels is: </span></p> <p><span id="childClass"> The first child of this div has a class of: </span></p> <p><span id="children"> This div has </span><span> child elements</span></p> </div>
You can see from the <span>
elements the kind of information we are going to obtain in this example. In the sandbox, we add:
//add the information after the given label var addInfoText = function (where, what) { var newSpan = document.createElement("span"); newSpan.innerHTML = what; Dom.addClass(newSpan, "infoText"); Dom.insertAfter(newSpan, where); }; //show some assorted information addInfoText("portWidth", Dom.getViewportWidth()); addInfoText("portHeight", Dom.getViewportHeight()); addInfoText("childClass", Dom.getFirstChild("info").className); addInfoText("children", Dom.getChildren("info").length);
The first function, addInfoText()
, helps us add any information (what), anywhere (where) by referring to the ID of the accompanying text. It creates a <span>
to hold it, fills it with the corresponding piece of information, adds a class name to the span to highlight the result (I have omitted the definition of that class; any suitable decoration would do) and finally inserts it after the given element.
We use that function to show information we can obtain using the Dom utility. We can easily find the viewport
dimensions, as well as class names and the number of children of a certain element we locate by its id
.
This allows us to see how .addClass()
and .insertAfter()
can be used but it really makes little sense to enclose the label in a <span>
instead of using it as a placeholder for the variable part of the text, which is what we will discuss next.
The Region class
The other class is YAHOO.util.Region
, which in turn has the subclass YAHOO.util.Point
. A region defines a rectangular area of an imaginary grid covering the page. You hardly ever create an instance of Region
directly, it is most often returned by calling Dom.getRegion()
.
Region has top, bottom, left, right, height
, and width
properties, plus values at indices 0 and 1, which correspond to the left
and top
properties for symmetry with the .getXY()
and .setXY()
methods. It also has methods to detect whether a region is contained in another region, to calculate its surface area, and the intersection and union with another region.
We will measure the dimensions of this region:
<div class="box" id="region"> <h2 class="header">Region Information</h2> <p>The region of this div is: <span id="area" class="infoText"></span></p> </div>
Using this code:
//get the Region of the element called region var elemRegion = Dom.getRegion("region"); //use YAHOO.lang.substitute to format that information Dom.get("area").innerHTML = Lang.substitute( "Top: {top}, right: {right}, bottom: {bottom}, left: {left}", elemRegion );
We first get the Region
for the element we are interested in. We are going to put it into the <span>
with an id
of area
so we can use .get()
to locate it and set its innerHTML
. So far nothing new. Now, we will use one of those functions that would be nice to have in native JavaScript and which the YAHOO.lang
class provides (which we alias to Lang
to make shorter). Method .substitute()
is like a primitive printf
for JavaScript. It takes a formatter string with placeholders for the data. Each placeholder is a name enclosed in curly brackets. Those placeholders will be filled by properties of that name from the object provided as the second argument. The Region
object already has properties with those names so we use them for the placeholders.
For our next example we will use the following HTML segment:
<div class="box" id="positions"> <h2 class="header">Positional Methods</h2> <p>The X position of this div is: <span id="elemX" class="infoText"></span></p> <p>The Y position of this div is: <span id="elemY" class="infoText"></span></p> <button id="btnMove">Move this div!</button> </div>
In this segment we have two placeholders to fill with the coordinates of the box, but we have also added a button to play with it. We first fill in the initial information:
//we write a function to show the position of the box var showPositions = function () { Dom.get("elemX").innerHTML = Dom.getX("positions"); Dom.get("elemY").innerHTML = Dom.getY("positions"); }; //we call it a first time showPositions();
As we are going to show the positional information more than once, we create a function to do it. In it, we just fill in the innerHTML
of each placeholder with the result of checking, via .getX()
and .getY()
, the coordinates of element positions. Now, we do something about the button:
// when the move button is clicked Event.on("btnMove", "click", function() { //the X and Y coordinates of the info box are swapped Dom.setXY("positions",Dom.getXY("positions").reverse()); //show those coordinates again showPositions(); //hide the move button //Dom.setStyle("btnMove","display","none"); });
We listen for a click on button btnMove
and when it happens, we read the XY coordinates with .getXY()
, which returns an array with two items corresponding respectively to the left (x) and top (y) coordinates, then we use method .reverse()
of Array
to swap them and set the element to those values via .setXY()
.
We show the new coordinates again, which should have been reversed from their previous values and, if we wished to allow only one swapping, we could further hide the move button, though it is funnier to keep flipping it from one place to another.
Some of the methods can loop through a set of results. Let's say that, for whatever reason you might want to imagine, we want all the table cells within a particular area that have a specific class name, to have the same width. We could do it like this:
var makeSameWidth = function () { var maxWidth = 0; Dom.getElementsByClassName("infoText", "td", "region2", function(el) { maxWidth = Math.max(Dom.getRegion(el).width, maxWidth); } ); Dom.getElementsByClassName("infoText", "td", "region2", function(el) { Dom.setStyle(el, 'width', maxWidth + 'px'); } ); };
We use method .getElementsByClassName()that
allows us to search in a way not envisioned in the traditional DOM model. We ask it to give us all the elements with the class name of infoText
, ignoring those elements that are not td
and starting at element region2
.
As the method can produce an array of elements, it can also accept a function to operate on those same elements. That function will receive a reference to each of the elements found, which we name el
. In the first call to .getElementsByClassName()
we read the .width
property of the region each cell occupies and compare it to the widest we have found so far. In the second call, we set the width of them all to the widest we've found in the first loop.
Note that a Region
returns all its sizes as integer pixel values. When we use .setStyle()
to set the width, we have to add the'px'
units to the end to make it a valid CSS size setting. Another important point is why we haven't used .getStyle()
to read the width of all elements. The problem is that the width
CSS property can have values such as auto
and inherit
and some browsers will report those values as strings; instead of a number you'll get "auto". Region
will report the actual width as it results from auto-adjusting or inheriting.
The page with all the segments put together looks like this:

The image shows the third info box already moved to its alternate position, leaving the empty space behind. Should the button be clicked again, the box would return to that gap.
Additional useful Dom methods
There are also some useful tools for working with CSS. The DOM standard simply states that the class
attribute or className
property (class
is a reserved word in most languages—even in JavaScript, though it is not used—so they had to come up with a less conflicting name) is a string containing the name or names of the CSS classes to be applied to the element. However, some browsers handle this property as something special, so not all regular methods to manipulate other attributes work with this one. Also, as a single element can have more than one class name at once, dealing with manipulating those classes is a little tricky, usually involving regular expressions or other string manipulation tricks.
The YUI provides us with several methods instead:
.addClass()
: Adds a single class name to an element, without affecting others that might already have it..removeClass()
: Removes the given class name leaving the others intact..replaceClass()
: Removes the first class name given, if found, and adds the second, regardless of whether it found the first, and leaving all the rest alone..hasClass()
: Checks whether the given class is within those in the element.
All of these also operate on an array of elements; their first argument can either be a single DOM node or a nodelist and they will perform the same action on each element (.hasClass()
will return a Boolean or an array of Booleans).
If, instead of changing the CSS style of an element by changing the class name we want to get or set a style directly, we can use methods .getStyle()
and .setStyle().
These methods are smart enough to know that when you are looking for float
, it should translate it to styleFloat
for IE and cssFloat
for other browsers and that as IE does not handle opacity
, it should apply a proprietary hack to do it.
Methods .getAttribute()
and .setAttribute()
let you read and write from any attribute, those that the DOM actually defines and also custom ones. Some browsers do not respond well to attributes that are not part of the definition of the DOM. These methods take care of that.
Methods .isAncestor()
and .inDocument()
allow you to ask whether a DOM node is an ancestor of another, that is, the second is contained in the first, or if an element is part of a particular document, as the same application might be manipulating several documents in different iframes.
Finally, given the following fragment:
<h1>header</h1> <p>content</p>
it would seem obvious that DOM's .nextSibling
property for the <h1>
element should point to the <p>
element but, depending on the browser, it might not be so. For some browsers, the next sibling is a text element containing a new-line character, for others, it is discarded as a whitespace character. Anyway, besides this inconsistency, you might really want to know which is the next DOM element, even if there was any significant text in between. The Dom utility provides .getNextSibling(), .getPreviousSibling(), .getFirstChild(), .getLastChild()
, and .getChildren()
that do what their similarly named DOM properties do but counting only DOM elements.
Other classes
The YAHOO.util.Point
class extends the Region
class and is used to define a special region that represents a single point on a grid. As expected, properties top
and bottom
would coincide, as would left
and right
, while width
and height
would be zero as is the area. A point cannot contain an area but the inverse might be true, an intersection would return the point itself while a union would operate normally.
The YAHOO.util.Dom.Color
has a short table of standard color names such as YAHOO.util.Dom.Color.KEYWORDS.aqua
and their values in hexadecimal. It also has utility functions .toHex()
and .toRGB()
to help converting color strings to these formats.