Dealing with XML



When using asynchronous objects to transfer complex structured data, you can use XML. Transferring XML only needs to organize data into a string composed of XML format, use 'POST' when calling the open function, set the 'Content-Type' header as 'text/xml', and pass the XML string to the send function. The XML string will be put in the POST body when sending. For example:
function toXML(data) {
    var xml = ['<data>'];
    for(var name in data) {
        xml.push('<' + name + '>');
        xml.push(data[name]);
        xml.push('</' + name + '>');
    }
    xml.push('</data>');
    return xml.join('');
}

...
var data = { // data is actually provided by a user.
    x : 10,
    y : 20,
    z : 30
};
var request = xhr(); // xhr() returns an asynchronous object.
request.onreadystatechange = handleStateChange; // handleStateChange refers to a function.
request.open('POST', url);
request.setRequestHeader('Content-Type', 'text/xml');
request.send(toXML(data));

After running the above example, the server will receive the following XML string (formatted for readability).
<data>
    <x>10</x>
    <y>20</y>
    <z>30</z>
</data>

Of course, the server should parse the XML string and dig out what it needs for processing. Here, I don't want to involve into how the server parses the XML string.

When a server send a XML response, the client can use the asynchronous object's responseXML property to get the parsed DOM object, and then use DOM API to dig out what it needs for processing. For instance, the first example of Making GET Requests sets the returned XML string to the div element's innerHTML. This way limits the client's page design. The server can use a XML response to give the client free with page design.

For example, if the server sends the following XML (Note that, the server should set the response header 'Content-Type' as 'text/xml'. If the XML contains non-ASCII characters, like Traditional Chinese characters, the charset property is also required.):
<?xml version="1.0" encoding="utf-8"?>
<select>
    <option value="algorithm">Algorithm</option>
    <option value="graphic">Computer Graphics</option>
    <option value="pattern">Design Pattern</option>
</select>

The following example rewrites the first example of Making GET Requests. This time, it deals with a XML response.
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN">
<html>
    <head>
        <meta content="text/html; charset=UTF-8" http-equiv="content-type">
        <script type="text/javascript">
            window.onload = function() {
                var xhr = window.XMLHttpRequest && 
                      (window.location.protocol !== 'file:' 
                          || !window.ActiveXObject) ?
                       function() {
                           return new XMLHttpRequest();
                       } :
                       function() {
                          try {
                             return new ActiveXObject('Microsoft.XMLHTTP');
                          } catch(e) {
                             throw new Error('XMLHttpRequest not supported');
                          }
                       };
                
                function param(obj) {
                    var pairs = [];
                    for(var name in obj) {
                        var pair = encodeURIComponent(name) + '=' + 
                                   encodeURIComponent(obj[name]);
                        pairs.push(pair.replace('/%20/g', '+'));
                    }
                    return pairs.join('&');
                }
                
                function ajax(option) {
                    option.type = option.type || 'GET';
                    option.header = option.header || {
                      'Content-Type':'application/x-www-form-urlencoded'};
                    option.callback = option.callback || function() {};
                    
                    if(!option.url) {
                        return;
                    }
                    
                    var request = xhr();
                    request.onreadystatechange = function() {
                        option.callback.call(request, request);
                    };
                    
                    var body = null;
                    var url = option.url;
                    if(option.data) {
                        if(option.type === 'POST') {
                            body = param(option.data);
                        }
                        else {
                            url = option.url + '?' + param(option.data) 
                                     + '&time=' + new Date().getTime();
                        }
                    }
                    
                    request.open(option.type, url);
                    for(var name in option.header) {
                        request.setRequestHeader(
                                name, option.header[name]);
                    }
                    request.send(body);
                }
                
                document.getElementById('category').onchange = function() {
                    ajax({
                        url     : 'XML-1.php',
                        data    : {category : this.value},
                        callback: function(request) {
                            if(request.readyState === 4) {
                                if(request.status === 200) {
                                    var select = 
                                         document.createElement('select');
                                    var xml = request.responseXML;
                                    var options = 
                                         xml.getElementsByTagName('option');
                                    for(var i = 0; i < options.length; i++) {
                                        var value = 
                                          options[i].getAttribute('value');
                                        // Note that, a text is also a node.
                                        var text = 
                                          options[i].firstChild.nodeValue;
                                        if(navigator.userAgent
                                            .indexOf('MSIE') === -1) {
                                            select.add(
                                             new Option(text, value), 
                                              select.options[
                                               select.options.length]);
                                        }
                                        else {
                                            select.add(
                                              new Option(text, value),
                                               select.options.length);
                                        }
                                    }
                                    var book = 
                                      document.getElementById('book');
                                    if(book.firstChild) {
                                        book.removeChild(book.firstChild);
                                    }
                                    book.appendChild(select);
                                }
                            }
                        }
                    });
                };
            };
        </script>        
    </head>
    <body>
        Book: <br>
        <select id="category">
            <option>-- Category --</option>
            <option value="theory">Theory</option>
            <option value="language">Language</option>
            <option value="web">Web</option>
        </select><br><br>
        Buy: <div id="book"></div>
    </body>
</html>

This example has the same functionality as the first example of Making GET Requests, yet the client can determine how to display the XML response.

The above example also demonstrates how to deal with the difference between standards-compliant browsers and Internet Explorer when appending option elements to the select element. We can't use object detection here, because standards-compliant browser and Internet Explorer both have an add function. Their difference is the second parameter. Standards-compliant browsers need an existing option, and the new option will be appended after that. Internet Explorer needs an existing option's index, and the new option will be appended after it.

Use object detection when possible. Here we can't do that, so use browser sniffing instead.