| |
| |
|
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.
|
| |
|
<?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.
- 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);
});
}
|