Welcome!

XML Authors: Katharine Hadow, Greg Schulz, Ambal Balakrishnan, Jeff Scholes, Brad Abrams

Related Topics: XML

XML: Article

Create Sortable HTML Tables Using XSLT

Create Sortable HTML Tables Using XSLT

In developing Web applications, the most common method for displaying multiple rows of data on a Web page has been to use the HTML <TABLE> element. Presenting the user with a static table of data might be fine in some instances, but if you're trying to develop a more user-friendly application, there are a few features missing that you'd find in a typical rich-client data grid.

If a user wants to change the order of the rows, another call to the server must be made to get the data from a database in a different order, and the HTML table with data must be sent to the browser again, which isn't very efficient. Another problem is that if there are many rows of data, the user must scroll the window, thus losing sight of the column headings.

If you're developing a spreadsheet application or an application in which the user is trying to find a specific row of data quickly, then the static table isn't very user-friendly. The addition of an XML parser to a Web browser, such as Microsoft's XML3 parser in Internet Explorer 6.0, allows developers to leverage the power of XSLT transformations in the browser to quickly re-sort and display rows of data without going back to the server. I'll show you how to make sortable tables using this technique, as well as a few other tricks to build full-featured data grids.

Browser-Side Transformations
The trick to browser-side transformations is to get both the XML data and XSLT presentation logic into the browser where it can be transformed via JavaScript. By using XML data islands, you can cache both the data and the presentation on the client machine. Since XSLT is well-formed XML, we can put the presentation logic on the client by placing the stylesheet between <XML> tags. Once that's done, the structure can be accessed as an XML DOM object in JavaScript by using the XMLDocument property of the tag ID. Listing 1 shows an example reference as:

document.all.tableXSL.XMLDocument

To do a transformation you need two XML data islands: one that contains the data, another that contains the XSLT that will create an HTML table from the data. You can then use JavaScript to access both XML DOM objects and do the transformation with the transformNode method.

newHTML = oXMLDOM
.transformNode(oXSLDOM)

The HTML that results from the transform can be placed on a page by using the innerHTML property of a DIV tag.

In Figure 1 there are actually two tables that make up the grid. The top one contains the column headings; the bottom one, the rows of data. The column headings are in a separate table so just the rows of data scroll and the column headings are always visible at the top of the grid - a nice feature if you have lots of rows and your columns aren't self-explanatory. The table with the data rows is placed within a

tag to allow scrolling of the rows. Every time the table is re-sorted, we transform the XML with the XSLT and change the innerHTML of the
to the newly sorted table data. Before I explain the actual sorting algorithm, let's look at how the table of data is actually created using XSLT.

Creating Tables with XSLT
The algorithm for creating HTML tables with XSLT is fairly simple: for every row of data in the XML, create one row in the HTML table. If you look at Listing 2, you'll see that the stylesheet starts with the obligatory document element <xsl:stylesheet> tag. The result of the transformation will be HTML, so you need the <xsl:output> tag with the method attribute set to "html". Then add the <TABLE> tag. Now use the tag to create a loop that will select each row of XML data.

<xsl:for-each select="//ROW">

We want to sort the rows in the XML by some default field when the table is first displayed.

<xsl:sort select="@/AcctNo" order=
"ascending" datatype="number"/>

The value of the select attribute is a valid XPath statement that tells the parser how to sort the data. In this case we're sorting on an attribute (as indicated by the "@" character) within the current ROW element named "AcctNo". The valid values for the order attribute are "ascending" or "descending." The datatype attribute tells the parser to do either a number or text sort, which can be tricky if you want to sort on something like date, but I'll touch on this later. Now create cells for each field you want to display in your table and add tags with the proper XPath select statement to plug in the values from the XML. The XSLT in Listing 2 contains the only presentation for the HTML table with the data in it. The rest of the page, including the column headers, is built with the HTML in Listing 3.

Sorting
After the table is displayed, we want to enable the user to click on a column heading and have the table re-sort on that column. The sorting of the rows is handled by the <xsl:sort/> tag within the XSLT. When a user clicks on a column heading, we want to (1) change the value of the select attribute to the attribute in the XML (see Listing 4) that matches this column, and (2) set the order attribute to "ascending". If the user clicks on the column again, we need to change the order attribute to "descending".

After the changes are made to the sort tag, we can retransform the XSLT stylesheet with the data in the XML data island and insert the new HTML table into the scrollable <DIV>. This all happens within the browser - there's no need to go to the server every time the user changes the sort order. To change the attributes on the sort tag, we'll use the API for the XML DOM to select the <xsl:sort/> using XPath and then update the attributes. In Listing 3 notice that the onClick event on the table headers calls the sort function in Listing 1, passing it the name of the attribute and datatype to sort on. The sort function uses the selectSingleNode method on the XML DOM object to get a reference to the select attribute within the xsl:sort node.

var objSelect = XSLIsland
.selectSingleNode
("//xsl:sort/@select")

The select attribute is then set to the new attribute from the XML we want to sort on. Next we check the current sort order and change it to the opposite order. Finally, we set the datatype attribute.

Another attribute you can add to the xsl:sort that isn't shown in the source is the "case-order" attribute, which can be set to either "upper-first" or "lower-first". If you don't include this attribute, the default will be the former - for example, "VanStueben" would come before "vanStueben."

The XSLT engine can sort by text or number by setting the datatype attribute. If all your columns have strings or numbers, you won't run into any problems. One of the biggest shortfalls is that you can't sort by date very easily, which is often a requirement of a data grid. Like most problems, there are a couple of workarounds. The first requires you to change the way you display dates. Most applications designed for the U.S. market display their dates in mm/dd/yyyy format - if you try to sort columns by text or number, 08/01/2001 will come before 08/31/2000. But if you change the date format to most significant to least significant, that is, yyyy/mm/dd, your date sorting will work fine as long as you display months and days that are less than 10 as 01, 02, and so on.

Another trick is to have an attribute within your XML that has the date in the yyyy/mm/dd format that isn't displayed in the table. Sort on it, but display the data in the traditional mm/dd/yyyy. So your XML might look like:

<row birthday="08/15/1954" sortdate=
"1954/08/15"/>

The column would show the birthday, but the onClick event in the TH tag would look like:

sort('./@sortdate','number')

Selecting Rows
You might also want to allow your users to select a row within the grid and have all the data from that row passed to another Web page. Most grid controls in rich-client applications allow the user to press the up and down arrows to highlight a row and then press the enter key or double-click the mouse to select the row. Listing 1 shows how the arrows are captured from the keyboard and how we keep the current selected row highlighted with the keyCheck and selectRow functions. In the sample code, for a selected row, we want to return all the data to the server as an XML string for processing. We'll do this by taking the value from the hidden key column and using XPath to select the data row from the XML data island and return it to the server. If you look at the XSLT in Listing 2, you'll see that the first column of the table contains an ID that isn't displayed to the user. When a user chooses a row, we'll get the ID by grabbing the innerText of this table cell and passing it to the selectSingleNode method on the data XML DOM object to get a reference to the node the user selected. We can then send the XML string of that node to the server by placing it into a FORM element and submitting it to the server via the POST method. In the function selectCurrentRow (see Listing 1) I just alert the XML string, so I'll leave it to you to create the FORM.

Where to Go from Here
There are some other features you might consider adding to make the table more like a spreadsheet. You could add an indicator in the column heading that shows which column the table is sorted by and in what order. Modify the sort function so that it shows the proper icon within the TH tag.

You could also add drag-and-drop features to the table headers so users could change the order of the column headers, similar to a spreadsheet. When the user drags one column and drops it onto another, you could manipulate the order of the column nodes in the XSLT and then retransform it with the XML. Remove the dragged node from the XSLT by using the removeChild method, then insert it before the dropped TR tag by using the insertBefore method. For example, to move the AcctNo column before the LastName column execute:

dragNode = XSLIsland.selectSingleNode
("//TD[@id='accountCol']")
dropNode = XSLIsland.selectSingleNode
("//TD[@id='lastnameCol']")
trNode = XSLIsland.selectSingleNode
("//TR[@id='trnode']")
trNode.insertBefore
(trNode.removeChild
(dragNode),dropNode)

Now do the transformation again and replace the old HTML table with the new HTML table.

These are just a few ideas to take your HTML tables to the next level. Because of the power of XSLT transformations and the MSXML3 parser, you can really build full-featured data grids that your users will enjoy.

More Stories By Sean McMullan

Sean McMullan is a Web application developer with ALLTEL Information
Services, Inc. He received his bachelor's degree in computer science
from Siena Col

Comments (1) View Comments

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.


Most Recent Comments
David Anderson 12/03/02 04:33:00 AM EST