Chapter 4. Manipulating Page Elements

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 the HTML elements of a web page are printed out, with each child nested under its parent, the top-level element is the html element, followed by the head and body elements. In the web page associated with the following print out, the head element contains title and style elements, while the body contains a couple of div elements and a script block. The first div element contains two paragraphs (p) and an unordered list (ul), which, itself, contains two list items (li). The other div element contains a paragraph with a span.

Each of these elements, or nodes as they’re called in the Document Object Model (DOM), can be directly or indirectly accessed, created, modified, or removed. The functionality is provided through JavaScript (ECMAScript) bindings—APIs that provide the basic DOM functionality.

See Also

The best place 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.

Access a Given Element and Find Its Parent and Child Elements

Problem

You want to access a specific web page 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 if an element with the identifier exists; otherwise, it returns null.

The returned element object has a set of properties, including several inherited from the node object. The node properties and methods are primarily focused on the node’s relationship to the document tree that contains it. For instance, to find the parent node for the element, access its parentNode property:

var parent = demodiv.parentNode; // parent node

What’s returned is an element object referencing the target element’s parent node. If you want to find out what children an element has, you can traverse a collection of them through the childNodes property, after first checking to ensure it does have children nodes with the hasChildNodes method:

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

When accessing the children of an element, you 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

In addition, if the web page document has an HTML comment (delimited by <!-- and -→), a #comment node is created.

The DOM tree printout at the beginning of the chapter listed all the HTML elements in the web page, but it’s actually not complete. What’s missing is the text, and a single comment. Incorporating these nodes in the tree displays a fuller, albeit busier, tree.

You can simplify the tree by removing non-existential material. Rather than add line breaks and spaces to your HTML document:

<div>
  <p>This is text</p>
  <p>This is more text</p>
</div>

Strip out this extraneous material, which has only been added for human readability:

<div><p>This is text</p><p>This is more text</p></div>

Now, the only #text nodes are the ones containing meaningful text.

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, containing the number of elements in the collection. The only NodeList method is item, which takes the index of the item, beginning with the first node at index 0, and returns the node reference for the page element:

var img = imgElements.item(1); // second image

The getElementsByTagName method can be used with other element types, not just document.

In Example 4-1 three img elements are contained in both an article and a div element, but we’re only interested in accessing img elements in the article. To ensure the application only gets the img elements in the article(s), we use getElementsByTagName with the +article element first, and then query each member of the article NodeList for img elements.

Example 4-1. Demonstrating use of getElementsByTagName
<!DOCTYPE html>
<head>
<title>Recipe</title>

<style>
img {
  width: 200px
  }
</style>
</head>
<body>
<div id="result"></div>
<article>
  <p><img src="http://burningbird.net/examples/media/orchids12.preview.jpg"
  alt="Orchid from MBG 2009 orchid show" /></p>
  <p><img src="http://burningbird.net/examples/media/orchids6.preview.jpg"
  alt="Orchid from MBG 2009 orchid show" /></p>
</article>

<div>
  <p><img src="http://burningbird.net/examples/media/orchids4.preview.jpg"
  alt="Orchid from MBG 2009 orchid show" /></p>
</div>

<script>

  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;

</script>
</body>

The src attribute for the collection of img elements contained in the article is printed out to the div element with an id of result.

NodeList is an intriguing object because it’s a live collection. A live collection means relevant changes made to the document after the NodeList is retrieved are reflected immediately in the collection. Example 4-2 demonstrates the NodeList live collection functionality, as well as getElementsByTagName.

In the example, getElementsByTagName is used at the very start of the script block to get a NodeList of the existing img elements in the page. In addition, two buttons allow the user to add a new picture to the web page, and to dump a print out of contents of the NodeList containing all images in the page.

When a new image is added, getElementsByTagName is used with the paragraph tags (p) in order to obtain a NodeList of all the paragraphs. The last paragraph’s parent element (found via the parentNode property) is accessed and a new paragraph element containing a new img element is appended to this element.

To learn more about adding new elements to the web page, see “Inserting Elements Before Existing Page Elements” later in the chapter, which covers adding new page elements in more detail.

When the images NodeList is dumped, the existing img NodeList is accessed and the src attribute value for each img is added to an unordered list (ul). When finished, the list is added to a div element using its innerHTML property.

Example 4-2. Demonstrating getElementsByTagName and NodeList live collection capability
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Example</title>
<script>

// get NodeList of img elements
var imgs = document.getElementsByTagName('img');

function addImage() {

   var p = document.createElement("p");
   var img = document.createElement("img");
   img.src="http://burningbird.net/examples/media/orchids4.preview.jpg";
   p.appendChild(img);

   var paras = document.getElementsByTagName('p');
   paras[paras.length-1].parentNode.appendChild(p);

}

function dumpCollection() {
  var result = document.getElementById('result');
  var output = "<ul>";
  for (var i = 0; i < imgs.length; i++) {
    output+= "<li>" + imgs[i].src + "</li>";
  }
  output+="</ul>";
  result.innerHTML = output;
}
</script>
</head>
<body>
  <button type="button" onclick="addImage()">Add new Picture</button>
  <button type="button" onclick="dumpCollection()">Dump images collection</button>
  <div id="result"></div>
  <p><img src="http://burningbird.net/examples/media/orchids12.preview.jpg"
  alt="Orchid from MBG 2009 orchid show" /></p
>
  <p><img src="http://burningbird.net/examples/media/orchids6.preview.jpg"
  alt="Orchid from MBG 2009 orchid show" /></p>
  <p><img src="http://burningbird.net/examples/media/orchids9.preview.jpg"
  alt="Orchid from MBG 2009 orchid show" /></p>

</body>
</html>

Clicking the button to dump the images collection before adding a new image displays the src attribute value for the three existing images; after adding a new picture, the collection now contains four images, and four src values are printed out. Clicking the button to add new copies of the image just adds that many more new images to the NodeList's live collection.

See Also

Using getElementsByTagName to get all the paragraphs just to find their parent in Example 4-2 is overkill. “Finding the Parent Element for a Group of Elements” demonstrates how to use the Selectors API to directly access just the parent element for the paragraphs. The parentNode property is introduced in “Access a Given Element and Find Its Parent and Child Elements”.

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, without having to query individual article elements.

Solution

Use the newer Selectors API, access the img elements contained within article elements using CSS-style selector strings:

var imgs = document.querySelectorAll("article img");

Discussion

The Selectors API is a relatively new capability that is now supported in all modern browsers. It provides more query flexibility than any other DOM functionality. It can definitely simplify your JavaScript.

There are two selector query API methods: the first, querySelectorAll, is demonstrated in the solution, and the second is querySelector. The difference between the two is querySelectorAll returns all elements that match the selector criteria, while query+Selector+ only returns the first found result.

The selectors syntax is derived from CSS selector syntax. 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 in the web page. 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, use the following:

var imgs = document.querySelectorAll("article> img");

If you’re interested in accessing all img elements that are immediately following a paragraph, use:

var imgs = document.querySelectorAll("p + img");

If you’re interested in img elements that have an empty alt attribute, 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 negation pseudo-class:

var imgs = document.querySelectorAll('img:not([alt=""])');

The negation pseudo-class (:not) is used to find all elements that do not match a selector string. In the previous code snippet, img elements that have a non-empty alt attribute are returned. To find all img elements that do have any alt attribute, use the following:

var imgs = document.querySelectorAll('img:not([alt]);

The use of [alt] returns all elements with an alt attribute, and the use of the negation psuedo-class returns just the opposite.

The collection of elements returned from querySelectorAll is not a live collection, unlike the collection of objects returned with getElementsByTagName. Updates to the page are not reflected in the query results if the updates occur after the results have been obtained.

Though the Selectors API is a wonderful creation, it shouldn’t be used for every document query. For one, it’s not efficient for accessing elements by a specific identifier, so you’re still better off using getElementById for this purpose.In addition, as just noted, the resulting collection isn’t a live collection, and won’t reflect recent updates.

The best bet is to test your application using the Selectors API and a variety of other methods and see which provides best performance, broadest support, and necessary functionality.

The Selectors API specification can be accessed at W3C: Selectors API Level 1. The CSS2 selector syntax can be reviewed in more detail within the W3C’s CSS2 specification and the CSS3 selector syntax can be explored at W3C’s Selectors Level 3.

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.

There is hope for us yet, because the concept of a parent selector is included in the working draft of CSS4, along with other goodies. However, it’s going to take some time before this new functionality comes to a computer, tablet, or smartphone near us.

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.

Earlier in Example 4-2 we used a cumbersome technique of finding all paragraphs using a getAllElementsByTagName with the paragraphs, accessing the last element, and then using parentNode.

We can simplify this functionality using querySelect with the p elements, which returns the first paragraph, which we can then query for the parent node with parentNode.

function addImage() {

   var p = document.createElement("p");
   var img = document.createElement("img");
   img.src="http://burningbird.net/examples/media/orchids4.preview.jpg";
   p.appendChild(img);

   //using querySelector rather than
   //getElementsByTagName
   var para  = document.querySelector('p');
   para.parentNode.appendChild(p);

}

See Also

See “Discover all Images in Articles Using the Selectors API” for more details on the Selectors API and the querySelector and querySelectorAll methods.

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 elements that have multiple classes:

var elems = document.getElementsByClassName("firstclass secondclass");

The second approach using querySelectorAll is an alternative. It, too, can search for multiple class names:

var elems = document.querySelectorAll(".firstclass, .secondclass");

However, performance tests demonstrate that getElementsByClassName is a superior option in all environments. In most cases, querySelectorAll is best reserved for those times when other specialized methods don’t provide what’s needed.

Once you have an array of elements, you can find out which type of element each is with nodeName. In the following code snippet, all elements that share the same class (firstclass) are returned, and what type each is is accessed via the nodeName property. A string of the results is returned.

var elements = document.getElementsByClassName("firstclass");

  // print out node name
  for (var j = 0; j < elements.length; j++) {
     var element = elements[j];
     elementString+=element.nodeName + " ";
  }

See Also

See “Discover all Images in Articles Using the Selectors API” for more details on the Selectors API and the querySelector and querySelectorAll methods.

Finding All Elements That Share an Attribute

Problem

You want to find all elements in a web document with a similiar class.

Solution

Use querySelectorAll with the substring matching attribute selector:

var elements = document.querySelectorAll('*[class*=test]');

Discussion

There are three substring matching attribute selectors. From the Selectors Level 3 specification:

[att^=val]
Represents an element with the att attribute whose value begins with the prefix "val". If "val" is the empty string then the selector does not represent anything.
[att$=val]
Represents an element with the att attribute whose value ends with the suffix "val". If "val" is the empty string then the selector does not represent anything.
[att*=val]
Represents an element with the att attribute whose value contains at least one instance of the substring "val". If "val" is the empty string then the selector does not represent anything.

If you have elements with class values such as aclass, pclass, lclass, and classl, you can find all of the elements with a query like the following:

var elems = document.querySelectorAll('[class*=class]');

However, if you only want class values where class occurs only at the end of the string, use:

var elems = document.querySelectorAll('[class$=class]');

Conversely, find all elements whose class values begin with class using the following:

var elems = document.querySelectorAll('[class^=class]');

Given the following HTML snippet:

<div id="div1" class="classa">
  <p id="para1" class="bclass">Testing different query strings.</p>
</div>
<ul class="classa" id="ul1">
  <li id="li1" class="bclass">one</li>
  <li id="li2" class="classa">two</li>
</ul>
<article id="art1" class="bclass">
  <p id="para2" class="bclass class2">Last paragraph</p>
</article>
<img id="img1" class="classa"
src="http://burningbird.net/examples/media/suns.jpg"
alt="Basket of brightly painted pottery suns with smiling faces" />
<img id="img2" class="bclass"
src="http://burningbird.net/examples/media/orchids4.preview.jpg"
alt="orchids" />

The following JavaScript snippet would return a string with the id attribute of any img element with a class name beginning with class:

var elems = document.querySelectorAll('[class^=class]');

var result="";
for (var i = 0; i<elems.length; i++) {
    result+= elems[i].getAttribute("id") + " ";
}

You can combine substring selections. The following would return all JPEG img elements that are from the burningbird.net domain:

var imgs = document.querySelectorAll('[src*=burningbird\.net][src$=jpg]');

Notice the use of the escape character (\) with the dot (.) in the substring match. The query looks for any src string that contains the specified domain, in any position, and the jpg string in the last part of the string. Only elements matching both are returned.

See Also

See “Discover all Images in Articles Using the Selectors API” for more details on the Selectors API and the querySelector and querySelectorAll methods.

Get Element Attributes

Problem

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

Solution

Use the getAttribute method, passing in the attribute name:

<input type="text" value="test" id="textId" />

...

var type = elem.getAttribute("type"); // text
var value = elem.getAttribute("value"); // test
var id = elem.getAttribute("id"); // textID

Discussion

When elements are defined in various HTML specifications, such as HTML5, they’re given a set of shared and/or unique attributes. Many of these attributes are then converted into object properties, which can be accessed in JavaScript directly:

var id = elem.id;

Some of the properties are given derived names, such as className for class. Nonstandard attributes that don’t have a property equivalent have to be accessed using the getAttribute method:

var role = elem.getAttribute("data-index");

Since the getAttribute method works equally well with standard and nonstandard attributes, and doesn’t require knowing what the derived attribute name is, you should, in most cases, use getAttribute to access all attribute values.

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");
   ...
}

Using hasAttribute bypasses the problem that can occur when different user agents return different values (empty string or null) when an attribute doesn’t exist.

I mentioned about using getAttribute in most cases. What about the times when you don’t want to use the method?

The getAttribute method works with attributes that are set in the element, but you’ll still need to directly access element properties when the element property and the element attribute aren’t guaranteed to have the same value. A good example is a check box, and the checked property:

<input type="checkbox" id="test" checked />

...

var attrChecked = document.getElementById("test").getAttribute("checked"); // empty string

var checked = document.getElementById("test").checked; // true or false

The checked attribute value and the checked property value differ. They are not synchronized.

If the check box is checked programatically using setAttribute (the method used to set an attribute value), then getAttribute works as expected:

<input type="checkbox" id="test" />

...

var checkbox = document.getElementById('test');
checkbox.setAttribute('checked', false);

...
var checked = checkbox.getAttribute('checked'); // returns false;

If you set the check box using setAttribute but then manually check (or uncheck) the check box, getAttribute reflects the setAttribute setting, not the actual state of the checkbox. The checked property does match the checked attribute—not unexpected when the attribute and property are not synchronized.

To make things even more twisty, some attributes and properties are synchronized, but only in one direction. A good example of this is the text input element.If the value is set in the element, it’s available from both the value property and getAttribute:

<input type="text" id="test2" value="some value" />
...

var text = document.getElementById("test2");

var textValue = text.value; // some value
var textValue2 = text.getAttribute("value"); // some value

If the text input value is changed using setAttribute, both the value and getAttribute reflect the change—this is that one way synchronization I mentioned earlier:

<input type="text" id="test2" value="some value" />
...

var text = document.getElementById("test2");
text.setAttribute("other value");
...

var textValue = text.value; // other value
var textValue2 = text.getAttribute("value"); // other value

However, if the text value is changed manually or by setting the value property, the new value is not reflected when accessed using getAttribute:

<input type="text" id="test2" value="some value" />
...

var text = document.getElementById("test2");
text.value = "other value";
...

var textValue = text.value; // other value
var textValue2 = text.getAttribute("value"); // some value

Synchronization flows from attribute to property, but not from property back to attribute.

How can you know when to use which method? A good rule of thumb is to use getAttribute with any standardized attribute that is set programatically, rather than modified by the user.

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 with the parent.

In Example 4-3, the solution for this recipe is embedded into a web page that originally consists only of one named div element and a button. When the button is pushed, a new sister div element is created and inserted into the document before the existing div element.

Example 4-3. Inserting a div element into a web page
<!DOCTYPE html>
<html>
<head>
<title>object detection</title>
<script>

function addDiv() {

  // get parent
  var sister = document.getElementById("sister");
  var parent = sister.parentNode;

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

  // add to page
  parent.insertBefore(newDiv,sister);
}
</script>
</head>
<body>
  <button type="button" onclick="addDiv()">Add div</button>

  <div id="sister">Existing div element</div>
</body>
</html>

In the example, the target div element is accessed using getElementById, and then parent is accessed with 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.

To create the new div element, 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 given some content through the innerHTML property. At this point, you can also assign one or more attribute values using setAttribute. The new 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.

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");
newPara.textContent = "No, I'm the third paragraph";

if (paras[2]) {
   div.insertBefore(newPara, paras[2]);
} 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.

Adding Text to a New Paragraph using createTextNode

Problem

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

Solution

You can use the textContent approach demonstrated in the last recipe, but you can also create the text node directly and add text to the paragraph with createTextNode:

// 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 4-4 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 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 4-4. Demonstrating various methods for adding content to a web page
<!DOCTYPE html>
<html>
<head>
<title>Recipe</title>
</head>
<body>
  <button type="button" onclick="addPara()">Add paragraph</button>

  <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>
<script>
function addPara() {

// 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")[1]; //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>
</body>
</html>

Why use createTextNode rather than the simpler textContent? One reason is you’re adding more than one element, such as two text nodes with a break element between them, matching the existing text within the example. The textContent property treats any embedded HTML (or script or style setting) as nothing more than text: it doesn’t process the text (making it a whole lot safer to work with if you’re accessing text from users).

You could use innerHTML instead of creating a text node, and the former will process the embedded markup. However, this isn’t a safe approach if you’re grabbing text from the user because the script kiddies have all our numbers. A good demonstration of the vulnerability of using innerHTML is the following:

para.innerHTML="<img src=x onerror=alert(1)>"; // guess what happens?

No, a better approach is to build the content in such a way that nothing creepy can crawl in. To match the structure of the existing paragraphs, the application accepts as input two text strings, creates two text nodes and a break element, and appends all three to the paragraph:

// create two text nodes
   var txtNode = document.createTextNode(txt);
   var txtNode2 = document.createTextNode(txt2);

   // create <br>
   var br = document.createElement("br");

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

   // append the text and break to the paragraph
   para.appendChild(txtNode); // notice order
   para.appendChild(br);
   para.appendChild(txtNode2);

   // insert new paragraph
   div.insertBefore(para, oldPara);

A little more work, but safer. Much safer.

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");

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