Build Interactive Apps with XML Data Islands

Web Languages
Typography
  • Smaller Small Medium Big Bigger
  • Default Helvetica Segoe Georgia Times

Do you have a network of Windows clients? Would you like to use a browser-based user interface for batch data entry? Do you cringe at the thought of installing and maintaining custom software on dozens or hundreds of client workstations? If so, read on.

As you probably know, XML stands for Extensible Markup Language. It’s a close cousin of HTML, and it can be manipulated with Document Object Model (DOM) methods. The World Wide Web Consortium (W3C) controls the standards for these and many other acronyms that are near and dear to our hearts. What you might not know is that there are several DOM implementations available; at this time, however, only the Microsoft version of XML (MSXML) provides support for Extensible Stylesheet Language (XSL) and works in the browser. Although Microsoft doesn’t completely implement the DOM standard, the lights are burning late into the night in Redmond in order to change that. By the time you read this, the situation might be different. The Microsoft DOM is reasonably complete and completely usable right now, so, if yours is a Microsoft shop, there’s no reason to wait any longer.

The examples I use in this article work with Internet Explorer Version 5.5 and MSXML version 3.0, which is the first production version of MSXML. Download the latest copy of Internet Explorer at www.microsoft.com/windows/ie/default.htm. Get the latest version of MSXML at http://msdn.microsoft.com/xml/general/xmlparser.asp and while you’re there, download the XML software development kit (SDK) so you’ll have easy access to the documentation. Install MSXML in Replace mode according to the instructions in the SDK (search for Replace Mode).

A Simple Data Island

As you might guess, an XML data island is an area in an HTML page that contains output rendered from XML instead of HTML. Microsoft has implemented a proprietary HTML element, the tag, to help you make data islands work. I’ll start by showing you how to use it. A little later, you’ll look at another way to accomplish the same thing without using nonstandard tags.

I will start by examining Figure 1. It shows a very simple HTML page displaying a list of North American time zones. Figure 1, Section A contains the document type declaration, a tag to identify the content type and encoding, another that specifies the default scripting language (ecmascript is the proper name for JavaScript or JScript) and



the page title. Note that the document type definition does not specify a document type description (DTD). Since you are using proprietary tags in the document, this page doesn’t conform to any HTML standard.

Skipping to Figure 1, Section C, the page content consists of a heading element, some text, and a

element that serves as a container for the data island. The
has an id attribute so that it can be referenced easily by name. Following this, at Figure 1, Section D, are two elements. The first identifies the XSL stylesheet that will transform the XML data into HTML, and the second references the XML document itself.

Now, go back and look at Figure 1, Section B. It contains a script that uses one of the MSXML methods, transformNode(), to populate the content of the island1

(its inner HTML property) in Figure 1, Section C. Note that transformNode() is a method of the object named data, which is the XML document referenced by the second element in Figure 1, Section D. When the browser loads this page, the documents referenced by the src attribute of the elements are retrieved and processed automatically by MSXML, so both data and stylesheet actually represent DOM document objects. In this example, both are static documents, but they could just as easily have been retrieved from the HTTP server. The script itself is a function named initialize(). It is executed when the onload event of the element fires (see Figure 1, Section C). This happens immediately after the HTML page has completely loaded in the browser.

The XML document is timezones.xml. Here’s a small piece of it:

- - etc.

Figure 2 shows the timezonelist.xsl stylesheet. Figure 2, Section A contains the required document type declaration and defines the XML namespace for stylesheets. It also specifies that the stylesheet output should be HTML. I should mention here that in the December 2000 MC article, “Demystifying XML: A Practical Example,” I showed a stylesheet that used an earlier, provisional version of XSL. There are some syntactic and functional differences between the two versions. The Microsoft DOM implementation is backward compatible. Internet Explorer will process either version if the namespace declaration is appropriate for the stylesheet content.

Figure 2, Section B contains the root template. In this stylesheet, its only purpose is to instantiate the lower-level templates. This is exactly the default behavior when there is no root template, so it’s not needed here but is included for clarity. The root template is instantiated once only and is a good place to output static content.

Figure 2, Section C contains a template that processes the element in the XML document. It contains the entire list of time zones, so this template is also instantiated only once, but unlike the root template, which processes the document itself, this template processes the document element. It creates an HTML table and extracts the groupname attribute of the element, which it formats as a heading for the table. It also creates column headings for the table data. The command passes control to any lower-level templates, of which there is only one.

At Figure 2, Section D, the element is processed by creating a table row () and table cells () to contain the id (@id) and name (@name) attributes of each within the scope of the calling context, which is all of them, because the calling context is the element, and it contains the entire document. Since there is no element specified in the template, all elements are processed in document order.



In addition to supporting an src attribute, which references an external document, the proprietary element also allows inline data. The XML data document itself could have been specified between the and tags.

You can view the result by downloading the example code from www.midrangecomputing.com/mc and double-clicking on the simpleisland.html file. The browser output is not pretty. I’ve intentionally not added any formatting so that you can easily identify the essential parts of the process.

Dynamic Document Loading with Script

The DOM standard defines the behavior of a proscribed group of functions and objects. With only a few exceptions, all DOM implementations must behave identically from the vantage point of the developer. However, some very necessary functions are not defined by the standard. There is, for instance, no method defined for retrieving an XML document. This very necessary function, unfortunately, is left to the discretion of the DOM implementer. Microsoft has provided this service with two functions in MSXML. The loadXML() method will create a DOM document object from a string containing XML markup, and the load() method will retrieve and instantiate a DOM document contained in a text stream, which could be represented by a file or an HTTP request.

This is all very fine, but it becomes unwieldy when there are multiple XML document and stylesheet combinations within the same scripting context. It simplifies matters greatly if an XML stylesheet and the processing that it implies are encapsulated in an object, which can both maintain scope and mask the complexity of implementing an XSL transform. Happily, JavaScript provides a way to build a reasonable facsimile of a traditional class object, which can serve both purposes, so I built one. It’s the templateProcessor class, which is included in the global scripts file, javascript/global.js, in the downloadable code for this article.

To create the time zone list with templateProcessor instead of tags, you need to make surprisingly few changes to the original source.

Referring back to Figure 1, Section A, you’ll insert a

At Figure 1, Section D, remove the tags. Once you’ve done this, you can now conform to the HTML 4.01 specification, so replace the document type definition at Figure1, Section A with:

http://www.w3.org/TR/html40/loose.dtd”>

At Figure 1, Section B, rewrite the script like this:

var timeZoneProcessor =
new templateProcessor(‘timezonelist.xsl’);

function initialize() {
timeZoneProcessor.loadXMLDocument(‘timezones.xml’);
island1.innerHTML = timeZoneProcessor.transform();
}

A global timeZoneProcessor variable has been declared. It contains an instance of the templateProcessor class, which encapsulates the timezonelist.xsl stylesheet. This object



is then used to populate the data island in a straightforward manner in the initialize() function. A listing of the templateProcessor methods and an explanation of what each of them does is contained in javascript/global.js. Double-click dynamicisland.html in the example code directory to see the result. In reality, it’s still not very dynamic. It could be, though—stay tuned.

The Batch Entry Application

I’ve extended the dynamicisland.html example into a complete interactive batch entry and edit application. Look at the important parts of the script that does the job. The getRow() function in Figure 3 (page 51), Section A locates and returns a reference to the element containing a specified zone ID. It does this by executing an XPath query. XPath is another of the W3C standards. It describes a query language that’s used to navigate through the structure of an XML document. If the zoneId function parameter contained the numeral 3, the expression would evaluate to:

recordNode = timeZones.selectSingleNode(‘timezone[@id=”3”]’);

If the requested zoneid value is not already in the XML document, recordNode will be null. Note that the timeZones object is a global reference to the XML document element ().

The selectSingleNode() function is a Microsoft extension to the DOM methods. If you wanted to use only standard functions, you would need to walk down the tree in script with something like this:

var childNodes = timeZones.childNodes;
var nodeCount = childNodes.length;
var i = 0;

while ((i < nodeCount) && (recordNode == null)) {
if (childNodes[i].getAttribute(‘id’) == zoneId) {
recordNode = childNodes[i];

}

i++;
}

The reSequence() function, shown in Figure 3, Section B, redisplays the list body in the requested order, based on the value attribute of a radio button. Sorting is not important in this example, but I’ve included it because I want to demonstrate how parameters are passed to an XSL stylesheet. Here’s what a radio button looks like, as defined in zoneedit.html:

value=”id;ascending;number” tabindex=”60”
checked onclick=”reSequence(this);”>

Notice that the value attribute actually contains three values delimited by semicolons (;). The reSequence() function separates them by calling the ECMAScript split() function. It returns the parsed words as elements of an array, which is ideal for the purpose here. The individual parameter values are passed to the XSL stylesheet by calling the templateProcessor’s addParameter() method.

If you look at Figure 4, Section A, you’ll see how these parameters are defined in the stylesheet. They each have a default value, which is used until you change it with an addParameter() call. These are top-level (global) parameters, because they are children of the element. They are in scope throughout the stylesheet. Figure 4, Section B



shows how they are actually used to define the sort. If you were specifying the sort key with a literal value, you would simply have written:

In this instance though, since you’re using a parameter, you need to use the slightly more complicated select syntax:


This really means “retrieve a node set containing all attributes of the current element, then filter that node set and return the subset having a name equal to the string value of the sort-element parameter.” That’s fairly ugly, but required because the lexical constraints of XSL effectively prevent an xsl:param or xsl:variable from matching anything that is not a value or a node-set. The name() function in the expression above converts the names of the attribute node children of the current element to string values so that they can be compared with the value of the sort-element parameter. The sort-element parameter can also be a node-set, but in this case is treated as a scalar, because it receives its value from a literal select attribute, rather than from an XPath expression or a child template body.

Notice also that the sort-order and sort-data-type parameters are enclosed in curly braces ({}). This syntax defines them to be attribute value templates, whose text value is used to provide the order and data-type attribute values. The select attribute of xsl:sort requires a string value. It will not accept an attribute value template.

Don’t feel badly if you don’t get that one. It’s one of the most confusing aspects of XSL and does require some additional reading. The XSLT Programmer’s Reference, cited at the end of this article, will help.

As long as you’re looking at Figure 4, I may as well point out that the timezone template in Figure 4, Section C has a match attribute which excludes elements that have the asterisk symbol (*) as a name value. This is a crude device for storing deleted time zones so that they can be returned to the host. The server code then must look for this condition and delete the appropriate data rows. There is no delete function in zoneedit.html, but you can make a node disappear by replacing its descriptive name with a single asterisk.

Returning back to Figure 3, Section C, the submitBatch() function contains the code needed to send the updated batch back to the server for processing. It’s commented out and replaced with an alert dialog, which will show you what the serialized XML looks like.

At Figure 3, Section D, I’ve shown the method that inserts or replaces elements in the XML document. Figure 3, Section E contains a helper function that appends the required attribute values to the node. I’ve used a DocumentFragment for manipulating nodes. Document fragments are designed to have low processing overhead. Try out the result by double-clicking zoneedit.html in the example code folder.

Get Started!

By now, you may have just as many questions as answers. Within the limited space available here, I’ve tried to give you a feel for the power of this new technology. Download the example code from www.midrangecomputing.com/mc. Use the examples as a starting point. Borrow pieces of them, if you like. Start with something simple, but if you haven’t already done so, get started. XML is becoming more pervasive every day. We all need a little of it in our bag of tricks.



REFERENCES AND RELATED MATERIALS

• Applied XML: A Toolkit for Programmers. Alex Ceponkus and Faraz Hoodbhoy. New York, New York: John Wiley & Sons, Inc., 1999
• Microsoft Windows Script Technologies page: http://msdn.microsoft.com/scripting
• XSLT Programmer’s Reference. Michael Kay. Birmingham, United Kingdom: Wrox Press Inc., 2000
• W3C XSLT Specification: www.w3.org/TR/xslt/
• W3C DOM Level 2 Core specification: www.w3.org/TR/DOM-Level-2-Core/ core.html




content="text/html; charset=UTF-8">
content="text/ecmascript">
Simple Data Island




A Simple Data Island


Here is a group of time zones







A

B

C

D

Figure 1: It doesn’t take much to build a simple data island.




xmlns:xsl="http://www.w3.org/1999/XSL/Transform">















Time Zones

IDName









A

B

C

D

Figure 2: The XSL stylesheet transforms XML to HTML.



// =============================================================

// If the zone ID is valid, construct an XPath query
// and execute it to retrieve the current value of
// the time zone being edited.
// (formzones is the HTML form)
// (timeZones is a global reference to the documentElement node)
// =============================================================

function getRow(zoneId, updateDisplay) {
var zone = parseInt(zoneId);
var recordNode = null;

checkData('zone');
formzones.updatebatch.disabled = true;

try {
recordNode = timeZones.selectSingleNode('timezone[@id="'

+ zone.toString(10) + '"]');

if (updateDisplay) {
if (recordNode == null) {
formzones.zonetext.value = '';
} else {
formzones.zonetext.value =
recordNode.selectSingleNode('@name').value;
}

setFocus(formzones.zonetext);
}

return recordNode;

} catch (e) {

if (e.number != E_OBJECT_REQUIRED) {
alert('An error occurred while retrieving data '

+ 'in function getRow() with input values '
+ zoneId + ', ' + updateDisplay + ': '
+ e.description);
}

formzones.zonetext.value = '';
setFocus(formzones.zonetext);
return null;

}

// =============================================================

// Return the updated batch to the server
// Actual submit code is commented out for this example
// =============================================================

function submitBatch() {
// var request = new ActiveXObject("Msxml2.XMLHTTP")
// request.Open('POST', requestURI, true);
// request.Send(timeZoneProcessor.xmlDocument);
alert(timeZoneProcessor.xmlDocument.xml);

}

// =============================================================

// Add or update a row in the XML document
// (timeZones is a global reference to the documentElement node)
// =============================================================

function updateDocument(f) {
var xmlDocument = timeZoneProcessor.xmlDocument;
var recordNode = null;
var timeZone = null
var fragment = null;
var attribute = null;
var zoneId = parseInt(f.zoneid.value).toString(10);

checkData('all');

if (!abort) {
timeZone = xmlDocument.createElement('timezone');
fragment = xmlDocument.createDocumentFragment();
fragment.appendChild(timeZone);
appendAttribute(xmlDocument, timeZone, 'id', zoneId);
appendAttribute(xmlDocument, timeZone, 'name', f.zonetext.value);
recordNode = getRow(zoneId, false);

if (recordNode == null) {
timeZones.appendChild(fragment);
} else {
timeZones.replaceChild(fragment, recordNode);
}

island1.innerHTML = timeZoneProcessor.transform();
f.zoneid.value = '';
f.zonetext.value = '';
f.submitbatch.disabled = false;
f.updatebatch.disabled = true;
setFocus(f.zoneid);

}

C

D

A

Figure 3: Let’s debunk the smoke and mirrors thing.


}

}

// =============================================================

// Resequence the batch data list
// radioButton.value contains either
// "id;ascending;number" or "name;ascending;text"
// sort order could also be "descending",
// but that's not used here
// =============================================================

function reSequence(radioButton) {
var parameters = radioButton.value.split(';');
timeZoneProcessor.addParameter('sort-element', parameters[0]);
timeZoneProcessor.addParameter('sort-order', parameters[1]);
timeZoneProcessor.addParameter('sort-data-type', parameters[2]);
island1.innerHTML = timeZoneProcessor.transform();

}

// =============================================================

// Append attributes to a new timezone node
// =============================================================

function appendAttribute(aDocument, aNode, aName, aValue) {
var attribute = aDocument.createAttribute(aName);
attribute.value = aValue;
aNode.setAttributeNode(attribute);

}

B E



xmlns:xsl="http://www.w3.org/1999/XSL/Transform">











order="{$sort-order}"
data-type="{$sort-data-type}" />











A

B

C

Figure 4: You can pass external parameters to XSL.


BLOG COMMENTS POWERED BY DISQUS