SiteExperts.com Logo Home | Community | Developer's Paradise | Jobs
User Groups | Site Tools | Site Information | Search

Inside Technique : Dynamic Content Techniques : Demonstration (IE4/5)

Demonstration requires IE4/5

Dynamic Content Techniques

Introduction

Internet Explorer 4.0 pioneered the concept of Dynamic Content with the introduction of the innerHTML/Text and outerHTML/Text properties. These properties made it possible to change any of your page's content without reloading the document. The inner* and outer* properties work by parsing an HTML string and rendering it in your page. Internet Explorer 5.0 adds new support for constructing content by building the tree of HTML out of objects representing each individual element. This support represents IE5's initial implementation of the W3C Document Object Model (DOM) recommendation.

First, let's look at the original IE4 string approach to dynamic contents using the inner* and outer* properties. In Internet Explorer 4.0, these properties are only accessible after the page has loaded. For example, using the innerHTML property we can insert a list with two items into the document:

myElement.innerHTML="<UL><LI>Item 1<LI>Item 2</UL>"

Internet Explorer 5.0 expands the string based approach by allowing HTML to be constructed element by element and allowing the document's contents to be changed even during the loading of the page. We can recreate our original list example by first creating the UL element and then creating and attaching each LI element to the UL element:

var el = document.createElement("UL")
var eLI = document.createElement("LI")
eLI.innerText="Item 1"
el.insertBefore(eLI)
var eLI = document.createElement("LI")
el.innerText="Item 2"
el.insertBefore(eLI)
insertAdjacentElement("afterBegin",el)

While the second approach appears more verbose, it offers a number of benefits that can help you create more dynamic and rich pages. For simple uses of dynamic content, the inner* and outer* properties are the most effective approach. For example, if you just want to change contents in response to a user action or timer.

The inner* and outer* properties become complicated when you want to add interactions between the new elements and the rest of the page. What if you want each LI element to contain a reference to another element or object? This requires you to have access to the object representing the LI element. Unfortunately, when you manipulate the HTML as a string there is no object to reference until after the element is inserted in the page. So, to reference the inserted list we would need to do it after the insertion:

myElement.innerHTML="<UL><LI>Item 1<LI>Item 2</UL>"
var getUL = myElement.all[0]
// Assign another object to myProp
getUL.myProp = anotherObject 

This approach requires you to map the inserted HTML to hard coded references into the underlying element collections. This makes your code more difficult to maintain. When building your page element-by-element you can assign references to other objects directly to the new element when created, not when inserted.

The remainder of this article explores both the string and element-based approached to dynamic contents. To demonstrate each approach we will show you how to automatically generate and insert a table of contents for your web-page. We start with the original Internet Explorer 4.0 string-based approach.

String-based Table of Contents

First, we are going to create a table of contents out of the headers in your document using the Internet Explorer 4.0 string-based approach. For each item in the table of contents we will associate it with the content in the document.

The first step is to extract all the headers from the document. This is very simple and can be accomplished with one quick scan of the body's all collection:

function doLoad() {
  var s="<UL>"
  var cAll = document.body.all
  var iAll = cAll.length
  // Tags to look for
  var tagList = "H1;H2;H3;H4;H5;H6;"
  for (var i=0;i < iAll;i++)
    if (tagList.indexOf(cAll[i].tagName+";")>=0)
      s+="<LI class=\"toc" + cAll[i].tagName + "\">" + cAll[i].innerText
  s+="</UL>"
  document.body.insertAdjacentHTML("AfterBegin",s)
}

The above script creates and inserts a table of contents. However, this table of contents is static. We want the table of contents to be live so clicking on an entry takes you to the content in the page.

Solving this problem points out a weakness to the string-based approach. Our goal is to associate a reference to the header in the content with the entry in the table of contents. This reference cannot be described in the HTML string and needs to be applied directly to each individual LI element in the table of contents. This is where limitations of string-based HTML manipulation become apparent.

Our approach to solving this problem is two-fold. First, as we parse the document and find a header, we store the element's reference in an array. This array maps one-to-one with the entries we are creating in the table of contents. Next, after we insert the table of contents, we scan the table of contents list and associate each list item with the real content in the document:

function goTarget() {
	if (this.target)
		this.target.scrollIntoView()
}

function doLoad2() {
  var s="<UL ID=myTOC>"
  var cHeaders = new Array
  var cAll = document.body.all
  var iAll = cAll.length
  var tagList = "H1;H2;H3;H4;H5;H6;"
  for (var i=0;i<iAll;i++)
    if (tagList.indexOf(cAll[i].tagName+";")>=0) {
      cHeaders[cHeaders.length] = cAll[i]
      s+="<LI class=\"toc" + cAll[i].tagName + "\">" + cAll[i].innerText
    }
  s+="</UL>"
  document.body.insertAdjacentHTML("AfterBegin",s)
  var cTOC = document.all.myTOC.children.tags("LI")
  var iTOC = cTOC.length
  for (var i=0;i<iTOC;i++) {
    cTOC[i].target = cHeaders[i]
    cTOC[i].onclick = goTarget
  }
}

The last step is to improve the appearance of the table of contents. When generating the table of contents we assigned a class to each table of contents entry. All that is left to do is define the appropriate style sheet for each header level. Below is a very simple style sheet that indents each entry based on the header's level. With CSS you can make your style sheets as fancy or as simple as you want:

<STYLE>
#myTOC {cursor: hand}
.tocH2 {margin-left:10pt}
.tocH3 {margin-left:20pt}
.tocH4 {margin-left:30pt}
.tocH5 {margin-left:40pt}
.tocH6 {margin-left:50pt}
</STYLE>

While this approach works it is not a very elegant solution. It takes two passes to build the table of contents and takes a bit of work if you decide to change the HTML used to in the generated table of contents. Next we show you how to create the same solution using some of the new DOM methods of Internet Explorer 5.0.

Object-based Table of Contents

Internet Explorer 5.0 adds new methods that let you construct your HTML element by element. These methods are part of Internet Explorer 5.0's initial implementation of the W3C DOM recommendation. Rather than build a large HTML string and insert it into your document you build the HTML piece by piece. To build your HTML tree you use the following three new methods:

  • document.createElement()
  • element.insertBefore()
  • element.insertAdjacentElement()

The createElement() method is used to create a new element object. By itself this new object is not associated with your document and therefore is not rendered. The createElement() method originated in with limited utility in Internet Explorer 4.0. In Internet Explorer 4.0, you could only create IMG, OPTION, and AREA elements. Internet Explorer 5.0 improves the support for this method to every HTML element.

For our table of contents example, we need to first create an UL element to act as our list container. We create the UL element by calling the document's createElement() method:

var el = document.createElement("UL")
el.ID="toc"

The createElement() method returns a reference to the newly created UL element. After creating the element we also set the element's ID.

The next step is to iterate over all the headers and create an LI element for each one. On each LI element we are also going to set the className, associate the entry with the contents, and hook up the onclick handler. Different from the IE4 approach, all of this can be done in one pass of the document:

  var el = document.createElement("UL")
  el.id = "toc"
  var cAll = document.body.all
  var iAll = cAll.length
  var tagList = "H1;H2;H3;H4;H5;H6;"
  for (var i=0;i<iAll;i++)
    if (tagList.indexOf(cAll[i].tagName+";")>=0) {
      var eLI = document.createElement("LI")
      eLI.className = "toc" + cAll[i].tagName
      eLI.target = cAll[i]
      eLI.onclick = goTarget
      eLI.innerText=cAll[i].innerText
      el.insertBefore(eLI)
    }

This approach causes the table of contents to be built iteratively. At the end of each loop iteration we are inserting the new element as a child of the list. The insertBefore method by default inserts the new element as the last child. The insertBefore method also has an optional second argument which lets you insert the element before another child. We did not need to use this argument in our example since all entries are added to the end of the list.

After this script executes, we have the complete table of contents. However, this table of contents is kept in memory and is not rendered in your document. This is because the elements we created were never inserted into the document. The last step is to insert our HTML fragment that represents the table of contents into the document.

To insert the table of contents, we are going to use the insertAdjacentElement method. This method is analogous to the insertAdjacentHTML method that inserts an HTML string into your document but is used for your object-based HTML sub-tree. This method enables you to insert the HTML tree before or after a the begin or end tag of an element. We will use this method to insert our new table of contents immediately following the begin BODY tag:

  document.body.insertAdjacentElement("afterBegin",el)

Below brings all of the code together into a single function:

function doLoad() {
  var el = document.createElement("UL")
  el.id = "toc"
  var cAll = document.body.all
  var iAll = cAll.length
  var tagList = "H1;H2;H3;H4;H5;H6;"
  for (var i=0;i<iAll;i++)
    if (tagList.indexOf(cAll[i].tagName+";")>=0) {
      var eLI = document.createElement("LI")
      eLI.className = "toc" + cAll[i].tagName
      eLI.target = cAll[i]
      eLI.onclick = goTarget
      eLI.innerText = cAll[i].innerText
      el.insertBefore(eLI)
    }
  document.body.insertAdjacentElement("afterBegin",el)
}

window.onload = doLoad

Next we conclude by explaining the relationship between the IE5 solution and the W3C DOM and providing a simple demonstration (in IE4/5).

Conclusion

You have now seen two approaches to building interactive dynamic content. Which approach you choose depends upon what you are trying to achieve. If Internet Explorer 4.0 compatibility is a must, then the first approach is your own solution. If this is not a requirement, consider using the second approach whenever you need to manipulate the individual HTML elements of the new content. You will find the object-oriented approach to constructing HTML much more direct and reliable compared to trying to manipulate the HTML after it has been inserted.

This article provides you a glimpse into the new HTML manipulation methods available in Internet Explorer. Beyond creating HTML objects Internet Explorer 5.0 also lets you create text node objects. These are objects that contain nothing but content that can be inserted into your HTML fragment. Looking at our object-approach for creating the table of contents, we still chose to use the innerText property to set the contents of each table of content entry. By using text-node objects we could have created a text object with the text for each table of content entry and then inserted it into the LI element. We chose not to take this approach as our needs to not require that additional flexibility.

Also, the new methods for building HTML documents node by node are from the new W3C Document Object Model recommendation. While the createElement and element insertion methods are part of the standard the innerText property is a Microsoft specific property. For this article we explicitly chose to focus on providing solutions that fit our specific task at hand and will cover the standards-based programming model in the future. The W3C recommendation is defined to provide generic tree manipulations and as such would have taken more code to solve the same task. The two primary differences are how we traversed the document and how we set the text of the LI node. For the document traversal, we would need to traverse the HTML document tree manually rather than walk the flattened all collection, and we would need to use text-nodes for setting any text. Also, even if we programmed to the DOM recommendation, the recommendation still does not contain an event model making it impossible to make a standard-compliant live table of contents.

Discuss and Rate this Article