Chapter 11. Accessing Page Elements
11.0. Introduction
A web document is organized like an upside-down tree, with the
topmost element at the root and all other elements branching out, beneath.
As you can see when you look at a web page within the Safari Web Developer
Elements window (Figure 11-1), the
top-level element is the html
element, followed by
the head
and body
elements. The head
element contains title
, script
, and meta
elements, while the body contains a couple
of div
elements, one
containing paragraphs (p
), the other containing
an unordered list (ul
) and list items
(li
)—one of which contains a paragraph,
which contains a span
.
Except for the root element (HTML), each element has a parent
node
, and all of the elements are
accessible from one object: document
.
There are several different techniques available for accessing these
document elements, or nodes as they’re called in the Document
Object Model (DOM). Today, we access these nodes through standardized
versions of the DOM, such as the DOM Levels 2 and 3, mentioned throughout
the book. Originally, though, a de facto technique was to access the
elements through the browser object model, sometimes referred to as DOM
Level 0. The DOM Level 0 was invented by the leading browser company of
the time, Netscape, and its use has been supported (more or less) in most
browsers since. The key object for accessing web page elements in the DOM
Level 0 is the document
object.
The DOM Level 0 Document
In the earlier browser object model, page elements were accessed
via the document
object, via a set of
element collections. For instance, to access an img
element, we would access the images
array, which contains entries for all
images in the page, in order of their occurrence in the page:
var selectImage = document.images[1]; // get second image in page
The earliest collections that can be accessed via the Document
object are:
Some of the collection elements themselves had collections, such
as being able to access all elements within a form via the form’s
elements
property:
var elemOne = document.forms[0].elements[0]; // first element in first form
As with images, elements could be accessed by array entry, with position in the array determined by the position of the element in the web page. In addition, elements given an identifier could also be accessed directly via the collection:
<form id="new"> ... </form> var newForm = document.forms["new"];
Forms also had name
attributes as well as id
s, either of which could be used to access
the form. The form could also be accessed, via a shortcut, by its
identifier/name:
var newForm = document.new; // form named "new"
Note, though, that this technique is not standardized via specification, though support for it is included in most (if not all) browsers.
Note
Also note that the name
attribute is only supported in a limited set of web page elements.
You’re encouraged to use the id
attribute instead.
In addition, all elements in the web page could be accessed via
the document.all
property, by specifying the identifier given the
element:
<div id="test"> ... var tstElem = document.all["test"]; // returns ref to test div element
The all
collection was created
by Microsoft in Internet Explorer, and eventually became another de
facto standard. The all
property and
the other collections are still available for use now, and many of the
element collections are now in the DOM Level 2 HTML specification, but
the all
property’s use is discouraged
in favor of the techniques formalized under the DOM Level 1
specification.
The Standardized DOMs
The problem with the earliest techniques in accessing web page
elements is that the browser companies didn’t agree on any one
technique, and to support all of the browsers we had to use a convoluted
set of if
statements, testing for
browser support.
The W3C remedied this problem by releasing a new, standard approach to working with the web page document object model: the DOM Level 1. Since then, the organization has worked to refine the DOM with releases of DOM Level 2, DOM Level 3, and the current work associated with HTML5—demonstrated in this chapter and in the rest of this book.
The W3C specifications provide a core API that can be used for more generic documents, as well as APIs specific to HTML. These include a new events model, support for XPath, keyboard access, in addition to various methods to access existing elements, and to create new elements that can then be inserted into the document tree. The W3C documentation for the DOM consists of the standards specifications and language bindings. We’re primarily interested in the ECMAScript language binding.
Note
Be aware that at the time this book was written, implementation of DOM Level 3 Events functionality was sketchy, at best.
The most used method supported in the DOM Level 2 and up is the
document
object method getElementById
:
<div id="test"> ... var testElement = document.getElementById("test");
The document.getElementById
method originated in the DOM Level 1 HTML API, and then moved over as a
more generalized method to DOM Level 2.
With document.getElementById
,
rather than have to access a specific element collection or determine if
document.all
was supported, we can
use the standard method and be assured of accessing any page element by
its given id
.
The getElementById
method was
just the beginning, and this very helpful method has been joined by
getElementsByTagName
,
to get all elements via a specific element tag; getElementsByClassName
,
to get all elements that share the same class name; and the very new
querySelector
and querySelectorAll
methods, which allow us to use the CSS style selectors in order to make
more sophisticated queries.
See Also
See Chapter 7 for coverage of event handling in DOM Level 2. The best way to find a summary of the different DOM specifications is via the W3C DOM Technical Reports page. Mozilla also provides a nice DOM summary, as does the Wikipedia entry on the DOM.
The ECMAScript binding for DOM Level 1 is at http://www.w3.org/TR/REC-DOM-Level-1/ecma-script-language-binding.html. DOM Level 2’s ECMAScript binding is at http://www.w3.org/TR/DOM-Level-2-Core/ecma-script-binding.html. The binding for DOM Level 3 is at http://www.w3.org/TR/DOM-Level-3-Core/ecma-script-binding.html.
11.1. Access a Given Element and Find Its Parent and Child Elements
Solution
Give the element a unique identifier, and use the document.getElementById
method:
<div id="demodiv"> ... var demodiv = document.getElementById("demodiv");
Find its parent via the parentNode
property:
var parent = demodiv.parentNode;
Find its children via the childNodes
property:
var children = demodiv.childNodes;
Discussion
The most commonly used DOM method is getElementById
. It takes one parameter: a
case-sensitive string with the element’s identifier. It returns an
element
object, which is referenced
to the element if it exists; otherwise, it returns null.
The returned element
object has
a set of methods and properties, including several inherited from the
node
object. The node
methods are primarily associated with
traversing the document tree. For
instance, to find the parent node for the element, use the
following:
var parent =demodiv.parentNode; // parent node property
If you want to find out what children an element has, you can
traverse a collection of them through the childNodes
property:
if (demodiv.hasChildNodes()) { var children =demodiv.childNodes; for (var i = 0; i < children.length; i++) { outputString+=" has child " + children[i].nodeName + "<br />"; } }
You can find out the type of element for each node through the
nodeName
property:
var type = parent.nodeName; // BODY
You also might be surprised at what appears as a child node. For
instance, whitespace before and after an element is, itself, a child
node, with a nodeName
of #text
. For the following div
element:
<div id="demodiv" class="demo"> <p>Some text</p> <p>Some more text</p> </div>
The demodiv
element (node) has
five children, not two:
has child #text has child P has child #text has child P has child #text
However, IE8 only picks up the two paragraph elements, which
demonstrates why it’s important to be specific with the queries and
check nodeName
to ensure you’re
accessing the correct elements.
11.2. Accessing All Images in the Web Page
Solution
Use the document.getElementsByTagName
method, passing in img
as the parameter:
var imgElements = document.getElementsByTagName('img');
Discussion
The getElementsByTagName
returns a collection of nodes (a NodeList
) of a given element type, such as the img
tag in the solution. The collection can be
traversed like an array, and the order of nodes is based on the order of
the elements within the document: the first img
element in the page is accessible at index
0, and so on:
var imgElements = document.getElementsByTagName('img'); for (var i = 0; i < imgElements.length; i++) { var img = imgElements[i]; ... }
Though the NodeList
collection
can be traversed like an array, it isn’t an Array
object—you can’t use Array
object methods, such as push()
and reverse()
, with a NodeList
. NodeList
’s only property is length
, which contains the number of elements in the collection.
The only method is item
, which takes
the index of the item, beginning with the first element at index
0:
var img = imgElements.item(1); // second image
NodeList
is an intriguing
object because it’s a live collection, which means changes made to the
document after the NodeList
is
retrieved are reflected in the collection. Example 11-1 demonstrates the
NodeList
live collection
functionality, as well as getElementsByTagName
.
In the example, three images in the web page are accessed as a
NodeList
collection using the
getElementsByTagName
method. The
length
property, with a value of
3
, is output in an alert. Immediately
after the alert, a new paragraph and img
elements are created, and the img
appended to the paragraph. To append the
paragraph following the others in the page, getElementsByTagName
is used again, this time
with the paragraph tags (p
). We’re
not really interested in the paragraphs, but in the paragraphs’ parent
element, found via the parentNode
property on each paragraph.
The new paragraph element is appended to the paragraph’s parent
element, and the previously accessed NodeList
collection variable’s length property
again printed out. Now, the value is 4
, reflecting the addition of the new img
element.
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>NodeList</title> <script type="text/javascript"> //<![CDATA[ window.onload=function() { var imgs = document.getElementsByTagName('img'); alert(imgs.length); var p = document.createElement("p"); var img = document.createElement("img"); img.src="orchids4.preview.jpg"; p.appendChild(img); var paras = document.getElementsByTagName('p'); paras[0].parentNode.appendChild(p); alert(imgs.length); } //]]> </script> </head> <body> <p><img src="orchids12.preview.jpg" alt="Orchid from MBG 2009 orchid show" /></p> <p><img src="orchids6.preview.jpg" alt="Orchid from MBG 2009 orchid show" /></p> <p><img src="orchids9.preview.jpg" alt="Orchid from MBG 2009 orchid show" /></p> </body> </html>
In addition to using getElementsByTagName
with a specific element
type, you can also pass the universal selector (*
) as a parameter to the method to get all
elements:
var allelems = document.getElementsByTagName('*');
Namespace Variation
There is a variation of getElementsByTagName
, getElementsByTagNameNS
, which can be used in documents that support multiple
namespaces, such as an XHTML web page with embedded MathML or
SVG.
In Example 11-2, an
SVG document is embedded in XHTML. Both the XHTML document and the
embedded SVG make use of the title
element. The title
element in the
XHTML document is part of the default XHTML namespace, but the title
in the SVG is part of the Dublin Core
namespace.
When the title
element is
accessed, information about the title
, including its namespace, the prefix,
the localName
and the textContent
are printed out. The prefix is the
dc
component of dc:title
, and the localName
is the title
part of dc:title
. The textContent
is a new property, added with the
DOM Level 2, and is the text of the element. In the case of title
(either the XHTML or the Dublin Core
element), it would be the title
text.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> <head> <title>Namespace</title> <script type="text/javascript"> //<![CDATA[ window.onload=function () { var str = ""; var title = document.getElementsByTagName("title"); for (var i = 0; i < title.length; i++) { str += title.item(i).namespaceURI + " " + title.item(i).prefix + " " + title.item(i).localName + " " + title.item(i).text + " "; } alert(str); str = ""; if (!document.getElementsByTagNameNS) return; var titlens = document.getElementsByTagNameNS("http://purl.org/dc/elements/1.1/", "title"); for (var i = 0; i < titlens.length; i++) { str += titlens.item(i).namespaceURI + " " + titlens.item(i).prefix + " " + titlens.item(i).localName + " " + titlens.item(i).textContent + " "; } alert(str);} //]]> </script> </head> <body> <h1>SVG</h1> <svg id="svgelem" height="800" xmlns="http://www.w3.org/2000/svg"> <circle id="redcircle" cx="300" cy="300" r="300" fill="red" /> <metadata> <rdf:RDF xmlns:cc="http://web.resource.org/cc/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns# "> <cc:Work rdf:about=""> <dc:title>Sizing Red Circle</dc:title> <dc:description></dc:description> <dc:subject> <rdf:Bag> <rdf:li>circle</rdf:li> <rdf:li>red</rdf:li> <rdf:li>graphic</rdf:li> </rdf:Bag> </dc:subject> <dc:publisher> <cc:Agent rdf:about="http://www.openclipart.org"> <dc:title>Testing RDF in SVG</dc:title> </cc:Agent> </dc:publisher> <dc:creator> <cc:Agent> <dc:title id="title">Testing</dc:title> </cc:Agent> </dc:creator> <dc:rights> <cc:Agent> <dc:title>testing</dc:title> </cc:Agent> </dc:rights> <dc:date></dc:date> <dc:format>image/svg+xml</dc:format> <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> <cc:license rdf:resource="http://web.resource.org/cc/PublicDomain"/> <dc:language>en</dc:language> </cc:Work> <cc:License rdf:about="http://web.resource.org/cc/PublicDomain"> <cc:permits rdf:resource="http://web.resource.org/cc/Reproduction"/> <cc:permits rdf:resource="http://web.resource.org/cc/Distribution"/> <cc:permits rdf:resource="http://web.resource.org/cc/DerivativeWorks"/> </cc:License> </rdf:RDF> </metadata> </svg> </body> </html>
The result of the application can vary between browsers. When
using Firefox and accessing title
without using the namespace variation, the only title
returned is the XHTML document title
. However, when using the namespace
variation (getElementsByTagNameNS
),
and specifying the Dublin
Core namespace, all of the Dublin Core titles in the RDF within
the SVG are returned.
When accessing the nonnamespaced version of getElementsByTagName
in Safari, Chrome, and
Opera, both the XHTML title and the Dublin Core titles are returned, as
shown in Figure 11-2.
Though IE8 doesn’t directly support the XHTML MIME type, if the
page is served as text/html
using
some form of content negotiation, IE will process the page as HTML.
However, though the getElementsByTagName
works with IE, the
namespaced version of the method, getElementsByTagNameNS
, does not. All of the
values are returned as undefined
. IE8 doesn’t return the
dc:title
entries in the SVG,
either.
If the Dublin Core namespace is declared in the html
element, instead of in the svg
element, IE8 does
return all of the dc:title
entries,
as well as the XHTML title:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:dc="http://xml:lang="en">
An alternative approach consists of using a tag name string that
concatenates the prefix
and localName
. All of the browsers will find the
dc:title
using the following:
var titles = document.getElementsByTagName("dc:title");
However, you can’t access the namespace-specific properties using
the pseudonamespace method. Your applications can’t access the namespace
properties using the IE approach of embedding all of the namespace
declarations in the html
tag, but you
can find out the namespace URI via Microsoft’s tagURN
property:
alert(title[i].tagURN); // for dc:title
Browsers can use the following to get all elements with a given
tag, regardless of namespace, if the document is served as application/xhtml+xml
or other XML
type:
var titles = document.getElementsByTagNameNS("*","title");
This JavaScript returns both the default XHTML namespace title and the titles in the Dublin Core namespace.
See Also
Using getElementsByTagName
to
get all the paragraphs just to find their parent in Example 11-1 is overkill. Recipe 11.5 demonstrates how
to use the Selectors API to directly access just the parent element for
the paragraphs. The parentNode
property is introduced in Recipe 11.1.
11.3. Discover All Images Within an Article
Solution
Find all article
elements in
the web page. Once they are found, find the img
elements for each one:
var imgString = ""; // find all articles var articles = document.getElementsByTagName('article'); // find all images in articles for (var i = 0; i < articles.length; i++) { var imgs = articles[i].getElementsByTagName('img'); // print out src for (var j = 0; j < imgs.length; j++) { var img = imgs[j]; imgString+=img.src + "<br />"; } } document.getElementById("result").innerHTML=imgString;
Discussion
The DOM method getElementsByTagName
is available for the element
object, as well as the document
object. This is handy if you want to
look for elements of a certain type throughout the document or within a
specific subtree of the document, with a given element as its
root.
In the solution, the first use of getElementsByTagName
returns a nodeList
, which is a collection of article
elements for the entire document. This
collection is traversed like an array, and the getElementsByTagName
is used again, this time
with each of the article
elements, to look for any
img
elements within the subtree
formed by the article
.
The example works with all of the book target browsers except IE8.
IE8 does pick up the articles from the first use of document.getElementsByTagName
. However, IE8
does not support the use of getElementsByTagName
with an element, so it
doesn’t pick up the images.
See Also
In order to access the new HTML5 article
element with IE8, you’ll need to use
an HTML5 shim. IE8 and earlier versions don’t
correctly process the new HTML5 elements, such as article
, without this additional assistance.
Recipe 12.4 has more
discussion on using an HTML5 shim.
Recipe 11.3 provides
in-depth coverage of the getElementsByTagName
method.
11.4. Discover all Images in Articles Using the Selectors API
Problem
You want to get a list of all img
elements that are
descendants of article
elements, but
not have to traverse HTML collection objects, which can be a slow
operation.
Solution
Use the newer Selectors API and access the img
elements contained within article elements
using CSS-style selector strings:
var imgs = document.querySelectorAll("article img");
Discussion
Once you’ve worked with the Selectors API, you’ll never look for
another way to access document
elements. The Selectors API is a new specification that is undergoing
work at the time this was written. It has broad implementation across
browsers, though there are some differences in implementation
support.
Note
Earlier versions of browsers, such as IE7, Firefox 2, and so on, do not support the Selectors API. You’ll have to use fallback methods to perform the same queries. In addition, IE8 does not support many selector variations.
There are two selector query API methods. The first, querySelectorAll
, was
demonstrated in the solution. The second is querySelector
. The difference between the two is querySelectorAll
returns all elements that
match the selector criteria, while query
Selector
only returns the first found
result.
The selectors syntax is derived from those that support CSS
selectors. In the example, all img
elements that are descendants of article elements are returned. To
access all img
elements, regardless
of parent element, use:
var imgs = document.querySelectorAll("img");
In the solution, you’ll get all img
elements that are direct or indirect
descendants of a article
element.
This means that if the img
element is
contained within a div
that’s within
an article, this img
element will be
among those returned:
<article> <div> <img src="..." /> </div> </article>
If you want only those img
elements that are direct children of an article
element, you would use the
following:
var imgs = document.querySelectorAll("article> img");
If you’re interested in accessing all img
elements that are immediately followed by
a paragraph, you would use:
var imgs = document.querySelectorAll("img + p");
If you’re interested in an img
element that has an empty alt
attribute, you can use the following:
var imgs = document.querySelectorAll('img[alt=""]');
If you’re only interested in img
elements that don’t have an empty alt
attribute, use the following:
var imgs = document.querySelectorAll('img:not([alt=""])');
The negation pseudoselector (:not
) is used to find
all img
elements with
alt
attributes that are not empty.
For these queries, which are looking for all img
elements that meet the given selectors,
you should get the same result with the more modern browsers, such as
Firefox 3.x, Opera 10.x, Safari 4.x, and Chrome 4.x. Unfortunately, IE8
has only limited support for selectors—the code in the solution does not
work.
The collection of elements returned from querySelectorAll
is not a “live” collection,
unlike the collection of objects returned from getElementsByTagName
. Updates to the page are
not reflected in the collection if the updates occur after the
collection is retrieved.
Note
Though the Selectors API is a wonderful creation, it shouldn’t
be used for every document query. For instance, it’s not efficient for
accessing elements by a specific identifier, so you’re still better
off using getElementById
for this purpose. Best
bet is to test your application using the Selectors API and a variety
of other methods and see which provides best performance and broadest
support.
Namespace Variation
CSS3 provides syntax for handling namespaces. This is how to define a namespace in CSS3, via the Namespace module:
@namespace svg "http://www.w3.org/2000/svg";
If an element is given with a namespace prefix, such as the following:
<q:elem>...</q:elem>
to style the element, you would use:
@namespace q "http://example.com/q-markup"; q|elem { ... }
and to style an attribute, you could use:
@namespace foo "http://www.example.com"; [foo|att=val] { color: blue }
Recipe 11.2 covered
the concept of namespaces when querying against the document, and
introduced the first of the namespace-specific methods: getElementsByTagNameNS
. Since the CSS
selectors allow for resolving namespaces, we might assume we could use
namespaces with querySelector
and
querySelectorAll
. In fact we could,
with earlier iterations of the API Selectors draft, but there is no way
to do so now.
Now, a namespace error will be thrown if the namespace is not resolved before using the Selectors API methods. Unfortunately, the Selectors API doesn’t provide an approach to resolve the namespace before using one of the methods.
Instead, the Selectors API specification recommends using
JavaScript processing to handle namespaces. For instance, to find all of
the dc:title
elements within an SVG
element in a document, you could use the following:
var list = document.querySelectorAll("svg title"); var result = new Array(); var svgns = "http://www.w3.org/2000/svg" for(var i = 0; i < list.length; i++) { if(list[i].namespaceURI == svgns) { result.push(list[i]); }
In the example code, querying for all of the titles that are
descendants of the svg
element will
return both SVG titles and any Dublin Core or other titles used in the
SVG block. In the loop, if the title is in the Dublin Core namespace,
it’s pushed on to the new array; otherwise, if the title is in some
other namespace, including the SVG namespace, it’s disregarded.
It’s not an elegant approach, but it is serviceable, and also the only option available for namespaces and the Selectors API at this time.
See Also
JavaScript access of the new HTML5 elements, such as article
, requires the use of a HTML5
shim: a small JavaScript application that enables
the use of these elements in IE8. Recipe 12.4 has more on the
use of the HTML5 shim.
There are three different CSS selector specifications, labeled as Selectors Level 1, Level 2, and Level 3. You can access CSS Selectors Level 3 at http://www.w3.org/TR/css3-selectors/, a site which contains links to the documents defining the other levels. These documents provide the definitions of, and examples for, the different types of selectors. In addition, the CSS3 Namespace module can be found at http://www.w3.org/TR/css3-namespace/, and is currently a Candidate Recommendation.
There are currently two Selectors API specifications under development: the Selectors API Level 1, which is a Candidate Recommendation, and the Selectors API Level 2, which is a working draft.
John Resig, the creator of the popular jQuery library, has provided a comprehensive test suite for selectors at http://ejohn.org/apps/selectortest/. The source for the test suite can be found at http://github.com/jeresig/selectortest/tree/master. The CSS3.info site also has a nice selectors test at http://tools.css3.info/selectors-test/test.html. This one is a little easier to view, and provides links with each test to the example code.
As noted, Selectors API support isn’t universal. However, many of the popular JavaScript framework libraries, such as jQuery, Prototype, and Dojo, provide workarounds so you can use the selector and it offers a fallback, if necessary.
Chapter 17 introduces using jQuery and other libraries with your application code.
11.5. Finding the Parent Element for a Group of Elements
Solution
Use the querySelector
method to
access the first paragraph in the set, and then access the parentNode
property for
this element:
var parent = document.querySelector("body p").parentNode;
Discussion
With all the ways we can access child nodes and siblings, not to
mention descendants to many depths, you’d think we’d also be able to
directly query for parent elements. Unfortunately, there is nothing in
CSS comparable to :parent
to return a
parent element. However, we can fake it by accessing a known child
element and then accessing the parent via the
parentNode
property.
In the solution, the querySelector
method will return the first
paragraph element that is a descendant of the body element. Since
querySelector
only returns one
element, you don’t have to use array reference to access an individual
element. Once we have one of the child elements, the parent is accessed
via the parentNode
property.
See Also
See Recipe 11.4
for more details on the Selectors API and the querySelector
and querySelectorAll
methods.
11.6. Highlighting the First Paragraph in Every Element
Problem
Based on some user event, you want to dynamically change the background color to
yellow for the first paragraph in every div
element.
Solution
Use the document.querySelectAll
with the appropriate CSS selector in order to reference all first
paragraphs in div
elements, and then modify the
paragraph’s CSS background color:
var paras = document.querySelectorAll('div p:first-of-type'); for (var i = 0; i < paras.length; i++) { paras[i].setAttribute("style","background-color: #ffff00"); }
If the specific pseudoselector syntax is not supported, use an alternative, such as the following:
var divs = document.querySelectorAll("div"); for (var j = 0; j < divs.length; j++) { var ps = divs.item(j).getElementsByTagName("p"); if (ps.length > 0) { ps[0].setAttribute("style","background-color: #ffff00"); } }
Discussion
We’re only interested in selectors where the paragraph element is
a descendant of a div
element:
var paras = document.querySelectorAll('div p');
In addition, we’re interested in the first paragraph element in
the div
, so at first glance, the
following looks acceptable:
var paras = document.querySelectorAll('div p:first-child');
However, there’s no guarantee that the div
element won’t contain elements of other
types, and if the first element is not a paragraph, the first paragraph
won’t be found. Instead, as shown in Example 11-3, the :first-of-type
CSS
selector is used so that the first paragraph in the div
element is highlighted when the document
is clicked, even if it isn’t the first element in the div
. The code is included in a try...catch
block in order to provide an
alternative if the type of selector syntax isn’t supported.
<!DOCTYPE html> <head> <title>paras</title> <meta charset="utf-8" /> <style> div { padding: 10px; border: 1px solid #000000; } </style> <script type="text/javascript"> window.onload=function() { document.onclick=function() { try { var paras = document.querySelectorAll('div p:first-of-type'); for (var i = 0; i < paras.length; i++) { paras[i].setAttribute("style","background-color: #ffff00"); } } catch(e) { var divs = document.querySelectorAll("div"); for (var j = 0; j < divs.length; j++) { var ps = divs.item(j).getElementsByTagName("p"); if (ps.length > 0) { ps[0].setAttribute("style","background-color: #ffff00"); } } } }; } </script> </head> <body> <div> <p>Paragraph one</p> <p>Paragraph two</p> <p>Paragraph three</p> </div> <div> <p>Paragraph one</p> <p>Paragraph two</p> </div> <div> <ul> <li>List item one</li> <li>List item two</li> </ul> <p>Paragraph one</p> <p>Paragraph two</p> </div> </body>
Figure 11-3 shows
that even though the first element in the third div
is an unordered list, the first paragraph
that follows is still highlighted. Another CSS selector that provides
the same functionality is :nth-of-type(1)
, where parentheses are used to
wrap the number of the target element.
Firefox, Safari, Chrome, and Opera support :first-of-type
. IE8 doesn’t, but it does
support :first-child
. However, as the example demonstrates, we can’t count on
the paragraph being the first element. Instead, we use a more
generalized query for all div
elements, and then access all the paragraphs with getElementsByTagName
.
We could use the getElementsByTagName
for the first query,
except that this method returns a live collection, and the first
approach doesn’t. We want the functionality to be the same for both
approaches, as much as possible. If you need to support IE7, though, you
should use getElementsByTagName
, as
this browser doesn’t support querySelectorAll
.
See Also
See Recipe 11.2 for
more on getElementsByTagName
and live
collections, and Recipe 11.4 for an in-depth
introduction to the Selectors API. Microsoft provides a page for the CSS
selectors it supports at http://msdn.microsoft.com/en-us/library/cc351024(VS.85).aspx.
Note, though, that Microsoft does have a habit of changing its URLs, so
this web page address may not work in the future.
11.7. Apply a Striping Theme to an Unordered List
Solution
Use the Selectors API to query for every other item in the list, and then change the background color:
var lis = document.querySelectorAll('li:nth-child(2n+1)'); for (var i = 0; i < lis.length; i++) { lis[i].setAttribute("style","background-color: #ffeeee"); }
or:
var lis = document.querySelectorAll('li:nth-child(odd)'); for (var i = 0; i < lis.length; i++) { lis[i].setAttribute("style","background-color: #eeeeff"); }
or access the list parent element and then traverse its child nodes, changing the background color of every other element, using the arithmetic modulo operator:
var parentElement = document.getElementById("thelist"); var lis = parentElement.getElementsByTagName("li"); for (var i = 0; i < lis.length; i++) { if (i % 2 == 0) { lis[i].setAttribute("style","background-color: #eeffee"); } }
Discussion
The :nth-child()
pseudoclass allows us to specify an algorithm pattern,
which can be used to find elements that match a certain pattern, such as
2n+1
, to find every other element.
You can also use the odd
and even
arguments to access the odd or even
elements of the type:
var lis = document.querySelectorAll('li:nth-child(odd)');
Not all browsers support this relatively new selector type.
Firefox, Opera, Safari, and Chrome do, but IE8 doesn’t support the first
two approaches given in the solution, and older versions of most other
browsers don’t. In these situations, you’ll want to use the third
approach in the solutions: get access to all of the elements using
whatever method, and then use the modulo
arithmetic operator to filter the
elements. The modulo
operator returns
the remainder of dividing the first operand by the second. Dividing the
numbers 0, 2, 4, 6, and so on by 2 returns 0; the condition is
successful, and the element is affected.
In the solution, the even elements are the ones affected. To access the odd elements, use the following:
if ((i + 1) % 2) { ... }
The setAttribute
with the style
property also doesn’t work for IE7 for the third approach. The
downloadable example code contains a workaround for this browser.
See Also
See Recipe 11.4
for more details on the Selectors API and the querySelector
and querySelectorAll
methods. See Recipe 12.15 for more on
setAttribute
.
11.8. Creating an Array of All Elements of a Given Class
Problem
You want to retrieve a collection of elements that have a specific class name within the document.
Solution
Use the getElementsByClassName
method to retrieve a collection of all elements in the document that
share the same class name:
var elems = document.getElementsByClassName("classname");
or use the Selectors API to get the class-named items:
var elems = document.querySelectorAll(".classname");
Discussion
The method, getElementsByClassName
, goes beyond one
element type to find all elements that share the same class value. It
can also work with multiple classes:
var elems = document.getElementsByClassName("firstclass secondclass");
Chrome, Safari, Firefox, and Opera support getElementsByClassName
, but IE8 doesn’t. The
second approach using querySelectorAll
is a good alternative option.
It, too, can search for multiple class names:
var elems = document.querySelectorAll(".firstclass, .secondclass");
See Also
See Recipe 11.4
for more details on the Selectors API and the querySelector
and querySelectorAll
methods.
11.9. Finding All Elements That Share an Attribute
Solution
Use the universal selector (*
) in combination with
the attribute selector to find all elements that have an attribute,
regardless of its value:
var elems = document.querySelectorAll('*[class]');
The universal selector can also be used to find all elements with an attribute that’s assigned the same value:
elems = document.querySelectorAll('*[class="red"]');
Discussion
The solution demonstrates a rather elegant query selector. All
elements are analyzed because of the use of the universal selector
(*
). To test the existence of an
attribute, all you need do is list the attribute name within square
brackets ([attrname]
).
In Recipe 11.8, a couple approaches were demonstrated for finding all elements that have a specific class name. The query selector used in the solution could be modified to do the same:
var elems = document.querySelectorAll('*[class"=test"]');
If you’re not sure of the class name, you can use the substring-matching query selector:
var elements = document.querySelectorAll('*[class*="test"]');
Now any class name that contains the substring test matches.
You could also modify the syntax to find all elements that don’t
have a certain value. For instance, to find all div
elements that don’t have the target class
name, use the :not
negation
operator:
var elems = document.querySelectorAll('div:not(.test)');
This and the selector syntax examples given in the solution work
with Opera, Chrome, Firefox, and Safari. Both of the selector syntax
examples in the solution work with IE8, but the use of the negation
operator, :not
, does not. The
querySelectorAll
method does not work
with IE7.
See Also
See Recipe 11.4
for more details on the Selectors API and the querySelector
and querySelectorAll
methods.
11.10. Finding All Checked Options
Solution
Use a :checked
pseudoclass selector to directly query all checked
checkbox input
elements:
var checked = document.querySelectorAll("#checks input[type='checkbox']:checked"); for (var i = 0; i < checked.length; i++) { str+=checked[i].value + " "; }
If the :check
ed selector fails,
use the following, which accesses all of the input
elements, checks their type, and then
checks to see if they’re selected:
var inputs = document.querySelectorAll("#checks input"); for (var j = 0; j < inputs.length; j++) { if (inputs.item(j).type == "checkbox" && inputs.item(j).checked) { str+=inputs.item(j).value + " "; } }
Discussion
The :checked
pseudoselector
will only return those checkbox or radio elements that are checked.
Since we only want the checkbox input
types, we further refined the selector syntax to look for a specific
type of input element.
The use of the :checked
pseudoclass selector is a nicely targeted approach, though it’s only
supported in Safari, Firefox, Chrome, and Opera, and not in IE8. The
alternative does work in IE8, but not IE7, which doesn’t support the
querySelectorAll
method.
When working with these new selectors, a good approach is to wrap
the selector query in a try
statement
and then provide an alternative in the catch
statement. Incorporating this into the
solution gives us:
var str = "checked values "; try { var checked = document.querySelectorAll("#checks input[type='checkbox']:checked"); for (var i = 0; i < checked.length; i++) { str+=checked[i].value + " "; } } catch(e) { var inputs = document.querySelectorAll("#checks input"); for (var j = 0; j < inputs.length; j++) { if (inputs.item(j).type == "checkbox" && inputs.item(j).checked) { str+=inputs.item(j).value + " "; } } } document.getElementById("results").innerHTML=str;
See Also
Recipe 11.4
includes more details on the Selectors API and the querySelector
and querySelectorAll
methods.
11.11. Summing All the Values in a Table Row
Solution
Use the Selectors API to access the specific table row cells directly, or retrieve a collection of all table rows, access the target row from the returned collection, and then access the table row’s cells. Once retrieved by either method, traverse the cells, accessing the cell data and converting the data to a number:
try { var cells = document.querySelectorAll("tr:nth-child(3) td"); } catch(e) { var tableElement = document.getElementById("thetable"); var trs = tableElement.getElementsByTagName("tr"); var cells = trs[2].getElementsByTagName("td"); } // process cell data var sum = 0; for (var i = 0; i < cells.length; i++) { var val = parseFloat(cells[i].firstChild.data); if (!isNaN(val)) { sum+=val; } } // output the sum alert("sum " + sum);
Discussion
There are several ways to get all the cells for a table row, including adding a click event handler to the rows, and then processing all of the cells when the row is clicked.
In the solution, we looked at two different approaches. The first
approach uses the Selectors API querySelectorAll
with a selector that targets
all table cells in the third table row. The second approach is to access
all of the table rows, using getElementsByTagName
, and then access all the
table cells using the same method, against the target row. I prefer the
first approach, because it reduces the processing involved and provides
better, targeted results. However, tr:nth-child(3) td
isn’t supported in IE8. To
prevent problems where the selector syntax or querySelectorAll
aren’t supported, the code is
wrapped in a try...catch
block, with
the second approach as the fallback option.
What if we want to sum a column of table cells, rather than a row?
In this case, what we really want is every table cell in a specific
position for every table row. For this type of functionality, especially
in light of some of the quirks with querySelectorAll
, we’ll need to use another
approach, such as getElementsByTagName
.
Example 11-4 shows an
example that sums values from the third column—skipping the first row,
which consists of column headers.
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <head> <title>Sum Table Column</title> <script> //<![CDATA[ window.onload=function() { var table = document.querySelector("table"); table.onclick=sum; } function sum() { var rows = document.getElementById("sumtable").getElementsByTagName("tr"); var sum = 0; // start with one to skip first row, which is col headers for (var i = 1; i < rows.length; i++) { sum+=parseFloat(rows[i].childNodes[2].firstChild.data); } alert(sum); } //]]> </script> </head> <body> <table id="sumtable"> <tr><th>Value 1</th><th>Value 2</th><th>Value 3</th><th>Value 4</th> </tr> <tr><td>--</td><td>**</td><td>5.0</td><td>nn</td></tr> <tr><td>18.53</td><td>9.77</td><td>3.00</td><td>153.88</td></tr> <tr><td>Alaska</td><td>Montana</td><td>18.33</td><td>Missouri</td> </tr> </table> </body> </html>
The actual data value is accessed via the data
property of the Text
node that is the td
child element. The parseFloat
method is used to convert the text
to a number. If you’re not sure that the table cells contain numbers,
you’ll want to test the value first, or you could up with a result of
NaN
.
In the example, the table rows are accessed using getElementsByTagName
directly on the table
element, which is retrieved using the
getElementById
method on the document
object. Rather than have to use all
of these methods individually, I chain the methods one after another.
Method chaining doesn’t work with all JavaScript
object methods, but it does work with many among the DOM objects.
You probably wouldn’t use a querySelector
or getElementsByTagName
with a static web table,
because you can create the sums as you’re building the table (if the
table is built via an Ajax call). However, if you’re using something
like editing in place to add or modify table
values, this isn’t a bad approach to update column or row sums after the
edit.
The example works with all the book’s target browsers, but does
not work with IE7 because of the use of querySelector
.
See Also
ParseFloat
is covered in Recipe 4.5. Recipe 16.13 has a demonstration
and a more in-depth explanation of method chaining. Recipe 11.4 includes more
details on the Selectors API and the querySelector
and querySelectorAll
methods.
11.12. Get Element Attributes
Solution
If the attribute is defined as a standard attribute in the DOM by the user agent (browser), you can access the attribute directly on the element:
<input id="field" type="check" checked="checked" value="test" /> ... var field = document.getElementById("field"); alert(field.checked); // true alert(field.value); // test alert(field.type); // text
Some attributes are renamed when you access them in JavaScript,
such as the class
attribute, which
you access as className
:
<div id="elem" class="test" role="article" data-index="1"> testing </div> ... var elem = document.getElementById("elem"); alert(elem.className); // test
For nonstandard attributes, or ones that have been newly defined
but are not considered standard by the specific browser, you need to use
the getAttribute
method:
var index = elem.getAttribute("data-index"); alert(index); // 1 var role = elem.getAttribute("role"); alert(role); // article
Discussion
When elements are defined in various HTML specifications, such as HTML5, they’re given a set of shared and/or unique attributes, which you can access directly from the object:
var id = elem.id;
Nonstandard or newly defined attributes have to be accessed using
the getAttribute
method:
var role = elem.getAttribute("role");
Since the getAttribute
method
works equally well with standard and nonstandard attributes, you should
get in the habit of using the method to access all attributes.
If the attribute doesn’t exist, the method returns a value of null
or the empty string (""
). You can check to see if an
attribute exists first, by using the hasAttribute
method:
if (elem.hasAttribute(role)) { var role = elem.getAttribute("role"); ... }
This approach bypasses the problem that can occur when different user agents return different values (empty string or null) when an attribute doesn’t exist.
See Also
There is a namespace variation of getAttribute
, getAttributeNS
, which
takes the namespace as the first parameter of the method. See Recipe 11.2 for more on working
with namespaces.
11.13. Get Style Information for an Element
Solution
If you want to access style information that’s set inline or via JavaScript, and access it directly on the element’s style property, use:
var width = elem.style.width;
If you want to access the element’s existing style information, regardless of how it’s set, you need to use a cross-browser approach:
function getStyle(elem, cssprop, cssprop2){ // IE if (elem.currentStyle) { return elem.currentStyle[cssprop]; // other browsers } else if (document.defaultView && document.defaultView.getComputedStyle) { return document.defaultView.getComputedStyle(elem, null).getPropertyValue(cssprop2); // fallback } else { return null; } } window.onload=function() { // setting and accessing style properties var elem = document.getElementById("elem"); var color = getStyle(elem,"backgroundColor", "background-color"); alert(color); // rgb(0,255,0) }
Discussion
Every web page element has a set of properties and methods, some
unique to the object, and some inherited from other objects, such as
Element
or Node
, covered in
earlier recipes. One of the properties elements share is the style
object, representing the CSS style settings for the
element.
There are a couple of ways you can get style information. The
first is to directly access the style
object, using the familiar dot notation used
throughout the book to access object properties and methods:
var elem = document.getElementById("elem"); var width = elem.style.width;
All inline or dynamically set style settings can be accessed using
this approach, but there is a special syntax you have to use. For
nonhyphenated property values, such as width
, you access the setting directly:
var width = elem.style.width;
However, for property names with hyphens, such as background-color
, use a CamelCase
notation such as the following:
var bkcolor = elem.style.backgroundColor;
Using background-color
doesn’t
work, because JavaScript interprets the hyphen as a subtraction
operator. The new name is formed by removing the hyphen, and capitalizes
the first letter of the word following the hyphen.
Another approach to accessing the style is to use the getAttribute
method to access the
style
object:
var style = elem.getAttribute("style");
However, you would then have to parse the values out of the
string. You’re better off just accessing the values directly on the
style
property.
Note
IE7 also returns an object, rather than a string with CSS
values, when you access the style property using getAttribute
.
Only those CSS values that are set inline, using the element’s
style
attribute, or set dynamically using JavaScript, are
accessible using either of the approaches just demonstrated. To access
CSS values set by default by the user agent, via a stylesheet, dynamically, or inline, you’ll need to use
a cross-browser approach:
var style; var cssprop = "fontFamily"; var cssprop2 = "font-family"; if (elem.currentStyle) { style = elem.currentStyle[cssprop]; } else if (document.defaultView && document.defaultView.getComputedStyle) { style = document.defaultView.getComputedStyle(elem, null).getPropertyValue(cssprop2); }
The currentStyle
object is an IE-specific object that consists of a
collection of all supported and applicable CSS style properties for an
element. It expects values to be in CamelCase notation, such as fontFamily
, rather than font-family
.
The book’s other target browsers support the window.getComputedStyle
method, which can also be accessed as document.defaultView.getComputedStyle
. This
method takes two parameters: the element, and a pseudoelement, which is
typically left null (or an empty string, ""
).
What’s returned using the cross-browser approach is rather
interesting. It’s the computed style for the element, which is a
combination of all CSS settings, including those that are set by default
by the browser, set using a stylesheet, or set dynamically using CSS.
What’s returned when accessing the font-family
depends on the
circumstances:
If the font-family is not set, you’ll get the browser default value, if any
If the font-family is set via a stylesheet, you’ll get the stylesheet value
If the font-family is set inline and in a stylesheet, you’ll get the inline value
If the font-family is set dynamically, regardless of whether it is set inline or in a stylesheet, you’ll get the dynamic value
See Also
Example 12-7, in Recipe 12.15, demonstrates various techniques for setting and retrieving style information.
Get JavaScript Cookbook 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.