/******************************************************************************
    Ajax.js
    Copyright (c) 2006-2008 Motorola, Inc.
    All rights reserved
******************************************************************************/
$package("netopia/ajax.html");

//--------------------

netopia.create("ajax").add({

//-----------------------------------------------------------------
// Some helpful constants:

kContentType : "Content-Type",
kJsonType    : "application/json",
kXmlType     : "application/xml",

// The various states of a request object:
kRequestUnsent          : 0,
kRequestOpened          : 1,
kRequestHeadersReceived : 2,
kRequestLoading         : 3,
kRequestDone            : 4,

isContentType : function (ctype, t)
{
    if (!ctype)
        return false;
    if (ctype == t)
        return true;
    if (ctype.length <= t.length)
        return false;
    var s = ctype.split(";");
    return s[0] == t;
},

/**
This class encapsulates a response to a request sent via XMLHttpRequest. This
class exists to allow us to extend the object for the request/response cycle,
in particular with a JSON responseObject property. We cannot do this (on all
browsers) using the natively provided XMLHttpRequest object. At least on IE6,
the ActiveX control behind it does not support setting properties that it did
not define.
*/
Response : $extends (netopia.core.Object,
{
    ctor : function (req, url, params)
    {
        this.params = params;
        this.request = req;
        this.url = url;

        req.onreadystatechange = this.onStateChange.xbind(this);
    },

    getAllResponseHeaders : function ()
    {
        return this.request.getAllResponseHeaders();
    },

    getResponseHeader : function (name)
    {
        return this.request.getResponseHeader(name);
    },

    onStateChange : function ()
    {
        var req = this.request;

        this.readyState = req.readyState;

        var done = (req.readyState == netopia.ajax.kRequestDone);
        if (done)
        {
            this.status     = req.status;
            this.statusText = req.statusText;

            req.onreadystatechange = $nop; // avoid IE ref cycle

            var ct = req.getResponseHeader(netopia.ajax.kContentType);
            if (netopia.ajax.isContentType(ct, netopia.ajax.kXmlType))
                this.responseXML = req.responseXML;
            else
            {
                this.responseText = req.responseText;
                if (netopia.ajax.isContentType(ct, netopia.ajax.kJsonType))
                    this.responseObject = netopia.json.parse(req.responseText);
            }
        }

        if (this.params.onreadystatechange)
            this.params.onreadystatechange(response);
        if (done && this.params.onload)
            this.params.onload(this);
    }
})

});

//-----------------------------------------------------------------------------

netopia.ajax.add(function ()
{
    var kIEXHRProgId = [ "MSXML2.XMLHTTP.5.0", "MSXML2.XMLHTTP.4.0",
                         "MSXML2.XMLHTTP", "Microsoft.XMLHTTP" ];

    function newHttpRequest ()
    {
        if (typeof XMLHttpRequest == "undefined" && typeof ActiveXObject != "undefined")
            for (var i = 0; i < kIEXHRProgId.length; ++i)
                try { return new ActiveXObject(kIEXHRProgId[i]); } catch (e) {}

        return new XMLHttpRequest(); // this may fail, but the error will read well
    }

    return $literal(
    {
        /**
        Creates and sends a request given the URL and other parameters. The parameters
        can contain the following properties:

          - verb : The HTTP verb (default is GET, but can be POST).
          - sync : True to make the request synchronously (default is async).
          - content : The content of the request.
          - object : The JSON object for the request (use instead of content).
          - accept : The type of response content that will be accepted (default
                is JSON if "object").
          - type : The type of request content (default is JSON if "object").
          - onload : A callback made when the request is complete (optional).
          - onreadystatechange : A callback made for each state change (optional).
          - username : The username for the request.
          - password : The password for the request.
          - headers : An (optional) object containing properties for each header.

        @param url The URL for the request.
        @param params The object containing request parameters. One of the following
            properties is required: sync, onload or onreadystatechange.
        @return The XmlHttpRequest object (already sent).
        */
        send : function (url, params)
        {
            params = params || { };
            params.headers = params.headers || { };

            if (params.object)
            {
                $requires("netopia/introspect.html");
                $requires("netopia/json.html");

                params.content = netopia.core.stringifyObject(params.object);
                params.type = netopia.ajax.kJsonType;
                params.accept = params.accept || netopia.ajax.kJsonType;
            }

            var verb = params.verb || (params.content ? "POST" : "GET");
            var async = !params.sync;

            var request = newHttpRequest();
            var response = new netopia.ajax.Response(request, url, params);

            request.open(verb, url, async, params.username || null,
                         params.password || null);
            if (params.headers)
                for (var hdr in params.headers)
                    if (netopia.core.hasOwnProp(params.headers, hdr))
                        request.setRequestHeader(hdr, params.headers[hdr]);

            if (params.accept)
                request.setRequestHeader("Accept", params.accept);
            if (params.type)
                request.setRequestHeader(netopia.ajax.kContentType, params.type);

            request.send(params.content || null);
            return response;
        }
    });
}()

); //netopia.ajax

