SilverAjax

SilverAjax: A(nother) Cross-Browser XMLHttpRequest Wrapper

About

No, I'm not living in a cave, I know they are plenty of XMLHttpRequest wrappers out there already. No, I am not trying to re-invent the wheel! I decided to put one together mostly as an exercise in JavaScript. Truth is, I've been working with MooTools for so long that I barely ever get to do any "real JavaScript", that is, without the help of a framework.

I decided it was time to do something in Javascript without the help of Mootools or Prototype or <insert your favorite JS framework here>… and, having never really played directly with the XMLHttpRequest object, I decided it would be an interesting project.

It turned out to be much easier than I expected (the XMLHttpRequest object that is, Javascript is still, and will always be, a bit of a mystery for me; Prototypal Inheritance, Cross-Browser issues etc..)… So anyway, here is a first draft of what I came up with, the documentation etc. etc.. will follow soon enough…in the meantime, there are some comments…

Brief Documentation

See the separate article: SilverAjax Documentation

The code

You can download it here: http://silverscripting.wikidot.com/local--files/silverajax/silverajax.js

This is the full source (with sweet syntax-highlighting)

// Silverajax by Jean-Nicolas Jolivet
// A simple cross-browser XHMLHttpRequest wrapper.
// Licensed under the MIT License
 
// IE 4 & 5 don't implement Function.apply( ).
// This workaround is based on code by Aaron Boodman.
 
if (!Function.prototype.apply) {
    // Invoke this function as a method of the specified object,
    // passing the specified parameters.  We have to use eval( ) to do this
    Function.prototype.apply = function (object, parameters) {
        var f = this;                // The function to invoke
        var o = object || window;    // The object to invoke it on
        var args = parameters || []; // The arguments to pass
        o._$_apply_$_ = f;
        var stringArgs = [];
        for(var i = 0; i < args.length; i++)
            stringArgs[i] = "args[" + i + "]";
        var arglist = stringArgs.join(",");
        var methodcall = "o._$_apply_$_(" + arglist + ");";
        var result = eval(methodcall);
        delete o._$_apply_$_;
        return result;
    };
}
 
// Silverajax constructor
// Arguments:
//   url: Where to send that request
//   options: a bunch of useful options
var Silverajax = function (url, options) {
 
    // define the default options
    this.options =  {
        timeout: 10000,                // The request timeout in milliseconds
        expectJson: false,            // Wheter or not to eval the json response
        expectJavascript: false,    // Whether or not to eval the response 
        method: 'GET',                // The request method ('POST' or 'GET')
        update: '',                 // An element to update with the responseText
        encoding: 'utf-8',            // The encoding to use for POST requests
        headers: {
            'X-Requested-With': 'XMLHttpRequest',
            'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
        }
    };
    this.url = url;
 
    // Browser detection
    if (window.XMLHttpRequest) {
        this.request = new XMLHttpRequest(); // nice, a decent browser
    }
    else if (window.ActiveXObject) { // Some version of IE...
        var ua = navigator.userAgent.toLowerCase();
        if (ua.indexOf('msie 5') === -1) {
            this.request = new ActiveXObject("Msxml2.XMLHTTP");
        } else {
            this.request = new ActiveXObject("Microsoft.XMLHTTP");
        }
    }
 
    this.options = this.mergeObjects(this.options, options);
 
    // Make sure the method is eighter POST or GET, else, default to GET
    this.options.method = (this.options.method === 'POST' || this.options.method === 'GET') ? this.options.method : 'GET';
};
 
// Method: initOptions
// Merges this.options with the options passed in the arguments
Silverajax.prototype.mergeObjects = function (orig, ext) {
    var name;
    // I searched for a better way to do this, (i.e. merge objects)
    // couldn't find anything so I decided to do it that way... it
    // might not be the best way but it works well...
    for (name in ext || {}) {
        if (typeof name !== "function") {
            orig[name] = ext[name];
        }
    }
    return orig;
};
 
// Method: handleRequest
// The callback for onreadystatechange
Silverajax.prototype.handleRequest = function () {
    if (this.request.readyState === 4) {  // If the request is finished
        clearTimeout(this.requestTimeout); // Clear our timeout
        this.completed(); // Fire onComplete
    }
 
};
 
// Method: requestTimedOut
// Called when the request has timed-out
Silverajax.prototype.requestTimedOut = function () {
    this.abort();
    this.onTimeout();
};
 
// Method: onComplete
// Called when the XMLHttpRequest is complete
Silverajax.prototype.completed = function () {
    this.response = {
        status: this.request.status,
        statusText: this.request.statusText,
        responseText: this.request.responseText,
        responseXml: this.request.responseXML,
        responseJson: {}
    };
    if (typeof this.onComplete === 'function') { this.onComplete(this.response);}
    if (this.request.status === 200) {
        if(this.options.update) {
            var el = typeof this.options.update === 'string' ? document.getElementById(this.options.update) : this.options.update;
            el.innerHTML = this.request.responseText;
        }
        if(this.options.expectJson) {
            this.response.responseJson = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(this.request.responseText.replace(/"(\\.|[^"\\])*"/g, ''))) && eval('(' + this.request.responseText + ')');
        }
        if(this.options.expectJavascript) {
            eval(this.request.responseText);
        }
        if (typeof this.onSuccess === 'function') {this.onSuccess(this.response);}
    }
    else {
        if (typeof this.onFailure === 'function') {this.onFailure(this.response);}
    }
};
 
// Method: send
// Will send the XMLHttpRequest
Silverajax.prototype.send = function (sendData) {
    var that = this;
    var data = sendData || null;
    var headers = {};
    // Add the callback for the onreadystatechange event
    this.request.onreadystatechange = function () {
        that.handleRequest.apply(that);    
    };
    // Initialize the request based on our options...
    this.url = this.options.method === "GET" && data ? this.url + "?" + data : this.url;
    this.request.open(this.options.method, this.url, true);
 
    if(this.options.method == 'POST' && data) {
        headers = {
            "Content-type": "application/x-www-form-urlencoded; charset=" + this.options.encoding,
            "Content-length": data.length,
            "Connection": "close"
        };
    }
    // Merge the options'headers with the local headers object and set them
    headers = this.mergeObjects(this.options.headers, headers);
    this.setHeaders(headers);
    // Send the request
 
    // Set the request timeout based on the options
    this.requestTimeout = setTimeout(this.requestTimedOut, this.options.timeout);
 
    this.request.send(data);
 
};
 
// Method: addHeader(key, value)
// Adds a new header to the current headers
Silverajax.prototype.addHeader = function (key, value) {
    this.options.headers = this.mergeObjects(this.options.headers, {key: value});
};
 
// Method: deleteHeader(key)
// Deletes the header at [key]
Silverajax.prototype.deleteHeader = function (key) {
    delete this.options.headers[key];
};
 
// Method: setHeaders
// Used internally to set the request Headers..
Silverajax.prototype.setHeaders = function (headers) {
    var name;
    for(name in headers) {
        if(typeof headers[name] !== 'function') {
            this.request.setRequestHeader(name, headers[name]);
        }
    }
};
 
// Method: abort
// Aborts the current XMLHttpRequest
Silverajax.prototype.abort = function () {
    this.request.abort();
};
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License