August 28, 2004

The Contender

[Archived Blog Posts (and a chance to comment on posts) can be found at http://www.metaphoricalweb.com/.]

Perhaps it has been because I've been out of the loop for a bit, but I've discovered a curious thing lately. Internet Explorer is getting, well ... old, and maybe more than a little decrepit. Kind of like what happens when a large and stately mansion which hasn't been occupied for a while begins to gray, with windows broken here and there, the lush gardens turned weed-strewn and seedy. It slips away from being the palace on the hill to being a high class fixer-upper for someone with more money than sense.

This observation was brought to my attention because I was in the process of putting together a web-based XML editor for a client. I took it as axiomatic that if you wanted to do anything beyond displaying web pages, the only real choice out there was Internet Explorer. Yet after an unfortunate encounter with a virus from a spoofed zip file (actually a PIF ... is there any reason whatsoever why we still need PIFs for anything?) sent from the virused computer of a student of mine, my Internet Explorer didn't work quite right anymore. I couldn't get it to open up new window instances. That was great for pop-ups, admittedly, until I realized that the pop-ups were showing up in memory (with all that nasty viral Javascript code still working its malicious mischief), the windows were just not being displayed.

Before I knew it, something was sitting there on my socket queue, ruining the ability to serve anything complex (even JPEG images would only download to 50% before freezing). Then I tried to REMOVE Internet Explorer. Did you know that you can remove Mozilla, Netscape, Opera, just about every web browser out there ... except for the one that ships with Microsoft. What you get when you attempt to remove it through the "accepted" channels is a sort of Ur browser that's still there, accessible every time you type in a URL from a folder window.

After this, I attempted to reinstall IE, only to be informed that I couldn't install anything on top of the current version, which was apparently internally listed as being several dozen generations in the future. In the end, I spent a day backing up everything from my hard drive, reformatted and installed the latest version of Windows, vowing to be very circumspect around Internet Explorer.

One of my current business clients has a Content Management System that was built, periodically, by people with different visions and different levels of skill, and while it is serviceable enough, it has become a nightmare to maintain. Consequently, I was brought in to re-engineer it. CMS systems are simple in design and maddeningly complex in execution, but they represent a good area for XML people to tackle at least once in their career. One thing I decided to do with it was to revamp the editor and make it more useful for actually working with the markup language that they used. I could have used a stand-alone client, but given the variety of platforms that users were deployed to, I decided to go the web browser route.

My goal was to produce a user interface that has basic similarities to my current favorite XML editor, Oxygen (http://www.oxygenxml.com/). I had first encounted Oxygen when I was playing around with Eclipse, and soon became enamored of it. You can easily assign schemas or DTDs to XML to get full intellisense functionality, you could customize the XSLT engine so that it could easily use third party tools such as Saxon (something that was VERY useful for working with XSLT 2.0), it has a first-class debugger, and it is quite reasonably priced ($79, last I checked). Moreover, it integrates cleanly with Oxygen, and is the first XML tool of any quality that works as well in Linux as it does in Windows.

With that as a model, I set about to create similar function in a browser. My client came back to me approving the design, with the caveat that about a dozen of his writers who would be using the system were using Macintosh laptops, and could I make sure that it would work for them. That complicated the picture a bit. When it was introduced, Internet Explorer for the Macintosh was one of the best browsers out there ... in 1997. Seven years later, its not even close, and most of the functionality that I wanted to have - client side XSLT, web service request pipes, and so forth, didn't even exist there.

Safari, for the Macintosh, is becoming a first class browser, but its still very much a work in progress, with the necessary features for complex application development still at least months if not years in the future. Thus, I started doing some web searching, and noticed a curious thing. Every time I looked for a particular feature on non-IE browsers, onebrowse kept popping up. CSS 2.0 and partial 3.0 compliance, XmlHttpRequest, XSLT, XPath, nodal replacement, DOM support ... ubiquity of platform ... full support for PNGs ... text range support ... hmmmmmmmmm ...

Suppose that you had once been a major player, then you got clobbered by the new kid on the block, forced to watch as everything you'd built up got picked over by creditors until you had wandered into the bad part of town as a ghostlyhas-been. A bottle in one hand, you were on the verge of drinking yourself into oblivion when a kid comes along with his friends, stares at you, and says "You were somebody once, weren't you?"

They help you back onto your feet, get you sobered up and you learn to play by a new set of rules, until the day that you walk back into the stadium owned by that once-New Kid and say "Payback time."

Not Your Father's Mozilla

So it's the the plotline of any number of schmaltzy movies. This could still very well serve as the story of Mozilla, and its younger sibling Firefox. AOL, temporarily bloated with Internet bubble money, managed to buy up media giant Time Warner, and after that picked up the ailing Netscape, pummelled by Microsoft in the browser wars. AOL/Time Warner then preceded to effectively abandon the Netscape browser, firing most of the Netscape Navigator team and then releasing it to what was no dout conceived as oblivion - a team of largely unsupported open source geeks, who were willing to work on it in their spare time.

Picked up from the gutter, the underlying, eight year old Mozilla engine was effectively stripped to the ground and rebuilt, piece by piece, utilizing concepts that had evolved considerably since the first Navigator browser was created. The new engine cleaved closely to the W3C CSS and DOM specifications, though there are some minor distinctions in some of the XML technology that reflect the Microsoft model (including the inclusion of the XMLHttpRequest object, one of the most significant additions to the browser in a long while and one becoming pretty much standard in other browsers as well to the extent that the W3C will likely change the W3C DOM 3.0 specification to reflect this. The rendering model is fast, solid, and modular enough to handle any likely changes to the Recommendations.

More recently, the development from Gecko has split into two forks - the Mozilla 1.7 browser retains much of the characteristics of older Mozilla browsers, and it contains expanded developer functionality -- it is essentially a developer's toolkit. Firefox, on the other hand, is the future of Gecko - lighter-weight, fast, built for XML and for a wide consumer audience. It is in fact the browser that I chose to use for developing the content management system.

Efforts to support SVG are underway, and IBM and Novell have announced that they plan to roll their comprehensive XForms engine technology into the Mozilla code base. This would make Mozilla the first stand-alone browser to support the W3C XForms 1.0 Recommendation, although there are numerous XForms instances of varying degrees of conformance available, including Mozquito DENG, x-port Forms Player, Orbeon Open XML Framework, and the Mobiform SVGView Plus (thanks to Micah Dubinko and his summary page of XForm viewers for this information)..

In many ways, Mozilla 1.7 in particular can be thought of as the antithesis of Internet Explorer in that it attempts to conform as closely as possible to open standards. In addition to the high degree of W3C DOM 2.0 (and 3.0) support and CSS support, it also utilizes the most recent versions of ECMAScript/Javascript through the SpiderMonkey implementation. This in turn means that Mozilla developers can utilize many of the more significant Javascript capabilities, including the Setter/Getter functionality. For instance, you can now do something like this:

Listing 1. The use of Setter/Getter in Javascript (Figure 1)
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script language="Javascript">//<![CDATA[
Object.prototype.data getter = function(){
return parseInt(this.innerHTML);
}
Object.prototype.data setter = function(n) {
if (n >0 && n <= 100){
// empty the element of any previous data.
while (this.childNodes.length){
this.removeChild(this.firstChild);
}
// create a text node with the new value
var newNode=document.createTextNode(String(Math.round(n)));
// and insert it into the reference element:
this.appendChild(newNode);
// return the value as well as a matter of course:
return n;
}
else {
// this throws an exception which an exception handler can catch
throw "Error: Number out of range";
}
}

function increment(value){
if (value == null){value=1;}
var numberHolder=document.getElementById('numberHolder');

// This shows the power of getters and setters
numberHolder.data = numberHolder.data + value;

// This is equivalent:
// numberHolder.data += value;

}
//]]></script>

</head>
<body>
<h1>Setter/Getter</h1>
<p>Click on the plus button to increment by one, the minus button to decrement by one</p>
<div id="numberHolder" style="font-weight:bold;font-size:18pt;background-color:lightBlue;border:inset 3px lightBlue;width:30pt;">95</div>
<input type="button" onclick="increment()" value="+"/>
<input type="button" onclick="increment(-1)" value="-"/>

</body>
</html>


Figure 1. The use of Setter/Getter in Javascript


The setter/getter functionality makes it possible to add "property" interfaces to HTML and Javascript objects so that you interactive with the objects (such as the numberHolder <div> element in Listing 1) by setting or reading those properties. Thus, adding 4 to the numberHolder object becomes as simple as saying:

numberHolder  = numberHolder + 4;
or even

numberHolder += 4; 
This also makes possible exception handling - if an attempt is made to set the value of the numberHolder
beyond 99, an exception will be raised.

As someone who has worked extensively with Microsoft Behaviors and the sometimes byzantine XML necessary to make them work, the simplicity of getters/setters, combined with prototyping comes as something of a relief.

Ich Spreche XML

What is a browser? Five years ago, you could reasonably say that a browser was a device to view HTML. However, in mid 2004, XML has been out for nearly seven years, and XHTML has been a standard for five of those. Yet for all of that Internet Explorer is notoriously unfriendly toward XHTML - recognizing neither the XHTML namespace nor even an XHTML prefix. It does recognize the XML stylesheet processing instructions, though PIs in general are being pushed out in favor of namespaces as the preferred way of identifying the functionality of objects. And Internet Explorer has this incredibly annoying tendency of rendering HTML output as non-XML compliant HTML, making it notoriously difficult to process without a huge amount of DOM manipulation work.

Mozilla and Firefox are realistically the first true XML-centric browsers. You can view XML as structured (and collapsible) XML trees, though it will also recognize XHTML namespaces, and PI assigned CSS and XSLT stylesheets work fine with XML content.. Work on SVG is proceeding, though it has not yet been folded into the main development fork, and as mentioned previously, XForms support, much more comprehensive XLink and XPointer support exist as well.

>From an application development standpoint, however, it is the other natively supported XML objects that offer such a tantalizing glimpse into the bounds of an XML oriented browser. These are given in Table 1:


Object name
Description
XMLDocument
Supports the standard W3C DOM Document implementation, and can be extracted from the web page document object.
XMLSerializer
Used for converting XML DOM Documents into either a string or streamed binary serial output.
DOMParser
Converts a stream, file or stream representation of an XML document into an internal DOM Document
XSLTProcessor
Creates an XSLT object that can be used for building transformations
XPath Parser
Performs XPath queries on XML documents.
Web Services
Handles SOAP and WSDL Services
XUL/XBL
These components are useful for adding functionality into the browser itself, and are XML based.


Unlike with Microsoft Internet Explorer, these objects are native within the brower, rather than being invoked as ActiveXObjects, so they participate much more fully in the security model of the browser itself. Moreover, they work across Windows, Linux, Unix and the Macintosh, whereas most of the ActiveX-based components are largely confined to Windows. This flexibility makes developing cross platform browser applications much easier.

The XMLDocument object isn't invoked directly, but comes from the document.implementation interface. While this marks a little bit of difference from the IE implementation, it is also something that can effectively be masked with some decent Javascript functions. For instance, the Internet Explorer XML DOM functions xml(), which serializes output to string, and loadXML(), which loads a string representation of an XML file into the dom, can be loaded into such an object (as shown in Listing 2)

Listing 2. DOMExtensionUtils.xml
// Defines XmlDocument.xml as the serialization mechanism
Document.prototype.__defineGetter__("xml",function(){
return (new XMLSerializer()).serializeToString(this);
}
)

// Loads an XML document from a text string
Document.prototype.loadXML = function(s){
var doc2 = (new DOMParser()).parseFromString(s,"text/xml");
while (this.hasChildNodes()){
this.removeChild(this.lastChild);
}
for (var i=0;i != doc2.childNodes.length;i++){
this.appendChild(this.importNode(doc2.childNodes[i],true));
}
}

You can then use the same functions with IE and Mozilla. Assume that you have a <div> element entitled "message", then you could actually load a message into XML and from there generate content:

var xmlDoc = document.implementation.createDocument("","",null);
xmlDoc.loadXML("<h1>A Message</h1><p>This is a message!</p>");
messageDisplay.innerHTML = xmlDoc.xml;

That example is trivial, of course, but illustrates how you could create effective XML services. A more comprehensive situation, though, would be a scenario where you had an XML document on a server containing various messages given by identifying key. As an example of this, consider the errorMessages.xml file (listing 3)::

Listing 3. errorMessage.xml
<messages>
<message code="1000">No information is currently known about this transaction.</message>
<message code="1001">Package appears to be corrupt.</message>
<message code="1002">Package manifest has bad pointer.</message>
<message code="1003">Package date exceeds limit.</message>
</messages>

When a given error condition occurs (here contained in the code attribute), then the appropriate message should be displayed in an error display box on the web page, as shown in Listing 4.

Listing 4. errorMessageXMLText.htm
<html>
<head>
<title>Example II - Error Messages</title>
<script type="text/Javascript">
var messageDisplay=null;
var messageData = document.implementation.createDocument("","",null);
function init(){
// Create a request pipeline to the server
messageDisplay = document.getElementById("messageDisplay");
var pipe=new XMLHttpRequest();
// Retrieve the messagesDocument via a synchronous (false) GET query
pipe.open("GET","errorMessages.xml",false);
pipe.send(null);
// Load the resultant text into the messageData XML "Data Island"
messageData.loadXML(pipe.responseText);
}

function displayMessage(id){
xpath = "messages/message[string(@code) = '"+id+"']/text()";
var res = messageData.evaluate(xpath, messageData, null, 0,null);
node = res.iterateNext();
if (node !=null){
messageDisplay.text = node.nodeValue;
}
}

// Defines XmlDocument.xml as the serialization mechanism
Document.prototype.__defineGetter__("xml",function(){
return (new XMLSerializer()).serializeToString(this);
}
)

// Loads an XML document from a text string
Document.prototype.loadXML = function(s){
var doc2 = (new DOMParser()).parseFromString(s,"text/xml");
while (this.hasChildNodes()){
this.removeChild(this.lastChild);
}
for (var i=0;i != doc2.childNodes.length;i++){
this.appendChild(this.importNode(doc2.childNodes[i],true));
}
}

Object.prototype.text setter = function(str){
while(this.firstChild){
this.removeChild(this.firstChild);
}
var textNode=document.createTextNode(str);
this.appendChild(textNode);
}

Object.prototype.text getter = function(){
return this.firstChild.nodeValue;
}


</script>
</head>
<body onload="init()">
<h1>XML Based Error Messages</h1>
<p>Select an error code to see its corresponding error message</p>
<select id="errorCodeSelector" onchange="displayMessage(this.value)">
<optgroup label="Choose an Error Code">
<option value="1000">Error Code 1000</option>
<option value="1001">Error Code 1001</option>
<option value="1002">Error Code 1002</option>
<option value="1003">Error Code 1003</option>
</optgroup>
</select>
<div><b>Error: </b><span id="messageDisplay"/></div>
</body>
</html>

Listing 4 provides a look at most of the XML based functions (with the exception of XSL Transformations) . Note that there are alternative approaches to many of these functions (such as the use of the load() method instead of the XMLHttpRequest object, but this at least illustrates the general usage of most of the basics.

The code fragment
var pipe=new XMLHttpRequest();
// Retrieve the messagesDocument via a synchronous (false) GET query
pipe.open("GET","errorMessages.xml",false);
pipe.send(null);
// Load the resultant text into the messageData XML "Data Island"
messageData.loadXML(pipe.responseText);
demonstrates the use of the XMLHttpRequest() component, which mirrors most of the same functionality as the Internet Explorer component of the same name. A connection is created with the server address given, using http GET, POST, PUT or similar http commands. By specifying whether you want the connection to be asynchronous (true) or synchronous (false) you can determine how you want to handle the incoming stream of information. The data once retrieved is contained in the responseText property, whichi in this case is reparsed into XML in the messageData variable. This is particularly useful for creating the equivalent of remote data islands within Mozilla or Firefox (creating local data islands will be covered in a subsequent blog).

The evaluate() function serves the same purpose as the selectNodes() and selectNode() function in Internet Explorer - a way to run an XPath expression on an XML document and retrieve the results. This function specifically implements the W3C DOM 3 XPath Interfaces, interfaces which can be confusing without some kind of reference. The evaluate() function itself takes five arguments:

xpath = "messages/message[string(@code) = '"+id+"']/text()";
var res = messageData.evaluate(xpath, messageData, null, 0,null);

with the first containing the XPath expression as a string, the second containing the context node where the XPath expression is evaluated (here the root node of the document), the third a namespace resolver for processing namespaces beyond the default (this will usually be null). The fourth parameter is a constant that can be used to indicate what kind of results should come from the expression, and again should usually be 0 (I'll be covering this more in subsequent blogs). The last parameter contains the variable that should receive the result - if set to null, then the output will be sent to the resulting value from the function. This last is typically used for internal processing with C++, not with Javascript, and consequently should almost invariably be sent to null.

The results of such an XPath expression will usually be an unorderd node set, accessed via the iterateNext() function, where each successive call will return an XML Node:
node = res.iterateNext();
if (node !=null){
messageDisplay.text = node.nodeValue;
}

If multiple nodes had been returned from the query, this could have been replaced with:
while(node = res.iterateNext()!=null)
messageDisplay.text = node.nodeValue;
}

which would have walked through each node in turn.

The Power of the Fox


By making this functionality easily accessible from the DOM, both Mozilla and Firefox open up a number of possibilities for multiplatform web development that was once the exclusive domain of IE on Windows. At the very minimum an interface can be set up that will let you emulate the functionality of IE (though this is also something of a downstep at this stage). If you are working with an intranet, the possibilities that Mozilla and Firefox offer for application development improve dramatically, because you don't have to spend as much time working on backword-compatible code. Given the speed and performance of both of these browsers, I suspect the Fox is likely to give Redmond a serious run for it's money.

I'll be coming back to this topic periodically, in part to discuss the XSLT side of things in greater detail and in part to look at other aspects of the "XML browser" such as intrinsic web services support. As per usually, feel free to use any code that is contained within these blogs, though I would mailto:kurt@kurtcagle.net to let me know how you're using it. Until the next blog, enjoy and keep coding.












69 comments: