Team LiB
Previous Section Next Section

The DOM and XML

Now considering that we can eventually present XML in a displayable format in a browser, readers may wonder how XML can be used to manipulate the document. In the case of XML that is transformed on the server side using XSLT, there is nothing special to consider as the output would be (X)HTML and thus we would use standard JavaScript and DOM techniques. However, if the document is delivered natively as XML, you might wonder how to manipulate the document? Hopefully, you already know the answer—use the DOM!

As discussed in Chapter 10, the DOM represents a document as a tree of nodes including elements, text data, comments, CDATA sections, and so on. The elements in this tree can be HTML elements, as we have seen so far, or they could be XML elements including things like our <<burger>> or <<employee>> tags. We could then access these elements and look at them and even modify their contents.

Internet Explorer Example

To demonstrate JavaScript, XML, and the DOM in action, let’s use Internet Explorer 5.5 or better to load an XML document containing our employee directory and see if we can manipulate it. First, to load in the document we create an instantiation of Microsoft’s XML parser using the JScript-specific ActiveXobject. Once the object is created, we load the appropriate XML document into memory. In this case, it is the pure XML file of employee records we saw earlier without style sheets or other references.

var xmldoc = new ActiveXObject("Microsoft.XMLDOM");
xmldoc.async = false;
xmldoc.load("staff2.xml");

Once loaded, we can then use the DOM to manipulate it. For example, we can access the root element of the document (<<directory>>) using

var rootElement = xmldoc.documentElement;

then we might alert out its nodeName property as shown in this example.

<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">>
<<html xmlns="http://www.w3.org/1999/xhtml">>
<<head>>
<<title>>XML Demo<</title>>
<<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />>
<</head>>
<<body>>
<<script type="text/jscript">>
<<!--
 var xmldoc = new ActiveXObject("Microsoft.XMLDOM");
 xmldoc.async = false;
 xmldoc.load("staff.xml");

 var rootElement = xmldoc.documentElement;
//-->>
<</script>>
<<form action="#" method="get">>
 <<input type="button" value="show node"
 onclick="alert(rootElement.nodeName);" />>
<</form>>
<</body>>
<</html>>

We should see

We could further use the DOM properties and methods we are familiar with from Chapter 10. Consider for example the following function that deletes the last node:

function deleteLastElement()
{
  var rootElement = xmldoc.documentElement;
  if (rootElement.hasChildNodes())
     rootElement.removeChild(rootElement.lastChild);
}

Really the only difference here is the use of the xmldoc object we created to reference the XML document rather than just plain document, which would reference the HTML Document object. Otherwise, the manipulations are the same as with HTML.

Given the previous example, we now present a simple demonstration of adding, deleting, and displaying data from an XML file under Internet Explorer 5.0 or better. The rendering of this example is shown in Figure 20-9.

Click To expand
Figure 20-9: XML document directly manipulated with JScript and the DOM
<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">>
<<html xmlns="http://www.w3.org/1999/xhtml">>
<<head>>
<<title>>XML Demo<</title>>
<<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />>
<</head>>
<<body>>
<<script type="text/javascript">>
<<!--
/* invoke parser and read in document */
var xmldoc = new ActiveXObject("Microsoft.XMLDOM");
xmldoc.async = false;
xmldoc.load("staff.xml");

function deleteLastElement()
{/* find root element and delete its last child */
  var rootElement = xmldoc.documentElement;
  if (rootElement.hasChildNodes())
  rootElement.removeChild(rootElement.lastChild);
}

function addElement()
{
  var rootElement = xmldoc.documentElement;
  /* create employee element*/

  var newEmployee = xmldoc.createElement('employee');
 
  /* create child elements and text values and append one by one */
  var newName = xmldoc.createElement('name');
  var newNameText = xmldoc.createTextNode(document.myform.namefield.value);
  newName.appendChild(newNameText);
  newEmployee.appendChild(newName);

  var newTitle = xmldoc.createElement('title');
  var newTitleText = xmldoc.createTextNode(document.myform.titlefield.value);
   newTitle.appendChild(newTitleText);
   newEmployee.appendChild(newTitle);
   
   var newPhone = xmldoc.createElement('phone');
   var newPhoneText = xmldoc.createTextNode(document.myform.phonefield.value);
     newPhone.appendChild(newPhoneText);
     newEmployee.appendChild(newPhone);
 
     var newEmail = xmldoc.createElement('email');
     var newEmailText = xmldoc.createTextNode(document.myform.emailfield.value);
     newEmail.appendChild(newEmailText);
     newEmployee.appendChild(newEmail);
 
  /* append completed record to the document */
  rootElement.appendChild(newEmployee);
}

function dump(string)
   {
    var currentvalue=document.myform.showxml.value;
       currentvalue+=string;
       document.myform.showxml.value = currentvalue;
   }
   
function display(node)
{
  var type = node.nodeType;
      if (type == 1)
        { // open tag
            dump("\<<" + node.tagName);
          // output the attributes if any
          attributes = node.attributes;
          if (attributes)
          {
              var countAttrs = attributes.length;
              var index = 0;
              while(index << countAttrs)
             {
                   att = attributes[index];
                   if (att)
              dump(" " + att.name + "=" + att.value);
                   index++;
                  }
             }
         // recursively dump the children
         if (node.hasChildNodes())
            {
              // close tag
              dump(">>\n");
              // get the children
              var children = node.childNodes;
              var length = children.length;
              var count = 0;
              while(count << length) 
               {
                child = children[count];
                display(child);
                count++;
               }
             dump("<</" + node.tagName + ">>\n");
            }
          else 
              dump("/>>\n");
          }
     else if (type == 3) 
            { // if it's a piece of text just dump the text
              dump(node.data+"\n");
            }
    }

//-->>
<</script>>

<<form id="myform" name="myform" action="#" method="get">>
<<strong>>XML Document:<</strong>><<br />>
<<textarea id="showxml" name="showxml" rows="10" cols="40">><</textarea>>
<<br />><<br />><<br />>
Name: <<input type="text" name="namefield" id="namefield" size="50" />><<br />>
Title: <<input type="text" name="titlefield" id="titlefield" size="30" />>
<<br />>
Phone: <<input type="text" name="phonefield" id="phonefield" size="20" />>
<<br />>
Email: <<input type="text" name="emailfield" id="emailfield" size="20" />>
<<br />>

<<input type="button" value="add record"
 onclick="addElement();document.myform.showxml.value='';
display(xmldoc.documentElement);" />>

<<input type="button" value="delete last record"   
 onclick="deleteLastElement();document.myform.showxml.value='';
display(xmldoc.documentElement);" />>

<<input type="button" value="redisplay XML document" 
onclick="document.myform.showxml.value='';
display(xmldoc.documentElement);" />>
<</form>>

<<script type="text/javascript">>
<<!--
  /* show initial XML document */
  display(xmldoc.documentElement);
//-->>
<</script>>
<</body>>
<</html>>

If it felt somewhat clunky to output the XML items of the page manually to the HTML form field, you’re right. Microsoft provides a method called data binding, discussed later

in this chapter, that is much cleaner. The point here was to explicitly show the XML tags during the manipulation. The next examples will work in XML even more directly.

Mozilla Example

Nothing is ever easy in the world of emerging standards. Mozilla-based browsers do not handle XML in quite the same fashion as Internet Explorer does. In order to make the last example compatible with Mozilla, we would have to use document.implementation.createDocument() and then load up the document after setting the async property to true or false and running the load() method, as shown here:

if (document.implementation&&document.implementation.createDocument)
      xmldoc=document.implementation.createDocument("","",null);
xmldoc.async = false;
xmldoc.load("staff.xml");

Obviously, with the use of an if statement we could make the previous example work in both browsers.

if (window.ActiveXObject) 
    var xmldoc=new ActiveXObject("Microsoft.XMLDOM");
else if (document.implementation&&document.implementation.createDocument)
      xmldoc=document.implementation.createDocument("","doc",null);
xmldoc.async = false;
xmldoc.load("staff.xml");

We leave it to the reader to make this modification to the previous example to make it cross-browser. However, take notice of the fact that once again differences abound as Mozilla represents whitespace in its DOM tree and doesn’t like to load the document initially. Just click the provided “Redisplay XML Document” button.

While it would seem from the previous paragraphs that Mozilla and IE aren’t far apart, that isn’t quite true as Mozilla provides the possibility of directly using XML and bringing in (X)HTML. IE can handle something like this but not very cleanly, as we’ll see.

In this particular example, we will use XML directly rather than XML accessed via an (X)HTML document. Because of this, we do not require a special XMLDocument object; instead, we reference the Document object just as we would expect. For example, to print out the nodeName property of the root element we would use

alert(document.documentElement.nodeName)

However, we need to bring in script to the XML document and then trigger it. There is no easy way to do that in XML so we rely on (X)HTML tags such as form elements, as shown in this next example.

<<?xml version="1.0"?>>
<<?xml-stylesheet href="staff.css" type="text/css"?>>
    
<<directory xmlns:html="http://www.w3.org/1999/xhtml"
              xmlns:xlink="http://www.w3.org/1999/xlink">>
<<employee>>
      <<name>>Fred Brown<</name>>
      <<title>>Widget Washer<</title>>
      <<phone>>(543) 555-1212<</phone>>
      <<email>>fbrown@democompany.com<</email>>
<</employee>>

<<html:form>>
      <<html:input type="button" id="test"
 onclick="alert(document.documentElement.nodeName);" value="Show Root
 Element"/>>
<</html:form>>
<</directory>>

Like the previous example under Internet Explorer, this will simply display a dialog showing the directory element. Oddly, while you can get this example to work under Internet Explorer, it will display the <<html>> tag rather than <<directory>> tag as the root element! Once again the browser vendors do things differently. In this case, IE assumes most things are HTML whether they indicate it or not and builds a DOM tree to deal with missing elements. We can hack our way around this, but it won’t be clean.

It is easy enough to adopt our more complex DOM example from the preceding section to Mozilla if we can just include the script code in the file. We’ll use a linked script to do the trick using yet another embedded (X)HTML tag like <<html:script src="xmldemo.js" />>. The complete example is shown here.

File: mozillademo.xml

<<?xml version="1.0"?>>
<<?xml-stylesheet href="staff.css" type="text/css"?>>

<<directory xmlns:html="http://www.w3.org/1999/xhtml"
           xmlns:xlink="http://www.w3.org/1999/xlink">>
     
<<html:form id="myform" name="myform">>

<<html:label>>Name: <<html:input type="text" name="namefield" id="namefield"
 size="50" />><</html:label>><<html:br />>
<<html:label>>Title: <<html:input type="text" name="titlefield" id="titlefield"
 size="30" />><</html:label>><<html:br />>
<<html:label>>Phone: <<html:input type="text" name="phonefield" id="phonefield"
 size="20" />><</html:label>><<html:br />>
<<html:label>>Email: <<html:input type="text" name="emailfield" id="emailfield"
 size="20" />><</html:label>><<html:br />>

<<html:input type="button" value="add record" onclick="addElement()" />>
<<html:input type="button" value="delete last record"
 onclick="deleteLastElement()" />>
<<html:hr />>
<</html:form>>

<<employee>>
      <<name>>Fred Brown<</name>>
      <<title>>Widget Washer<</title>>
      <<phone>>(543) 555-1212<</phone>>
      <<email>>fbrown@democompany.com<</email>>
<</employee>>

<<employee>>
      <<name>>Cory Richards<</name>>
      <<title>>Toxic Waste Manager<</title>>
      <<phone>>(543) 555-1213<</phone>>
      <<email>>mrichards@democompany.com<</email>>
<</employee>>

<<employee>>
      <<name>>Tim Powell<</name>>
      <<title>>Big Boss<</title>>
      <<phone>>(543) 555-2222<</phone>>
      <<email>>tpowell@democompany.com<</email>>
<</employee>>

<<employee>>
      <<name>>Samantha Jones<</name>>
      <<title>>Sales Executive<</title>>
      <<phone>>(543) 555-5672<</phone>>
      <<email>>jones@democompany.com<</email>>
<</employee>>

<<employee>>
      <<name>>Eric Roberts<</name>>
      <<title>>Director of Technology<</title>>
      <<phone>>(543) 567-3456<</phone>>
      <<email>>eric@democompany.com<</email>>
<</employee>>

<<employee>>
      <<name>>Frank Li<</name>>
      <<title>>Marketing Manager<</title>>
      <<phone>>(123) 456-2222<</phone>>
      <<email>>fli@democompany.com<</email>>
<</employee>>

<<html:script src="mozillaxmldemo.js" />>

<</directory>>

File: mozillaxmldemo.js

function deleteLastElement()
{

       /* Get list of the employee elements */
       var employeeList = document.getElementsByTagName('employee');
       if (employeeList.length >> 0)
         { // find the last employee and delete it
          var toDelete = employeeList.item(employeeList.length-1);
          document.documentElement.removeChild(toDelete);
         }
       else
         alert('No employee elements to delete');
}


function addElement()
{
       var rootElement = document.documentElement;
  
       var name = document.getElementById('namefield').value;
       var title = document.getElementById('titlefield').value;
       var phone = document.getElementById('phonefield').value;
       var email = document.getElementById('emailfield').value;

       /* create employee element*/
       var newEmployee = document.createElement('employee');

       
       /* create child elements and text values and append one by one */
       var newName = document.createElement('name');
       var newNameText = document.createTextNode(name);
       newName.appendChild(newNameText);
       newEmployee.appendChild(newName);

       var newTitle = document.createElement('title');
       var newTitleText = document.createTextNode(title);
       newTitle.appendChild(newTitleText);
       newEmployee.appendChild(newTitle);
       
       var newPhone = document.createElement('phone');
       var newPhoneText = document.createTextNode(phone);
       newPhone.appendChild(newPhoneText);
       newEmployee.appendChild(newPhone);
      
       var newEmail = document.createElement('email');
       var newEmailText = document.createTextNode(email);
       newEmail.appendChild(newEmailText);
       newEmployee.appendChild(newEmail);
       
       /* append completed record to the document */
       rootElement.appendChild(newEmployee);
}

A rendering of this example under Netscape 7 that also includes the staff.css file used earlier is presented in Figure 20-10.

Click To expand
Figure 20-10: Netscape 6 and Mozilla can easily manipulate XML directly.
Note 

The Mozilla implementation of XML can be very buggy and may require a manual reload to get the demo to work. You also may try to add a JavaScript window reload() as well. Note that the demo also crashed under different versions of the Mozilla engine, but worked under others.

Now in order to get this example to work in Internet Explorer, you are going to have to hack in the delete and insert functions to find the proper location since IE thinks the page is HTML. The easiest approach would be to first determine if we are dealing with Internet Explorer, and then if so find the real root of the document (<<directory>>) and then use the DOM methods appropriately. This code fragment shows the portion of the function addElement() in the previous example that would have to be changed.

 /* append completed record to the document */
if (document.all)
  {
  /* hack this in because IE thinks it is looking at HTML */    
  var insertSpot = document.getElementsByTagName('directory');
     insertSpot[0].appendChild(newEmployee);
 }
else
 rootElement.appendChild(newEmployee);

The deleteLastElement() function could be modified in a similar manner. If you think this is a hack, it is. As of the writing of this edition, there is just not a clean way for Internet Explorer to handle this approach to direct browser use of XML. However, on the other hand, IE does support a very interesting way to handle embedded XML data in the form of data islands.


Team LiB
Previous Section Next Section