Set as You Go dataSrc

As of December 2011, this topic has been archived. As a result, it is no longer actively maintained. For more information, see Archived Content. For information, recommendations, and guidance regarding the current version of Internet Explorer, see Internet Explorer Developer Center.

Michael Wallent
Microsoft Corporation

December 6, 1999

Contents

Dynamic Table
Collections

Dynamic Table

Last month, I reviewed hierarchical data binding, and how that could be used to view and update information with XML. A similar application of the hierarchical binding model can be applied to create a tree-list implementation. However, one of the common problems for tree-list views is performance. Large lists (thousands of elements) are sometimes slow to load and interact. I still want to use data binding—but to get around this limitation, I want to avoid loading the whole table structure in the first pass.

The typical workaround is to create the list manually, and to create a set of child nodes only as the node list is expanded. However, using this model, you need to manage updates manually. I want to get the best of both worlds: the performance and control of a manual list and the automatic update and templating of data binding.

View the sample.

The solution I've found is to create the nested data-binding template, but to set the dataSrc property on a table only when it should be exposed. This creates some additional overhead for each node initially, but it gives the benefit of being very dynamic.

Here's the code for the table template:

<table id=PrimaryTable cellspacing=0 cellpadding=0 datasrc=#TheData border=0>
  <tr onclick="toggle(this)">
     <td>
        <img id=Icon datafld="image">
        <span datafld="value"></span>
        <span id=HasChildren style="display: none" datafld="haschildren"></span>
    </td></tr>
  <tr style="display: none">
    <td>
     <!-- second level -->
     <table cellspacing=0 cellpadding=0 datafld="node" border=0>
       <tr onclick="toggle(this)">
         <td>
            <img id=Icon datafld="image">
            <span datafld="value"></span>
            <span id=HasChildren style="display: none" datafld="haschildren">
            </span>
         </td>
       </tr>
       <tr style="display: none">
         <td>
       <!-- third level -->
         <table cellspacing=0 cellpadding=0 datafld="node.node" border=0>
           <tr>
                  <td>
                     <img id=Icon datafld="image">
                     <span datafld="value"></span>
                     <span id=HasChildren style="display: none" datafld="haschildren">
                     </span>
                 </td>
               </tr>
        </table>
          </td>
        </tr>
      </table>
    </td>
  </tr>
</table>

This is a very basic three-level hierarchy. The one difference is that for the two nested tables, the dataSrc property hasn't been set yet. As a node is clicked, if it has children, the inner table is bound at that point. Here's the code to do that:

function toggle(e) {
  var nextRow;

  nextRow = e.nextSibling;

  hc = e.all.HasChildren;

  if (!hc)
    return;

  if (hc.innerText == "false")
    return;

  if (nextRow.style.display == "none") {
    nextRow.style.display = "";
    e.all.Icon.src = "ofolder.gif";
    // skips the td and comment, getting the third child,
    // which is the table
    nextTable = nextRow.all[2];
    if (!nextTable.dataSrc) {
      nextTable.dataSrc = "#TheData";
    }
  } else {
    nextRow.style.display = "none";
    e.all.Icon.src = "folder.gif";
  }
}

When the folder is clicked, it gets the <TD> element with which it is associated. If the node is specified to have children, the table below is shown; and if the table is not yet bound to a dataSrc, the dataSrc is hooked up.

The performance impact of this change is very positive. To load a 200-node tree with a standard data-binding model takes about 4 seconds. Loading the same tree with this incremental load model takes about 0.5 seconds, and scales better as well. Also, if the underlying XML data changes, the changes will be immediately reflected in the tree. This type of incremental data binding delivers the best of both worlds.

Collections

One DHTML featurette that I've been using quite a lot lately is the collections support on document objects and other elements in the tree.

The most common access to any element is by a simple reference to its ID:

<div id=Example>
</div>

<script>
alert(Example.tagName);
</script>

Notice that I referred to the element's ID directly—no document.all.Example indirection. Not only is the document.all.Example indirection more code, and harder to read, it's slower, since it requires an additional access to the document object and a collection. Direct ID reference is faster and more compact. As discussed previously, if you are making multiple accesses to the same object, it's even faster to cache the reference in a local variable, and to use that variable to access the element.

<div id=Example>
</div>

<script>
var a, b, i;
a = Example;
for (i=0; i<1000; i++) {

   b = a.tagName;
}
</script>

If you have multiple elements with the same ID on the page, the reference to the ID will return a collection.

In the following example, the alert will show "2".

<div id=Example>
</div>

<div id=Example>
</div>

<script>
alert(Example.length);
</script>

By now, you are probably familiar with the document.all collection. Each element also has both an all collection and a children collection. The all collection contains all descendent elements of an element, whereas the children collection contains only direct descendents of an element.

In the following example, the first alert will show "2"; the second alert will show "1".

<div id=Example>
<b>Child <u>Grandchild</u></b>
</div>

<script>
alert(Example.all.length);
alert(Example.children.length);
</script>

A very useful method to apply to any collection is the tags() method. This allows the easy subsetting of any collection by the tag names of the contained items, and can be used for both HTML tags and XML tags. However, for XML tags, the tags() method doesn't utilize the namespace of the tag, just the base tag name.

<html xmlns:v>
<body>
<div id=Example>
<b>Child <u>Grandchild</u></b>
<v:myTag>Custom Tag</v:myTag>
</div>

<script>
var bTags, uTags, mTags;
bTags = Example.all.tags("B");
uTags = Example.all.tags("U");
mTags = Example.all.tags("myTag");
alert(bTags.length);
alert(uTags.length);
alert(mTags.length);
</script>
</body>
</html>

When this sample runs, three alerts will appear, each showing "1". Note that for HTML tags, the tag names are all in caps. For XML tags, the tag names are case-sensitive, and must exactly match the specification of the tag itself—in this instance, a mixed-case example: "myTag".

This brings us to my motivating example for this: I'd like to create a table of 100 rows, but for each row, I want to insert some unique data. I could use the createElement() method to create each of the elements in the row, but for a complex row, I might be creating 20 or 30 elements each time. This loses the declarative benefits of DHTML. Instead, I've created a simple templating model. First, I create a table:

<table>
  <tbody id=TheBody>
    <tr id=TemplateRow style="display: none">
      <td id=CellOne></td>
    </tr>
  </tbody>
</table>

This row will serve as the template, and is hidden from display by using the display: none cascading style sheets (CSS) property. To create each of the rows, I'm going to use the cloneNode() method, and then insert the cloned row into the table. Before I insert the new row into the table, I need to change the text in the <TD> to indicate which row it is. Now, I could simply inspect this construct, and realize that the <TD> is the first child of the <TR>. However, this is a somewhat fragile method; if I decide that I want a <TD> preceding the row number, my algorithm is then broken. However, I can give the target <TD> a known ID, and I can use the all collection of the <TR> to look up that element precisely—without interfering with any other content that may be added to the <TR> later.

Here's the table creation code:

<script>
function init() {
  var i, r, b, d;

  b = TheBody;

  for (i=0; i<100; i++) {
    r = TemplateRow.cloneNode(true);
    r.id = "NewRow";
    r.style.display = "";
    r.all.CellOne.innerText = i;
    b.insertBefore(r, null);
  }
}
window.onload = init;
</script>

Note that after cloning each row, I set the text of the cell with the following line:

    r.all.CellOne.innerText = i;

I use the collection on the <TR> to subset all of the elements in my document (as I could have very many nodes with the ID "CellOne" by the time this has run).

The collections support in Internet Explorer gives you powerful ways to delve into the structure and content of your pages. The aggregation of simple methods can create some pretty impressive and powerful applications (even a spreadsheet).

Happy Holidays, and may you never have to hear the phrase Y2K again.

 

DHTML Dude

Michael Wallent is Microsoft's group program manager for Internet Explorer.


  
Show: