[ Team LiB ] Previous Section Next Section

14.6 Transforming XML Data into HTML Tables

NN 6, IE 5(Win)

14.6.1 Problem

You want to render embedded XML data as a traditional HTML table.

14.6.2 Solution

For a table of known column headings and XML data structure, you can set up the initial table element container, prepped for the addition of the data rows:

<table id="cupFinals">
<thead>
<tr><th>Year</th>
    <th>Host Country</th>
    <th>Winner</th>
    <th>Loser</th>
    <th>Score (Win - Lose)</th>
</tr>
</thead>
<tbody id="matchData"></tbody>
</table>

Use the XML-loading scenario described in Recipe 14.4. The initialization function (triggered by the onload event handler) calls `verifySupport( ) to make sure the browser is capable of XML loading, and then invokes drawTable( ) (shown in Example 14-1 of the Discussion) through the setTimeout( ) method:

// initialize first time -- invoked onload
function init(xFile) {
    // confirm browser supports needed features and load .xml file
    if (verifySupport(xFile)) {
        // let file loading catch up to execution thread
        setTimeout("drawTable( )", 1000);
    }
}
...
<body onload="init('worldcup.xml');">

14.6.3 Discussion

Example 14-1 shows the drawTable( ) function, which assembles table rows and cells from the XML document's node tree.

Example 14-1. The drawTable( ) function dynamically generates a table's contents
// Draw table from xDoc document tree data
function drawTable(tbody) {
    var tr, td, i, j, oneRecord;
    tbody = document.getElementById(tbody);
    // node tree
    var data = xDoc.getElementsByTagName("worldcup")[0];
    // for td class attributes
    var classes = ["ctr","","","","ctr"];
    for (i = 0; i < data.childNodes.length; i++) {
        // use only 1st level element nodes to skip 1st level text nodes in NN
        if (data.childNodes[i].nodeType =  = 1) {
            // one final match record
            oneRecord = data.childNodes[i];
            tr = tbody.insertRow(tbody.rows.length);
            td = tr.insertCell(tr.cells.length);
            td.setAttribute("class",classes[tr.cells.length-1]);
            td.innerHTML = 
              oneRecord.getElementsByTagName("year")[0].firstChild.nodeValue;
            td = tr.insertCell(tr.cells.length);
            td.setAttribute("class",classes[tr.cells.length-1]);
            td.innerHTML = 
               oneRecord.getElementsByTagName("location")[0].firstChild.nodeValue;
            td = tr.insertCell(tr.cells.length);
            td.setAttribute("class",classes[tr.cells.length-1]);
            td.innerHTML = 
               oneRecord.getElementsByTagName("winner")[0].firstChild.nodeValue;
            td = tr.insertCell(tr.cells.length);
            td.setAttribute("class",classes[tr.cells.length-1]);
            td.innerHTML = 
               oneRecord.getElementsByTagName("loser")[0].firstChild.nodeValue;
            td = tr.insertCell(tr.cells.length);
            td.setAttribute("class",classes[tr.cells.length-1]);
            td.innerHTML = 
               oneRecord.getElementsByTagName("winscore")[0].firstChild.nodeValue + 
               " - " + 
               oneRecord.getElementsByTagName("losscore")[0].firstChild.nodeValue;
        }
    }
}

Once the drawTable( ) function runs, the table shown in Figure 14-1 appears on the page.

Figure 14-1. Sample table from embedded XML data
figs/jsdc_1401.gif

The regularity of the DOM node tree of record-based XML data provides an excellent analog to the row and cell formatting of an HTML table. Starting with the container of all record elements (the worldcup element in our example), it's a comparatively simple looping routine through the next level elements, each of which represents a record.

To generate the HTML for the table, Example 14-1 uses W3C DOM table-modification methods for inserting rows and cells. A style sheet, located in the head of the document, defines some rules for table components as well as a class called ctr:

<style type="text/css">
table {table-collapse:collapse; border-spacing:0}
td {border:2px groove black; padding:7px; background-color:#ccffcc}
th {border:2px groove black; padding:7px; background-color:#ffffcc}
.ctr {text-align:center}
</style>

An array called classes is near the top of the drawTable( ) function. Each entry of the array corresponds to a column of the table. For a couple of the columns, the ctr class needs to be assigned to the class attribute of the cell. This takes place within the for loop, as the setAttribute( ) method is invoked for every table cell. Then, if you later wish to modify the behavior of a particular column, simply define a new class rule, and add that class name to the classes array constructor.

The choice for populating the content of the tables via the innerHTML property is arbitrary. With a couple of more lines per cell, a fully W3C DOM-compliant approach could have been used instead. Assuming one more local variable, txt, defined at the top of the function, the replacement for each innerHTML assignment looks as follows:

txt = document.createTextNode(oneRecord.getElementsByTagName("year")[0].
   firstChild.nodeValue);
td.appendChild(txt);

If it weren't for the fact that the table in this example combines data from two properties into a single table column (the last column), you could create a generic XML-to-HTML table transformation function that even creates the table headers. Header labels could be read from the tag names of the elements nested inside one of the records, and then modified to capitalize the first letter for the sake of aesthetics. This works, of course, only if the tag names are meaningful.

14.6.4 See Also

Recipe 14.4 for embedding external XML data into a page; Recipe 14.8 for converting XML data into JavaScript objects; Recipe 14.17 for details on walking a document node tree.

    [ Team LiB ] Previous Section Next Section