JavaScript Editor Free JavaScript Editor     JavaScript Debugger 




Main Page

Previous Page
Next Page

8.3. The XMLHttpRequest Object

As interesting as the previous section may have been, remember that it was only an appetizer. Now the time has come for the entrée: the XMLHttpRequest object. If you've never used the XMLHttpRequest object, it is, as described previously, an object that gives web browsers the capability to communicate directly with the server, without the unload/reload cycleor "blink," as the peasants call it.

8.3.1. Avoiding the Unload/Reload Cycle

The best analogy that I can think of to the XMLHttpRequest object is the transporter from any of the various incarnations of Star Trek. With the transporter, only the personnel essential to a particular mission need go down to the planet's surface. The alternative would be to either land the Starship, if it were capable of planetary landings, or send a shuttlecraft. In either case, there would be a lot of unnecessary equipment and personnel being moved about at great expense, as opposed to the "move only what you need" philosophy of the transporter.

The XMLHttpRequest object is the web equivalent of the transporter. Why transmit an entire web page when all that is really needed is the data itself?

The HTML and JavaScript for presentation are already there, so just change the data and we're good to go. I should point out that although the data being beamed from the server to the client doesn't necessarily have to be XML, in all these examples, it is XML.

8.3.2. Browser Differences

Before describing the actual syntax necessary to use XMLHTTP, I recommend that you sit down because I don't want to shock you or anything. Sitting down? Good. The syntax used for the XMLHttpRequest object is different in Microsoft Internet Explorer than from every other browser that supports it. In fact, from Microsoft's perspective, somewhere on the surface of Charon, not even the World Wide Web Consortium got it right. As a matter of fact, they made exactly the same mistake as Firefox. Fortunately, because the error is consistent among all nonInternet Explorer browsers all that is necessary is to code for IE and everybody else. Mmm, I wonder if maybe...nah!

The first thing is to create an instance of the XMLHttpRequest object in the following manner:

try {

  var x = new DOMParser();
  var _IE = false;

}

catch(e) { var _IE = true; };
var _XMLHTTP;

if(_IE)
   _XMLHTTP = new ActiveXObject('Microsoft.XMLHTTP');
else
   _XMLHTTP = new XMLHttpRequest();

Before proceeding any further, a couple of decisions must be made that involve just how we'd like the page to work.

Synchronous or asynchronous?

GET or POST?

The choice of synchronous or asynchronous is a relatively big one, but it boils down to waiting for a response or being notified when there is a response. As long as you remember to specify a state change handler for responses to asynchronous requests, things should work. The GET or POST question is also an important decision. Fortunately, it is the same decision that has been around ever since the introduction of HTML forms, so as long as we follow the same rules, everything will be alright.

Let's say, for instance, that we want to retrieve the XML file of states and provinces shown in Listing 8-8 from the server. The first thing that is needed is to determine the browserbasically, Microsoft Internet Explorer and everyone else. The next task is to create an instance of the XMLHttpRequest object, followed by setting the event handler, for asynchronous requests. Finally, the XMLHttpRequest object is opened with three parameters:

  • GET or POST

  • The URL for the request

  • Either TRue for asynchronous or false for synchronous

However, you must remember one thing about coding a state change handler. It is a state change handler, not an "I'm finished" handler. There are other states than "complete"; we're interested in 4, which indicates that the request is complete. Listing 8-9 shows a page that retrieves the XML from Listing 8-8, storing it in an XML Data Island and binding it for display purposes.

Listing 8-8. Sample XML Document

<states>
      <state>
            <state_abbreviation>AB</state_abbreviation>
            <state_name>Alberta</state_name>
            <country_id>3</country_id>
      </state>
      <state>
            <state_abbreviation>AK</state_abbreviation>
            <state_name>Alaska</state_name>
            <country_id>1</country_id>
      </state>
      <state>
            <state_abbreviation>AL</state_abbreviation>
            <state_name>Alabama</state_name>
            <country_id>1</country_id>
      </state>
      <state>
            <state_abbreviation>AR</state_abbreviation>
            <state_name>Arkansas</state_name>
            <country_id>1</country_id>
      </state>
      <state>
            <state_abbreviation>AS</state_abbreviation>
            <state_name>American Samoa</state_name>
            <country_id>1</country_id>
      </state>
      <state>
            <state_abbreviation>AZ</state_abbreviation>
            <state_name>Arizona</state_name>
            <country_id>1</country_id>
      </state>
</states>

Listing 8-9. HTML Document Using an XML Data Island

<html>
    <head>
            <title>XML Data Island Test</title>
            <style type="text/css">
xml
{
      display: none;
      font-size: 0px
}
            </style>
            <script language="JavaScript">
try {
  var x = new DOMParser();
  var _IE = false;
}
catch(e) { var _IE = true; };
var _URL = 'http://localhost/chapter4/states.xml';
var _XMLHTTP;

/*
   Perform page initialization.
*/
function initialize() {
    if(_IE)
        _XMLHTTP = new ActiveXObject('Microsoft.XMLHTTP');
    else
        _XMLHTTP = new XMLHttpRequest();

    _XMLHTTP.onreadystatechange = stateChangeHandler;

    _XMLHTTP.open('GET',_URL,true);     // Asynchronous (true)
    _XMLHTTP.send(null);
}

/*
   Handle the asynchronous response to a XMLHttpRequest,
   including the loading of the XML Data Island.
*/
function stateChangeHandler() {
  if(_XMLHTTP.readyState == 4) {
    var strHTML = '';
    var nodeCount;

    if(_IE) {

document.getElementById('xmlDI').XMLDocument.load(_XMLHTTP.responseXML);
      nodeCount =
document.getElementById('xmlDI').XMLDocument.getElementsByTagName('state_n
ame').length;
    } else {
      document.getElementById('xmlDI').innerHTML = _XMLHTTP.responseText;
      nodeCount = document.body.getElementsByTagName('state_name').length;
    }
      try {
          _XMLHTTP.close();            //  Close XMLHttpRequest
      }
      catch(e) {}

      for(var i=0;i < nodeCount;i++)
        strHTML += '<div xmldi="xmlDI" xmlnode="state_name"></div>';

      document.getElementById('show').innerHTML = strHTML;

      _bind();                              //  Bind XML and HTML
  }
}

/*
   Handle the logic necessary to bind HTML elements to XML
   nodes. Note that in some instances this binding is a two-way
   street. For example, if the value in a text box should
   change the corresponding value in the XML data island will
   also change.
*/
function _bind() {
  if(arguments.length == 0) {
    doBind(document.body.getElementsByTagName('div'));
    doBind(document.body.getElementsByTagName('input'));
    doBind(document.body.getElementsByTagName('select'));
    doBind(document.body.getElementsByTagName('span'));
    doBind(document.body.getElementsByTagName('textarea'));
  } else {
    applyChange(arguments[0],arguments[1]);
    _bind();                                 // Re-bind
  }

  /*
     To handle data-binds for specific nodes based upon HTML
     element type and browser type.
  */
  function doBind(objects) {
    var strTag;                         //  HTML tag
    var strDI;                          //  XML data island id
    var strNode;                        //  XML node name
    var strValue;                       //  XML node value

    for(var i=0;i < objects.length;i++) {
      strTag = objects[i].tagName;
      strDI = objects[i].getAttribute('xmldi');
      strNode = objects[i].getAttribute('xmlnode');
      if(strDI != null && strNode != null) {
        if(_IE)
            strValue =
document.getElementById(strDI).XMLDocument.selectNodes('//' +
strNode).item(i).text;
        else
            strValue =
document.getElementById(strDI).getElementsByTagName(strNode)[i].innerHTML;

        switch(strTag) {
            case('DIV'):
            case('SPAN'):
                objects[i].innerHTML = strValue;

                break;
            case('INPUT'):
                switch(objects[i].type) {
                    case('text'):
                    case('hidden'):
                    case('password'):
                        objects[i].value = strValue;
                        objects[i].onchange = new Function("_bind(this," +
i.toString() + ")");
                        break;
                    case('checkbox'):
                        if(objects[i].value == strValue)
                            objects[i].checked = true;
                        else
                            objects[i].checked = false;
                        objects[i].onclick = new Function("_bind(this," +
i.toString() + ")");
                        break;
                    case('radio'):
                        if(_IE)
                            strValue =
document.getElementById(strDI).XMLDocument.selectNodes('//' +
strNode).item(0).text;
                        else
                            strValue =
document.getElementById(strDI).getElementsByTagName(strNode)[0].innerHTML;

                        if(objects[i].value == strValue)
                            objects[i].checked = true;
                        else
                            objects[i].checked = false;

                        objects[i].onclick = new
Function("_bind(this,0)");
                        break;
                }

                break;
            case('SELECT'):
            case('TEXTAREA'):
                objects[i].value = strValue;
                objects[i].onchange = new Function("_bind(this," +
i.toString() + ")");

                break;
        }
      }
    }
  }

  /*
     To handle changes to the bound HTML elements and apply
     those changes to the appropriate XML node.
  */
  function applyChange(obj,index) {
    var strDI = obj.getAttribute('xmldi');
    var strNode = obj.getAttribute('xmlnode');
    var strValue = obj.value;

    if(obj.type == 'checkbox')
        if(obj.checked)
            strValue = obj.value;
        else
            strValue = '';
    if(_IE)
        document.getElementById(strDI).XMLDocument.selectNodes('//' +
strNode).item(index).text = strValue;
    else

document.getElementById(strDI).getElementsByTagName(strNode)[index].
innerHTML = strValue;
  }
}
            </script>
      </head>
      <body onload="initialize()">
            <xml id="xmlDI">
            </xml>
            <b>XML Data Island Test</b>
            <br />
            <div id="show"></div>
      </body>
</html>

Essentially, the JavaScript in Listing 8-9 makes an asynchronous XMLHTTP request. This entails, beyond the usual "which browser is it?" stuff, creating an instance of the XMLHttpRequest object, setting an event handler for the response, and making the request using the request type, the URL, and TRue for asynchronous. The state change handler, er, handles the response from the server. If you look closely, you'll see a condition testing the readyState property to see if it is equal to 4, which is complete. The reason for testing the readyState property is that this handler fires multiple times for different reasons, ranging from the equivalent of "I'm sitting here" to "Hey, I'm getting a response."

The previous example illustrated how to use the XMLHttpRequest object to asynchronously obtain an XML document from a file located on the server. Think of it as something along the lines of a proof of concept because the odds are against the XML document needed sitting in a folder on the web server. Instead, there will probably be some script version of Igor sitting around watching Oprah, waiting for some real work to do.

Several different methods exist for getting data to and from our virtual Igor, ranging from a simple custom approach to slightly more complex XML-based standards. One of the standards that can be used to get the virtual Igor moving is called XML Remote Procedure Calling, or XML-RPC, for short. In a nutshell, XML-RPC is a World Wide Web Consortium Recommendation that describes a request/response protocol. A request is posted to the web server, and the web server acts upon the request and returns a response. This entire process might sound rather complex, but it really isn't any more difficult than what we've already accomplished. The only differences are that instead of a GET, we'll be doing a POST, and the request needs to be in XML, as shown in Listing 8-10 and the response in Listing 8-11.

Listing 8-10. XML-RPC Request

<?xml version="1.0"?>
<methodCall>
      <methodName>igor.getGuildName</methodName>
      <params>
            <param>
                  <value>
                        <int>1</int>
                  </value>
            </param>
      </params>
</methodCall>

Listing 8-11. XML-RPC Response

<?xml version="1.0"?>
<methodResponse>
      <params>
            <param>
                  <value>
                        <string>Mad Scientist</string>
                  </value>
            </param>
      </params>
</methodResponse>

As you've probably deduced from this, the structure of the XML document goes along the lines of methodCall, params, param, value, and, finally, data type (integer, in this instance). The rule for the structure goes along the lines of one methodResponse, one params, and at least one param. In addition, each param can have only one valueno more, no less. Values, in turn, have a single node that both describes and holds the data. Table 8-1 shows the valid data types for XML-RPC.

Table 8-1. XML-RPC Data Types

Type

Description

int

4-byte signed integer

i4

4-byte signed integer

boolean

True = 1 and false = 0

sting

Character string

double

Double-precision floating point

dateTime.iso8601

Date/time

base64

Base 64 binary


Of course, communicating a single item of information as shown is pretty rare. More common are more complex data structures, such as arrays or the record-line structs. Both arrays and structs work pretty much along the same lines as the simpler example earlier. Listing 8-12 shows an example of an array, and Listing 8-13 shows an example of a struct.

Listing 8-12. XML-RPC Array

<?xml version="1.0"?>
<array>
      <data>
            <value>
                  <int>5</i4>
            </value>
            <value>
                  <string>Lab Coat</string>
            </value>
            <value>
                  <double>29.95</double>
            </value>
      </data>
</array>

Listing 8-13. XML-RPC Struct

<?xml version="1.0"?>
<struct>
      <member>
            <name>name_last</name>
            <value>
                  <string>Woychowsky</>
            </value>
      </member>
      <member>
            <name>name_first</name>
            <value>
                  <string>Edmond</string>
            </value>
      </member>
      <member>
            <name>purpose</name>
            <value>
                  <int>42</int>
            </value>
      </member>
</struct>

The array example shown is merely an elaboration of the earlier simple XML document, but the struct example is more complex. Along with specifying the parameter type and value, it specifies the name of the parameter. This might not seem like much, but it is useful in applications with so many parameters that it becomes difficult to keep their relative positions straight.

This leads us to the question, what does the response look like when the relative positions aren't kept straight? That's simple enough; a fault like the one in Listing 8-14 is returned.

Listing 8-14. XML-RPC Fault

<?xml version="1.0"?>
<methodResponse>
      <fault>
            <value>
                  <struct>
                        <member>
                              <name>faultCode</name>
                              <value>
                                    <int>86</int>
                              </value>
                        </member>
                        <member>
                              <name>faultString</name>
                              <value>
                                    <string>
                                          Invalid data type.
                                    </string>
                              </value>
                        </member>
                  </struct>
            </value>
      </fault>
</methodResponse>

Now that we know what the request looks like ordinarily, the next step is to modify the previous example, in which the XSLT was retrieved through the XMLHttpRequest object and a GET to use XML-RPC. This time, however, we skip the examples and progress directly to what is considered by some the protocol of choice when creating web services: SOAP.

8.3.3. Cleaning Up with SOAP

Other than being something for cleaning, SOAP is an acronym for Simple Object Access Protocol, a protocol used to communicate between web browsers and web servers. SOAP is probably one of the more difficult subjects to research on the web, if for no other reason than the multiple websites that deal with the original SOAP. Nevertheless, when searching, you eventually will obtain the desired results and discover that SOAP is nothing more than a wrapper for XML.

XML-RPC was designed to provide a standard structure. However, with SOAP, a slightly different approach was used. Instead of the strict params-param-value used by XML-RPC, which rigidly ties the information with the wrapper, SOAP uses a more flexible envelope method. As with a physical envelope, a SOAP envelope both identifies the recipient and contains the message within. The only real difference between a SOAP envelope and a physical envelope is that the message contained by a SOAP envelope must be well formed, like the one shown in Listing 8-15.

Listing 8-15. SOAP Request

<?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>
    <getItems xmlns="http://tempuri.org/">
      <guild_item_id>string</guild_item_id>
      <guild_id>string</guild_id>
    </getItems>
  </soap:Body>
</soap:Envelope>

As with the XML-RPC example, there are two possible responses to a SOAP request. Either the web service worked and returned a SOAP response, as shown in Listing 8-16, or some kind of error occurred, and the request failed and a SOAP fault was returned. Listing 8-17 contains an example of a SOAP fault.

Listing 8-16. SOAP Response

<?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>
    <getItemsResponse xmlns="http://tempuri.org/">
      <getItemsResult>xml</getItemsResult>
    </getItemsResponse>
  </soap:Body>
</soap:Envelope>

Listing 8-17. SOAP Fault

<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-
instance"xmlns:xsd="http://www.w3.org/2001/XMLSchema"xmlns:soap="http://sc
hemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <soap:Fault>
      <faultcode>soap:MustUnderstand</faultcode>
      <faultstring>Mandatory Header error.</faultstring>
      <faultactor>http://localhost/AJAX4/chapter4.asmx</faultactor>
      <detail>Web Service coffee break.</detail>
    </soap:Fault>
  </soap:Body>
</soap:Envelope>


Previous Page
Next Page




JavaScript Editor Free JavaScript Editor     JavaScript Debugger


©