Rob Kinyon - HTTP.Request-0.01

Documentation | Source

NAME

HTTP.Request

DESCRIPTION

This provides a way to make a HTTP request in a completely browser-independent fashion, if the browser can support this kind of activity.

DEPENDENCIES

None.

CLASSES

*/

if ( typeof HTTP == "undefined" ) { HTTP = {}; }

if ( typeof Method == "undefined" ) { Method = {}; }

if ( typeof Method["bind"] == "undefined" ) { Method.bind = function ( method, object ) { return function() { method.apply(object, arguments); } }; }

/*

HTTP.Request

This is the basic AJAX communicator. It does all of its work within the constructor, using callbacks as provided (if in asynchronous mode).

The construct takes a hash of options. All option names are case-insensitive. These options are:

  • method (post|get)
  • This defaults to 'post'. If any value other than 'post' or 'get' is passed, this will be set to 'post'.

  • asychronous (true|false)
  • This defaults to true.

  • parameters
  • This is a string containing the URL parameters you wish to pass. Depending on the method, they will either be appended to the URL or sent as if they were in a form.

  • transport
  • This is the class that is used for the transport. It defaults to HTTP.Request.Transport and is expected to conform to its API.

  • onUninitialized
  • This is a callback that will be called when the Uninitialized event occurs. It will only be called if the request is asynchronous.

  • onLoading
  • This is a callback that will be called when the Loading event occurs. It will only be called if the request is asynchronous.

  • onLoaded
  • This is a callback that will be called when the Loaded event occurs. It will only be called if the request is asynchronous.

  • onInteractive
  • This is a callback that will be called when the Interactive event occurs. It will only be called if the request is asynchronous.

  • onComplete
  • This is a callback that will be called when the Complete event occurs. It will only be called if the request is asynchronous.

  • on[status] / onSuccess / onFailure
  • These are callbacks that will be called when the Complete event occurs. If [status] is found (numeric), then that will be called. Otherwise, onSuccess will be called if this is a success (as defined by isSuccess()) or onFailure will be called.

The callback functions must be defined as so:

  onComplete: function (trans) {
      ...
  }

where 'trans' is the transport being used. This will be an object of HTTP.Request.Transport, as detailed below.

*/

if ( typeof HTTP.Request == "undefined" ) { HTTP.Request = function ( options ) { if ( !options ) options = {}; this.options = options;

        for ( var i in this.options ) {
            this.options[i.toLowerCase()] = this.options[i];
        }

        if ( ! this.options.method ) {
            this.options.method = "post";
        }
        else {
            this.options.method = this.options.method.toLowerCase();
            if ( ! ( this.options.method == "get" || this.options.method == "post" ) ) {
                this.options.method = "post";
            }
        }

        if ( typeof this.options["asynchronous"] == "undefined" )
            this.options.asynchronous = true;
        if ( typeof this.options["parameters"] == "undefined" )
            this.options.parameters = "";

        if ( typeof this.options["transport"] == "undefined" )
            this.options.transport = HTTP.Request.Transport;

        if ( this.options.uri )
            this.request();
    };

    HTTP.Request.VERSION = 0.01;

    HTTP.Request.EventNames = [
        "uninitialized"
       ,"loading"
       ,"loaded"
       ,"interactive"
       ,"complete"
    ];

    HTTP.Request.prototype.request = function ( uri ) {
        if ( ! uri ) uri = this.options.uri;
        if ( ! uri ) return;

        var parameters = this.options.parameters || "";
        // XXX Why?
        if (parameters.length > 0) parameters += "&_=";

        try {
            if (this.options.method == "get") {
                uri += "?" + parameters;
            }

            this.transport = new (this.options.transport)();
            this.transport.open(
                this.options.method
               ,uri
               ,this.options.asynchronous ? true : false
            );

            if ( this.options.asynchronous ) {
                this.transport.onreadystatechange = Method.bind(
                    this.onStateChange, this
                );

                setTimeout(
                    Method.bind( function() { this.respondToReadyState(1) }, this)
                   ,10
               );
            }

            this.setRequestHeaders();

            var body = this.options.postBody ? this.options.postBody : parameters;
            this.transport.send( this.options.method == "post" ? body : null );
        } catch (e) {
            alert( "Within HTTP.Request.request():\n" + e );
        }

    };

    HTTP.Request.prototype.setRequestHeaders = function() {
        this.transport.setRequestHeader( "X-Requested-With", "XMLHttpRequest" );
        this.transport.setRequestHeader( "X-HTTP-Request-Version", HTTP.Request.VERSION );

        if (this.options.method == "post") {
            this.transport.setRequestHeader( "Content-type", "application/x-www-form-urlencoded" );

            /* Force "Connection: close" for Mozilla browsers to work around
             * a bug where XMLHttpReqeuest sends an incorrect Content-length
             * header. See Mozilla Bugzilla #246651. 
             */
            if (this.transport.overrideMimeType) {
                this.transport.setRequestHeader( "Connection", "close" );
            }
        }

/* TODO Add support for this back in later if (this.options.requestHeaders) requestHeaders.push.apply(requestHeaders, this.options.requestHeaders); */ };

    // XXX This confuses me a little ... how are undefined and 0 considered a success?
    HTTP.Request.prototype.isSuccess = function () {
        return this.transport.status == undefined
            || this.transport.status == 0 
            || (this.transport.status >= 200 && this.transport.status < 300);
    };

    HTTP.Request.prototype.onStateChange = function() {
        var readyState = this.transport.readyState;
        if (readyState != 1) {
            this.respondToReadyState( this.transport.readyState );
        }
    };

    HTTP.Request.prototype.respondToReadyState = function( readyState ) {
        var event = HTTP.Request.EventNames[readyState];

        if (event == "complete") {
            var func = this.options[ "on" + this.transport.status ];
            if ( ! func ) {
                if ( this.isSuccess() ) {
                    func = this.options[ "onSuccess" ];
                }
                else {
                    func = this.options[ "onFailure" ];
                }
            }

            if ( func ) {
                ( func )( this.transport );
            }
        }

        if ( this.options["on" + event] )
            ( this.options["on" + event] )( this.transport );

        /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
        if (event == "complete") {
            this.transport.onreadystatechange = function (){};
        }
    };
}

/*

HTTP.Request.Transport

This is the class that actually does the tranport. In Gecko-based browsers, this is equivalent to the XMLHttpRequest class. In IE-based browsers, this is equivalent to the correct ActiveX object. In other browsers, this is an object that abstracts away the layers/iframe method that is used.

*/

if ( typeof HTTP.Request.Transport == "undefined" ) { if ( window.XMLHttpRequest ) { HTTP.Request.Transport = window.XMLHttpRequest; } else if ( window.ActiveXObject ) { var msxmls = new Array( "Msxml2.XMLHTTP.5.0" ,"Msxml2.XMLHTTP.4.0" ,"Msxml2.XMLHTTP.3.0" ,"Msxml2.XMLHTTP" ,"Microsoft.XMLHTTP" ); for ( var msxml in msxmls ) { try { new ActiveXObject(msxml); HTTP.Request.Transport = function () { return new ActiveXObject(msxml); } break; } catch(e) { } } }

    if ( typeof HTTP.Request.Transport == "undefined" ) {
        // This is where we add DIV/IFRAME support masquerading as an XMLHttpRequest object
    }

    if ( typeof HTTP.Request.Transport == "undefined" ) {
        throw new Error("Unable to locate XMLHttpRequest or other HTTP transport mechanism");
    }
}

/*

TODO

  • Add support for DIV/Layers/IFRAME methods of transport
  • Lay out the API for HTTP.Request.Transport
  • Better error-handling
  • Add user-defined headers
  • Remove need for try/catch (degrade to JS 1.2 or lower, if possible)
  • Tests, tests, and more tests

SUPPORT

Currently, there is no mailing list or IRC channel. Please send bug reports and patches to the author.

AUTHOR

Rob Kinyon (rob.kinyon@iinteractive.com)

Code taken from several sources, including those written by Sam Stephenson, Adam Kennedy, and Eric Andreychek. Many thanks to their generous help.

My time is generously donated by Infinity Interactive, Inc. http://www.iinteractive.com

/*

*/