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.
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.
|