Trying not to get lost with a topic map   Table of contents   Indexes   Metadata deployment for publishing environments

 

99 Tricks To Amaze Your Friends and Impress Your Boss with the DOM and XML in Web Browsers

 Michael   Leventhal
  Architecture/Development
  CiTEC  Silmukkatie 2
Vaasa   Finland  FIN-65101
Phone: +358 50 537 6091
Fax: +358 6 324 0800
Email: mle@citec.fi Web: www.doczilla.com
 
Biographical notice:
 
Michael Leventhal is a member of the team that created DocZilla, an SGML and XML "superbrowser" constructed on the Mozilla source. Mr. Leventhal cut his teeth in the SGML field as a developer of a pre-Web SGML browser, Oracle Book and also worked in the area of editing tools as VP of Technology for Grif in addition to countless other diverse areas related to SGML, XML, and the Web in running his own consulting firm, Text Science. He has taught SGML and XML publishing for U.C. Berkeley Extension and published a book and articles on XML.
 
ABSTRACT:
 
The client-side capability of the Web is about to change and improve dramatically with the introduction of the  DOM  (Document Object Model) and XML in web browsers. Many new and pretty damn hot genres of applications, applications which were totally impossible in earlier browsers, can now be created with relative ease. And in a standard, clean, and maintainable way thanks to the efforts of the W3C  (World Wide Web Consortium) with XML and the DOM . This paper and presentation will show what can be done and show you how to do it. The presentation aims to be practical, easy, and clear.
 
The presentation will be carefully structured to be interesting and accessible to intelligent DOM neophytes who possess a basic understanding of XML and Web browser technology. Old hands, however, will not find the talk a complete waste of time as the presenter is a seasoned XML developer and will probably let a few tidbits of more rarefied interest slip out as well. In addition, this paper contains walkthroughs of code as well as a high level descriptions of the processing which should be of interest to XML web developers. Code will not be presented in the talk but will be made available for all applications demonstrated.
application
 

Three DOM applications are presented in this paper: a rapid document structure viewer, a document annotation tool, and a table which can be sorted on any column. The last example compares DOM and XSL  (Extensible Stylesheet Language) approaches to this problem, arguing for the advantages of the DOM in terms of superior capability and comprehensibility of the code.
 
Entirely new and postively thrilling demonstrations will be shown during the conference presentation.
browsers
 

The presentation will be given at approximately the same time as browser vendors have either newly-released production releases or soon-to-be-released advanced beta code which supports the  DOM and XML. This presentation will offer timely and accessible information of immediate value on a topic of broad general interest.
 

Mea Culpa

 browser  
 

The primary objective of this paper and associated presentation is to show what you can do with the  DOM in the new web browsers from Microsoft and the Mozilla-based browsers from Netscape and CiTEC.
 DOM, Document Object Model 
 

Although it is a rare enough occasion that the author has ever finished a paper before the deadline there was this time good cause for extreme procrastination. As of approximately two months before the XML Europe Conference none of the above browsers had a full and debugged implementation of the DOM . Since this is a practical presentation with working examples the job of the author was difficult. However, when the actual presentation is given at the end of April Microsoft will have released the final version of IE5 and the Mozilla browsers, which are today claiming a very high degree of DOM -compliance will be pretty well debugged. So there will be new and exciting stuff which is not in this paper at the presentation and an update on the bugs and compliance situation.
DocZilla
 

While we are getting the mea culpas out of the way here let me add two more. First, the author is a member of development team that created CiTEC's DocZilla and the examples shown in this paper are known only to run under DocZilla. It is true that the DOM is a standard intended to enable browser-independent processing of structured documents but the scripting language in which the  DOM is implemented will contain historical and other accidental variants. I promise to try all the scripts under all the browsers by the conference and report on the results. The reader will probably notice a general DocZilla slant for which I ask for the reader's comprehension that DocZilla happens to be what I know and love best.
 
The second mea culpa is for the fact that the untutored reader will find the introductory material about the DOM to be sparse. This paper was not intended to be a DOM tutorial. (There is so much introductory material already out there that it doesn't seem to make much sense to try to duplicate that.) However, if you should so unlucky in life not to have learned anything about the DOM until now you'll find just enough material for you to understand what it is and then you'll see some of the things it can do. If you happen to be one of DOM -elect there is also code to show how stuff gets done. The code part is optional though and in the presentation there will probably be no code shown or maybe a little if there happens to be some interesting tidbit. (However, code for all demonstrations shown during the presentation will be made publicly available.)
 

Just Enough DOM

 DOM, Document Object Model 
 SGML  
 

Here goes my attempt to recite all the truth in the universe in the time that I can remain standing on one foot.
 
The DOM describes a programmer or script writer's interface to structured documents. The interface it describes is intended to let you get at any and all the bits in an XML, SGML, or HTML document. The "bits" include all the stuff you know: elements, attribute names and values, element content, entities, notations, processing instructions, comments. But the "bits" do not include DTD  (Document Type Definition) s and anything in SGML that isn't also in XML. The DOM also knows all about HTML documents although almost everything that one does with the HTML interfaces could just as well be done with the more general parts used with HTML and SGML documents.
 
"Getting at" any and all bits means that you can read and modify anything in an existing document, add new bits to existing documents and create entirely new documents. That is to say, you can pretty much do anything there is to do with a structured document.
tree
 

The term "tree" will be used often in this presentation to describe the representation of the document which is exposed by the DOM . In fact, every well-formed XML document does have a implicit tree representation but the DOM in the strictest sense never mandates any underlying structure of the data, only interfaces that make DOM operations look a lot like simply navigating through XML's document tree. The caution about what goes on under the covers really applies only to those brave souls implementing base DOM support as they should not assume a straight and simple tree implementation will produce the kind of performance required by many applications. The person writing DOM scripts in browsers will, on the other hand, find it quite useful to visualize the DOM document as a tree with elements and other XML objects as branches, with the branches decorated with attribute name/value pairs. The most common operation will be traversing the document through parent, child, and sibling relationships. If you can understand processChildren in the first application shown in this paper you will be well on you way to becoming a buff (expressions like this are occasionally put into this paper to help my European colleagues learn some American (Californian even!)) DOM script developer.
 
Naturally, trees alone aren't quite enough because the DOM interfaces also compose array lists in response to simple queries such as finding all elements that have a certain name. In this case we no longer are traversing the elements through the tree structure although we could accomplish the same operation using our processChildren and capturing all the nodes matching our query. But the DOM does this for us (hopefully it actually does something more efficient than processChildren) and this convenience function is used very often.
 JavaScript 
XPCOM
XPCONNECT
 components 
 

In this paper we illustrate the use of the DOM through JavaScript programs. In fact the DOM can be accessed in the browser environment at several different levels and in several languages. The DOM standard itself is not only underlying data structure independent but also language-independent. Mozilla-based browsers implement the DOM as an internal interface in C++. This layer is accessible through the component interface of Mozilla, XPCOM, to other Mozilla components, to JavaScript programs through XPCONNECT, and to Java applets. But the JavaScript layer is probably the easiest and safest to start with and the correct level at which to program most applications. I will very likely show however other types of DOM applications during the presentation.
DHTML
 

It should be noted that the DOM is not to limited to use in browsers; the interface it specifies is being employed in many applications ranging from operating systems to e-commerce middleware. There is, however, a historical relationship to DHTML  (Dynamic HTML) and the browser environment in that one the original objectives of the DOM activity was to harmonize Netscape and Microsoft's non-interoperable implementations. Fortunately, at the same time as this need arose XML was embraced as a new Web standard by the W3C with the result that the DHTML problem was addressed by giving the DOM a solid architectural footing by basing its core on XML structured document concepts.
DOM Level 2
 DOM, Document Object Model 
events
stylesheets
 

Currently the DOM Level 1 is a W3C Recommendation and it is hoped that the browser vendors will be forthcoming with 100% clean and beautiful DOM implementations. A draft is out of the next revision the DOM , Level 2, but it is too early to state definitely what will finally be in it. Mozilla has, however, committed to including early implementations in two areas, browser events and access to stylesheets. The former is needed to provide standard mechanisms to replace the DHTML-related mostly non-interoperable event handling capabilities in JavaScript and to expand event handling into non-browser related applications. Access to the stylesheet is a hugely cool thing that will let you create and modify styles on the fly. In the applications shown in this paper events are handled in a pre- DOM level 2 fashion, although closer to the  DOM way then the old and bad DHTML style. Dynamic stylesheets are presented through tricks involving multiple stylesheets and jiggling hide and show display properties. It is pretty clever but scripts will be much less clever and much better when the DOM lets you do this stuff the right way. These not-yet-really-standard-based improvements will be shown in applications to be unveiled at the conference.
 
This might be a good place to mention again that writing good DOM applications in these early, wild, and fun days was damn hard because stuff that you need to work didn't and improvisation was the order of the day. I haven't attempted to hide any of the tricks. I have always indicated what the correct way to do something would be and hopefully will be. I also think the tricks have some instructive value - at least I have found the ability to deal with less than ideal situations to have been useful in both the technical and non-technical aspects of life.
REC-DOM-Level-1
 

Of course, the place to get definitive answers on what the DOM does and does not do is the W3C Recommendation itself at http://www.w3.org/TR/REC-DOM-Level-1 . I quite honestly believe that this is a very good specification, short, sweet, clear, always erring on the side of saying too little rather than overspecifying, admitting its imperfections from the start and teaching us how to cope with them. The Recommendation includes the JavaScript language bindings, an appendix which will become your dearest friend if you are going to start writing DOM applications. You'll find that you can pretty much toss out that nice JavaScript book you bought 4 weeks ago.
 
OK, so much for all the truth in the universe. Now on to some real stuff!
 

View Branch Demo

 

View Branch Demo - Functionality

tree view
 

The View Branch script displays the name of the element which the cursor is currently over, its attributes, and each of its ancestors up to the document root. This information is displayed in the status bar of the browser and is automatically refreshed whenever the cursor is moved over another element. In DocZilla the operation is very fast; the branch information is written nearly instantaneously and the user does not perceive any slowness in the operation of the browser. The application works on any XML, SGML, or HTML file.
 
An example of the application is shown in the figure below. The element the cursor is over is circled and the arrow points to the branch information for that element displayed in the status bar and also circled in red. The first element (reading from left to right) shown is the element the mouse is currently over and its attributes (name, value pairs) are shown immediately after the name in square brackets. We then have its ancestors, with each separated from its parent by the '<-' symbol.
 
 
 
In the example shown the cursor is over the element containing the text "Display of XML, SGML, and HTML documents with CSS  (Cascading Style Sheets) .". In the status bar we see the following branch information:
 
PARA[]<-LISTITEM<-ITEMIZEDLIST<-SECT2<-SECT1<-PREFACE<-BOOK
 
which is telling us that our document's markup structure looks like this:
 
<BOOK>
<PREFACE>
<SECT1>
<SECT2>
<ITEMIZEDLIST>
<LISTITEM>
<PARA>Display of XML, SGML and HTML documents with CSS.</PARA>
</LISTITEM>
</ITEMIZEDLIST>
</SECT2>
</SECT1>
</PREFACE>
</BOOK>
contextual editing
structured search
stylesheet editing
 

Many concrete applications can be imagined for this application - for example, contextual editing, guiding structured search, exposing "hidden" information in element names and attribute values. It has been extremely handy in creating CSS stylesheets for existing XML and SGML documents.
mouseover event
 

The DOM is used, of course, to get the name and attributes of the current element and to obtain the name of each ancestor, using the DOM structure which links a child element to its parent. A mouseover event assigning a handler (a function which obtains and displays the structure information when the mouse passes over the element) is set for each element in the document when the document is loaded. This requires us to traverse the entire document tree through the DOM .
 

View Branch Demo - Code

 
The "main routine" has just a single line initiating the assignment of an event handler to each element in the document.
 
setTimeout(setUpEvents,0);
 
setUpEvents assigns the function wstatus as the onmouseover event handler on the document element and invokes processChildren to do the same on the entire document tree.
 
function setUpEvents() {
document.documentElement.onmouseover = wstatus;
processChildren(document.documentElement);
}
recursion
 

processChildren is a simple recursive procedure for traversing the document tree.
 
function processChildren(node) {
var currentNode = node.firstChild;
while (currentNode != node.lastChild && currentNode != null) {
currentNode.onmouseover = wstatus;
if (currentNode.hasChildNodes) {
processChildren(currentNode);
}
currentNode = currentNode.nextSibling;
}
if (node.lastChild != null) {
currentNode.onmouseover = wstatus;
if (node.lastChild.hasChildNodes) {
processChildren(node.lastChild);
}
}
}
 
wstatus is the event hander which obtains and prints branch information on the actual mouseover event. It is is passed the element 'e' on which the mouseover event occured.
 
function wstatus(e) {
bubbling
 

If cancelBubble is not set to true the event will "percolate" up to the element's ancestors (which will be treated as the current element).
 
e.cancelBubble = true;
pseudo-element
 

The event actually occurs on a text pseudo-element (specified by the DOM ) so to get the GI of the current element we actually need to get the parent.
 
var pnode = e.target.parentNode;

wintext = pnode.nodeName;
 
Here we obtain the attribute information for the current element.
 
if (pnode.attributes != null) {
wintext = wintext + " [";
for (var i=0; i<pnode.attributes.length; i++) {
var currAttr = pnode.attributes[i];
wintext = wintext + " " + currAttr.nodeName + "=" + currAttr.nodeValue;
}
wintext = wintext + " ]";
}
 
And here we will go up the branch adding each parent until we have no more parents.
 
pnode = pnode.parentNode;

while (pnode != null) {
wintext = wintext + "<-" + pnode.nodeName;
pnode = pnode.parentNode;
}
window.status = wintext;
}
 

Annotation Demo

 

Annotation Demo - Functionality

annotation
 

This demo shows the annotation of a document in the browser through the DOM .
French
 

In the figure below you will see an SGML document displayed in the right-hand frame "Selections from French Poetry". Clicking on an element (which we do when we click anywhere in the right frame) causes the name of that element to be written in the HTML form document in left frame in the small field to the right of the text "Source Element:" and the textual content of the element to be written in the big textarea below. This confirms to the user what element he or she is actually annotating, showing the "extent" of the element (element content is not shown in the branch view on the status bar), and also provides the text of the element to assist in formulating the annotation (whether to copy part of the text into the Annotation Text area or simply to more easily refer to it visually). We may then enter an annotation in the textarea below the text "Enter Annotation Text". Before adding the annotation to the SGML document in the right-hand frame by clicking on the "Attach Annotation" button we select one or more of the checkboxes to indicate the type of the annotation (note, hint, or translation). When the annotation has been attached it will appear in the right-hand frame below the element it annotates surrounded by a frame color-coded by annotation type. The annotation type checkboxes also act as hide/show toggles for existing annotations: when the checkbox of an annotation type is selected all annotations of that type are shown and if the checkbox is not selected all annotations of that type are hidden.
 
 
 
In the example shown above, we have clicked on the line "Comme il pleut sur la ville". We see in the left hand side
 
Source Element:LIGNE
Comme il pleut sur l
a ville
 
(The line-breaking algorithm is quite crude!). We read the structure of the document and the content of click-event element in the SGML frame from a script associated with a document in the HTML frame and we also access the form fields in the HTML frame using the DOM .
 
We wish to annotate the document with the translation of "Comme il pleut sur la ville". In "Enter Annotation Text" textarea "Like it is raining on the town" is entered, the checkbox "Translation" is selected and the user clicks on the "Attach Annotation" button. We now see a red box created in the SGML frame underneath the line "Comme il pleut sur la ville" with the text "Like it is raining on the town" written in it.
 
Something quite exciting has just happened, something you are not likely to have ever seen before. We have actually modified, once more through the DOM of course, the SGML document in the browser (in-memory) to add new SGML elements and content. If the cursor is positioned over the annotation you will see (using the Branch View application) that the annotation is actually part of the document tree with a branch of
 
TRAN[]<-STROPHE<-POEME<-SECTION<-POESIE
 
We have created a TRAN element nested inside the STROPHE immediately following the LIGNE containing the text we have annotated. Had we clicked the checkbox for Hint the container element would be HINT and for Note it would be NOTE.
 
Because the annotation is truly a part of the document tree we can even annotate the annotation by clicking on it.
language learning
 

For this demo application we imagined that it could be useful to hide some types of annotations in certain situations. For example, suppose this is being used by students learning French. They might wish to view only Hints as they are trying to understand the French text and only look at the Translation when they wish to verify their understanding of the poem. The demo provides this functionality. Let us create a hint for "Comme il pleut sur la ville". We click again on the line of poetry, type "Bring your umbrella" in the "Enter Annotation Text" textarea, click on the "Hint" checkbox, uncheck the "Translation" checkbox, and hit the "Attach Annotation" button. We will have seen the red translation annotation disappear and a green hint annotation appear with our text. If we wish to see the translation appear again we simply check the translation checkbox again and our translation reappears.
disable stylesheet
 

The functionality is achieved through access to the disable property of the cascading stylesheet documents. We actually have 4 stylesheets, one base and three cascades which simply override the display property of annotation elements. Each time we wish to hide or show one of the annotation elements we simply enable or disable its cascading stylesheet that controls whether or not it is displayed. This operation can be done even more efficiently with  DOM Level 2 operations which can directly manipulate the stylesheet to switch between display: hide and display: show properties of the annotation elements.
 

Annotation Demo - Code

 
The code has been trimmed down to just show manipulations of the note annotation type. Some cloned code dealing with translation and hint types has been excised.
 
The process for setting up events on individual elements using setUpEvents and processChildren is the same as used for the Branch View application.
 
var notes = new Array();
var annNode;
var noteNode;
var textNode;
var noteCount = 0;
var noteLast;

setTimeout(setUpEvents,0);

function setUpEvents() {
var there = parent.frames.poesie.document;
getElementsByTagName
 

The next little bit is grungy. When this application was written it was not actually possible to create new DOM nodes as the code for this DOM operation had not been completed. So we fake it by appending nodes to the end of the SGML document which we be repositioned in the DOM tree when an annotation is actually "created" by the user. The user does not see those notes at the end because the are hidden by the stylesheets. This all works o.k. except that we have to copy all our note elements to a new array as the one returned by the getElementsByTagName  DOM operation is read-only.
 
noteSet = there.getElementsByTagName("note");
noteLast = noteSet.length;
for (var i=0; i<noteLast; i++) {
notes[i] = noteSet[i];
}

there.documentElement.onmousedown = fillsource;
processChildren(there.documentElement);
}

function processChildren(node) {
var currentNode = node.firstChild;
while (currentNode != node.lastChild && currentNode != null) {
currentNode.onmousedown = fillsource;
if (currentNode.hasChildNodes) {
processChildren(currentNode);
}
currentNode = currentNode.nextSibling;
}
if (node.lastChild != null) {
currentNode.onmousedown = fillsource;
if (node.lastChild.hasChildNodes) {
processChildren(node.lastChild);
}
}
}
 
This is our onmousedown event handler. It is similar to the event handler in the Branch View application. The element's content (nodeValue) is extracted and the line-breaking applied before putting it in the HTML form textarea "source".
 
function fillsource(e) {
e.cancelBubble = true;

annNode = e.target.parentNode;
document.form1.ae.value = annNode.nodeName;

var input = new String(e.target.nodeValue);
minput = input.replace(/\\s/g," ");
var output = new String(");
iterations = Math.floor(minput.length / 20) + 1;
for (i=0; i<iterations; i++) {
start = i*20;
end = (i+1)*20;
output = output + minput.slice(start,end) + '\\r\ ';
}
document.form1.source.nodeValue = output;
document.form1.annot.nodeValue = ";
}
 
attach is invoked from the HTML form when the Attach Annotation button is pushed. The value of the next empty note element is changed to the content of the annotation textarea and the note is inserted before the next sibling of the node being annotated.
 
function attach() {
var typeNode;

if (document.form1.atype.parentNode.childNodes[0].checked) {
typeNode = notes[noteCount];
noteCount++;
typeNode.firstChild.nodeValue = document.form1.annot.nodeValue;
if (annNode.nextSibling != null) {
annNode.parentNode.insertBefore(typeNode,annNode.nextSibling);
}
else {
annNode.parentNode.appendChild(typeNode);
}
}
 
Here we toggle the display of the note annotation. showNote is invoked twice to ensure that the stylesheet that hides notes is turned off and the base stylesheet which shows notes is in effect.
 
if (document.form1.atype.parentNode.childNodes[0].checked) {
showNote();
showNote();
}
}

function showNote() {
setTimeout(toggleNoteStyleSheet, 0);
}

var noteEnabled = true;
function toggleNoteStyleSheet()
{
if (noteEnabled) {
parent.frames.poesie.document.styleSheets[1].disabled = true;
}
else {
parent.frames.poesie.document.styleSheets[1].disabled = false;
}
noteEnabled = !noteEnabled;
}
 
These are some empty notes added to the end of our SGML document - a kludge that is not necessary in most recent versions of DocZilla but perhaps a good trick to remember anyway.
 
<templates>
<note>.</note>
<note>.</note>
 
The empty note nodes are not displayed to the user because they are enclosed in a templates element with a display property set to none.
 
templates
{
display: none;
}
 
This is from the base stylesheet which sets the formatting of the notes.
 
note
{
display: block;
margin-left: 40px;
font-size: 10pt;
border: 2px solid blue;
}
 
And this is the entire contents of the stylesheet we enable when we want to hide notes.
 
note
{
display: none;
}
 

Sortable Table Demo (Stock Index)

 DOM, Document Object Model 
 XSL 
interactive
 transformation 
 

In this section we will compare the effectiveness of XSL to the DOM for transforming XML documents and scripting dynamic, interactive behavior.
 sorting 
 

The concept for the application and the XSL code are from Microsoft XML demo material. In fact, the original objective, very successful achieved I might add, was to show that "anything XSL can do the DOM can do better". However, the primary objective of this paper is to show how to use the DOM so there will only be a little explanation of what is going on in the XSL . The DOM application is a bit different, better in fact as it supports attribute-controlled sorting algorithm selection and reads and displays the system time.
 CSS, Cascading Style Sheets 
XML glitterati
 XSL 
 

Although such statements have done little to increase my popularity among the XML glitterati XSL does not add one iota of capability over what can be accomplished right now with the DOM and CSS . And XSL is a year or two away and the DOM and CSS are here now. OK, I've got that off my chest. Now see for yourself.
 

XSL Stock Sorter - Functionality

stock table
 

The rows of the table shown in the figure below can be sorted based on values in any individual column by clicking on the header of the column. The sort is a straight case-sensitive (z comes before A) string sort and it does not handle numeric or other types of values.
 
 
 
The file downloaded to the browser is XML. A snippet is shown below:
 
<?xml:stylesheet type="text/xsl" href="portfolio.xsl"?>
<portfolio>
<date>October 13, 1998 at 3:56PM</date>
<stock>
<symbol>ACXM</symbol>
<name>acxiom corp</name>
<price>$18.875</price>
<change>-1.250</change>
<percent>-6.21%</percent>
<volume>0.23M</volume>
</stock>
HTML conversion
 

 XSL is used to convert the XML into an HTML table. Each table header has a nested Div tag with an onClick event to invoke the  XSL sort, passing the name of the field to be sorted to the routine. The entire document is regenerated in HTML each time the table is sorted.
 

XSL Stock Sorter - Code

 
The transformation of the XML data shown above to an HTML table is defined here. Scripts for sorting on a specific column field and for sorting on document load are also embedded here.
 
<?xml version="1.0"?>
<HTML xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<HEAD>
<STYLE>
BODY {margin:0}
.bg {font:8pt Verdana; background-color:purple; color:white}
H1 {font:bold 14pt Verdana; width:100%; margin-top:1em}
.row {font:8pt Verdana; border-bottom:1px solid #CC88CC}
.header {font:bold 9pt Verdana; cursor:hand;
	 padding:2px; border:2px outset gray}
</STYLE>
<XML id="portfolio">
<xsl:apply-templates select="portfolio">
<xsl:template>
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:apply-templates>
</XML>
<XML id="sorted"><xsl:eval/></XML>
<XML id="sortStocks" src="sort-stocks.xsl"><xsl:eval/></XML>
</HEAD>
<SCRIPT><xsl:comment><![CDATA[
function sort(field)
{
sortField.value=field;
sorted.XMLDocument.loadXML(
portfolio.transformNode(sortStocks.XMLDocument));
}
]]></xsl:comment></SCRIPT>

<SCRIPT for="window" event="onload"><xsl:comment><![CDATA[
sortField = sortStocks.selectSingleNode("//@order-by");
sort("price");
]]></xsl:comment></SCRIPT>
<BODY>
<TABLE width="100%" cellspacing="0">
<TR>
<TD class="bg"/>
<TD class="bg">
<H1>Stock Index for <xsl:value-of select="portfolio/date"/></H1>
</TD>
</TR>
<TR>
<TD class="bg" width="120" valign="top">
<P>Click on the column headings to sort by that field.</P>
<P>Note that sorting is by string value only and not numeric</P>
</TD>
<TD valign="top">
<TABLE class="listing" datasrc="#sorted">
<THEAD>
<TD width="200">
<DIV class="header" onClick="sort('name')">Company</DIV>
</TD>
<TD width="80">
<DIV class="header" onClick="sort('symbol')">Symbol</DIV>
</TD>
<TD width="80">
<DIV class="header" onClick="sort('price')">Price</DIV>
</TD>
<TD width="80">
<DIV class="header" onClick="sort('change')">Change</DIV>
</TD>
<TD width="80">
<DIV class="header" onClick="sort('percent')">%Change</DIV>
</TD>
</THEAD>
<TR>
<TD>
<DIV class="row" datafld="name"><xsl:eval/></DIV>
</TD>
<TD>
<DIV class="row" datafld="symbol"><xsl:eval/></DIV>
</TD>
<TD>
<DIV class="row" datafld="price" STYLE="text-align:right">
<xsl:eval/>
</DIV>
</TD>
<TD>
<DIV class="row" datafld="change" STYLE="text-align:right">
<xsl:eval/>
</DIV>
</TD>
<TD>
<DIV class="row" datafld="percent" STYLE="text-align:right">
<xsl:eval/>
</DIV>
</TD>
</TR>
</TABLE>
</TD>
</TR>
</TABLE>
</BODY>
</HTML>
 
The contents of sort-stocks.xsl, referenced in the script above, is shown below. It triggers the transformation of the document, I think.
 
<xsl:template xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<xsl:apply-templates select="portfolio">
<xsl:template>
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="portfolio">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:apply-templates select="stock" order-by="name"/>
</xsl:copy>
</xsl:template>
</xsl:apply-templates>
</xsl:template>
 

DOM Stock Sorter - Functionality

 sorting 
 

The functionality is similar to the XSL Stock Sorter - but with some important differences. Type-specific and directional sorting has been implemented, the default values are actually encoded in the column header (One could imagine being able to change properties of the sort from controls in the page.) There is an XML column header instead of the column headings hard-coded in the script found in the XSL application. Here is a snippet of data showing the header and a row of stock information:
 
<?xml version="1.0"?>
<?xml-stylesheet href="stuff.css" type="text/css"?>
<portfolio>
<stockhead>
<headrow>
<colhead field="string/g" direct="asc">Symbol</colhead>
<colhead field="string/g" direct="asc">Name</colhead>
<colhead field="curr" direct="desc">Price</colhead>
<colhead field="num" direct="desc">Change</colhead>
<colhead field="units" direct="desc">%Change</colhead>
<colhead field="units" direct="desc">Volume</colhead>
</headrow>
</stockhead>
<stockbody>
<stock>
<symbol>ACXM</symbol>
<name>Acxiom Corp</name>
<price>$18.875</price>
<change>-1.250</change>
<percent>-6.21%</percent>
<volume>0.23M</volume>
</stock>
 
The colhead field attribute allows one to specify case-insensitive string sorting (/g - z comes after A), currency, numeric, and numeric with a units symbol sorts and the direct attribute states whether the sort should be ascending or descending.
CSS2
table
 

The table is formatted in XML using CSS 2 table display properties. It looks quite good, I daresay:
 
 
 
The stylesheet for this application is shown below:
 
portfolio
{
display: table;
margin-top: 0.5em;
margin-bottom: 0.5em;
margin-left: 0.5em;
}

symbol, name, price, change, percent, volume
{
display: table-cell;
font-family: Arial, Helvetica, sans-serif;
font-size: x-small;
line-height: 1.2;
vertical-align: top;
padding-left: 0.2em;
padding-right: 0.1em;
padding-top: 0.1em;
border-bottom: 1px solid navy;
}

colhead
{
cursor: pointer;
display: table-cell;
font-family: Arial, Helvetica, sans-serif;
font-weight: bolder;
font-size: small;
text-align: center;
line-height: 1.2;
vertical-align: top;
background: #CCC;
color: black;
padding-left: 0.2em;
padding-right: 0.2em;
padding-bottom: 0.05em;
border: 2px inset black;
}

stockhead stock symbol,
stockhead stock name
{
text-align: left;
}

change,
price,
percent,
volume
{
text-align:right;
padding-right: 0.4em;
}

stock, headrow
{
display: table-row;
}

stockhead
{
display: table-header-group;
}

stockbody
{
display: table-row-group;
}
 
We use the DOM to traverse the elements of the column header and to set an onMouseDown event sort handler for each one. We use DOM operations to also get attribute information to guide the sort and to actually handle the mechanics of rearranging the node.
code
 

Of course, we have no need to regenerate the entire document. Another very important point about the DOM approach is that the code is all totally generic - it will work on any table having matrix (no spanned rows or colums) structure and headers.
 

DOM Stock Sorter - Code

 
In the main routine four global variables must be declared which are specific to the table markup. An array is created which will hold the table cells and makeSortableTable is called. That is all that is needed to make the table sortable by clicking on column headings. The four global variable which much be declared contain the name of the column header elements, the name of the row container elements and the attribute names used to identify the direction of the sort and the field type.
 
var gHeaderRowCellGI            = "COLHEAD";
var gRowCellGI                  = "STOCK";
var gSortDirectionAttributeName = "DIRECT";
var gFieldTypeAttributeName     = "FIELD";

var gTableCells = new Array();

makeSortableTable(gRowCellGI);
 architectural forms 
 

It may be of interesting to note that this information could be passed to the application by means of architectural forms, a technique by which any element or attribute can be associated with an abstract element type such as COLHEAD, STOCK or attribute type such as DIRECT or FIELD and thereby entirely avoiding any markup-specific code. (If, of course, the browser has the ability to understand architectural forms. DocZilla will probably have that property in its first release.)
 
function makeSortableTable(rowCellGI)
{
 
We will set up sort event handlers on the cell headers. The technique is the same employed in the two previous  DOM application examples.
 
setTimeout(SetSortOnCellHeaderEvents,0);
 
We get all the row elements and store them in global array. Then we call putDOMNodeIntoSortTable to extract the cell elements in the rows and store them in a two dimensional array ready for sorting.
 
var celllist = document.getElementsByTagName(rowCellGI);

for (var i=0; i < celllist.length; i++) {
gTableCells[i] = celllist[i];
}
putDOMNodesIntoSortTable(gTableCells);
}

function putDOMNodesIntoSortTable(nodes)
{
var i, j;
var ncount = nodes.length;

for (i = 0; i < ncount; i++) {
var node = nodes[i];
var childNodes = node.childNodes;
var ccount = childNodes.length;

for (j = 0; j < ccount; j++) {
var child = childNodes[j];
node[j] = child.firstChild.nodeValue;
}
}
}
 
The technique used to set events on column headers in SetSortOnCellHeaderEvents should be well understood by now from the prior DOM examples.
 
function SetSortOnCellHeaderEvents() {
var headerCells = document.getElementsByTagName(gHeaderRowCellGI);

for(var i=0; i<headerCells.length;i++) {
headerCells[i].onmousedown = sortOnColumn;
}
}
 sorting 
 

sortOnColumn is the event handler invoked when the user clicks on a header cell. First it must get the fieldtype and direction attributes from the column header to invoke the proper sorting for the column. Actually we just iterate over the column header cells until we find a node which matches the node on which the event was triggered. Once that node is found all we need to do is invoke the low level sort routine, passing the column number, fieldtype, and direction as arguments.
 
function sortOnColumn(e) {
// node is text, parent is headercell, parentparent is
// container for header cols
var node = e.target.parentNode.parentNode.firstChild;
for (i=0; i<e.target.parentNode.parentNode.childNodes.length; i++)
{
if (node == e.target.parentNode) {
var fieldtype = ";
var direction = ";
for (var j=0; j<node.attributes.length; j++) {
var currAttr = node.attributes[j];
if (currAttr.nodeName == gFieldTypeAttributeName) {
fieldtype = currAttr.nodeValue;
}
else if (currAttr.nodeName == gSortDirectionAttributeName) {
direction = currAttr.nodeValue;
}
}
sort(gTableCells,i,fieldtype,direction);
break;
}
else {
node = node.nextSibling;
}
}
}
 sorting 
 

This is a really bad sort routine so I should be careful here to blame the folks I stole it from: it was lifted from one of Netscape's demos. I added the nice bits with handling the fieldtypes and direction. The sort is just a bubble sort, the more unsorted the table is the more swapping of nodes takes place. Comparison between nodes is done with compare function which takes into account the fieldtype and direction.
 
function sort(collection, key, fieldtype, direction)
{
var i, j;
var count = collection.length;
var parent, child;

for (i = count-1; i <= 0; i--) {
for (j = 1; j <= i; j++) {
if (compare(collection[j-1][key], collection[j][key],
fieldtype, direction)) {
// Move the item both in the local array and
// in the tree
child = collection[j];
parent = child.parentNode;

collection[j] = collection[j-1];
collection[j-1] = child;

parent.removeChild(child);
parent.insertBefore(child, collection[j]);
}
}
}
}
 JavaScript 
 

compare makes use of JavaScript's decent regular expression, string, and math classes, quite necessary even in a relatively small application such as this one.
 
function compare(value1, value2, fieldtype, direction) {
var str;
if (fieldtype.toUpperCase() == "STRING/G") {
value1.toUpperCase();
value2.toUpperCase();
}
else if (fieldtype.toUpperCase() == "CURR") {
var str = value1.match(/\\$\\s*(\\d*\\.\\d*)/);
value1 = RegExp.$1 * 1.000;
var str = value2.match(/\\$\\s*(\\d*\\.\\d*)/);
value2 = RegExp.$1 * 1.000;
}
else if (fieldtype.toUpperCase() == "NUM") {
value1 = value1 * 1.000;
value2 = value2 * 1.000;
value1 = Math.abs(value1);
value2 = Math.abs(value2);
}
else if (fieldtype.toUpperCase() == "UNITS") {
var str = value1.match(/((+|-)?\\d*\\.\\d*)/);
value1 = RegExp.$1 * 1.000;
var str = value2.match(/((+|-)?\\d*\\.\\d*)/);
value2 = RegExp.$1 * 1.000;
value1 = Math.abs(value1);
value2 = Math.abs(value2);
}

if (direction.toUpperCase() == "DESC") {
return (value1 < value2);
}
else {
return (value1 > value2);
}
}
 

Conclusion

 DOM, Document Object Model 
 

You can do absolutely anything with XML, CSS , the DOM and a scripting language in Web browsers that fully support those four things. Period. The promised land is here. What are you waiting for??!!

Trying not to get lost with a topic map   Table of contents   Indexes   Metadata deployment for publishing environments