XML and Ajax
- To create a new DOM Document with JavaScript.
- To load XML data from the server.
- To access, create and modify XML nodes.
- To create your own library of XML functions.
- To pass and receive XML with Ajax.
- 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.
| 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. |
| 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>
The browser output is shown below:
![]()
- The page loads and shows three buttons: "Load XML", "Display XML", and "Clear XML". Only the first is enabled.
- 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.
- 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.
- 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:
- Creating a new element node.
- Appending the element node as a child of another element.
- Creating a new text node.
- Appending the text node to the new element node.
- 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:
- When the user clicks on the link, JavaScript is used to make a call to Employees.jsp on the web server.
- 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.) - 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>
Let's walk through the code step by step in the order the page is processed.
- 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.
- The initial HTML in the body of the page looks like this:
We'll use JavaScript/Ajax to populate the table header row (HeaderRow) and table body rows (BodyRows).<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>- The onclick event of the "Show Table" link calls the BuildTable() function (shown below).
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.function BuildTable() { RemoveAllChildren(document.getElementById("HeaderRow")); RemoveAllChildren(document.getElementById("BodyRows")); CreateHeaderRow(); GetRows(); }- CreateHeaderRow() looks like this.
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.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); } }- 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 }); }- The ShowResult() function is shown below.
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.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 AddTableRowsFromXmlDoc() function is shown below.
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.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); } } }
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.
