XML and Ajax

In this lesson of the Ajax tutorial, you will learn...
  1. To create a new DOM Document with JavaScript.
  2. To load XML data from the server.
  3. To access, create and modify XML nodes.
  4. To create your own library of XML functions.
  5. To pass and receive XML with Ajax.
  6. To integrate XML data into an Ajax application.

Creating a DOM Document with JavaScript

The W3C DOM specifies an implementation property of the document object. While this property is supported by both Mozilla and Internet Explorer, its createDocument() method, which is used to create a virtual W3C DOM Document, is not supported by Internet Explorer 6. So, we have to branch our code. First, we'll examine the W3C standard method for creating a DOM Document.

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

The parameters have to do with namespaces and validation. For our purposes, the code should be written exactly as shown above.

Internet Explorer, as you might guess, uses an ActiveX control to create a new DOM Document:

var XmlDoc = new ActiveXObject("Microsoft.XMLDOM");

Both Mozilla and Internet Explorer support the non-standard load() method of the DOM Document to load XML files. A cross-browser function for creating and populating a DOM Document with an XML file is shown below.

function DomDoc(FILE,ASYNC)
{
 var Doc;
 try
 {
  Doc = document.implementation.createDocument("", "", null);
 }
 catch (exc)
 {
  Doc = new ActiveXObject("Microsoft.XMLDOM"); 
 }
 if (typeof ASYNC != "undefined") Doc.async=ASYNC;
 Doc.load(FILE);
 return Doc;
}

The value of the async property determines whether the Document can handle multiple processes simultaneously. Setting async to false ensures that one process is complete before another begins.

Accessing, Creating and Modifying XML Nodes

The properties and methods we saw for accessing and inserting nodes into HTML documents are also applicable to XML documents. They are shown again below.

Properties for Accessing Element Nodes
Property Description
childNodes[] A nodeList containing all of a node's child nodes.
firstChild A reference to a node's first child node.
lastChild A reference to a node's last child node
nextSibling A reference to the next node at the same level in the document tree.
parentNode A reference to a node's parent node.
previousSibling A reference to the previous node at the same level in the document tree.
Methods for Inserting Nodes
Method Description
appendChild() Takes a single parameter: the node to insert, and inserts that node after the last child node.
insertBefore() Takes two parameters: the node to insert and the child node that it should precede. The new child node is inserted before the referenced child node.
replaceChild() Takes two parameters: the new node and the node to be replaced. It replaces the old node with the new node and returns the old node.
setAttribute() Takes two parameters: the attribute name and value. If the attribute already exists, it replaces it with the new value. If it doesn't exist, it creates it.

The demo below shows how to load an XML document, to read parts of it into a string and then output that string as HTML to a part of the page.

Code Sample: XML/Demos/XML.html

<html>
<head>
<title>XML</title>
---- Code Omitted ----
<script type="text/javascript" src="DOM.js"></script> <script type="text/javascript"> var XmlDoc; var NodesAsList=""; function init() { document.getElementById("btnLoad").disabled=false; document.getElementById("btnDisplay").disabled=true; document.getElementById("btnClear").disabled=true; } function LoadXml() { XmlDoc = DomDoc('Beatles.xml',false); NodesAsList=""; document.getElementById("btnLoad").disabled=true; document.getElementById("btnDisplay").disabled=false; } function DisplayNodes(DOC) { NodesAsList += "<ol style='font-size:smaller;'>"; for (var i=0; i < DOC.childNodes.length; i++) { Node=DOC.childNodes[i]; Class = (Node.nodeType==TEXT_NODE) ? "TextNode" : "NonTextNode"; NodesAsList += "<li class=" + Class + ">"; NodesAsList += Node.nodeName + ": " + Node.nodeValue; if (Node.hasChildNodes()) { DisplayNodes(Node); } NodesAsList += "</li>"; } if(DOC.nodeType==ELEMENT_NODE) { for (var i=0; i < DOC.attributes.length; i++) { NodesAsList += "<li class='Attribute'>"; NodesAsList += DOC.attributes[i].nodeName + ": " + DOC.attributes[i].nodeValue; NodesAsList += "</li>"; } } NodesAsList += "</ol>"; document.getElementById("divXml").innerHTML = NodesAsList; document.getElementById("btnDisplay").disabled=true; document.getElementById("btnClear").disabled=false; } function ClearXml() { document.getElementById("divXml").innerHTML = ""; NodesAsList=""; document.getElementById("btnDisplay").disabled=false; document.getElementById("btnClear").disabled=true; } </script> </head> <body onLoad="init();"> <form> <input type="button" id="btnLoad" value="Load XML" onClick="LoadXml();"> <input type="button" id="btnDisplay" value="Display XML" onClick="DisplayNodes(XmlDoc);"> <input type="button" id="btnClear" value="Clear XML" onClick="ClearXml();"> </form> <div id="divXml" style="font-size:xx-large;"></div> </body> </html>
Code Explanation

The browser output is shown below:

  1. The page loads and shows three buttons: "Load XML", "Display XML", and "Clear XML". Only the first is enabled.
  2. When the user clicks on the "Load XML" button, the DOM Document is created and populated using the DomDoc() function we saw earlier. Aside from the buttons, the page does not change but the new DOM Document is available for processing.
  3. The user can then click on the "Display XML" button, which builds an HTML list as a string and assigns it to the innerHTML property of the divXml div. The list appears on the page.
  4. The user then has the option to click on the "Clear XML" button, which simply changes the innerHTML property of the divXml div to an empty string, thereby removing the list from the page.

Note that the DOM Document's async property is set to false to ensure that the XML document is completely loaded before we begin reading its nodes.

Creating an AddChild() Function

Adding nodes to the DOM can be a lot of work. It can involve several steps, including:

  1. Creating a new element node.
  2. Appending the element node as a child of another element.
  3. Creating a new text node.
  4. Appending the text node to the new element node.
  5. Adding one or more attributes with the setAttribute() method of the new element.

The user defined function below makes this process easier.

function AddChild(DOC, PARENT, CHILD, CHILDTEXT, ATTRIBUTES)
{
 var ChildElement;
 if (typeof(CHILD) == "string")
 {
  ChildElement = DOC.createElement(CHILD);
 }
 else
 {
  ChildElement = CHILD;
 }
 
 if (typeof CHILDTEXT != "undefined" && CHILDTEXT != null)
 {
  var ChildText = DOC.createTextNode(CHILDTEXT);
  ChildElement.appendChild(ChildText);
 }
 
 if (typeof ATTRIBUTES != "undefined")
 {
  var Attributes = ATTRIBUTES;
  for (Att in Attributes)
  {
   ChildElement.setAttribute(Att,Attributes[Att]); 
  }
 }
 
 PARENT.appendChild(ChildElement);
 return ChildElement;
}

This AddChild() function requires the first three parameters: the DOM Document, parent-to-be node, and child-to-be (either an existing node or the name of the node-to-be). The function can also take the text that will become the contents of the new child and an array of attributes for the new child. The function would be called as follows.

var NewChild = AddChild(DomDocument, ParentNode, ChildNodeOrName, ChildText, AttributesArray);

Receiving XML Responses

As you have probably guessed, all this manipulation of XML with JavaScript would eventually take us back to Ajax and the XMLHttpRequest object. We will start with a script that makes a call to the web server to get data to populate an HTML table. When it first loads, the page has a simple link that reads "Show Table". When the link is clicked, the table shows up on the page:

The page works as follows:

  1. When the user clicks on the link, JavaScript is used to make a call to Employees.jsp on the web server.
  2. The Employees.jsp page is used to make a call to the database, which returns a recordset of all employees in the Employees table. That recordset is looped through to create and return an XML document to the browser. The structure of the XML document is shown below:(The nodes for Employees 3 through 9 are collapsed in the diagram above.)
  3. As soon as the browser receives and completely loads the response, it iterates through the Employee nodes creating an HTML table and then displays that table on the page.

Let's take a look at the code.

Code Sample: XML/Demos/Table.html

<html>
<head>
<title>Dynamic Table</title>
<link rel="stylesheet" type="text/css" href="Table.css">
<script type="text/javascript" src="../../prototype.js"></script>
<script src="DOM.js" type="text/javascript"></script>
<script type="text/javascript">
 var MainTable, TableDataSrc, RowElement, ColumnNames, ColumnWidths;
 
 MainTable = "MainTable";
 TableDataSrc = "Employees.jsp";
 RowElement = "Employee";
 ColumnNames = new Array("Salesperson","Title","Birth Date","Hire Date","Extension");
 ColumnWidths = new Array(24,24,21,21,8);
 
 function GetRows()
 {
  new Ajax.Request(TableDataSrc, 
   {
    method: "get",
    onComplete: ShowResult
   });
 }
   
 function BuildTable()
 {
  RemoveAllChildren(document.getElementById("HeaderRow"));
  RemoveAllChildren(document.getElementById("BodyRows"));
  CreateHeaderRow();
  GetRows();
 }
 
 function CreateHeaderRow()
 {
  var HeaderRow = document.getElementById("HeaderRow");
  
  for (var i=0; i<ColumnNames.length; i++)
  {
   var width=ColumnWidths[i];
   var atts = new Object();
   atts["width"] = width+"%";
   AddChild(document, HeaderRow, "th", ColumnNames[i], atts);
  }
 }
 
 function ShowResult(REQ)
 {
  var xmlDoc = REQ.responseXML.documentElement;
  RemoveTextNodes(xmlDoc,true);
  
  var OutputResult = document.getElementById("BodyRows");
  var RowData = xmlDoc.getElementsByTagName(RowElement);
  AddTableRowsFromXmlDoc(RowData,OutputResult);
  document.getElementById(MainTable).style.display = "";
 }
 
 function AddTableRowsFromXmlDoc(XmlNodes,TableNode)
 {
  for (i=0; i<XmlNodes.length; i++)
  {
   var newRow = AddChild(document, TableNode, "tr");
   
   if (i%2==0)
    newRow.className = "EvenRow";
   else
    newRow.className = "OddRow";
   
   for (j=0; j<XmlNodes[i].childNodes.length; j++)
   {
    AddChild(document, newRow, "td", XmlNodes[i].childNodes[j].firstChild.nodeValue);
   }
  }
 }
</script>
</head>

<body>
<a href="javascript:void(0);" onclick="BuildTable()">Show Table</a>
<div id="divBody">
 <table border="1" width="780" cellpadding="2" cellspacing="0" id="MainTable" align="center" style="display:none">
 <thead>
  <tr id="HeaderRow"></tr>
 </thead>
 <tbody id="BodyRows"></tbody>
 </table>
</div>
</html>
Code Explanation

Let's walk through the code step by step in the order the page is processed.

  1. As the page loads, the following variables are declared and assigned values:
    var MainTable, TableDataSrc, RowElement, ColumnNames, ColumnIds, ColumnWidths;
    
    MainTable = "MainTable";
    TableDataSrc = "Employees.jsp";
    RowElement = "Employee";
    ColumnNames = new Array("Salesperson","Title","Birth Date","Hire Date","Extension");
    ColumnWidths = new Array(24,24,21,21,8);
    • MainTable holds the name of the div in which the table will be displayed.
    • TableDataSrc holds the name of the server-side script that will return the XML content.
    • RowElement holds the name of the node in the XML document that will correspond to a single row. In this case, each Employee node will become a single table row.
    • ColumnNames and ColumnWidths hold arrays containing values that will correspond to the table header text and table column widths, respectively.
  2. The initial HTML in the body of the page looks like this:
    <a href="javascript:void(0);" onclick="BuildTable()">Show Table</a>
    <div id="divBody">
     <table border="1" width="780" cellpadding="2" cellspacing="0" id="MainTable" align="center">
     <thead>
      <tr id="HeaderRow"></tr>
     </thead>
     <tbody id="BodyRows"></tbody>
     </table>
    </div>
    We'll use JavaScript/Ajax to populate the table header row (HeaderRow) and table body rows (BodyRows).
  3. The onclick event of the "Show Table" link calls the BuildTable() function (shown below).
    function BuildTable()
    {
     RemoveAllChildren(document.getElementById("HeaderRow"));
     RemoveAllChildren(document.getElementById("BodyRows"));
     CreateHeaderRow();
     GetRows();
    }
    This function uses the RemoveAllChildren() function from our DOM.js library to empty out the table header row and all the table body rows. It then calls CreateHeaderRow() and GetRows(). We'll examine those next.
  4. CreateHeaderRow() looks like this.
    function CreateHeaderRow()
    {
     var HeaderRow = document.getElementById("HeaderRow");
     
     for (var i=0; i<ColumnNames.length; i++)
     {
      var width=ColumnWidths[i];
      var Atts = new Array();
      Atts["width"] = width+"%";
      Atts["id"] = ColumnIds[i];
      AddChild(document, HeaderRow, "th", ColumnNames[i], Atts);
     }
    }
    This function loops through the ColumnNames array and uses the AddChild() function from our DOM.js library to insert th elements in our table header row.
  5. GetRows() takes care of the call to the server using Prototype's Ajax.Request() constructor.
    function GetRows()
    {
     var ContentDiv = document.getElementById(MainTable);
     new Ajax.Request(TableDataSrc, 
      {
       method: "get",
       onComplete: ShowResult
      });
    }
  6. The ShowResult() function is shown below.
    function ShowResult(REQ)
    {
     var xmlDoc = REQ.responseXML.documentElement;
     RemoveTextNodes(xmlDoc,true);
     
     var OutputResult = document.getElementById("BodyRows");
     var RowData = xmlDoc.getElementsByTagName(RowElement);
     AddTableRowsFromXmlDoc(RowData,OutputResult);
     document.getElementById(MainTable).style.display = "";
    }
    The first two lines of the function store the document element of the DOM Document in the XmlDoc variable and remove all whitespace-only text nodes. The next three lines store the table body in the OutputResult variable and the XML nodeList in the RowData variable and pass both to the AddTableRowsFromXmlDoc() function. The last line makes the table appear on the page.
  7. The AddTableRowsFromXmlDoc() function is shown below.
    function AddTableRowsFromXmlDoc(XmlNodes,TableNode)
    {
     for (i=0; i<XmlNodes.length; i++)
     {
      var newRow = AddChild(document, TableNode, "tr");
      
      if (i%2==0)
       newRow.className = "EvenRow";
      else
       newRow.className = "OddRow";
      
      for (j=0; j<XmlNodes[i].childNodes.length; j++)
      {
       AddChild(document, newRow, "td", XmlNodes[i].childNodes[j].firstChild.nodeValue);
      }
     }
    }
    This function loops through the passed-in XML nodeList, creating a new table row for each node in the list. It then sets the class name of the row. The inner loop iterates through all the child nodes (e.g, Salesperson, Title, etc.) of the current node (e.g, the current Employee), creating a table data cell for each.

XML and Ajax Conclusion

XML plays an integral role in most Ajax applications. In this lesson, you have learned to create XML objects with JavaScript and to receive XML data from the server.

To continue to learn Ajax go to the top of this page and click on the next lesson in this Ajax Tutorial's Table of Contents.

Use of this website implies agreement to the following:

Copyright Information

All pages and graphics on this Web site are the property of Webucator, Inc. unless otherwise specified.

None of the content on this website may be redistributed or reproduced in any way, shape, or form without written permission from Webucator, Inc.

No Printing or saving of web pages

This content may not be printed or saved. It is for online use only.


Linking to this website

You may link to any of the pages on this website; however, you may not include the content in a frame or iframe without written permission from Webucator, Inc.


Warranties

This website is provided without warranty of any kind. There are no guarantees that use of the site will not be subject to interruptions. All direct or indirect risk related to use of the site is borne entirely by the user. All code and explanations provided on this site are provided without warranties to correctness, performance, fitness, merchantability, and/or any other warranty (whether expressed or implied).

For individual private use only

You agree not to use this online manual to deliver or receive training. If you are delivering or attending a class that is making use of this online manual, you are in violation of our terms of service. Please report any abuse to courseware@webucator.com. If you would like to deliver or receive training using this manual, please fill out the form at http://www.webucator.com/Contact.cfm.