XML in Firefox 1.5, Part 3: JavaScript meets XML in Firefox

Learn how to manipulate XML in the Firefox browser using JavaScript features

In this third article of the XML in Firefox 1.5 series, you learn to manipulate XML with the JavaScript implementation in Mozilla Firefox. In the first two articles, XML in Firefox 1.5, Part 1: Overview of XML features and XML in Firefox 1.5, Part 2: Basic XML processing, you learned about the different XML-related facilities in Mozilla Firefox, and the basics of XML parsing, Cascading Style Sheets (CSS), and XSLT stylesheet invocation.

Share:

Uche Ogbuji (uche@ogbuji.net), Principal Consultant, Fourthought Inc.

Photo of Uche OgbujiUche Ogbuji is a consultant and co-founder of Fourthought Inc., a software vendor and consultancy specializing in XML solutions for enterprise knowledge management. Fourthought develops 4Suite, an open source platform for XML, RDF, and knowledge-management applications. Mr. Ogbuji is also a lead developer of the Versa RDF query language. He is a computer engineer and writer born in Nigeria, living and working in Boulder, Colorado, USA. You can find more about Mr. Ogbuji at his Weblog Copia or contact him at uche@ogbuji.net.



01 August 2006

Once you've learned about basic XML viewing and styling in the Firefox browser, the next capability you're likely to care about is scripting. In this article, I show the basics of how you can manipulate XML with JavaScript code. I include code examples and screenshots, all of which I created and tested using Firefox 1.5.0.4 on Ubuntu Linux® with a fresh profile (that is, with no extensions and with the default options as installed). If you want to write cross-browser code for XML processing, you might have to use additional browser-sniffing techniques, but I don't cover those in this article.

Loading an XML file

You can load an XML document from JavaScript code embedded in a Web page. I'll start with an example of an HTML Web page that loads a simple XML mailing list format for dynamic update. The XML to be loaded is shown in Listing 1 (labels.xml).

Listing 1. (labels.xml) Address label XML
<?xml version="1.0" encoding="iso-8859-1"?>
<labels>
  <label id='ep' added="2003-06-10">
    <name>Ezra Pound</name>
    <address>
      <street>45 Usura Place</street>
      <city>Hailey</city>
      <province>ID</province>
    </address>
  </label>
  <label id='tse' added="2003-06-20">
    <name>Thomas Eliot</name>
    <address>
      <street>3 Prufrock Lane</street>
      <city>Stamford</city>
      <province>CT</province>
    </address>
  </label>
  <label id="lh" added="2004-11-01">
    <name>Langston Hughes</name>
    <address>
      <street>10 Bridge Tunnel</street>
      <city>Harlem</city>
      <province>NY</province>
    </address>
  </label>
  <label id="co" added="2004-11-15">
    <name>Christopher Okigbo</name>
    <address>
      <street>7 Heaven's Gate</street>
      <city>Idoto</city>
      <province>Anambra</province>
    </address>
  </label>
</labels>

Listing 2 is an HTML page that starts out as nothing more than a link that says, "Click here to load addresses." When you click the link, information from the address labels is added to the page.

Listing 2. HTML with JavaScript to load XML for dynamic update
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
  "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta content="text/html; charset=iso-8859-1" http-equiv="Content-Type">
    <title>Address book</title>
    <script type="application/javascript">
var ELEMENT_NODE = 1
//TEXT_NODE

function loadAddresses()
{
  xmlDoc = document.implementation.createDocument("", "", null);
  xmlDoc.onload = writeList;
  xmlDoc.load("labels.xml");
}

function writeList()
{
  var labels = xmlDoc.getElementsByTagName('label');
  var ol = document.createElement('OL');

  for (i=0; i < labels.length; i++)
  {
    var li = document.createElement('LI');
    for (j=0; j < labels[i].childNodes.length; j++)
    {
      if (labels[i].childNodes[j].nodeType != ELEMENT_NODE) continue;
      var cdata = document.createTextNode(
        labels[i].childNodes[j].firstChild.nodeValue);
      li.appendChild(cdata);
    }
    var labelId = document.createTextNode('(' +
      labels[i].getAttribute('id') + ')');
    li.appendChild(labelId);
    ol.appendChild(li);
  }
  document.getElementById('updateTarget').appendChild(ol);
}
    </script>
  </head>
  <body id='updateTarget'>
    <p>
      <a href="javascript:loadAddresses()">Click here to load addresses</a>
    </p>
  </body>
</html>

The dynamic features are in the script element, defining a JavaScript function loadAddresses, which the link in the HTML invokes. This function creates an empty document instance and then reads in Listing 1 (labels.xml) using the load function. The load function executes asynchronously so that the script moves on to the next line while the XML is being read, making it necessary for you to use a trigger function to run as soon as the XML loads. This is why I set the onload property to a separate function writeList. This function iterates over the labels using the handy Document Object Model (DOM) method getElementsByTagName. If your XML document uses namespaces, you might need to use the getElementsByTagNameNS form instead and specify the namespace as the first parameter. You'll see an example of this in the next section. In Listing 2, I only use the base level of DOM for XML processing, called DOM Level 1. For namespace-aware applications, you need DOM Level 2, which brings along namespace-aware versions of many Level 1 methods. Listing 2 creates a subtree representing an ordered list, using the main HTML document as a factory for creating nodes. This makes the resulting subtree suitable for insertion into the main HTML document. Listing 2 follows a general pattern of reading the source XML tree and then adding corresponding nodes to the output HTML subtree.

By looping over labels[i].childNodes for each label element, you can find the name and address children. To avoid the text node children, use a nodeType test. You obtain the child text of the name element from the firstChild.nodeValue access. In the case of address, the first child is white space. You don't access any of the text of the children of address. You access the ID by using the getAttribute method. You add all the text you've gathered to the list item. Once you compiled all the list item elements, you use the appendChild method to update the HTML document with the subtree. You can use the updateTarget ID to mark the element (body) to which you want to add this subtree. When you first load this HTML in Firefox, all you see is the link, as shown in Figure 1.

Figure 1. Browser display upon loading Listing 2
Initial browser display of Listing 2

Once you click on the link, you get the updated display instantly, as shown in Figure 2.

Figure 2. Browser display upon loading Listing 2 and clicking the link
Updated browser display of Listing 2

Using script with an XML main document

In Part 1 of this series, I covered how to browse XML files in Firefox. To use JavaScript in such cases, embed a reference to a script file. Listing 3 (designers.xml) is an example of an XML file, a list of designers. The XML references a separate CSS file in order to display the XML, and a separate JavaScript file in order to create link elements.

Listing 3. (designers.xml) XML format representing links to fashion designers
<?xml version='1.0' encoding='utf-8'?>
<?xml-stylesheet type="text/css" href="designers.css"?>
<designers>
  <blurb>
    <designer  homepage="http://doria.co.uk">Doria Ltd.</designer>
    of London
  </blurb>
  <blurb>
    <designer homepage="http://samsara.biz">Samsara Saris</designer>
    of Mumbai
  </blurb>
  <blurb>
    <designer homepage="http://pcp.co.uk">Pikeman Camouflage, Plc.</designer>
    of London
  </blurb>
  <blurb>
    <designer homepage="http://mandalay.co.jp">Mandalay</designer>
    of Tokyo
  </blurb>
  <xhtml:script xmlns:xhtml="http://www.w3.org/1999/xhtml"
                src="designers.js"
                type="application/javascript"/>
</designers>

The stylesheet processing instruction provides basic instructions for displaying the XML. Listing 4 (designers.css) features the CSS.

Listing 4. (designers.css) CSS for basic display of the XML format in Listing 3
* { display: inherit; }

designers { display: block; }

blurb {
  margin: 1em;
  width: 20em;
}

a {
  display: inline;
  text-decoration: none;
  color: green;
  border: thin blue solid;
}

script { display: none; }

The stylesheet instructs the browser to treat designers and blurb as block regions and to ignore script elements for display purposes. It also handles links (a elements) by displaying them inline with visual cues to make the links easier to spot. You'll notice that no a elements exist in the XML source. This is where the scripting comes in. The JavaScript code replaces the designer elements with XHTML link elements. It is provided in an XHTML script element, which you can embed in any arbitrary XML, as far as Firefox is concerned (you might run into separate problems with schema compliance, of course). Firefox runs scripts as it encounters them in the loaded document, which is why you place it after all the elements that need to be manipulated. Listing 5 contains the JavaScript code, designers.js.

Listing 5. (designers.js) Script for turning XML elements from Listing 3 into XHTML links
//Save the XHTML namespace for when you need it
var xhtmlns = "http://www.w3.org/1999/xhtml";
//get all elements named "blurb" in the document
//The first "" indicates no namespace for the element we're seeking
var blurbs = document.getElementsByTagNameNS("", "blurb");
//Loop over each element we found
for (var i=0; i < blurbs.length; i++)
{
    //retrieve the blurb element from the collection
    var blurb = blurbs[i];
    //Get the designer element within the blurb
    //Assumes only one designer element per blurb
    var designer = blurb.getElementsByTagNameNS("", "designer").item(0);
    //In DOM the text in designer is actually a text node object child
    //of blurb. The link title is the value of this text node
    //Assumes the text node is normalized
    var link_title = designer.firstChild.nodeValue;
    //Link URL is the homepage attribute of designer, in no namespace
    var link_url = designer.getAttributeNS("", "homepage");
    //Create a new XHTML namespace link element
    var xhtml_link = document.createElementNS(xhtmlns, "a");
    //Create a new text node with the link title
    var new_text_node = document.createTextNode(link_title);
    //Set the href attribute to the link URL
    xhtml_link.setAttributeNS("", "href", link_url);
    //Attach the text node with the link title to the XHTML link
    xhtml_link.appendChild(new_text_node);
    //Replace the designer element with the new XHTML link element
    blurb.replaceChild(xhtml_link, designer);
}

Because this script in Listing 5 involves namespace awareness (specifically for the XHTML namespace that's being displayed), I use namespace-aware DOM methods. Figure 3 shows the results of viewing Listing 3. As you can see, the browser applies the CSS and script immediately.

Figure 3. Browser display upon loading Listing 3
Browser display upon loading Listing 3

Invoking XSLT

You have access to most browser facilities from JavaScript, including the XSLT engine. Listing 6 is a snippet of a script that executes an XSLT transform.

Listing 6. JavaScript code to load an XML document and an XSLT transform and to execute the transform
//Create an XSLT processor instance
var processor = new XSLTProcessor();
//Create an empty XML document for the XSLT transform
var transform = document.implementation.createDocument("", "", null);
//Load the XSLT
transform.onload = loadTransform;
transform.load("display.xslt");

//Triggered once the XSLT document is loaded
function loadTransform(){
  //Attach the transform to the processor
  processor.importStylesheet(transform);
  source = document.implementation.createDocument("", "", null);
  source.load("source.xml");
  source.onload = runTransform;
}

//Triggered once the source document is loaded
function runTransform(){
  //Run the transform, creating a fragment output subtree that
  //can be inserted back into the main page document object (given
  //in the second argument)
  var frag = processor.transformToFragment(source, document);
  //insert the result subtree into the document, using the target element ID
  document.getElementById('updateTarget').appendChild(frag);
}

If you want to create a full output document rather than a subtree for insertion into another document, use the transformToDocument method rather than transformToFragment.


There's more to the world than Mozilla

An important disclaimer is in order: Web scripting of XML hasn't been fully standardized yet. Much of what I present here is not standardized across browsers. Since this is a Firefox-specific series, I haven't covered cross-browser modifications, but you would do well to learn how to accommodate the widest variety of user interfaces. Cross-browser libraries are available for JavaScript manipulation of XML and even XSLT. Use these if you aren't developing exclusively for Mozilla-based browsers. As always when working with Web scripts, keep accessibility considerations in mind. Also be mindful of the separation of content, processing, and presentation, and avoid embedding references to script until the last moment. You also should store and manage XML without such references, and insert them only at the last moment before sending to the browser.

You've now learned how to load and process a separate XML file from a Web script, how to invoke a script from an XML main document, and how to call the XSLT processor from a script. You can do much more XML processing from JavaScript, because all the browser's features are available to you. You can apply many of the scripting techniques you might have learned as Dynamic HTML (DHTML) to XML processing as well. ECMAScript for XML (E4X) is a set of language enhancements to JavaScript (technically ECMAScript) that makes XML processing with JavaScript even easier. This new standard adds specialized idioms for XML processing. Firefox 1.5 supports E4X, and I'll cover it in a future article. Modest and well-designed scripts are a great way to maintain the pizzazz of modern Web applications without sacrificing the powerful structural features of XML.

Resources

Learn

Get products and technologies

  • Firefox: Get the Mozilla-based Web browser that offers standards compliance, performance, security, and solid XML features. The current version is 1.5.0.4.

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into XML on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=XML, Web development
ArticleID=150536
ArticleTitle=XML in Firefox 1.5, Part 3: JavaScript meets XML in Firefox
publish-date=08012006