Using XMLHTTP with zeroCode applications
 
 

Web applications are usually structured so that accesses to the web server coincide with one of three kinds of events: the user clicking a link, submitting a form, or typing a URL in the browser's address bar. In each case, the user initiates a request, and the web server responds with a page. So there is usually a direct correspondence between a server request and the user's clicking a link or submitting a form.

But this need not always be the case. Quite often, we need to initiate requests in response to other events. For example, consider a web page via which a system administrator adds a new user to the system. Suppose page includes the new user's login name. Suppose also that there is an additional requirement: As soon as the administrator tabs out of the login name field, the application should verify that the given name is not already assigned to someone else. Part of the requirement, of course, is that the user should not see the page flashing or being redrawn when the verification occurs. A natural way to meet this requirement is to initiate a request to the server in response to the tab-out event, and display a suitable message in the page if the name is already assigned.

This "out-of-band" communication between the browser and the server can be achieved using the XMLHTTP facility. This facility enables client-side JavaScript to initiate a request for a URL, parse the response as an XML document, extract its content and use the result in the client web page. Since this communication occurs entirely via JavaScript, the browser display is not affected. Both Internet Explorer and Mozilla support this facility, albeit in slightly different ways. Here we will illustrate the use of the XMLHTTP facility to meet the needs of the above example.

To verify that the login name is not already taken, we can create a UDM that takes a login name as parameter, and retrieves the login record associated with that name, if one exists. We can then create a FreeMarker template that uses this UDM to populate an XML document containing the retrieved data. The diagram below illustrates the UDM and the template.

 
getLogin UDM
<?xml verion="1.0">
<loginRecord>
<login
name=""
id=""/>
</loginRecord>
Template file getLoginWithName.xml

Assume that you have created this UDM in the udmFiles directory of site mySite, and also that you have created the template file getLoginWithName.xml (with the content shown above) in the site's webFiles/templates directory. So you can access the XML template via the URI /zcSite/mySite/getLoginWithName.xml.

In order to use the content of the XML template in an XMLHTTP request, you must specify to zeroCode that when it responds to a request for that URI, it must specify the content type text/xml. You can specify this via the site's content type map. In the zDE, click on the site's config directory, and then the file contentTypeMap.xml. In the resulting page, add a new content type map entry with the URI getLoginWithName.xml and content type text/xml. Note that it is very important for the application to serve the page with the correct content type. Without it, the content type defaults to text/html, in which case the Microsoft XML parser fails silently and returns no data.

Finally, you can create JavaScript that invokes XMLHTTP and uses the XML content produced by the above template. First, here is an HTML stub that invokes a JavaScript handler:

... Other HTML here...
<input type="text" name="loginName" onblur="verifyLoginAvailable(this.value)">

This HTML invokes the JavaScript function verifyLoginAvailable(), which looks like the code fragment below.

<script src="/mySite/xmlExtras.js"></script>
<script>
function verifyLoginAvailable(loginName) {
var xmlHttp = XmlHttp.create();
xmlHttp.open ("GET",
"/zcSite/mySite/getLoginWithName.xml?loginName=" + loginName,
false);
xmlHttp.send (null);
var doc = xmlHttp.responseXML;
var loginRecords = doc.getElementsByTagName ("login");
var loginId = loginRecords[0] ? loginRecords[0].getAttribute ("id") : null;
if (loginId && loginId != "") {
alert ("That login name is unavailable. Please choose another.");
}
}
</script>
A few things about this code fragment are worth noting.
  • The code relies on another script file xmlExtras.js. The latter contains JavaScript that wraps the differences between Internet Explorer and other DOM-compliant browsers such as Mozilla with a single interface. Using this script file, we can ignore browser differences, and simply create an instance of the XmlHttp object and use its methods.
  • The open method takes three parameters: The request type (which must be "GET" or "POST"), the URI, and a boolean value specifying whether the request is asynchronous. Specifying false for the last value causes the method to wait until the response to the request is available.
  • The responseXML method returns a DOMDocument object which can then be interrogated for the returned data.
  • The getAttribute method is used to obtain the value of the id attribute produced by the XML template. If this attribute has a value, there is already a user with the specified login name.

Summary
To use XMLHTTP in a zeroCode application for retrieving XML data, you must:

  • Create a UDM that provides the data contained in the XML document;
  • Create a corresponding FreeMarker template that produces the "layout" of the data in XML;
  • Tell the zeroCode application, via zDE's content type map editor, to serve up the XML document with content type text/xml.
Resources
  • The XMLHTTP documentation at Microsoft.
  • The XMLHTTP documentation at Mozilla.
  • The wrapper script in xmlExtras.js, available at WebFX.
Appendix: The script xmlExtras.js
//<script>
//////////////////
// Helper Stuff //
//////////////////
// used to find the Automation server name
function getDomDocumentPrefix() {
if (getDomDocumentPrefix.prefix)
return getDomDocumentPrefix.prefix;
var prefixes = ["MSXML2", "Microsoft", "MSXML", "MSXML3"];
var o;
for (var i = 0; i < prefixes.length; i++) {
try {
// try to create the objects
o = new ActiveXObject(prefixes[i] + ".DomDocument");
return getDomDocumentPrefix.prefix = prefixes[i];
}
catch (ex) {};
}
throw new Error("Could not find an installed XML parser");
}
function getXmlHttpPrefix() {
if (getXmlHttpPrefix.prefix)
return getXmlHttpPrefix.prefix;
var prefixes = ["MSXML2", "Microsoft", "MSXML", "MSXML3"];
var o;
for (var i = 0; i < prefixes.length; i++) {
try {
// try to create the objects
o = new ActiveXObject(prefixes[i] + ".XmlHttp");
return getXmlHttpPrefix.prefix = prefixes[i];
}
catch (ex) {};
}
throw new Error("Could not find an installed XML parser");
}
//////////////////////////
// Start the Real stuff //
//////////////////////////
// XmlHttp factory
function XmlHttp() {}
XmlHttp.create = function () {
try {
if (window.XMLHttpRequest) {
var req = new XMLHttpRequest();
// some versions of Moz do not support the readyState property
// and the onreadystate event so we patch it!
if (req.readyState == null) {
req.readyState = 1;
req.addEventListener("load", function () {
req.readyState = 4;
if (typeof req.onreadystatechange == "function")
req.onreadystatechange();
}, false);
}
return req;
}
if (window.ActiveXObject) {
return new ActiveXObject(getXmlHttpPrefix() + ".XmlHttp");
}
}
catch (ex) {}
// fell through
throw new Error("Your browser does not support XmlHttp objects");
};
// XmlDocument factory
function XmlDocument() {}
XmlDocument.create = function () {
try {
// DOM2
if (document.implementation && document.implementation.createDocument) {
var doc = document.implementation.createDocument("", "", null);
// some versions of Moz do not support the readyState property
// and the onreadystate event so we patch it!
if (doc.readyState == null) {
doc.readyState = 1;
doc.addEventListener("load", function () {
doc.readyState = 4;
if (typeof doc.onreadystatechange == "function")
doc.onreadystatechange();
}, false);
}
return doc;
}
if (window.ActiveXObject)
return new ActiveXObject(getDomDocumentPrefix() + ".DomDocument");
}
catch (ex) {}
throw new Error("Your browser does not support XmlDocument objects");
};
// Create the loadXML method and xml getter for Mozilla
if (window.DOMParser &&
window.XMLSerializer &&
window.Node && Node.prototype && Node.prototype.__defineGetter__) {
// XMLDocument did not extend the Document interface in some versions
// of Mozilla. Extend both!
//XMLDocument.prototype.loadXML = 
Document.prototype.loadXML = function (s) {
// parse the string to a new doc	
var doc2 = (new DOMParser()).parseFromString(s, "text/xml");
// remove all initial children
while (this.hasChildNodes())
this.removeChild(this.lastChild);
// insert and import nodes
for (var i = 0; i < doc2.childNodes.length; i++) {
this.appendChild(this.importNode(doc2.childNodes[i], true));
}
};
/*
* xml getter
*
* This serializes the DOM tree to an XML String
*
* Usage: var sXml = oNode.xml
*
*/
// XMLDocument did not extend the Document interface in some versions
// of Mozilla. Extend both!
/*
XMLDocument.prototype.__defineGetter__("xml", function () {
return (new XMLSerializer()).serializeToString(this);
});
*/
Document.prototype.__defineGetter__("xml", function () {
return (new XMLSerializer()).serializeToString(this);
});
}