| By Anil Sharma | Article Rating: |
|
| April 16, 2007 08:00 AM EDT | Reads: |
2,197 |
This content is reprinted from Real-World AJAX: Secrets of the Masters published by SYS-CON Books. To order the entire book now along with companion DVDs for the special pre-order price, click here for more information. Aimed at everyone from enterprise developers to self-taught scripters, Real-World AJAX: Secrets of the Masters is the perfect book for anyone who wants to start developing AJAX applications.
XMLToDataSet Class
In the previous sections, we've discussed the HTML template and a component class called "NavBar." Next we'll discuss more templates and the component classes that are bound to the application data. But before that, we'll discuss how the data is converted to the internal form. The XML document received by the client is converted to the JavaScript object tree. The XMLToDataSet class is responsible for the conversion.
The code below shows how to convert the response XML of an AJAX call to a JavaScript object tree using XMLToDataSet.
var xmlToDS = new XMLToDataSet();
xmlToDS.fromXml(response.responseXML);
Listing 5.9 and embedded comments explain the XMLToDataSet class in detail. Also see the section above on Model for details of the XML and JavaScript object tree.
Listing 5.9 - XMLToDataSet Class
/**
The following function (class) converts XML to a tree of JS objects. "this.data" gives
the top-level object that corresponds to the first element of the XML (excluding header).
Each nested element is represented by a JavaScript object. By default a nested element
is treated as a list type (one-to-many association). Therefore it is added to an array,
which in-turn is stored as the attribute of the parent object using "tag name" as the
attribute name. If a nested element has a special attribute called association="one-toone"
then the object is stored directly as the attribute of the parent object. Simple
nested elements are stored as the scalar attributes of the corresponding JavaScript object.
'class' and 'id' are treated as special attributes. 'class' indicates the class of the
object. 'id' represents object identifier. They are stored using __class and __id as attribute
names.
*/
function XMLToDataSet() {
this.data = new Object();
}
XMLToDataSet.prototype.fromXml = function(xml) {
// Special case when JavaScript is loaded from the file system during the development and
// data is loaded from a WEB server. Firefox requires that the privilege is enabled.
// It is not required if JavaScript and data are accessed from the same server.
if (!is_ie && is_ns) {
try {
netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
} catch (e) {
alert("Permission UniversalBrowserRead denied.");
}
}
// top level object
this.data = new Object();
var nodes = xml.childNodes;
var rootNode = nodes[nodes.length-1];
// store top level objects class and attributes
if (rootNode.attributes != null) {
var attr = rootNode.attributes.getNamedItem('class');
if (attr != null) {
this.data.__className = attr.value;
}
attr = rootNode.attributes.getNamedItem('id');
if (attr != null) {
this.data.__id = attr.value;
}
}
this.processXMLNode(this.data, rootNode);
return this.data;
}
XMLToDataSet.prototype.processXMLNode = function(data, parent) {
var nodes = parent.childNodes;
// process the child nodes of the give parent node.
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
var doProcess = node.nodeType == Node.ELEMENT_NODE ||
node.nodeType == Node.DOCUMENT_NODE
|| node.nodeType == Node.DOCUMENT_FRAGMENT_NODE;
if (!doProcess) {
continue;
}
var a = node.childNodes;
if (a.length == 1) {
var gchild = a[0];
// This is a simple element case. Store as the attribute of the given object (data).
if (gchild.nodeType == Node.TEXT_NODE) {
data[node.tagName] = gchild.data;
continue;
}
}
// This is a nested element case.
// Extract special attributes, class, id and association.
var myClass = null;
var id = null;
var assoc = null;
var attrs = node.attributes;
if (attrs != null) {
for (var j = 0; j<attrs.length; j++) {
var attr = attrs[j];
data[attr.name] = attr.value;
if (attr.name == 'class') {
myClass = attr.value;
} else if (attr.name == 'id') {
id = attr.value;
} else if (attr.name == 'association') {
assoc = attr.value;
}
}
}
// tag name of the nested element that will be used as the attribute name
var s = nodes[i].tagName;
// JavaScript object is created for the nested element.
var data2 = new Object();
// store reference of the parent usign special attribute name
data2.__parent = data;
// store class and id using special attribute names
data2.__className = myClass;
data2.__id = id;
if (assoc == 'one_to_one') {
// it is an one-to-one association case.
// add the object as an attribute of the parent.
data[s] = data2;
} else {
// it is a one-to-many association case
// retrieve the list (array) that stores the multiple nested elements of the same
// tag name. This will be present if this is not the first nested element.
var c = data[s];
if (c) {
// if the list (array) is present, simply add to it.
c.push(data2);
} else {
// this is the first nested element of the tag name given by 's'.
// constuct a new list
c = new Array();
// list parent is the parent of the objects held by it
c.__parent = data;
// the class of the parent is same as the class of objects held by it
c.__className = myClass;
// point to the list from the parent using tag name as the attribute
data[s] = c;
c.push(data2);
}
}
// recursively process the nested elements of the 'data2'.
this.processXMLNode(data2, node);
}
}
Project List Page
Refer to MainPage_4.html for the discussion in this section.
So far we've discussed the navigation bar using the HTML template and component class and how to use XMLToDataSet to convert the response XML to a JavaScript object tree. Our controllers attached to the navigation bar did nothing but display a "not yet implemented" message. In this section we'll extend one of the controllers to display a table, use an AJAX call to retrieve data from the server, and use XMLToDataSet to convert it to JavaScript objects to be bound to the table.
Our goal is to display a list of projects in the "MainArea." The list of projects is also our default page so it's displayed at startup. Later, it's displayed every time the user clicks on the "Project List" item on the navigation bar.
Say we ask our UI designer to design a page for the project list with some dummy data. She designs the page and names it ProjectList.html. The screenshot below shows the page when it's loaded in a browser.
Next, we want to use ProjectList.html as a template and attach it to the "MainArea" of the MainPage-4.html. In ProjectList.js, we write the following JavaScript code to do so.
Listing 5.10 - Displaying ProjectList.html Template
var projectList = null;
var projectListContainer = null;
function displayProjectList() {
if (this.projectListContainer == null) {
// project list template is not yet loaded
// load the template
loadProjectListElement();
} else {
// project list template has been loaded
// simply attach it
// See Util.js for the details of attachElement function.
attachElement('MainArea', projectListContainer);
}
}
function loadProjectListElement() {
var req = new DataRequest(basePageURL + 'ProjectList.html',
requestCompletedProjectListElement,
requestFailedProjectListElement,
requestTimedoutProjectListElement);
ajaxEngine.processRequest(req);
}
function requestCompletedProjectListElement(response) {
projectListContainer = setElementFromHtml(response.responseText,
'MainArea',
'projectListContainer');
}
When we invoke "displayProjectList" from the "initialize" method of the MainPage-4.html, the MainPage-4.html shown below is the result.
function initialize() {
loadTopNaviagation();
displayProjectList();
}
To display the project list when a user selects the Project List item from the navigation bar, we change the event listener in the TopNavBar.js as follows:
navBar.setHandler("projectList", displayProjectList);
SimpleTable
In the last section, we saw how to load the ProjectList.html template and display it in the "MainArea" of the page. We also modified the controller's event listener to display it when the user clicks on the "Project List" item of the navigation bar. The data displayed inside is some dummy data that was added by the page designer to the HTML template. When we click on a row in the table, we don't notice any selection behavior. To provide data-binding functionality and any additional behavior, we implement a component class called SimpleTable. This section discusses the design of this class.
The SimpleTable component design assumes that:
- The table element has a THREAD element containing column elements.
- The column header ID is the same as the attribute name.
- The table has a TBODY element where the data is displayed.
- A special column name "_rowHeader" is used to display the row heading. If it's not there, then no row header is displayed.
Listing 5.11 - SimpleTable Class
function SimpleTable() {
// list of columns
this.columns = null;
// the body element where data is shown
this.tbody = null;
// the actual table element
this.tableElement = null;
this.currentRow = -1;
this.savedColor = new Array();
this.dlist = null;
this.cellSelectedListeners = new Array();
}
SimpleTable.prototype.setHtmlElement = function(e) {
this.tableElement = e;
var collection = new ElementCollection(e);
var thead = collection.getFirstElement('THEAD');
this.tbody = collection.getFirstElement('TBODY');
if (thead == null) {
alert("SimpleTable requires THEAD element inside TABLE element");
return;
}
if (this.tbody == null) {
alert("SimpleTable requires TBODY element inside TABLE element");
return;
}
// makes a list of columns
this.columns = new ElementCollection(thead).getElements('TH');
}
SimpleTable.prototype.setData = function(dlist) {
this.dlist = dlist;
// removes all nodes user the body element, cleans it up
removeAll(this.tbody);
this.currentRow = -1;
// click handler is added to each cell
var self = this;
self.clickHandler = function(event) {self.cellClicked(event);};
// for each object is the list creates a TR element
for (var i=0; i<dlist.length; i++) {
var row = dlist[i];
// creates a TR element
var tr = document.createElement('tr');
// for each column, creates TD element
for (j=0; j<this.columns.length; j++) {
var col = this.columns[j];
var td = document.createElement('td');
// assign id that can give row, column of the cell
td.id = "Cell_" + i + "_" + j;
// set onclick handler for the cell
td.onclick = self.clickHandler;
// looks up value of the attribute with the same
// name as the column id
var v = row[col.id];
if (v == null) {
v = ' ';
}
var className = 'tdContent';
// check if it is a special column, sets it style using
// className 'tableCellRowHeader'
if (col.id == '_rowHeader') {
className = 'tableCellRowHeader';
} else {
if (j==0) {
className = 'tdContent1st';
}
}
// sets style of TD element
td.className = className;
// set the value of TD element
td.appendChild(document.createTextNode(v));
// add the TD to TR element
tr.appendChild(td);
}
// add TR to body element
this.tbody.appendChild(tr);
}
}
Now that we have our SimpleTable class, we can associate it with our table element. In the PlanList. js, the following code associates the table element with the SimpleTable component.
function requestCompletedProjectListElement(response) {
projectListContainer = setElementFromHtml(response.responseText,
'MainArea', 'projectListContainer');
// our projectListContainer element contains the projectListElement
projectListElement = document.getElementById("projectListElement");
// create an instance of SimpleTable component
projectList = new SimpleTable();
// set the table element
projectList.setHtmlElement(projectListElement);
... ... ...
}
Loading Table Data
Our next step is to retrieve data from the server and display it inside the project list table. Refer to the MainPage-5.html for the following discussion.
Since we don't have our application server ready yet, we'll use the XML file as the data source. We assume that the XML file will be accessed from a Web server. The example XML file is called ProjectList.xml and its partial contents are shown in Listing 5.12.
Listing 5.12 - ProjectList.xml
<ProjectList class="ProjectList">
<name>ProjectList</name>
<project class="Project">
<name>Marketing Campaign</name>
<resource>Monti</resource>
<startDate>10/3/2005</startDate>
<endDate>11/4/2006</endDate>
<status>Green</status>
<description>Marketing Campaign Project</description>
</project>
<project class="Project">
... ... ...
</project>
<project class="Project">
... ... ...
</project>
<project class="Project">
... ... ...
</project>
</ProjectList>
Next we implement the following functions in ProjectList.js to load the ProjectList.xml and display it inside the table.
Listing 5.13 - Load ProjectList.xml
// load ProjectList.xml
function loadProjectList() {
var req = new DataRequest(baseURL + 'ProjectList.xml',
requestCompletedProjectList,
requestFailedProjectList,
requestTimedoutProjectList);
ajaxEngine.processRequest(req);
}
// the file is loaded successfully
// convert it to dataset (JavaScript objects)
function requestCompletedProjectList(response) {
var xmlToDS = new XMLToDataSet();
var data = xmlToDS.fromXml(response.responseXML);
// the array of project is given by the attribute name 'project'
// notice that in the result XML, it is the tag name of the objects.
// bind the 'projectList' component with this array
projectList.setData(data["project"]);
}
In Listing 5.13 we used an XML file as the data source. Once we have our application server ready, we simply have to change the request URL to point to the application server, and the client will continue to work without any change as long as the XML format is the same.
The last step, which displays the data inside the table, is to invoke the "loadProjectList" method. Notice that in the ProjectList.js, after loading the project list template, we call a function of the "projectTemplateLoaded()" as follows:
function requestCompletedProjectListElement(response) {
... ... ...
projectTemplateLoaded();
}
The function "projectTemplateLoaded" is defined in the MainPage-5.html. Here we call "loadProjectList," i.e., we call "loadProjectList" for the first time after the project list template is loaded.
function projectTemplateLoaded() {
loadProjectList();
}
This content is reprinted from Real-World AJAX: Secrets of the Masters published by SYS-CON Books. To order the entire book now along with companion DVDs, click here to order.
Published April 16, 2007 Reads 2,197
Copyright © 2007 SYS-CON Media, Inc. — All Rights Reserved.
Syndicated stories and blog feeds, all rights reserved by the author.
More Stories By Anil Sharma
Anil Sharma is a founder of Vertex Logic and architect of its AjaxFace product. He is working with Vertex Logic's customers and helping them deploy WEB 2.0 applications built using AjaxFace. These applications are in the areas of online printing, community WEB sites and social networking. He is driving future products of Vertex Logic. Prior to Vertex Logic, he was CTO and founder of Softrock Systems and Component Plus. There he built a model driven application platform using J2EE. His primary interests include user interface infrastructures and model driven applications. He is one of the contributors of the book "Real World AJAX - Secrets of the Master".
- Cloud Computing on Gartner's Top 10 List and SYS-CON Events' 2010 Calendar
- Confessions of a Ulitzer Addict
- IBM Hardware Chief, Intel VC Exec Arrested in Insider Trading Scam
- My Thoughts on Ulitzer
- Tactical Cloud Computing Panel at 1st Annual GovIT Expo
- Ulitzer.com Named Exclusive "New Media" Sponsor of Cloud Computing Conference & Expo
- Moving Your RIA Apps into the Cloud: Seven Challenges
- Adobe’s Aiming ColdFusion at Multiple Clouds
- Windows 7 – Microsoft’s First Step to the Cloud
- Ulitzer Provides a Powerful Social Journalism Platform
- Jill Tummler Singer, Deputy CIO of CIA, Keynotes at GovIT Expo
- Open Source Mobile Cloud Sync and Push Email
- Practical Approaches for Optimizing Website Performance
- The Difference Between Web Hosting and Cloud Computing
- Cloud Computing on Gartner's Top 10 List and SYS-CON Events' 2010 Calendar
- Ajax in RichFaces 3.3, JSF 2 and RichFaces 4
- Confessions of a Ulitzer Addict
- IBM Hardware Chief, Intel VC Exec Arrested in Insider Trading Scam
- My Thoughts on Ulitzer
- Tactical Cloud Computing Panel at 1st Annual GovIT Expo
- US Post Office Hops a Ride on NetSuite’s Cloud
- Ulitzer.com Named Exclusive "New Media" Sponsor of Cloud Computing Conference & Expo
- WPF Controls by DevExpress
- Moving Your RIA Apps into the Cloud: Seven Challenges
- Building a Drag-and-Drop Shopping Cart with AJAX
- What Is AJAX?
- Google Maps! AJAX-Style Web Development Using ASP.NET
- Flashback to January 2006: Exclusive SYS-CON.TV Interviews on "OpenAjax Alliance" Announcement
- AJAXWorld Conference & Expo to Take Place October 2-4, 2006, at the Santa Clara Convention Center, California
- AJAX Sponsor Webcasts Are Now Available at AJAXWorld Website
- How and Why AJAX, Not Java, Became the Favored Technology for Rich Internet Applications
- "Real-World AJAX" One-Day Seminar Arrives in Silicon Valley
- AJAXWorld University Announces AJAX Developer Bootcamp
- AJAX Support In JadeLiquid WebRenderer v3.1
- Where Are RIA Technologies Headed in 2008?
- Struts Validations Framework Using AJAX




































