Chapter 12. Creating and Removing Elements and Attributes

12.0. Introduction

The existing Document Object Models provide a plethora of methods you can use to create new web document elements. Most of the methods I use in this chapter and in the following chapters are from the DOM Levels 1 and 2 and, since most of the examples in this chapter are specific to HTML or XHTML documents, the methods and objects described inherit functionality from both Core and HTML DOM specifications.

There is one older property, innerHTML, from the nonstandard DOM Level 0, that I’ll also demonstrate, primarily because it’s so popular, and also because of new support in HTML5.

Most of the methods and associated properties are available with all of the modern browsers. I’ll make a note where a method or property isn’t supported by one or more browsers.

See Also

See the Introduction to Chapter 11, for a more in-depth look at the Document Object Model and the DOM levels.

12.1. Using innerHTML: A Quick and Easy Approach to Adding Content

Problem

You want to add a couple of paragraphs with text to a div element, and you want to do so quickly and easily.

Solution

Use the innerHTML property to overwrite an element’s existing contents, with new material:

var div = document.getElementById("target");
div.innerHTML = "<p>This is a paragraph</p><p>This is a second</p>";

Discussion

The innerHTML property has been around for a very long time, and is part of what is known as DOM Level 0—the first de facto API developed by the browser companies. It can be much faster when you’re making complex additions to the web page, because its processing is handled by the HTML parser, rather than the DOM engine.

Because innerHTML was never part of a standard, there are some variations in how it’s implemented. For instance, not all browsers support the use of innerHTML with tables, so you won’t want to use the property for adding table rows. The use of innerHTML isn’t standard with XML formats, such as XHTML, as there’s no way to ensure that what is appended into the page is well formed. In earlier years, the use of innerHTML wasn’t even supported with XHTML documents.

Now, though, innerHTML is becoming officially blessed by being defined within the HTML5 effort. In addition, the outerHTML property has also been formally defined in the HTML5 specification. It differs from innerHTML in that outerHTML represents both the element and the element’s contents, while innerHTML only represents the element’s contents.

Note

Currently, the HTML5 specification states that the use of innerHTML will abort the current X/HTML parsing process. And not all browsers allow innerHTML to be set until after the page is finished loading. Based on these restrictions, it’s best to not use innerHTML until after the page is completely loaded.

12.2. Inserting Elements Before Existing Page Elements

Problem

You need to add a new div element to the web page before an existing div element.

Solution

Use the DOM method createElement to create a new div element. Once created, attach it to the web page before an existing element, using another DOM method, insertBefore:

// get the existing element
var refElement = document.getElementById("sister");

// get the element's parent node
var parent = refElement.parentNode;

// create new div element
var newDiv = document.createElement("div");

// attach to page before sister element
parent.insertBefore(newDiv, refElement);

Discussion

Adding a web page element is uncomplicated, as long as you keep in mind the tree-like structure of the web page. If you’re interested in attaching a web page element before another element, you’ll not only need to access this element, but also the target element’s parent element, in order to create the actual placement.

The reason you need the parent element is that there is no functionality to insert an element before another, given just the target element. Instead, you have to access the target element’s parent element, and use the insertBefore method to insert the new element before the existing one.

In Example 12-1, the solution for this recipe is embedded into a web page that originally consists only of one named div element. When this element receives a click event, a new sister div element is created and inserted into the document before the existing element.

Example 12-1. Inserting a div element into a web page
<!DOCTYPE html>

<head>
<title>object detection</title>
<style type="text/css">
div
{
  width: 50%;
  height: 20px;
  padding: 10px;
  margin: 10px 0;
}

#div1
{
  background-color: #ffff00;
  }

.divclass
{
  background-color: #ccffcc;
}

</style>
<script type="text/javascript">

window.onload=function() {
  document.getElementById("div1").onclick=addDiv;
}

function addDiv() {

  // get parent
  var parent = this.parentNode;

  // create new div
  var newDiv = document.createElement("div");
  newDiv.className = "divclass";
  newDiv.innerHTML = "<p>I'm here, I'm in the page</p>";

  // add to page
  parent.insertBefore(newDiv,this);
}
</script>
</head>
<body>
  <div id="div1">
     <p>Click me to add new element</p>
  </div>
</body>

In the example, since the event is attached to the original div element, a reference to it can be accessed using this within the event function, because the element is the owner of the onclick event handler function. You can then get the element’s parent through the parentNode property. Once you have a reference to both the parent and the existing element, all you need to do is create the new div element and insert it.

Use the document.createElement method, passing in the type of element—in this case, div. Since the current document is an HTML document, the createElement method creates a new HTMLElement, which inherits all functionality of the more generic Element class, as well as additional methods and properties. The new element is assigned a CSS style through the className property, which is a standard property for all HTMLElement objects. It’s given some content using the innerHTML property. The new div element is then added to the web page with insertBefore.

Each successive click of the original div element prepends a new div element to the original, each with the same class and content. Figure 12-1 shows the web page after the original div element has been clicked several times.

Inserting div elements into a web page
Figure 12-1. Inserting div elements into a web page

Namespace Variation

Recipe 11.2 included a discussion on namespaces and their impact on some of the DOM methods that allow us to query and add new web page elements in a document that can contain multiple namespaces. If you’re working within an environment where namespaces are supported, such as an XHTML or SVG document, and you create a new element or attribute, it’s automatically added to the document’s default namespace unless you use one of the namespace-specific DOM methods.

Generally, the default behavior is sufficient. However, if you want to create an element within a specific namespace, such as creating a title in the Dublin Core namespace rather an XHTML title, use the namespace variation of createElement, createElementNS:

var dcTitle = document.createElementNS("http://purl.org/dc/elements/1.1/","title");

The createElementNS takes two parameters: the first is the namespace for the element; the second is the element tag.

The namespace method createElementNS is not supported in HTML currently, as namespaces aren’t supported in HTML. The method is also not currently supported in IE8, but should be in IE9, when Microsoft adds support for XHTML.

See Also

See Recipe 11.2 for more information about namespaces and the DOM. For a specific description of the HTMLElement, see the related W3C page.

12.3. Appending a New Element to the End of a Page

Problem

You want to add a new element to a web page, but you want to append it to the end of the page.

Solution

Access the highest level web page element, the body element, and use the appendChild method to append the new element to the page:

var bdy = document.getElementsByTagName("body")[0]; // body element
var newDiv = document.createElement("div");

// append to body
bdy.appendChild(newDiv);

Discussion

Since we’re appending the new element to the end of the page, it makes sense to access the top-level page element (the body element) directly, using the DOM getElementsByTagName method. Since the method always returns an array (more properly, a nodeList), we get the individual element in the first array index:

var bdy = document.getElementsByTagName("body")[0];

Once we have the parent element, the appendChild method appends the new element to the end of the parent element. The method takes just one parameter: the newly created element.

Will a document always have a body element? Typically, yes, if the document is HTML or XHTML. However, if you’re working with other document types, such as SVG or MathML, or are concerned about ensuring that the new element is appended to whatever is the top-level document element, you can use the approach demonstrated in Recipe 12.0 to get the parent of an existing element that will be a sibling to the new element:

var bdy = document.getElementById("div1").parentNode;

This sibling-parent approach ensures that the new element is appended as a sibling of the existing element, and at the same level in the document tree.

12.4. Triggering Older Versions of IE to Style New Elements

Problem

You want to use one of the new HTML5 elements, such as article, and have it styled or programmatically accessible in IE7/8.

Solution

Use document.createElement in order to trigger IE8 (and IE7) into properly handling new HTML5 elements:

// add article element
document.createElement("article");

Discussion

The article element is one of the new HTML5 elements, accessible via the DOM by all of the book’s target browsers except for IE. In order to style or programmatically access article with IE8 or older, you need to apply an HTML5 shim. This shim introduces the article and other new elements to the IE DOM tree by using the document.createElement method to create one instance of the element:

document.createElement("article");

The element doesn’t have to be assigned to a variable name or inserted into the web page—it just has to be created. Once an instance of the element type is created, IE can recognize the element. This step is essential not only for JavaScript applications, but to ensure the element can be styled with CSS.

Entire libraries now exist in order to define all of the HTML5 elements. All you need do is include the library before any other script in the page. The html5-shims page at Google Code maintains a list of HTML5 shims and other resources. The library I use in the book examples is the html5shiv, originally created by John Resig and now maintained by Remy Sharp, at http://html5shiv.googlecode.com/svn/trunk/html5.js.

See Also

See Recipes 11.3 and 11.4 for JavaScript applications that are dependent on the HTML5 shim.

12.5. Inserting a New Paragraph

Problem

You want to insert a new paragraph just before the third paragraph within a div element.

Solution

Use some method to access the third paragraph, such as getElementsByTagName, to get all of the paragraphs for a div element. Then use the createElement and insertBefore DOM methods to add the new paragraph just before the existing third paragraph:

// get the target div
var div = document.getElementById("target");

// retrieve a collection of  paragraphs
var paras = div.getElementsByTagName("p");

// if a third para exists, insert the new element before
// otherwise, append the paragraph to the end of the div
var newPara = document.createElement("p");
if (paras[3]) {
   div.insertBefore(newPara, paras[3]);
} else {
   div.appendChild(newPara);
}

Discussion

The document.createElement method creates any HTML element, which then can be assigned other elements or data and appended or inserted into the page. In the solution, the new paragraph element is inserted before an existing paragraph using the insertBefore method.

Since we’re interested in inserting the new paragraph before the existing third paragraph, we need to retrieve a collection of the div element’s paragraphs, check to make sure a third paragraph exists, and then use the insertBefore method to insert the new paragraph before the old. If the third paragraph doesn’t exist, we can append the element to the end of the div element using the appendChild method instead.

See Also

Chapter 11 demonstrates several techniques for accessing page elements, including getElementsByTagName. If your target browsers support it, you could also use the Selectors API to fine-tune the query.

The following recipe, Recipe 12.6, contains a complete example demonstrating how to access the div element and the paragraphs, and add a paragraph with text just before the second paragraph.

12.6. Adding Text to a New Paragraph

Problem

You want to create a new paragraph with text and insert it just before the second paragraph within a div element:

// use getElementById to access the div element
var div = document.getElementById("target");

// use getElementsByTagName and the collection index
// to access the second paragraph
var oldPara = div.getElementsByTagName("p")[1]; // zero based index

// create a text node
var txt =
 document.createTextNode("The new paragraph will contain this text");

// create a new paragraph
var para = document.createElement("p");

// append the text to the paragraph, and insert the new para
para.appendChild(txt);
div.insertBefore(para, oldPara);

Discussion

The text within an element is itself an object within the DOM. Its type is a Text node, and it is created using a specialized method, createTextNode. The method takes one parameter: the string containing the text.

Example 12-2 shows a web page with a div element containing four paragraphs. The JavaScript that runs after the page loads creates a new paragraph from text provided by the user via a prompt window. The text could just as easily have come from an Ajax application, as we’ll see later in the book.

The provided text is used to create a text node, which is then appended as a child node to the new paragraph. The paragraph element is inserted in the web page before the first paragraph.

Example 12-2. Demonstrating various methods for adding content to a web page
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Adding Paragraphs</title>
<script type="text/javascript">
//<![CDATA[

window.onload=function() {

   // use getElementById to access the div element
   var div = document.getElementById("target");

   // get paragraph text
   var txt = prompt("Enter new paragraph text","");

   // use getElementsByTagName and the collection index
   // to access the first paragraph
   var oldPara = div.getElementsByTagName("p")[0]; //zero based index

   // create a text node
   var txtNode = document.createTextNode(txt);

   // create a new paragraph
   var para = document.createElement("p");

   // append the text to the paragraph, and insert the new para
   para.appendChild(txtNode);

   div.insertBefore(para, oldPara);

  }
//]]>
</script>
</head>
<body>
<div id="target">
  <p>
    There is a language 'little known,'<br />
    Lovers claim it as their own.
  </p>
  <p>
    Its symbols smile upon the land, <br />
    Wrought by nature's wondrous hand;
  </p>
  <p>
    And in their silent beauty speak,<br />
    Of life and joy, to those who seek.
  </p>
  <p>
    For Love Divine and sunny hours <br />
    In the language of the flowers.
  </p>
</div>
</body>
</html>

Figure 12-2 shows a web page after some text has been added.

Demonstrating adding paragraphs and text to web page
Figure 12-2. Demonstrating adding paragraphs and text to web page

Note

Inserting user-supplied text directly into a web page without scrubbing the text first is not a good idea. When you leave a door open, all sorts of nasty things can crawl in. Example 12-2 is for demonstration purposes only.

See Also

See Chapter 18 for demonstrations of using Ajax to retrieve new page contents.

12.7. Adding Attributes to an Existing Element

Problem

You want to add one or more attributes to an existing element.

Solution

You can use the createAttribute method to create an Attr node, set its value using the nodeValue property, and then use setAttribute to add to an element:

var someElement = document.getElement("elem");
var newAttr = document.createAttribute("newAttribute");
newAttr.nodeValue = "testvalue";
someElement.setAttribute(newAttr);

or you can set the value directly with setAttribute, passing in the attribute name and value:

someElement.setAttribute("newAttribute","testvalue");

Discussion

You can add any number of attributes to a document element using either createAttribute and setAttribute, or setAttribute directly. Both approaches are equally efficient, so unless there’s a real need, you’ll most likely want to use the simpler approach of setting the attribute name and value directly in setAttribute.

When would you need to use createAttribute? If the attribute value is going to be another entity reference, as is allowable with XML, you’ll need to use the createAttribute to create an Attr node, as setAttribute will only support simple strings.

You can also use setAttribute to modify the value for an existing attribute, such as the id or class attributes:

someElement.setAttribute("id", "newId");

Note, though, that some values have a different name when set directly, than when they’re set using setAttribute. One such as is class, which is set directly using className:

someElement.className = "new";
someElement.setAttribute("class", "new");

If the attribute already exists, assigning a value to the attribute directly or using setAttribute modifies the attribute’s value.

Namespace Variation

As discussed in Recipes 11.2 and 12.2, if you use the createAttribute or setAttribute methods in a document where multiple namespaces are supported (such as in an XHTML or SVG document), the engine processing the request sets the attribute without a namespace. If you mix setAttribute and setAttributeNS in the same document, you can end up with the same attribute in different namespaces.

If you’re working in a document that’s using elements and attributes from different namespaces, such as an XHTML document that incorporates SVG and MathML, you’ll need to use the namespace-sensitive variants, createAttributeNS and setAttributeNS:

someElement.setAttributeNS("http://somecompany.com/namespace",
                           "class",
"somename");

12.8. Testing for a Boolean Attribute

Problem

You want to test to see if an element has a Boolean attribute.

Solution

Use the hasAttribute method to check for the existence of the Boolean or any other attribute:

var targetNode = document.getElementById("target");
if (targetNode.hasAttribute("class")) {
   alert(targetNode.getAttribute("class"));
}

Discussion

Boolean attributes, referred to as attribute minimization in the XHTML specifications, are attributes in HTML where the attribute’s presence signifies a state, and is assigned no value. An example is the use of the Boolean attribute compact with the dl element:

<dl compact>

In XHTML, these attributes have to be assigned a value, so they’re assigned a value comparable to the name of the attribute:

<dl compact="compact">

You can check for the Boolean attribute (or any other) using hasAttribute or its namespace variation:

var res = div.hasAttributeNS("http://some.com/namespace","attrnm");

A value of true signifies the attribute is present; false that it is not.

Note

The hasAttribute method is not supported in IE7.

See Also

See Recipes 11.2 and 12.2 for more on namespace issues.

12.9. Removing an Attribute

Problem

You want to remove an attribute from an element.

Solution

Use the removeAttribute method:

if (targetNode.hasAttribute("class")) {
   targetNode.removeAttribute("class");
   alert(targetNode.getAttribute("class")); // null
}

Discussion

Most of the time, you’re probably interested in changing an attribute’s value, but there may be times when you want to remove an attribute completely. You might be tempted to set the value of the attribute to null to remove it:

div.setAttribute("class",null);

However, this is not correct. To remove any attribute, use the removeAttribute method, or its namespace variant, removeAttributeNS:

div.removeAttributeNS("http://somecom.com/namespace","customattr");

You don’t technically have to use hasAttribute to check if the attribute exists first—using removeAttribute on a nonexistent attribute does not throw an exception. Handy, since IE7 does not support hasAttribute.

See Also

See Recipes 11.2 and 12.2 for more on namespace-specific methods.

12.10. Moving a Paragraph

Problem

You want to move the last paragraph to before the first.

Solution

Get a reference to the paragraph you want to move, the first paragraph, and the parent element for the first paragraph, and use insertBefore to insert the last paragraph before the first:

var para = document.getElementsByTagName("p");
var parent = para[0].parentNode;
parent.insertBefore(para[para.length-1], para[0]);

Discussion

An element exists only once in the DOM regardless of its location. When you insert or append the element in a new location, it’s automatically removed from its previous position in the page layout.

See Also

See Recipe 12.2 for a description of insertBefore.

Problem

You want to scan a web page for links, remove the links from the page, and replace them with text-based footnote bullets at the end of the document.

Solution

You’ll have to use a variety of techniques to accomplish this task. Example 12-3 demonstrates a full application that moves all of the links contained in a paragraph to a bulleted footnote list at the end of the document—copying the contents of the link to the original link position first, and adding a superscript to reference the new footnote.

Example 12-3. Application to pull links out of the web document and append in a list at the end
<!DOCTYPE html>
<head>
<title>Moving Links</title>
<style>
 ul li
  {
    list-style-type: none;
    padding-bottom: 5px;
  }
</style>
<script type="text/javascript">

window.onload=function() {

  var links = document.querySelectorAll("a");
  var footnote = document.createElement("ul");

  // for all links
  for (var i = 0; i < links.length; i++) {

    // get parent element
    var parent = links[i].parentNode;

    // create number index text
    var num = document.createTextNode(i+1);
    var sup = document.createElement("sup");
    sup.appendChild(num);

    // process the children
    var children = links[i].childNodes;
    for (var j = 0; j < children.length; j++) {
         var newChild = children[j].cloneNode(true);
         parent.insertBefore(newChild,links[i]);
    }

    // add number subscript
    var sup2 = sup.cloneNode(true);
    parent.insertBefore(sup2,links[i]);

    // add a link to footnote
    var li = document.createElement("li");
    li.appendChild(sup);
    li.appendChild(links[i]);

    footnote.appendChild(li);
  }

  document.getElementsByTagName("body")[0].appendChild(footnote);}
</script>
</head><body>  <div id="target">
    <p>A favorite place of mine to visit in St. Louis is the <a
href="http://http://www.mobot.org/">Missouri Botanical Gardens</a>.
Great flowers all year round, and one of the finest annual
orchid shows. My most visited places, though, are the <a
href="http://www.stlzoo.org/">St.
Louis Zoo</a>, the <a href="http://www.nps.gov/jeff/index.htm"><em>Gateway
Arch</em></a>,
 the new <a href="http://www.citygardenstl.org/">City Garden</a>,
and the <a href="http://mdc.mo.gov/areas/cnc/powder/">Powder
Valley Conservation Nature Center</a>.
    </p>
  </div>
</body>

Discussion

As demonstrated in the solution, you can use querySelectorAll to find all the links in the page, passing in the anchor tag:

var links = document.querySelectorAll("a");

You can’t use getElementsByTagName to get the links, and I’ll explain why a little later in the discussion. The solution also creates a new unordered list to contain the links we pull out of the document.

Once you have a reference to all the links in the page, it’s time for the fun part. One of the interesting challenges with moving a link is that you typically want to preserve the link’s text in place—especially if the link references meaningful content—beyond the to-be-discouraged “here” or “link”.

You can’t assume the link contents are text because a link can contain other elements (though not another link). You could use innerHTML to access and copy the contents, but innerHTML isn’t well supported in XHTML.

Another approach is to move all of the child nodes out of the link. Now, you might think a way to move the link contents out of the link is to use something like the following:

var children = links[i].childNodes;
for (var j = 0; j < children.length; j++) {
     parent.insertBefore(children[j],links[i]);
}

The problem with this approach, though, is that childNodes points to a nodeList. In Chapter 11, we learned that nodeLists are live collections, which means changes in the page are reflected immediately in the nodeList in our code. So, if the link is something like the following:

<a href="http://oreilly.com">O'Reilly sells <em>books!</em></a>

The childNodes collection would have a length of 2 for the text node and the em. However, in the first loop iteration, once the first element has been moved (in this case, inserted before the existing link’s location, but it could be moved to the bullet list), the length of the childNodes value is now 1, but the for loop iterator has been incremented to 1—there is now only one child in the collection, and the for loop exits, leaving us with a link with the contents of the em element.

This is all really nice functionality, but it works against us sometimes. This is also the reason I didn’t use the getElementsByTagName method to get a list of the anchor tags. The getElementsByTagName also returns a live nodeList, and when we append the links to the in-memory unordered list, the link is removed from the document, and the length of the collection is affected.

Luckily, as the solution demonstrates, we have other methods that do what we want, without the interesting side effects. The cloneNode method is used to clone the child elements, which the solution places just before the link. Once the children are taken care of, the entire link element is moved to the bulleted list using appendChild on a newly created list element (li), which is then appended to our unordered list (ul).

When using cloneNode, especially in a circumstance such as this, you’ll want to pass a parameter of true when cloning the node:

var newElement = oldElement.cloneNode(true);

This ensures that all of the node’s children are also cloned in place, in addition to the element. Once all of the links are processed in the document, the last act is to append the unordered list to the end of the document. Figure 12-3 shows the web page after the JavaScript is finished.

The application works with all the book’s target browsers. It doesn’t work with IE7, because that browser version does not support the querySelectorAll method.

12.12. Adding Rows to an Existing Table

Problem

You want to add one or more rows to an HTML table.

Solution

Adding table rows isn’t complicated, but depending on the size of the table, it can be tedious. For each table cell, you have to create a textNode for the value, append the value to a new td element, append the element to a new tr element, and then append the whole thing to the table:

var table = document.getElementById("targettable");

var tr = document.createElement("tr");
var td = document.createElement("td");
var txt = document.createTextNode("some value");

td.appendChild(txt);
tr.appendChild(td);
table.appendChild(tr);
Web page after application has pulled all in-page links to a separate bulleted list
Figure 12-3. Web page after application has pulled all in-page links to a separate bulleted list

Discussion

Usually, when we’re creating table rows it’s because we’ve received data back from an Ajax function call. Typically, the data is organized in such a way that we can use loops of some form to process the data and simplify the process. Example 12-4 demonstrates how for loops can be used to process data in arrays, and to create all the necessary table elements.

Example 12-4. Extracting data from arrays and creating and appending table rows
<!DOCTYPE html>
<head>
<title>Sum Table Column</title>
<script>
window.onload=function() {

  var values = new Array(3);
  values[0] = [123.45, "apple", true];
  values[1] = [65, "banana", false];
  values[2] = [1034.99, "cherry", false];

  var mixed = document.getElementById("mixed");

  // IE7 only supports appending rows to tbody
  var tbody = document.createElement("tbody");

  // for each outer array row
  for (var i = 0 ; i < values.length; i++) {
     var tr = document.createElement("tr");

     // for each inner array cell
     // create td then text, append
     for (var j = 0; j < values[i].length; j++) {
       var td = document.createElement("td");
       var txt = document.createTextNode(values[i][j]);
       td.appendChild(txt);
       tr.appendChild(td);
     }

     // append row to table
     // IE7 requires append row to tbody, append tbody to table
     tbody.appendChild(tr);
     mixed.appendChild(tbody);
   }

}

</script>

</head>
<body>
<table id="mixed">
<tr><th>Value One</th><th>Value two</th><th>Value three</th></tr>
</table>
</body>

The data for the table is in static arrays, but could easily be XML or JSON returned from an Ajax call.

To repeat, the steps to create and add a table row are:

  1. Create the table row (tr).

  2. For each table cell, create a table cell element (td).

  3. For each table cell, create a text node for its data, and set the data.

  4. Append the text node to the table cell.

  5. Append the table cell to the table row.

  6. When all of the table cells have been appended to the table row, append the table row to the tbody element, and the tbody element to the table.

The application includes support for creating a tbody element and appending the table rows to this element, which is then appended to the table. IE7 does not allow for direct appending of table rows to the table. IE8 does, though, as do the other supported browsers.

See Also

Working with XML- and JSON-formatted data is covered in Chapter 19.

12.13. Removing a Paragraph from a div Element

Problem

You want to remove a paragraph from the web page.

Solution

You need to find the paragraph’s parent and use the removeChild method to remove the paragraph:

var para = document.getElementById("thepara");
para.parentNode.removeChild(para);

Discussion

The DOM removeChild method removes the element from the display and the DOM tree. The element still remains in memory, though, and you can capture a reference to the element when you make the removeChild call:

var oldpara = paraParent.removeChild(child);

Example 12-5 demonstrates how to remove paragraphs from the page. When the page is first loaded, all of the paragraphs are accessed via getElementsByTagName and the onclick event handler for each assigned to the pruneParagraph function.

The pruneParagraph function references the element that received the click event, finds its parent node, and then removes the paragraph. You can continue to click paragraphs until they’re all gone. After the paragraph is removed, the paragraphs in the page are queried again, and their count printed out.

Example 12-5. Removing paragraph elements from the page
<!DOCTYPE html>
<head>
<title>removeChild</title>
<style>
p
{
   padding: 20px;
   margin: 10px 0;
   width: 400px;
   background-color: #eeeeff;
}
</style>
<script>

window.onload=function() {

   var paras = document.getElementsByTagName("p");
   for (var i = 0; i < paras.length; i++)
     paras[i].onclick=pruneparagraph;
}

function pruneparagraph() {
  var parent = this.parentNode;
  parent.removeChild(this);

  alert("paras " + document.getElementsByTagName("p").length);
}

</script>

</head>
<body>
   <p>This is paragraph one</p>
   <p>This is paragraph two</p>
   <p>This is paragraph three</p>
   <p>This is paragraph four</p>
   <p>This is paragraph five</p>
</body>

Figure 12-4 shows the page after the second and fourth paragraphs have been removed. Notice that the paragraphs that follow the removed elements move to fill in the space newly freed.

Page with two paragraphs removed
Figure 12-4. Page with two paragraphs removed

12.14. Deleting Rows from an HTML Table

Problem

You want to remove one or more rows from an HTML table.

Solution

You can use the DOM removeChild method on an HTML table row, and all of the child elements, such as the table cells, will also be removed:

var parent = row.parentNode;
var oldrow = parent.removeChild(parent);

Discussion

When you remove an element from the web document, you’re not only removing the element, you’re removing all child elements. You can also get a reference to the removed element if you want to process its contents before it’s completely discarded. The latter is helpful if you want to provide an undo method in case the person accidentally selects the wrong table row.

To demonstrate the nature of DOM pruning, Example 12-6 is a modification of Example 12-4, except that before each new table row is added, the tr element’s onclick event handler is assigned to a function. When any of the new table rows is clicked, it’s removed from the table. The removed table row element is then traversed and the data in its cells is extracted and concatenated to a string, which is then printed out.

Example 12-6. Adding and removing table rows and associated table cells and data
<!DOCTYPE html>
<head>
<title>Adding and Removing Elements</title>
<style>
table {
   border-collapse: collapse;
}
td, th {
   padding: 5px;
   border: 1px solid #ccc;
}
tr:nth-child(2n+1)
{
   background-color: #eeffee;
}
</style>
<script>
window.onload=function() {

  var values = new Array(3);
  values[0] = [123.45, "apple", true];
  values[1] = [65, "banana", false];
  values[2] = [1034.99, "cherry", false];

  var mixed = document.getElementById("mixed");

  // IE 7 requires tbody
  var tbody = document.createElement("tbody");

  // for each outer array row
  for (var i = 0 ; i < values.length; i++) {
     var tr = document.createElement("tr");

     // for each inner array cell
     // create td then text, append
     for (var j = 0; j < values[i].length; j++) {
       var td = document.createElement("td");
       var txt = document.createTextNode(values[i][j]);
       td.appendChild(txt);
       tr.appendChild(td);
     }

     // attache event handler
     tr.onclick=prunerow;

     // append row to table
     tbody.appendChild(tr);
     mixed.appendChild(tbody);
   }
}

function prunerow() {
  var parent = this.parentNode;
  var oldrow = parent.removeChild(this);

  var datastring = "";
  for (var i = 0; i < oldrow.childNodes.length; i++) {
    var cell = oldrow.childNodes[i];
    datastring+=cell.firstChild.data + " ";
  }

  alert("removed " + datastring);
}
</script>

</head>
<body>
<table id="mixed">
<tr><th>Value One</th><th>Value two</th><th>Value three</th></tr>
</table>
</body>

The example demonstrates that it’s a whole lot simpler to prune the DOM tree than to grow new branches.

See Also

Example 12-4 can be found in Recipe 12.12.

12.15. Changing the Element’s CSS Style Properties

Problem

You want to modify one or more CSS properties for an element.

Solution

If you’re only modifying a single property, you can change the CSS setting directly using the element’s style property:

var elem = document.getElementById("elem");
elem.style.width = "500px";

If you’re modifying one or more values, you can use the element’s setAttribute method:

elem.setAttribute("style","width: 500px; background-color: yellow;");

Discussion

An element’s CSS properties can be modified in JavaScript using one of two approaches. As the solution demonstrates, the simplest approach is to set the property’s value directly using the element’s style property:

elem.style.width = "500px";

If the CSS property contains a hyphen, such as font-family or background-color, use a CamelCase notation for the property:

elem.style.fontFamily = "Courier";
elem.style.backgroundColor = "rgb(255,0,0)";

You can also use the element’s setAttribute method to set the style property:

elem.setAttribute("style","font-family: Courier; background-color: yellow");

However, when you set the style property using setAttribute, it erases any previously set values in the JavaScript.

Example 12-7 demonstrates how the style-setting techniques work, including the impact of using setAttribute. Various techniques are used to set and get style attributes, including a cross-browser approach to access the computed style for the attribute.

Example 12-7. Demonstrating setting and retrieving CSS style settings
<!DOCTYPE html>
<head>
<title>Changing style</title>
<meta charset="utf-8" />
<style>
#elem
{
  width: 200px; background-color: lime;
}
</style>
<script type="text/javascript">


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)

   elem.style.width = "500px";
   elem.style.backgroundColor="yellow";

   alert(elem.style.width); // 500px
   alert(elem.style.backgroundColor); // yellow

   // array notation
   elem.style["fontFamily"] = "Courier";

   // demonstrating overwriting properties
   var style = elem.getAttribute("style");
   alert(style); // should display color: purple; width: 500px;
                 // background-color: yellow;

   elem.setAttribute("style","height: 100px");
   var style = elem.getAttribute("style");
   alert(style); // now only displays height, resets styles

   var font = getStyle(elem,"fontFamily", "font-family");
   alert(font); // default font family

}
</script>
</head>
<body>
<div id="elem" style="color: purple">
testing
</div>
</body>

As soon as the page loads, the div element is accessed using getElementById, and its background-color is retrieved using a cross-browser function that gets the computed style for the attribute. The message output is “rgb(0,255,0)”, representing the lime color set in the page’s stylesheet.

Next, two CSS properties are set using the element’s style property: the width and background-color. Now the div element has a yellow background and is 500, rather than 200, pixels wide. Both of the modified values are accessed and printed out, so we can confirm that yes, the values have changed.

Next, the font-family for the element is set to Courier, using the array notation, which is another approach you can use to set and get style property values. Now the div element is 500 pixels wide, with a yellow background, and its font family is Courier.

The style property is accessed using getAttribute. A string of the values set using the style property is returned for all browsers:

color: purple; width: 500px; background-color: yellow;
font-family: Courier;

The purple font color is set inline within a style attribute in the div element.

Next, I’m using the setAttribute method to change the element’s height. A couple of things happen when I used the setAttribute method in the example. The height of the element is changed to 100 pixels, but the previously set style properties (color, width, background-color, and font-family) have been “erased,” and revert back to the original settings in the stylesheet, or the defaults by the user agent. The element is now 200 pixels wide, 100 pixels tall, with a green background, and the font reverts back to the default font for the browser (typically a serif value), and the default font color, black.

As you can see, using setAttribute to change the style element property can significantly impact on previous settings, including any inline CSS settings. You should only use setAttribute if you’re changing many values at once, and you don’t use any inline style attributes or haven’t modified the element’s style settings previously in your application.

The effects demonstrated in this recipe work the same with all of the book’s target browsers, except for IE7. The style property is an actual object in IE7, so when you access style with getAttribute, you’ll get an object, not a string. Since it is an object, it’s read only, which means you can’t use setAttribute with IE7.

See Also

See Recipe 11.13 for more information about accessing the computed style for an element.

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.