NodeList

A NodeList is a specialized subclass of Array that is expressly designed with some fantastic extensions for manipulating collections of DOM nodes with ease. One of the more seductive features of a NodeList is its ability to provide chaining via the dot operator, although many specialized capabilities such as mapping, filtering, and looking up the index of a node exist as well.

Table 5-2 provides an overview of the NodeList methods available. These methods are named according to the very same convention as Base's Array functions. The only caveats are that they return NodeLists instead of Arrays.

Tip

For a review of the fundamentals involving the following Array manipulations, see the section "Array Processing" in Chapter 2.

Table 5-2. NodeList methods

Name

Comment

[a]

indexOf(/*DOMNode*/n)

Returns the first location of an item in the NodeList.

lastIndexOf(/*DOMNode*/n)

Returns the last location of an item in the NodeList.

every(/*Function*/f)

Returns true if the function returns true for every item in the NodeList.

some(/*Function*/f)

Returns true if the function returns true for at least one item in the NodeList.

forEach(/*Function*/f)

Runs each item through a function and returns the original NodeList.

map(/*Function*/f)

Runs each item through a function and returns the results as a NodeList.

filter(/*Function*/f)

Runs each item through a NodeList, returning only the items that meet the function criteria, or applies CSS query filtering to the list of nodes.

concat(/*Any*/item, ...)

Returns a new NodeList with the new items appended, behaving just like the Array.concat method except that it returns a NodeList.

splice(/*Integer*/index, /*Integer*/howManyToDelete, /*Any*/item, ...)

Returns a new NodeList with the new items inserted or deleted, behaving just like the Array.splice method except that it returns a NodeList.

slice(/*Integer*/begin, /*Integer*/end)

Returns a new NodeList with the new items sliced out, behaving just like the Array.slice method except that it returns a NodeList.

addClass(/*String*/class)

Adds a class to every node.

removeClass(/*String*/class)

Removes a class from every node.

style(/*String|Object*/style)

Gets or sets a particular style to every node when style is a String. Works just like dojo.style to set multiple style values if style is an Object.

addContent(/*String*/ content, /*String?|Integer?*/ position)

Adds a text string or node to the relative position indicated for each node. Valid values for position include first, last, before, and after. Position values first and last are a function of the node's parent, while before and after are relative to the node itself.

place(/*String|Node*/ queryOrNode, /*String*/ position)

Places each item in the list relative to node, or to the first item matched by the query criteria. Valid values for position are the same as with method addContent (see above).

coords( )

Returns the box objects for all elements in the list as an Array—not as a NodeList. Box objects are of the form { l: 50, t: 200, w: 300: h: 150, x: 100, y: 300 }, where l specifies the offset from the left of the screen, t specifies an offset from the top of the screen, w and h correspond to the width and height of the box, and x and y provide the absolute position of the cords.

orphan/*String?*/ filter

Removes DOM nodes from the list according to the filter criteria and returns them as a new NodeList.

adopt(/*String|Array|DomNode*/ queryOrListOrNode, /*String?*/ position)

Inserts DOM nodes relative to the first element of the list.

connect(/*String*/ methodNameOrDomEvent, /*Object*/ context, /*String*/ funcName)

Attaches event handlers to every item in the NodeList, using dojo.connect so event properties are normalized internally. The signature is just like dojo.connect in that you provide a method name or DOM event for connecting along with an optional context and function name. DOM event names should be normalized to all lowercase. For most use cases, you will instead use the shortcuts discussed later in this chapter in "Dom Event Shortcuts."

instantiate(/*String|Object*/declaredClass, /*Object?*/properties)

Handy for instantiating widgets in bulk.[a] Assuming the NodeList contains a number of arbitrary source nodes, this method tries to parse them into the widget class defined as declaredClass, passing in any widget properties provided in properties.

[a] Widgets are not formally introduced until Chapter 11; consequently, no examples in this chapter demonstrate usage of instantiate.

Array-Like Methods

As you may recall, there are several functions available for manipulating arrays that are included in Base. You'll be pleased to know that many of these same methods are available to NodeList. In particular, indexOf, lastIndexOf, every, some, forEach, map, and filter work just like the corresponding functions for an array—although NodeList 's filter function offers some additional features depending on the parameter passed. (More on that shortly.)

To get started, we'll need to create ourselves a NodeList. You can use the same syntax as you would with an array, which explicitly provides some elements to the NodeList, or you can also use a NodeList 's built-in concat method to create a NodeList from an existing Array object.

Here are a few of the possible ways to construct a new NodeList:

var nl = new dojo.NodeList(  ); //create an empty NodeList

var nl = new dojo.NodeList(foo, bar, baz);
//create a NodeList with some existing nodes

var a = [foo, bar, baz];
// suppose there is an existing Array object with some nodes in it

a = nl.concat(a); //turn the  Array into a NodeList

Warning

If you create a NodeList with the following approach, you may not end up with what you expect:

var nl = new dojo.NodeList([foo, bar, baz]);

The previous line of code returns a NodeList that contains an Array object with three numbers in it—this is the exact same result you'd get as a result of new Array([foo,bar,baz]).

Chaining NodeList results

While Dojo's array methods are extremely useful if you don't need to stream in the results of a previous operation into another operation, or if you need to strictly deal with an Array, you may otherwise find NodeList s to be your new data structure of choice because the syntax is quite elegant. The following example illustrates chaining together some operations:

var nl = new dojo.NodeList(node1,node2,node3,node4,...);

nl.map(
  /* Map some elements... */
  function(x) {
    /* ... */
  }
)
.filter(
  /* And now filter them... */
  function f(x) {
    /* ... */
  }
)
.forEach(
  function(x) {
    /* Now do something with them... */
  }
);

Had we used the standard Dojo functions to accomplish this same workflow, take a look at the clutter that would have been introduced by way of intermediate state variables:

var a0 = new Array(node1,node2,node3,node4,...);

/* Map some elements... */
var a1 = dojo.map(a0,
  function(x) {
    /* ... */
  }
);

/* And now filter... */
var a2 = dojo.filter(a1
  function f(x) {
    /* ... */
  }
);

/* Now do something with them... */
dojo.forEach(a2
  function f(x) {
    /* ... */
  }
);

Warning

Be advised that although chaining together the results of operations via the dot operator can produce really elegant code, the lack of intermediate state variables can also have a significant impact on your ability to debug and maintain an application. As always, use discretion.

String-as-Function style Arguments

Just like Base's methods for manipulating Array s, you can use the special index, array, and item identifiers if you choose to use String arguments as described in the section "Array Processing" in Chapter 2. To recap, consider the following example:

//Suppose you have an existing NodeList called nl...

//Use the item identifier instead of writing out the entire function wrapper
nl.forEach("console.log(item)");

Enhanced filtering

In addition to NodeList 's filter method, which provides the traditional array-like capabilities like dojo.filter, NodeList also provides CSS query-style filtering when you pass in a String parameter. For example, the previous code block illustrated passing a function into NodeList to operate on each individual piece of data. The following block of code uses CSS query syntax to filter an actual list of DOM nodes by the query string:

dojo.query("div")
.forEach(
  /* Print out all divs */
  function f(x) {
    console.log(x);
  })
.filter(".div2") //filter on a specific class and print again.
.forEach(
  /*Now, print only div.div2 divs*/
  function f(x) {
    console.log(x);
   }
  });

Style

Given that you can use CSS query syntax to fetch a list of nodes, it seems entirely possible that you may want to perform style operations on them. For this very reason, NodeList includes a few methods to help you get the job done. NodeList 's style method is especially noteworthy in that it can act as a getter or as a setter depending upon whether you provide a second parameter. This behavior is just like the dojo.style function.

As a reminder of how dojo.style works, recall that dojo.style(someNode, "margin") would return the margin value of a DOM node, while dojo.style(someNode, "margin", "10px") would set the node's margin to a value of 10 pixels.

Manipulating a NodeList is just the same except that there's no need for an explicit first parameter that denotes a particular node anymore. Like any other NodeList function that processed nodes, the method is applied to each node in the list:

// dojo.style approach...
var a = [];

/* load the Array with some nodes */

// iterate over the nodes and apply style
dojo.forEach(a, function(x) {
  dojo.style(x, "margin", "10px");
});

//NodeList approach...
dojo.query( /* some query */ )
.style("margin", "10px");

NodeList also includes methods for adding and removing classes via addClass and removeClass —again, just like the corresponding dojo.addClass and dojo.removeClass functions. That is, you can manually set style properties for elements via style, or explicitly add or remove classes via addClass and removeClass. Note that the style method is especially useful when you don't actually have an existing class that accomplishes the purpose, whereas the addClass and removeClass methods are useful for those times when you already have classes that you want to toggle on or off. Just like style, the syntax is for these methods is predictable:

dojo.query("span.foo", someDomNode).addClass("foo").removeClass("bar");
dojo.query("#bar").style("color","green");

Placement

Not surprisingly, a few methods for manipulating the placement of nodes on the page are included as methods of NodeList. You may recognize the coords method, which, like its dojo counterpart, returns an Array containing the coordinate objects for each node in the list. Likewise, NodeList 's place method is similar to dojo.place in that it provides a way to insert the entire NodeList into the DOM in a sequential fashion based on a specific position.

The addContent method, however, is a method that doesn't have a corresponding counterpart elsewhere in the toolkit; it provides a way to add a node or text string to a relative position for each item in a NodeList.

Here's an example of using addContent to insert a text string (which gets wrapped as an inline span) after each page container. This particular example might be useful a method for an application in which you have various displays involving tab and stack containers:

/* Add a footer message after each container identifed by the pageContainer class*/
var nl = dojo.query("div.pageContainer").addContent("footer goes here!", "after");

Recalling that the place method functions by inserting the entire NodeList into the page relative to another node, you might do the following to insert the entire list inside of a container node identified by an id value of debugPane :

var nl = dojo.query("div.someDebugNodes").place("#debugPane", "last");

dojo.coords, like its counterpart, returns an object of key/value pairs that represent the coordinates for each item in the NodeList. Recall that the coords object includes keys for top and left offsets, length and height, and absolute x and y positions, which can be transformed to be relative to a viewport.

Warning

The result of coords is an Array, not a NodeList. Inspect the output of the following blurb in the Firebug console and see for yourself:

dojo.forEach(
  dojo.query("div").coords(  ),
  function(x) { console.log(x); }
);

A somewhat unique method provided by NodeList for placement that does not have a dojo counterpart is its orphan method, which applies a simple filter (single CSS selector—no commas allowed) to each of its elements, and each child element involved in a relationship that matches the filter criteria is removed from the DOM. These child elements that have been removed—or orphaned—are then returned as a new NodeList. The orphan method is often used to remove nodes from the DOM in a much less kludgy manner than the DOM accessor functions otherwise dictate, which is the following pattern for a node called foo : foo.parentNode.removeChild(foo).

For example, to remove all hyperlink elements that are children of a span from the DOM and return them as a new NodeList, you'd do the following:

var nl = dojo.query("span > a").orphan(  )

Warning

The > selector is whitespace-sensitive; you must include a whitespace on each side of the selector.

The adopt method is essentially the inverse of the orphan operator in that it allows you to insert elements back into the DOM. The function is quite flexible, allowing you to pass in a particular DOM node, a query string, or a NodeList. The nodes that will be inserted are positioned relative to the first element in the NodeList that provides the adopt method. The second parameter providing positional information allows for the usual positional information (first, last, after, and before ):

var n = document.createElement("div");
n.innerHTML="foo";
dojo.query("#bar").adopt(n, "last");

DOM Event Shortcuts

Given that you can do just about everything else with a NodeList, you probably won't be too surprised to find out that you can also batch process nodes to respond to particular DOM events such as blurs, mouse movements, and key presses. Firing custom actions in response to one or more DOM events is such a common occurrence that NodeList provides a built-in method for accomplishing this task with ease.

The following DOM events are offered as events for batch processing with NodeList s:

  • onmouseover

  • onmouseenter

  • onmousedown

  • onmouseup

  • onmouseleave

  • onmouseout

  • onmousemove

  • onfocus

  • onclick

  • onkeydown

  • onkeyup

  • onkeypress

  • onblur

As an example, consider the use case of capturing mouse movement events over a particular element. You'd simply fill in the function for the onmouseover function like so:

dojo.query("#foobar").onmousemove(
  function(evt) {
    console.log(evt); // you should really do something more interesting!
  }
);

The event objects that are available via the DOM Event methods are standardized, because internally dojo.connect is being used. The event model as provided via dojo.connect is standardized in accordance with the W3C specification.

There is no direct way to manage and disconnect the connections you create with NodeList 's connect method, although a future 1.x dot release may provide that ability. If it's not enough to have these connections automatically torn down when the page unloads, you can opt to use the normal dojo.connect method inside of a NodeList 's forEach method if you have a really good reason to manage the connections yourself.

For example, if you needed to manually manage the connections from the previous example, you might do it like so:

var handles =
  dojo.query("a").map(function(x) {
    return dojo.connect(x, "onclick",
      function(evt) { /* ... */ });
  });

/* Sometime later... */
dojo.forEach(handles, function(x) {
  dojo.disconnect(x);
});

Animation

Tip

You may want to skim this section and then read it again more closely after you've read Chapter 8, which provides complete coverage of animating content.

Producing animations with DHTML has often been perceived as a bit cumbersome—and it certainly can be. NodeList, however, makes this task just as simple as anything else you can do with a NodeList. From an application development standpoint, that means that you can perform fades trivially, and can even perform more complex operations via the _Animation.animateProperty method.

Warning

The _Animation that is operated upon has a leading underscore. In this particular context, the leading underscore signifies that the API is not final and, in general, _Animation objects should be treated somewhat opaquely. While the information presented in this section is current as of Dojo 1.1 and the _Animation API is fairly stable, future versions of Dojo could change it.

The methods listed in Table 5-3 involving animation are currently available, but must be explicitly retrieved via a call to dojo.require("dojo.NodeList-fx"). Each of these methods takes an associative array of key/value pairs that provide properties such as the animation duration, position information, colors, etc.

Table 5-3. NodeList extensions for animation

fadeIn

Fades in each node in the list.

fadeout

Fades out each node in the list.

wipeIn

Wipes in each element in the list.

wipeOut

Wipes out each element in the list.

slideTo

Slides each element in the list to a particular position.

animateProperties

Animates all elements of the list using the specified properties.

anim

Similar to animateProperties except that it returns an animation that is already playing. See dojo.anim for more details.

As you might already be thinking, animations are fun to toy around with. Dojo makes this so simple to do. Like anything else in the toolkit, you can just open up the Firebug console and start experimenting. You might start out with simple fades, like so:

dojo.require("dojo.NodeList-fx");

//Once NodeList-fx has loaded...
dojo.query("p").fadeOut(  ).play(  )

Then, when you're ready to begin trying more advanced animations, add some key/value pairs to the associative array and see what happens:

dojo.require("dojo.NodeList-fx");

//Once NodeList-fx has loaded...
dojo.query("div").animateProperty({
  duration: 5000,
  properties: {
    color: {start: "black", end: "green"}
  }
}).play(  );

Note that the actual result of the various effects method is an _Animation object, and that its play method is the standard mechanism for activating it.

Get Dojo: The Definitive Guide now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.