Translation

Ads

Granjuxx
Professional IT services. Special offers for small companies Websites.
Hostnet
Hosting starting at R$9,90. Free domains. 30 days free trial.


Place your ad here.

Public audience report XHTML valid

Home

Social

DOM XML Wrapper for JavaScript PDF Print
User Rating: / 0
PoorBest 
Written by Bruno Grange   
Wednesday, 03 September 2008 00:36

By Nicholas C. Zakas ( This e-mail address is being protected from spambots. You need JavaScript enabled to view it )

Introduction

In my opinion, one of the most useful parts of JavaScript is the DOM Document object. With this object, a developer not only can build a DOM Document from scratch, but also can load a file via HTTP into it. The result is the possibility of having client-side JavaScript being loaded with server-side information. What could possibly be better? That answer is simple: if there was a common interface for the DOM Document between Internet Explorer and Netscape.

This article will focus on creating a single DOM Document interface for developers to use in both Internet Explorer 5.0+ (Windows) and Mozilla (The Mozilla browser itself, or Netscape 6.1+). This interface will use both IE's ActiveX approach as well as Mozilla's standards-compliant approach. Because the IE implementation uses ActiveX technology, this will not work on IE for the Mac.

A Little Background

Developers have access to a full library of XML-related objects through Microsoft's MSXML Parser. Each of these objects is an ActiveX object. You can create a DOM Document in Internet Explorer by doing the following:

var objXMLDOM = new ActiveXObject("Msxml.DOMDocument");

Mozilla follows the W3C's DOM standard (http://www.w3.org/DOM/). As such, you create a DOM Document in the following way:

var objXMLDOM =
document.implementation.createDocument("", "", null);

The interesting thing to note is that because an ActiveX object is compiled code on the client machine (Note: not the client Web browser), you cannot have expando properties. The following will cause an error in IE, but not in Mozilla:

objXMLDOM.myAttribute = "blue";

What this means for us is that if we want to extend one interface to be more compatible with the other, it will have to be the Mozilla interface that is customized to be more compatible with the IE interface.

The Factory

As part of this process, we are going to make a JavaScript factory. A factory is a type of object that serves only as a method to access a group of functions. For instance, the following uses the native JavaScript Date object as a factory:

var bIsValidDate = Date.parse(strDate);

Note that we didn't instantiate a Date object; we just used one of its methods. We will create a factory called jsXML, which will have one method called createDOMDocument(). Let's set up the JavaScript file:

//define factory
function jsXML() { }

//define method
jsXML.createDOMDocument = function() {}

A Cross-Browser DOM Document Wrapper

Developer News
Google to Shake Up Browsers With Own Launch
Mozilla's Ubquity Mashup: For The Masses?
iPhone Users Just Want to Have Fun

The createDOMDocument() Function

The first step to creating a cross-browser interface is to have a common way to create the DOM Document. The createDOMDocument() method will serve this purpose. The first step is to determine which browser is being used and then create the DOM Document in the appropriate way. However, IE throws a little wrinkle at us.

The DOM Document in IE is an ActiveX object, as discussed before. What this means for us is that even though a user has IE 5.0 on their computer, they may not necessarily have the latest version of MSXML. Of course, we want to use the best and most current version, but how do we know what the user's machine currently has installed?

Unfortunately, there is no pretty way to determine what version of MSXML the user has on their machine. The only way to determine if an ActiveX object can be created is to try to create it. If the ActiveX object is not available on the client machine, it will cause a JavaScript error. So in order to find out what version of MSXML the user has installed, we have to try to create each ActiveX object and look for one that doesn't cause an error using a try...catch block.

First, we'll define the array of possible ActiveX objects to use. This array is in the order of most recent version to least recent, which will allow us to cycle through the array in order to make sure we get the most current version available on the user's machine:

var ARR_ACTIVEX = ["MSXML4.DOMDocument", "MSXML3.DOMDocument", "MSXML2.DOMDocument", "MSXML.DOMDocument", "Microsoft.XmlDom"]

Next, we define a constant string that will be filled with the appropriate prefix when it is determined:

var STR_ACTIVEX = "";

Now, here comes the try...catch block:

//if this is IE, determine which string to use if (isIE) { //define found flag var bFound = false; //iterate through strings to determine which one to use for (var i=0; i < ARR_ACTIVEX.length && !bFound; i++) { //set up try...catch block for trial and error //of strings try { //try to create the object, it will cause an //error if it doesn't work var objXML = new ActiveXObject(ARR_ACTIVEX[i]); //if it gets to this point, the string worked, //so save it STR_ACTIVEX = ARR_ACTIVEX[i]; bFound = true } catch (objException) { } //End: try } //End: for //if we didn't find the string, send an error if (!bFound) throw "MSXML not found on your computer." }

With the ActiveX string now determined, we can continue on to create the createDOMDocument() method with a simple browser test:

jsXML.createDOMDocument = function() { //variable for the created DOM Document var objDOM = null; //determine if this is a standards-compliant browser like Mozilla if (document.implementation && document.implementation.createDocument) { //create the DOM Document the standards way objDOM = document.implementation.createDocument("","", null); } else if (isIE) { //create the DOM Document the IE way objDOM = new ActiveXObject(STR_ACTIVEX); } //return the object return objDOM; }

The next step in this process is to determine the parameters for this method. In IE's interface, there are no parameters to the creation of the DOM Document. In Mozilla's interface, there are three parameters. The first two are the namespace and tag name for the root node (document element) of the DOM Document that is being created. The third is an object representing the document type that is being created. The third parameter has not yet been activated in Mozilla (according to their documentation), so only the first two are of interest.

If we were to create a DOM Document in Mozilla like this:

var objDOM = document.implementation.createDocument("http://www.nczonline.net/", "myroot", null);

The resulting XML string would be:

<a0:myroot xmlns:a0="http://www.nczonline.net/" />

That seems to be a pretty useful thing, to be able to initialize the DOM Document with a namespace and root tag name, so let's make two parameters for our createDOMDocument() method: the namespace and root tag name. The important thing to note is that if there is only a namespace and no root tag name specified, it has no effect. If, however, there is a root tag name specified and no namespace, it still works to produce an XML string.

So first, we add the parameters into the function definition:

jsXML.createDOMDocument = function(strNamespaceURI, strRootTagName) { ... }

For Mozilla, these two parameters can be passed through to the native creation method:

//create the DOM Document the standards way objDOM = document.implementation.createDocument(strNamespaceURI, strRootTagName, null);

In IE, we will have to do the work ourselves. We can check for the parameters and use IE's proprietary loadXML() method to simulate Mozilla's implementation:

//create the DOM Document the IE way objDOM = new ActiveXObject(STR_ACTIVEX); //if there is a root tag name, we need to preload the DOM if (strRootTagName) { //If there is both a namespace and root tag name, then //create an artifical namespace reference and load the XML. if (strNamespaceURI) { objDOM.loadXML("<a0:" + strRootTagName + "xmlns:a0=\"" + strNamespaceURI + "\" />"); } else { objDOM.loadXML("<" + strRootTagName + "/>"); } }

 

A Cross-Browser DOM Document Wrapper

Developer News
Google to Shake Up Browsers With Own Launch
Mozilla's Ubquity Mashup: For The Masses?
iPhone Users Just Want to Have Fun

Making Interfaces Play Together

We have just finished our common creation method for a DOM Document, but we are not done yet. While both the IE and Mozilla interfaces have the same standards-compliant attributes and methods, both have also added proprietary attributes and methods that we need in order to protect the future of our interface.

The load() and loadXML() Methods

To begin, both IE and Mozilla support a proprietary load() method, which allows developers to load an XML document via HTTP in the following way:

objDOM.load("http://www.nczonline.net/test.xml");

IE adds the additional loadXML() method (which we used earlier). The loadXML() method allows the developer to pass in an XML string to load into the DOM Document. Mozilla doesn't provide a simple way to pass in an XML string, but we can create our own loadXML() method for the Mozilla version.

The name of the DOM Document class in Mozilla is simply called Document. We can extend the Document class just as we can extend any other native JavaScript class. So, we will check for Mozilla, and then extend the Document class to add the loadXML() method:

if (isMoz) {

//add the loadXML() method to the Document class
Document.prototype.loadXML = function(strXML) {

}
}

But how can we take the XML string and get it parsed into a DOM Document? Mozilla provides the DOMParser class to do just this. The DOMParser class is part of the XMLExtras module of Mozilla (click here for more information) and has a method called parseFromString() that will take an XML string and a content-type (in our case, "text/xml") and produce a Document object.

First, let's add in the DOMParser:

if (isMoz) {

//add the loadXML() method to the Document class
Document.prototype.loadXML = function(strXML) {

//create a DOMParser
var objDOMParser = new DOMParser();

//create new document from string
var objDoc = objDOMParser.parseFromString(strXML, "text/xml");

} //End: function
} //End: if

The additional problem for us is how to transfer the data in the Document object produced by the DOMParser into the current Document. We need to be sure to clear the Document of all data already in it. To do this, we need to remove every top-level child node of the Document, like so:

//make sure to remove all nodes from the document
while (this.hasChildNodes())
this.removeChild(this.lastChild);

Now that the Document is cleared of data, we need to add in the data from the DOMParser Document. To do this, we need to iterate through all of the top-level nodes of the DOMParser Document. Then, we need to add each of those nodes into the current Document. The added wrinkle is that you cannot add a node from one Document object into another directly. You need to first import the node into the Document, using the importNode() method and then add the node into the current Document.

The importNode() method takes two parameters: the node to import, and a boolean indicating if the node's children should also be imported. Since we want to copy all of the data, we set the boolean to true. This method returns the imported node, which then can be added into the current Document. This is done as follows:

//add the nodes from the new document
for (var i=0; i < objDoc.childNodes.length; i++) {

//import the node
var objImportedNode = this.importNode(objDoc.childNodes[i], true);

//append the child to the current document
this.appendChild(objImportedNode);

} //End: for

We can now use the loadXML() method in both IE and Mozilla, which will make many people happy.

The xml Attribute

Microsoft provided the loadXML() method to allow the developer to input an XML string into a DOM Document. Likewise, they provided a way to get that XML string back. The xml attribute of the DOM Document will return the complete XML string being represented by the DOM Document. The xml attribute is also on every node in the DOM Document in order to return XML from any specific location in the DOM Document. This is the natural complement to the loadXML() method, so let's add it into the Mozilla interface.

Normally, if you wanted to add an attribute to a class, you would do it as follows:

Document.prototype.xml = "";

However, in this case, we won't be doing that. Instead, we will define an attribute "getter;" or the function that is called when a developer tries to get the value of an attribute. Doing this will automatically create the attribute for the class.

An important point to make before going on is that we want the xml attribute to be on every node in the Document, as well as on the Document itself. To do this, we could add the xml attribute on the Document and Element classes, which is not optimal. The other option is to just add the xml attribute to the Node class. Every type of node in a DOM Document, whether it is Document, Element, or anything else, is inherited from the Node class. So, we will just add the xml attribute to the Node class, and all of the others will automatically inherit it.

Adding a getter is done using the hidden method __defineGetter__(). The __defineGetter__() method takes two parameters: a string indicating the attribute to watch and a function to call when the attribute is accessed. Let's assume we will define a function called _Node_getXML() to handle this, so we will define the getter for the Node as follows:

Node.prototype.__defineGetter__("xml", _Node_getXML);

Now, all that is left is to create the _Node_getXML() function. In order to convert a Node to an XML string, Mozilla provides an XMLSerializer class. The XMLSerializer has a method called serializeToString(), which takes a Node (which is what both Element and Document are extended from) and returns the XML string representing the Node. Our function will use the XMLSerializer and pass in this (for those unaware, this represents the object that is calling the method). The XML string must then be returned:

function _Node_getXML() {

//create a new XMLSerializer
var objXMLSerializer = new XMLSerializer;

//get the XML string
var strXML = objXMLSerializer.serializeToString(this);

//return the XML string
return strXML;

Error Handling

We've now come to the last part of our project, error handling. As I'm sure you can guess, IE and Mozilla do it two different ways. IE's DOM Document has a parseError attribute that is filled with an error code if an error occurs. However, an error in the DOM Document does not throw a JavaScript error, so execution does not stop. In Mozilla, any error inside the load() method is thrown as a JavaScript error, and execution stops cold. So once again, we will make Mozilla more like IE and create a parseError attribute.

The parseError attribute starts out as 0, which indicates that there is no error. Whenever someone wants to check if there has been an error, a simple check to see if the parseError attribute is not equal to 0 does the trick. So let's start out by adding our parseError attribute with a default value of 0:

Document.prototype.parseError = 0;

Next, we will add a try...catch block in our _Document_load() method. Because it would be impossible to map all of the errors in Mozilla to their IE counterparts, we'll just set the parseError attribute to –9999999 to indicate that there is an error (we will also change the readyState attribute to indicate the loading action is done). Important thing to note: we must set the parseError attribute to 0 every time the load() method is called, because it is essentially wiping the slate clean for the Document object:

function _Document_load(strURL) { //set the parseError to 0 this.parseError = 0; //change the readyState changeReadyState(this, 1) //watch for errors try ( //call the original load method this.__load__(strURL); } catch (objException) { //set the parseError attribute this.parseError = -9999999; //set readyState to 4, we are done loading changeReadyState(this, 4); } // End: try...catch }

And here's another interesting wrinkle: if an error is caused while parsing, Mozilla doesn't throw an exception. Instead, it creates an XML string containing the details of the error, for example:

<parsererror xmlns="http://www.w3.org/1999/xhtml">XML Parsing Error: mismatched tag. Expected: </root>. Location: file:///C:/DOCUME~1/Nicholas/MYDOCU~1/MYARTI~1/USINGT~1/Example/Invalid.xml Line Number 3, Column 37:<sourcetext> <root image="root.gif">My Root</boot> ------------------------------------^</sourcetext></parsererror>

So, we actually need to check for an error after the XML is loaded to see if one of these XML error strings has been generated (or if there is no documentElement for some reason). Because we have incredible foresight, we will create a function called handleOnLoad() to encapsulate this process. The handleOnLoad() function will take one parameter, the Document to check. It will also set the readyState attribute to 4 (regardless of any errors, the loading is over):

function handleOnLoad(objDOMDocument) { //check for a parsing error if (!objDOMDocument.documentElement || objDOMDocument.documentElement.tagName == "parsererror") objDOMDocument.parseError = -9999999; //change the readyState changeReadyState(objDOMDocument, 4); }

Now, we need to add this into two places, the _Document_onload() function and the loadXML() method (note that we aren't adding it to the _Document_load() function, because we can only determine parsing errors after the XML has been loaded):

function _Document_onload() { //handle the onload event handleOnLoad(this); } //add the loadXML() method to the Document class Document.prototype.loadXML = function(strXML) { //change the readystate changeReadyState(this, 1); //create a DOMParser var objDOMParser = new DOMParser(); //create new document from string var objDoc = objDOMParser.parseFromString(strXML, "text/xml"); //make sure to remove all nodes from the document while (this.hasChildNodes()) this.removeChild(this.lastChild); //add the nodes from the new document for (var i=0; i < objDoc.childNodes.length; i++) { //import the node var objImportedNode = this.importNode(objDoc.childNodes[i], true); //append the child to the current document this.appendChild(objImportedNode); } //End: for //we can't fire the onload event, so we fake it handleOnLoad(this); } //End: function

Now the parseError attribute should be fairly accurate for Mozilla.

Conclusion

After a somewhat lengthy trek, we have created our cross-browser (IE5/NS6) DOM Document wrapper. To review, you can now create a DOM Document object by doing the following:

var objDOMDocument = jsXML.createDOMDocument(strNamespaceURI, strRootTagName);

You can load an XML string into the DOM Document by doing:

objDOMDocument.loadXML("<myroot />");

You can retrieve the XML string by doing:

var strXML = objDOMDocument.xml;

To load an external XML file, we do the following:

objDOMDocument.async = false; objDOMDocument.load("myfile.xml");

To load it asynchronously:

objDOMDocument.async = true; objDOMDocument.load("myfile.xml");

You can set an event handler to determine when an XML file has been fully loaded by doing:

objDOMDocument.onreadystatechange = myFunction; function myFunction() { if (this.readyState == 4) alert("Loaded"); }

And to check for errors, you can do the following:

if (objDOMDocument.parseError != 0) alert("Error");

We are now ready to use a DOM Document the same way across Internet Explorer 5.0+ (Windows) and Mozilla (Mozilla or Netscape 6.1+). This example illustrates all of these uses. I ask that you try it in both browsers. There are slight differences, but the overall functionality is the same.

About the Author

Nicholas C. Zakas is a user interface designer for web applications at MatrixOne, Inc. in Massachusetts. Nicholas works primarily as a client-side developer using JavaScript, DHTML, XML and XSLT. He can be reached via e-mail at This e-mail address is being protected from spambots. You need JavaScript enabled to view it or at his Web site, http://www.nczonline.net.


}

Now, the Mozilla interface can use the xml attribute in the same way that IE uses it.




Related Articles:
 

Add comment


Security code
Refresh