Click here to Skip to main content
11,492,639 members (57,768 online)
Click here to Skip to main content

JavaScript SOAP Client

, 24 Jan 2006 684.6K 12.1K 133
Rate this:
Please Sign up or sign in to vote.
Using AJAX to call a Web Service.

Introduction

A lot of talking about AJAX is taking place here and there; AJAX is the acronym of "Asynchronous JavaScript and XML", a technology based on XMLHttpRequest, which is now supported by all main browsers. The basic idea is quite simple - and not actually a breakthrough - but it allows updating a page following a server request, without reloading the entire set of data. Some examples can be found on GMail or Google Suggest. For additional information about AJAX, you can see Wikipedia.

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:

  1. On the server side, we only have to expose a Web Service with the required methods (instead of generating dynamic pages incorporating data that are based on a custom syntax or on a generic XML).
  2. On the client side, we use the WSDL (Web Service Description Language) to automatically generate a JavaScript proxy class so as to allow using the Web Service return types - that is similar to what Visual Studio does when a Web Reference is added to the solution.

The following diagram shows the SOAP Client workflow for asynchronous calls:

The Client invokes the "SOAPClient.invoke" method using a JavaScript function and specifies the following:

  • Web Service URL (please note that many browsers do not allow cross-domain calls for security reasons).
  • Web method name.
  • Web method parameter values.
  • Call mode (async = true, sync = false).
  • Callback method invoked upon response reception (optional for sync calls).

The "SOAPClient.invoke" method executes the following operations (numbers refer to the previous diagram):

  1. It gets the WSDL and caches the description for future requests.
  2. It prepares and sends a SOAP (v. 1.1) request to the server (invoking method and parameter values).
  3. It processes the server reply using the WSDL so as to build the corresponding JavaScript objects to be returned.
  4. If the call mode is async, the callback method is invoked, otherwise it returns the corresponding object.

Using the code

After 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: "SOAPClientParameters":

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 "add" method allows appending new parameters, while the "toXml" method provides XML serialization for SOAP requests (see "SOAPClient._sendSoapRequest").

Let's define the "SOAPClient" class, which can only contain static methods in order to allow async calls, and the only "public" method within this class: "SOAPClient.invoke".

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.invoke" method interface is described above; our implementation checks whether the call is async (call result will be passed to the callback method) or sync (call result will be directly returned). The call to the Web Service begins by invoking the "SOAPClient._loadWsdl" method:

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 XMLHttpRequest, according to the required mode (sync or not). Once an answer is obtained from the server, the "SOAPClient._onLoadWsdl" method is invoked:

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" method is executed:

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" method will be invoked upon receiving the server response:

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" method optimizes XPath queries according to the available XML parser:

SOAPClient._getElementsByTagName = function(document, tagName)
{
    try
    {
        return document.selectNodes(".//*[local-name()=\""+ 
                                           tagName +"\"]");
    }
    catch (ex) {}
    return document.getElementsByTagName(tagName);
}

A factory function returns the XMLHttpRequest according to the browser type:

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 Interest

By 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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Matteo Casati

United States United States
No Biography provided

Comments and Discussions

 
GeneralRe: Problem handling SOAP-response from server Pin
Matteo Casati1-Jun-07 4:14
memberMatteo Casati1-Jun-07 4:14 
GeneralRe: Problem handling SOAP-response from server Pin
tddb692-Jun-07 5:18
membertddb692-Jun-07 5:18 
GeneralNeed Example of how to call the JavaScript Pin
SandB23-May-07 3:25
memberSandB23-May-07 3:25 
GeneralRe: Need Example of how to call the JavaScript Pin
Matteo Casati23-May-07 3:30
memberMatteo Casati23-May-07 3:30 
GeneralMarco sei un figo Pin
Alberto Marabini17-May-07 20:22
memberAlberto Marabini17-May-07 20:22 
GeneralRe: Marco sei un figo Pin
Matteo Casati17-May-07 22:07
memberMatteo Casati17-May-07 22:07 
GeneralRe: Marco sei un figo Pin
Alberto Marabini4-Jun-07 16:26
memberAlberto Marabini4-Jun-07 16:26 
GeneralRe: Marco sei un figo Pin
Matteo Casati4-Jun-07 22:05
memberMatteo Casati4-Jun-07 22:05 
QuestionWeb service built in Java? Pin
Gu Gu9-May-07 20:20
memberGu Gu9-May-07 20:20 
GeneralWCF Pin
Dante R. Otero24-Apr-07 11:20
memberDante R. Otero24-Apr-07 11:20 
QuestionMultiple "pending" requests problem Pin
Taros3-Apr-07 1:54
memberTaros3-Apr-07 1:54 
AnswerRe: Multiple "pending" requests problem Pin
Matteo Casati3-Apr-07 3:07
memberMatteo Casati3-Apr-07 3:07 
GeneralNuSOAP Structure Pin
pgiuseppe8-Mar-07 8:22
memberpgiuseppe8-Mar-07 8:22 
NewsAnother SOAP client solution Pin
Matthias Hertel25-Jan-07 1:29
memberMatthias Hertel25-Jan-07 1:29 
You can find another SOAP client on codeproject at
http://www.codeproject.com/soap/JavaScriptProxy_01.asp and AJAX web controls built on top of it at http://www.mathertel.de/AJAXEngine/.

GeneralSimply, Excellent Pin
Typed_stratoCASTer16-Jan-07 15:34
memberTyped_stratoCASTer16-Jan-07 15:34 
GeneralRe: Simply, Excellent Pin
Matteo Casati16-Jan-07 22:24
memberMatteo Casati16-Jan-07 22:24 
GeneralRe: Simply, Excellent Pin
bibiboule26-Mar-07 3:05
memberbibiboule26-Mar-07 3:05 
GeneralWon't work with WCF Pin
nantcom19-Dec-06 9:47
membernantcom19-Dec-06 9:47 
Questiondid not get the Response/Output Pin
jwalin khatri28-Nov-06 6:59
memberjwalin khatri28-Nov-06 6:59 
AnswerRe: did not get the Response/Output Pin
Gator7631-Jan-07 4:04
memberGator7631-Jan-07 4:04 
NewsJavaScript SOAP Client on CodePlex Pin
Matteo Casati27-Oct-06 3:19
memberMatteo Casati27-Oct-06 3:19 
GeneralExcellent Pin
masta chef26-Oct-06 6:25
membermasta chef26-Oct-06 6:25 
GeneralRe: Excellent Pin
Matteo Casati27-Oct-06 3:21
memberMatteo Casati27-Oct-06 3:21 
QuestionHow to secure webmethod? Pin
vinh2b25-Oct-06 18:26
membervinh2b25-Oct-06 18:26 
AnswerRe: How to secure webmethod? Pin
nagual_hsu26-Oct-06 16:56
membernagual_hsu26-Oct-06 16:56 
QuestionIsn't time for a source repository somewhere? Pin
MarcoPolloPollo25-Oct-06 11:08
memberMarcoPolloPollo25-Oct-06 11:08 
AnswerRe: Isn't time for a source repository somewhere? Pin
Matteo Casati26-Oct-06 0:03
memberMatteo Casati26-Oct-06 0:03 
QuestionRead NuSOAP response Pin
simwood20-Oct-06 7:27
membersimwood20-Oct-06 7:27 
AnswerRe: Read NuSOAP response [modified] Pin
Kerem Kacel NBCU23-Oct-06 11:52
memberKerem Kacel NBCU23-Oct-06 11:52 
GeneralBrilliant Pin
Jim Blackler19-Oct-06 8:55
memberJim Blackler19-Oct-06 8:55 
GeneralRe: Brilliant Pin
Matteo Casati27-Oct-06 3:08
memberMatteo Casati27-Oct-06 3:08 
QuestionHow to call ATL Web Service with javascript? Pin
stanley guan17-Oct-06 23:47
memberstanley guan17-Oct-06 23:47 
GeneralIE7 fix Pin
Kerem Kacel NBCU16-Oct-06 12:23
memberKerem Kacel NBCU16-Oct-06 12:23 
QuestionUsing Javascript SOAP Client with gsoap in the server side? Pin
nagual_hsu12-Oct-06 1:42
membernagual_hsu12-Oct-06 1:42 
AnswerRe: Using Javascript SOAP Client with gsoap in the server side? Pin
MarcoPolloPollo25-Oct-06 10:57
memberMarcoPolloPollo25-Oct-06 10:57 
GeneralRe: Using Javascript SOAP Client with gsoap in the server side? Pin
nagual_hsu26-Oct-06 1:22
membernagual_hsu26-Oct-06 1:22 
QuestionHow to set time out? Pin
vinh2b6-Oct-06 0:36
membervinh2b6-Oct-06 0:36 
AnswerRe: How to set time out? Pin
MarcoPolloPollo25-Oct-06 10:43
memberMarcoPolloPollo25-Oct-06 10:43 
GeneralRe: Latest version Demo Page Pin
Dewey22-Sep-06 14:21
memberDewey22-Sep-06 14:21 
GeneralRe: Latest version Demo Page Pin
Matteo Casati5-Oct-06 1:12
memberMatteo Casati5-Oct-06 1:12 
GeneralUsing with Java WS Pin
noomyai3-Sep-06 20:32
membernoomyai3-Sep-06 20:32 
QuestionDemo files: what is .asmx file? Pin
mike00421-Aug-06 2:40
membermike00421-Aug-06 2:40 
AnswerRe: Demo files: what is .asmx file? Pin
Matteo Casati21-Aug-06 2:59
memberMatteo Casati21-Aug-06 2:59 
GeneralRe: Demo files: what is .asmx file? Pin
mike00421-Aug-06 3:11
membermike00421-Aug-06 3:11 
GeneralRe: Demo files: what is .asmx file? Pin
Matteo Casati21-Aug-06 3:16
memberMatteo Casati21-Aug-06 3:16 
QuestionIt is beyond my.. Pin
Sendilkumar.M26-Jul-06 19:44
memberSendilkumar.M26-Jul-06 19:44 
AnswerRe: It is beyond my.. [modified] Pin
Matteo Casati26-Jul-06 22:19
memberMatteo Casati26-Jul-06 22:19 
AnswerRe: It is beyond my.. Pin
MarcoPolloPollo8-Aug-06 8:27
memberMarcoPolloPollo8-Aug-06 8:27 
GeneralNamespace issues with SOAP XML [modified] Pin
JoeInBoston7-Jul-06 8:39
memberJoeInBoston7-Jul-06 8:39 
GeneralRe: Namespace issues with SOAP XML Pin
Daniel Tingstrom5-Mar-07 11:32
memberDaniel Tingstrom5-Mar-07 11:32 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150520.1 | Last Updated 24 Jan 2006
Article Copyright 2006 by Matteo Casati
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid