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.

Example of a document tree
Figure 11-1. Example of a document tree

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:

images

All images in the page

forms

Any forms in the page

links

All links in the page (declared with <a href="...">)

cookie

Access, add, and modify web page cookies

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 ids, 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

Problem

You want to access a specific document 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

Problem

You want to access all img elements in a given document.

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.

Example 11-1. Demonstrating getElementsByTagName and the NodeList live collection property
<!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('*');

Note

IE7, or IE8 running in IE7 mode, will return an empty nodelist if you use the universal selector with the getElementsByTagName method.

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.

Example 11-2. The differences between the namespace and nonnamespace variation of getElementsByTagName
<!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.

Using getElementsByTagNameNS to get namespaced elements
Figure 11-2. Using getElementsByTagNameNS to get namespaced elements

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.

Note

As mentioned earlier, IE8 doesn’t properly support namespaces, but IE9 should with its new support for XHTML.

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

Problem

You want to access all images within article elements only.

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 querySelector 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.

Note

IE8 and earlier do not support the namespaceURI property, nor do they support XHTML or SVG, where you would most likely need to use namespaces. IE9 should provide support for namespaces, XHTML, and SVG.

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

Problem

You want to access the parent element for a group of paragraphs.

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.

Example 11-3. Using the first-of-type selector in order to highlight the first paragraph element in a div
<!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.

Page displaying highlighted first paragraphs in every div element
Figure 11-3. Page displaying highlighted first paragraphs in every div 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

Problem

You want to modify the appearance of unordered list items so that the list appears striped.

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

Problem

You want to find all elements in a web document that share the same 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

Problem

You want to find all checkbox input elements that are selected (checked):

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 :checked 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

Problem

You want to sum the numbers in table cells per row (or per column).

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.

Example 11-4. Application that sums cells in third table column
<!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

Problem

You want to access the information contained in an element attribute.

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

Problem

You want to get one or more CSS style settings 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.