Fun with Data Binding

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

November 1, 1999

Many times in this space, I've talked about how to use data binding with Internet Explorer. I get more questions about this feature than any other from users who are building industrial-strength applications. There are two powerful aspects of data binding that I haven't really talked about before: hierarchy and data updating.

Hierarchical data binding allows easy display of nested content. For example, you want to show a list of customers, but under each customer you want to show all their open orders. With the advent of XML Data, more and more information is deeply nested and requires hierarchy to display the content properly.

Here's an example of the simple "Customer has Orders" style of data display.

Here's the code:

<html>
<head>
<xml id=Simple>
<?xml version="1.0" ?>
  <ROOT>
   <customer name="Joe Smith">
     <order description="Toys"/>
     <order description="Groceries"/>
     <order description="Electronics"/>
   </customer>
   <customer name="Harry Johnson">
     <order description="Books"/>
     <order description="Posters"/>
   </customer>
  </ROOT>
</xml>
</head>
<body>
<table datasrc=#Simple border=1>
  <tr><td><span datafld="name"></span></td></tr>
  <tr><td>
     <table datasrc=#Simple datafld="order" border=1>
        <tr><td><span datafld="description"></span></td></tr>
     </table>
  </tr></td>
</table>
</body>
</html>

The XML in the head is nothing new—an XML data island with some simple nested XML. The outer data-bound table definition is also like any other data-bound table. The <TABLE> sets the datasrc property to the XML data island's ID. The first span binds to the "name" field, showing the customer name. The inner table is where the real change is. That table is inside a <TD> of the first table, and also sets the datafld property to "order," indicating that the "description" field, referred to later, is qualified to the description inside the order record for the current customer object.

This nesting can be repeated for any number of levels. In the next example, we'll see how three-level nesting is achieved. The sample shows three levels of hierarchy: customer, holdings, and holding detail.

Here's the structure of the tables:

  <table border=1 id=CustPortfolio datasrc=#portfolio>
    <tr>
      <td>
        <div type=text datafld=customername></div>
      <input type=text datafld=customername>
      </td>

    </tr>
    <tr>
      <td>
        <table datasrc=#portfolio datafld=holding>
          <tr>
            <td>
                <div datafld=TotalValue></div>
            </td>

          </tr>
          <tr style="display: none">
            <td>
              <table id=HoldingDetail datasrc=#portfolio datafld=holding.holdingdetail>
                <tr>
                  <td>
                    <div datafld=buyprice></div>
                  </td>
                </tr>
              </table>
            </td>
          </tr>
        </table
      </td>
    </tr>
  </table>

I've deleted some of the bindings within the table, but the structure is still the same. Here are the three table definitions:

  <table border=1 id=CustPortfolio datasrc=#portfolio>

   <table datasrc=#portfolio datafld=holding>

     <table id=HoldingDetail datasrc=#portfolio
           datafld=holding.holdingdetail>

The first two tables look identical to the Customer/Order example. Once you are below two levels of nesting, you need to specify the full path of the datafld. In the case of the third level, its value is holding.holdingdetail. Here's an example of the XML data that's used:

<customer>
     <customername>Sam Shareholder</customername>
     <totalValue>$35,000</totalValue>
       <holding>
          <name>Aardvark Aerospace</name>
          <symbol>AAAE</symbol>
          <quantity>1000</quantity>
          <totalvalue>$5,500</totalvalue>
            <holdingdetail>
               <date>2/14/99</date>
               <quantity>600</quantity>
               <buyprice>$9</buyprice>
            </holdingdetail>
       </holding>
</customer>

One note on datafld bindings: When XML namespaces are used, they must also be provided in the datafld binding. If you had <v:customername>, instead of just <customername>, the datafld would specify v:customername, and not the unqualified customername.

One problem with heavily nested data is that the user may become overwhelmed with the quantity of information they see. This sample tries to remedy this by collapsing the third level of the hierarchy until the user clicks to expand it. The expansion is done with the time tested "display: none" toggle technique. When you click on the green block next to the holdings line, the holdings detail will be shown. Click again, and it will collapse back.

In the examples so far, we've used the standard Data Source Objects (DSOs) that Microsoft provides with Internet Explorer (Remote Data Services, XML and the Tabular Data Control). These controls serve in different ways, but, for some customers, the best model is to create their own DSO. Custom DSOs are easy to create. Here's a good place to start: OLE-DB Simple Provider: A Data Binding API for IE4/MSHTML.

One of the most common reasons for wanting to build a custom DSO—the data being bound to is dynamically changing. As the data changes, the bound information on the client should update as well. The cool thing about designing your application this way is that all you need to do is change the data: The view is automatically updated for you. Data in existing rows can change—the update happens. New rows can be added—the update happens. Rows can even be deleted, and the update automatically happens.

To make it simple, I've simulated this type of operation by writing code (in the last sample) that updates the underlying XML data, and shows how that data change is reflected in the bound table.

Let's look back to that sample.

The first thing to notice is that the on the first row, the customer name "Sam Shareholder" is shown twice. I've bound the same field to two different HTML elements—a <span> and an <input>. I did this so you could change the value, and see what happens. Click into the <input> where you see "Sam Shareholder," and change the text. As soon as you tab off that input, or click away (whenever it loses focus), an interesting thing happens. The <span> above the input is updated as well. There is no code on the HTML page making this happen—it's automatic. When you tab away, the XML node that's bound to the <span> and the <input> is updated. As soon as this update happens, the ondatasetchanged event is fired, and all bound elements dependent on that node are updated.

This also works for inserting and removing rows. When you click the Add Row button on the sample, a new row should appear (unless you previously deleted all the rows). This may seem like nothing new—I've already shown you how to add rows to a table. However, this was a little different. Instead of adding the row directly to the bound table, I changed the XML data behind the table.

function addRow() {
   var xmld, fc, nfc;
   xmld = portfolio.XMLDocument.documentElement;
   fc = xmld.firstChild;
   if (!fc)
     return;
   nfc = fc.cloneNode(true);
   xmld.insertBefore(nfc, null);
}

This code gets the XML document for the data island, copies the first element, and inserts it at the end. As soon as this happens, the onrowsinserted event fires, and a new row, representing the new XML data, is inserted into our bound table.

When you click the Remove Row button, the XML data is manipulated again. This time, the first element in the XML data is deleted. Here's the code for that:

function removeRow() {
  var xmld, fc, nfc;
  xmld = portfolio.XMLDocument.documentElement;
  fc = xmld.firstChild;
  if (fc) {
    xmld.removeChild(fc);
  }
}

When the XML element is removed, the onrowsdeleted event fires, and the row is removed from the bound table.

Hopefully, this shows some of the primary reasons for wanting to data bind on the client and not the server. If we were doing the data binding on the server, with every data change an entire new HTML document would have to be round-tripped to the client. With client-side data binding, not only can very rich data sets be easily represented, but making that data live and interactive is also a piece of cake.

 

DHTML Dude

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


  
Show: