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 Array
s.
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] | |
| Returns the first
location of an item in the |
| Returns the last
location of an item in the |
| Returns true if the
function returns true for every item in the |
| Returns true if the
function returns true for at least one item in the |
| Runs each item through
a function and returns the original |
| Runs each item through
a function and returns the results as a |
| Runs each item through
a |
| Returns a new |
| Returns a new |
| Returns a new |
| Adds a class to every node. |
| Removes a class from every node. |
| Gets or sets a
particular style to every node when style is a |
| Adds a text string or
node to the relative position indicated for each node. Valid
values for position include |
| 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 |
| Returns the box objects
for all elements in the list as an Array—not as a |
| Removes DOM nodes from
the list according to the filter criteria and returns them as
a new |
| Inserts DOM nodes relative to the first element of the list. |
| Attaches event handlers
to every item in the |
| Handy for instantiating
widgets in bulk.[a] Assuming
the |
[a] Widgets are not formally introduced until Chapter 11; consequently, no examples in
this chapter demonstrate usage of |
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])
.
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:
vara0
= new Array(node1,node2,node3,node4,...); /* Map some elements... */ vara1
= dojo.map(a0
, function(x) { /* ... */ } ); /* And now filter... */ vara2
= 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.
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
)");
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); } });
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");
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");
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); });
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
| Fades in each node in the list. |
| Fades out each node in the list. |
| Wipes in each element in the list. |
| Wipes out each element in the list. |
| Slides each element in the list to a particular position. |
| Animates all elements of the list using the specified properties. |
| Similar to |
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.