Try an online Ajax class for free!
Additional Resources

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);

Exercise: Loading XML Data into an HTML Page

Duration: 30 to 40 minutes.

In this exercise, you will read in an XML document and use it to modify the HTML page. The exercise files are shown below:

Code Sample: XML/Exercises/Presidents.xml

<?xml version="1.0" encoding="UTF-8"?>
<Presidents>
 <President id="GeorgeWashington">
  <Name>George Washington</Name>
  <Bio>
   <Para>On April 30, 1789, George Washington, standing on the balcony of Federal Hall on Wall Street in New York, took his oath of office as the first President of the United States. "As the first of every thing, in our situation will serve to establish a Precedent," he wrote James Madison, "it is devoutly wished on my part, that these precedents may be fixed on true principles." </Para>
   <Para>Born in 1732 into a Virginia planter family, he learned the morals, manners, and body of knowledge requisite for an 18th century Virginia gentleman.</Para>
  </Bio>
  <ImgPath>georgewashington.jpg</ImgPath>
 </President>
 <President id="JohnAdams">
  <Name>John Adams</Name>
  <Bio>
   <Para>Learned and thoughtful, John Adams was more remarkable as a political philosopher than as a politician. "People and nations are forged in the fires of adversity," he said, doubtless thinking of his own as well as the American experience.</Para>
   <Para>Adams was born in the Massachusetts Bay Colony in 1735. A Harvard-educated lawyer, he early became identified with the patriot cause; a delegate to the First and Second Continental Congresses, he led in the movement for independence.</Para>
  </Bio>
  <ImgPath>johnadams.jpg</ImgPath>
 </President>
</Presidents>

Code Sample: XML/Exercises/Presidents.html

<html>
<head>
<title>Presidents</title>
<link rel="stylesheet" type="text/css" href="Presidents.css">
<script type="text/javascript" src="DOM.js"></script>
<script type="text/javascript">
 function CreateMessageBox(e)
 {
  if (!e) e = window.event;
  var target = e.target || e.srcElement;
  var id = target.id;
  var msgContainer = document.getElementById("MessageBoxes");

  /*
   Load the Presidents.xml document into an xmlDoc variable.  Set the asynchronous flag to false.
   Use the RemoveTextNodes() function in the DOM.js library to remove all whitespace-only nodes.
   Read all the President tags as a nodeList into a presidents variable.
   
   Pass the node from the presidents nodeList whose id attribute matches the variable defined above to the GetTitleText() and GetBodyParas() functions and store the results in txtTitle and nlParas attributes. (Hint: you will need to loop through the nodeList).
  */
  
  var msgBox = AddChild(document, msgContainer, "div");
  var msgTitle = AddChild(document, msgBox, "h1",txtTitle);
  msgTitle.className = "PresTitle";
  
  var msgBodyPara, msgBodyParaTxt;
  
  /*
   Append a p tag to msgBox for each Para node in the nlParas nodeList.  The text of the p tag should be the same as the text of the Para element of the original XML document.
   Set className of new p element to "PresBodyPara".
  */
 }
 
 function Inactivate(e)
 {
  if (!e) e = window.event;
  var target = e.target || e.srcElement;
  target.disabled=true;
 }
 
 /*
  This function returns the text inside 
  the Name element of the President element
 */
 function GetTitleText(NODE)
 {
  return NODE.firstChild.firstChild.nodeValue;
 }
 
 /*
  This function returns a nodeList of Para elements
  inside the President element
 */
 function GetBodyParas(NODE)
 {
  return NODE.childNodes[1].getElementsByTagName("Para");
 }
 
 function init()
 {
  var buttons=GetElementsByClassName("btn");
  for (var i=0; i < buttons.length; i++)
  {
   AttachEvent(buttons[i],"click",CreateMessageBox);
   AttachEvent(buttons[i],"click",Inactivate);
  }
 }
 
 window.onload = init;
</script>
</head>

<body>
<form onsubmit="return false">
 <input class="btn" type="button" id="GeorgeWashington" value="George Washington" />
 <input class="btn" type="button" id="JohnAdams" value="John Adams" />
</form>
<div id="MessageBoxes"></div>
</body>
</html>
  1. Open XML/Exercises/Presidents.html for editing.
  2. Review the GetTitleText() and GetBodyParas() functions. Both functions expect a President node from the read-in XML document as the only parameter.
    • GetTitleText() returns the text of the Name element.
    • GetBodyParas() returns a nodeList of the Para elements inside the Bio element of the passed-in President node.
  3. Modify the CreateMessageBox() function as specified in the comments.
  4. Test your solution in the browser. After clicking each button, the page should look like this:

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. (see footnote)
  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.

Exercise: Handling responseXML

Duration: 20 to 30 minutes.

In this exercise, you will modify the Tables.html file we have just been discussing so that it has two links, one for showing employees and the other for showing orders. The orders data will be retrieved from Orders.jsp. The XML will be structured like this:

  1. Open XML/Exercises/Table.html for editing.
  2. Modify the BuildTable() function so that it takes a single argument: TABLE.
    • If "Employees" is passed in, set the variables as shown below:
      TableDataSrc = "Employees.jsp";
      RowElement = "Employee";
      ColumnNames = new Array("Salesperson","Title","Birth Date","Hire Date","Extension");
      ColumnWidths = new Array(24,24,21,21,8);
    • If "Orders" is passed in, set the same variables with the proper values. The widths of the columns should be 29%, 34%, 14%, and 23%. The column names should be meaningful.
    • The rest of the function can stay as is.
  3. Change the page so that it displays two links reading "Employees" and "Orders". When the user clicks on the Employees link, the employees should be listed on the page. When the user clicks on the Orders link, the orders should be listed.
  4. Test your solution by opening XML/Exercises/Table.html in a browser.

When the tables load, there is sometimes a delay that might make the user wonder what's happening. Provide a "Loading" message that shows up when the user clicks the link and goes away when the table appears.

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.

Footnotes

  1. Internet Explorer 6 does not support "table" as a value for display, so we have to set display to an empty string, which tells the browser to use the default display (i.e, table).

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.
Last updated on 2009-03-22

Use of http://www.learn-ajax-tutorial.com (Website) implies agreement to the following:

Copyright Information

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

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

No Printing or saving of pages or content on Website

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


Linking to Website

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


Warranties

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