|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionA lot of talking about AJAX is taking place here and there; AJAX is the acronym of "Asynchronous JavaScript and XML", a technology based on In this article, we propose a solution based on AJAX that has a great advantage with respect to those commonly found in Internet: calls are made to the Web Services. This permits:
The following diagram shows the SOAP Client workflow for asynchronous calls:
The Client invokes the "
The "
Using the codeAfter having exposed our idea about consuming a Web Service via JavaScript, we only have to analyze the code. Let's start with the class for the definition of the parameters to be passed to the Web method: " function SOAPClientParameters()
{
var _pl = new Array();
this.add = function(name, value)
{
_pl[name] = value;
return this;
}
this.toXml = function()
{
var xml = "";
for(var p in _pl)
{
if(typeof(_pl[p]) != "function")
xml += "<" + p + ">" +
_pl[p].toString().replace(/&/g,
"&").replace(/</g,
"<").replace(/>/g,
">
The code simply consists of an internal dictionary (associative array) with the parameter name (key) and the related value; the " Let's define the " Note: since JavaScript does not foresee access modifiers - such as "public", "private", "protected", etc. - we'll use the "_" prefix to indicate private methods. function SOAPClient() {}
SOAPClient.invoke = function(url, method,
parameters, async, callback)
{
if(async)
SOAPClient._loadWsdl(url, method,
parameters, async, callback);
else
return SOAPClient._loadWsdl(url, method,
parameters, async, callback);
}
The " SOAPClient._loadWsdl = function(url, method, parameters, async, callback)
{
// load from cache?
var wsdl = SOAPClient_cacheWsdl[url];
if(wsdl + "" != "" && wsdl + "" != "undefined")
return SOAPClient._sendSoapRequest(url, method,
parameters, async, callback, wsdl);
// get wsdl
var xmlHttp = SOAPClient._getXmlHttp();
xmlHttp.open("GET", url + "?wsdl", async);
if(async)
{
xmlHttp.onreadystatechange = function()
{
if(xmlHttp.readyState == 4)
SOAPClient._onLoadWsdl(url, method,
parameters, async, callback, xmlHttp);
}
}
xmlHttp.send(null);
if (!async)
return SOAPClient._onLoadWsdl(url, method, parameters,
async, callback, xmlHttp);
}
The method searches the cache for the same WSDL in order to avoid repetitive calls: SOAPClient_cacheWsdl = new Array();
If the WSDL is not found in the cache (it's the first call in the current context), it is requested from the server using an SOAPClient._onLoadWsdl = function(url, method,
parameters, async, callback, req)
{
var wsdl = req.responseXML;
SOAPClient_cacheWsdl[url] = wsdl;
return SOAPClient._sendSoapRequest(url, method,
parameters, async, callback, wsdl);
}
A WSDL copy is stored into the cache and then the " SOAPClient._sendSoapRequest = function(url, method,
parameters, async, callback, wsdl)
{
var ns = (wsdl.documentElement.attributes["targetNamespace"] +
"" == "undefined") ?
wsdl.documentElement.attributes.getNamedItem(
"targetNamespace").nodeValue :
wsdl.documentElement.attributes["targetNamespace"].value;
var sr =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<soap:Envelope " +
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
"xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" " +
"xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
"<soap:Body>" +
"<" + method + " xmlns=\"" + ns + "\">" +
parameters.toXml() +
"</" + method + "></soap:Body></soap:Envelope>";
var xmlHttp = SOAPClient._getXmlHttp();
xmlHttp.open("POST", url, async);
var soapaction =
((ns.lastIndexOf("/") != ns.length - 1) ? ns + "/" : ns) + method;
xmlHttp.setRequestHeader("SOAPAction", soapaction);
xmlHttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
if(async)
{
xmlHttp.onreadystatechange = function()
{
if(xmlHttp.readyState == 4)
SOAPClient._onSendSoapRequest(method,
async, callback, wsdl, xmlHttp);
}
}
xmlHttp.send(sr);
if (!async)
return SOAPClient._onSendSoapRequest(method,
async, callback, wsdl, xmlHttp);
}
The service namespace is taken out of the WSDL (using different XPath queries for Internet Explorer and Mozilla / FireFox), then a SOAP v. 1.1 request is created and submitted. The " SOAPClient._onSendSoapRequest = function(method,
async, callback, wsdl, req)
{
var o = null;
var nd = SOAPClient._getElementsByTagName(
req.responseXML, method + "Result");
if(nd.length == 0)
{
if(req.responseXML.getElementsByTagName(
"faultcode").length > 0)
throw new Error(500,
req.responseXML.getElementsByTagName(
"faultstring")[0].childNodes[0].nodeValue);
}
else
o = SOAPClient._soapresult2object(nd[0], wsdl);
if(callback)
callback(o, req.responseXML);
if(!async)
return o;
}
The server response is processed looking for faults: if found, an error is raised. Instead, if a correct result is obtained, a recursive function will generate the return type by using the service description: SOAPClient._soapresult2object = function(node, wsdl)
{
return SOAPClient._node2object(node, wsdl);
}
SOAPClient._node2object = function(node, wsdl)
{
// null node
if(node == null)
return null;
// text node
if(node.nodeType == 3 || node.nodeType == 4)
return SOAPClient._extractValue(node, wsdl);
// leaf node
if (node.childNodes.length == 1 &&
(node.childNodes[0].nodeType == 3 ||
node.childNodes[0].nodeType == 4))
return SOAPClient._node2object(node.childNodes[0], wsdl);
var isarray = SOAPClient._getTypeFromWsdl(node.nodeName,
wsdl).toLowerCase().indexOf("arrayof") != -1;
// object node
if(!isarray)
{
var obj = null;
if(node.hasChildNodes())
obj = new Object();
for(var i = 0; i < node.childNodes.length; i++)
{
var p = SOAPClient._node2object(node.childNodes[i], wsdl);
obj[node.childNodes[i].nodeName] = p;
}
return obj;
}
// list node
else
{
// create node ref
var l = new Array();
for(var i = 0; i < node.childNodes.length; i++)
l[l.length] =
SOAPClient._node2object(node.childNodes[i], wsdl);
return l;
}
return null;
}
SOAPClient._extractValue = function(node, wsdl)
{
var value = node.nodeValue;
switch(SOAPClient._getTypeFromWsdl(
node.parentNode.nodeName, wsdl).toLowerCase())
{
default:
case "s:string":
return (value != null) ? value + "" : "";
case "s:boolean":
return value+"" == "true";
case "s:int":
case "s:long":
return (value != null) ? parseInt(value + "", 10) : 0;
case "s:double":
return (value != null) ? parseFloat(value + "") : 0;
case "s:datetime":
if(value == null)
return null;
else
{
value = value + "";
value = value.substring(0, value.lastIndexOf("."));
value = value.replace(/T/gi," ");
value = value.replace(/-/gi,"/");
var d = new Date();
d.setTime(Date.parse(value));
return d;
}
}
}
SOAPClient._getTypeFromWsdl = function(elementname, wsdl)
{
var ell = wsdl.getElementsByTagName("s:element"); // IE
if(ell.length == 0)
ell = wsdl.getElementsByTagName("element"); // MOZ
for(var i = 0; i < ell.length; i++)
{
if(ell[i].attributes["name"] + "" == "undefined") // IE
{
if(ell[i].attributes.getNamedItem("name") != null &&
ell[i].attributes.getNamedItem("name").nodeValue ==
elementname && ell[i].attributes.getNamedItem("type") != null)
return ell[i].attributes.getNamedItem("type").nodeValue;
}
else // MOZ
{
if(ell[i].attributes["name"] != null &&
ell[i].attributes["name"].value ==
elementname && ell[i].attributes["type"] != null)
return ell[i].attributes["type"].value;
}
}
return "";
}
The " SOAPClient._getElementsByTagName = function(document, tagName)
{
try
{
return document.selectNodes(".//*[local-name()=\""+
tagName +"\"]");
}
catch (ex) {}
return document.getElementsByTagName(tagName);
}
A factory function returns the SOAPClient._getXmlHttp = function()
{
try
{
if(window.XMLHttpRequest)
{
var req = new XMLHttpRequest();
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(SOAPClient._getXmlHttpProgID());
}
catch (ex) {}
throw new Error("Your browser does not support XmlHttp objects");
}
SOAPClient._getXmlHttpProgID = function()
{
if(SOAPClient._getXmlHttpProgID.progid)
return SOAPClient._getXmlHttpProgID.progid;
var progids = ["Msxml2.XMLHTTP.5.0",
"Msxml2.XMLHTTP.4.0",
"MSXML2.XMLHTTP.3.0",
"MSXML2.XMLHTTP",
"Microsoft.XMLHTTP"];
var o;
for(var i = 0; i < progids.length; i++)
{
try
{
o = new ActiveXObject(progids[i]);
return SOAPClient._getXmlHttpProgID.progid = progids[i];
}
catch (ex) {};
}
throw new Error("Could not find an installed XML parser");
}
Points of InterestBy using a single little (less than 10 KB) JavaScript library and, on the server side, simply exposing a Web Service with remote methods, you can use AJAX to create dynamic Web applications with no need for reloading the entire page. See the on-line demo for an example of usage. | ||||||||||||||||||||