Subversion Repositories Applications.papyrus

Rev

Blame | Last modification | View Log | RSS feed

if(!dojo._hasResource["dojo._base.xhr"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojo._base.xhr"] = true;
dojo.provide("dojo._base.xhr");
dojo.require("dojo._base.Deferred");
dojo.require("dojo._base.json");
dojo.require("dojo._base.lang");
dojo.require("dojo._base.query");

(function(){
        var _d = dojo;
        function setValue(/*Object*/obj, /*String*/name, /*String*/value){
                //summary:
                //              For the nameed property in object, set the value. If a value
                //              already exists and it is a string, convert the value to be an
                //              array of values.
                var val = obj[name];
                if(_d.isString(val)){
                        obj[name] = [val, value];
                }else if(_d.isArray(val)){
                        val.push(value);
                }else{
                        obj[name] = value;
                }
        }

        dojo.formToObject = function(/*DOMNode||String*/ formNode){
                // summary:
                //              dojo.formToObject returns the values encoded in an HTML form as
                //              string properties in an object which it then returns. Disabled form
                //              elements, buttons, and other non-value form elements are skipped.
                //              Multi-select elements are returned as an array of string values.
                // description:
                //              This form:
                //
                //                      <form id="test_form">
                //                              <input type="text" name="blah" value="blah">
                //                              <input type="text" name="no_value" value="blah" disabled>
                //                              <input type="button" name="no_value2" value="blah">
                //                              <select type="select" multiple name="multi" size="5">
                //                                      <option value="blah">blah</option>
                //                                      <option value="thud" selected>thud</option>
                //                                      <option value="thonk" selected>thonk</option>
                //                              </select>
                //                      </form>
                //
                //              yields this object structure as the result of a call to
                //              formToObject():
                //
                //                      { 
                //                              blah: "blah",
                //                              multi: [
                //                                      "thud",
                //                                      "thonk"
                //                              ]
                //                      };
        
                var ret = {};
                var iq = "input:not([type=file]):not([type=submit]):not([type=image]):not([type=reset]):not([type=button]), select, textarea";
                _d.query(iq, formNode).filter(function(node){
                        return (!node.disabled);
                }).forEach(function(item){
                        var _in = item.name;
                        var type = (item.type||"").toLowerCase();
                        if(type == "radio" || type == "checkbox"){
                                if(item.checked){ setValue(ret, _in, item.value); }
                        }else if(item.multiple){
                                ret[_in] = [];
                                _d.query("option", item).forEach(function(opt){
                                        if(opt.selected){
                                                setValue(ret, _in, opt.value);
                                        }
                                });
                        }else{ 
                                setValue(ret, _in, item.value);
                                if(type == "image"){
                                        ret[_in+".x"] = ret[_in+".y"] = ret[_in].x = ret[_in].y = 0;
                                }
                        }
                });
                return ret; // Object
        }

        dojo.objectToQuery = function(/*Object*/ map){
                //      summary:
                //              takes a key/value mapping object and returns a string representing
                //              a URL-encoded version of that object.
                //      example:
                //              this object:
                //
                //              |       { 
                //              |               blah: "blah",
                //              |               multi: [
                //              |                       "thud",
                //              |                       "thonk"
                //              |               ]
                //              |       };
                //
                //      yeilds the following query string:
                //      
                //      |       "blah=blah&multi=thud&multi=thonk"


                // FIXME: need to implement encodeAscii!!
                var ec = encodeURIComponent;
                var ret = "";
                var backstop = {};
                for(var x in map){
                        if(map[x] != backstop[x]){
                                if(_d.isArray(map[x])){
                                        for(var y=0; y<map[x].length; y++){
                                                ret += ec(x) + "=" + ec(map[x][y]) + "&";
                                        }
                                }else{
                                        ret += ec(x) + "=" + ec(map[x]) + "&";
                                }
                        }
                }
                if(ret.length && ret.charAt(ret.length-1) == "&"){
                        ret = ret.substr(0, ret.length-1);
                }
                return ret; // String
        }

        dojo.formToQuery = function(/*DOMNode||String*/ formNode){
                // summary:
                //              return URL-encoded string representing the form passed as either a
                //              node or string ID identifying the form to serialize
                return _d.objectToQuery(_d.formToObject(formNode)); // String
        }

        dojo.formToJson = function(/*DOMNode||String*/ formNode, /*Boolean?*/prettyPrint){
                // summary:
                //              return a serialized JSON string from a form node or string
                //              ID identifying the form to serialize
                return _d.toJson(_d.formToObject(formNode), prettyPrint); // String
        }

        dojo.queryToObject = function(/*String*/ str){
                // summary:
                //              returns an object representing a de-serialized query section of a
                //              URL. Query keys with multiple values are returned in an array.
                // description:
                //              This string:
                //
                //                      "foo=bar&foo=baz&thinger=%20spaces%20=blah&zonk=blarg&"
                //              
                //              returns this object structure:
                //
                //                      {
                //                              foo: [ "bar", "baz" ],
                //                              thinger: " spaces =blah",
                //                              zonk: "blarg"
                //                      }
                //      
                //              Note that spaces and other urlencoded entities are correctly
                //              handled.

                // FIXME: should we grab the URL string if we're not passed one?
                var ret = {};
                var qp = str.split("&");
                var dc = decodeURIComponent;
                _d.forEach(qp, function(item){
                        if(item.length){
                                var parts = item.split("=");
                                var name = dc(parts.shift());
                                var val = dc(parts.join("="));
                                if(_d.isString(ret[name])){
                                        ret[name] = [ret[name]];
                                }
                                if(_d.isArray(ret[name])){
                                        ret[name].push(val);
                                }else{
                                        ret[name] = val;
                                }
                        }
                });
                return ret; // Object
        }

        /*
                from refactor.txt:

                all bind() replacement APIs take the following argument structure:

                        {
                                url: "blah.html",

                                // all below are optional, but must be supported in some form by
                                // every IO API
                                timeout: 1000, // milliseconds
                                handleAs: "text", // replaces the always-wrong "mimetype"
                                content: { 
                                        key: "value"
                                },

                                // browser-specific, MAY be unsupported
                                sync: true, // defaults to false
                                form: dojo.byId("someForm") 
                        }
        */

        // need to block async callbacks from snatching this thread as the result
        // of an async callback might call another sync XHR, this hangs khtml forever
        // must checked by watchInFlight()

        dojo._blockAsync = false;

        dojo._contentHandlers = {
                "text": function(xhr){ return xhr.responseText; },
                "json": function(xhr){
                        if(!djConfig.usePlainJson){
                                console.debug("Consider using mimetype:text/json-comment-filtered"
                                        + " to avoid potential security issues with JSON endpoints"
                                        + " (use djConfig.usePlainJson=true to turn off this message)");
                        }
                        return _d.fromJson(xhr.responseText);
                },
                "json-comment-filtered": function(xhr){ 
                        // NOTE: we provide the json-comment-filtered option as one solution to
                        // the "JavaScript Hijacking" issue noted by Fortify and others. It is
                        // not appropriate for all circumstances.

                        var value = xhr.responseText;
                        var cStartIdx = value.indexOf("\/*");
                        var cEndIdx = value.lastIndexOf("*\/");
                        if(cStartIdx == -1 || cEndIdx == -1){
                                throw new Error("JSON was not comment filtered");
                        }
                        return _d.fromJson(value.substring(cStartIdx+2, cEndIdx));
                },
                "javascript": function(xhr){ 
                        // FIXME: try Moz and IE specific eval variants?
                        return _d.eval(xhr.responseText);
                },
                "xml": function(xhr){ 
                        if(_d.isIE && !xhr.responseXML){
                                _d.forEach(["MSXML2", "Microsoft", "MSXML", "MSXML3"], function(i){
                                        try{
                                                var doc = new ActiveXObject(prefixes[i]+".XMLDOM");
                                                doc.async = false;
                                                doc.loadXML(xhr.responseText);
                                                return doc;     //      DOMDocument
                                        }catch(e){ /* squelch */ };
                                });
                        }else{
                                return xhr.responseXML;
                        }
                }
        };

        dojo._contentHandlers["json-comment-optional"] = function(xhr){
                var handlers = _d._contentHandlers;
                try{
                        return handlers["json-comment-filtered"](xhr);
                }catch(e){
                        return handlers["json"](xhr);
                }
        };

        /*=====
        dojo.__ioArgs = function(kwArgs){
                //      url: String
                //              URL to server endpoint.
                //      content: Object?
                //              Contains properties with string values. These
                //              properties will be serialized as name1=value2 and
                //              passed in the request.
                //      timeout: Integer?
                //              Milliseconds to wait for the response. If this time
                //              passes, the then error callbacks are called.
                //      form: DOMNode?
                //              DOM node for a form. Used to extract the form values
                //              and send to the server.
                //      preventCache: Boolean?
                //              Default is false. If true, then a
                //              "dojo.preventCache" parameter is sent in the request
                //              with a value that changes with each request
                //              (timestamp). Useful only with GET-type requests.
                //      handleAs: String?
                //              Acceptable values depend on the type of IO
                //              transport (see specific IO calls for more information).
                //      load: Function?
                //              function(response, ioArgs){}. response is an Object, ioArgs
                //              is of type dojo.__ioCallbackArgs. The load function will be
                //              called on a successful response.
                //      error: Function?
                //              function(response, ioArgs){}. response is an Object, ioArgs
                //              is of type dojo.__ioCallbackArgs. The error function will
                //              be called in an error case. 
                //      handle: Function
                //              function(response, ioArgs){}. response is an Object, ioArgs
                //              is of type dojo.__ioCallbackArgs. The handle function will
                //              be called in either the successful or error case.  For
                //              the load, error and handle functions, the ioArgs object
                //              will contain the following properties: 
        }
        =====*/

        /*=====
        dojo.__ioCallbackArgs = function(kwArgs){
                //      args: Object
                //              the original object argument to the IO call.
                //      xhr: XMLHttpRequest
                //              For XMLHttpRequest calls only, the
                //              XMLHttpRequest object that was used for the
                //              request.
                //      url: String
                //              The final URL used for the call. Many times it
                //              will be different than the original args.url
                //              value.
                //      query: String
                //              For non-GET requests, the
                //              name1=value1&name2=value2 parameters sent up in
                //              the request.
                //      handleAs: String
                //              The final indicator on how the response will be
                //              handled.
                //      id: String
                //              For dojo.io.script calls only, the internal
                //              script ID used for the request.
                //      canDelete: Boolean
                //              For dojo.io.script calls only, indicates
                //              whether the script tag that represents the
                //              request can be deleted after callbacks have
                //              been called. Used internally to know when
                //              cleanup can happen on JSONP-type requests.
                //      json: Object
                //              For dojo.io.script calls only: holds the JSON
                //              response for JSONP-type requests. Used
                //              internally to hold on to the JSON responses.
                //              You should not need to access it directly --
                //              the same object should be passed to the success
                //              callbacks directly.
        }
        =====*/



        dojo._ioSetArgs = function(/*dojo.__ioArgs*/args,
                        /*Function*/canceller,
                        /*Function*/okHandler,
                        /*Function*/errHandler){
                //      summary: 
                //              sets up the Deferred and ioArgs property on the Deferred so it
                //              can be used in an io call.
                //      args:
                //              The args object passed into the public io call. Recognized properties on
                //              the args object are:
                //      canceller:
                //              The canceller function used for the Deferred object. The function
                //              will receive one argument, the Deferred object that is related to the
                //              canceller.
                //      okHandler:
                //              The first OK callback to be registered with Deferred. It has the opportunity
                //              to transform the OK response. It will receive one argument -- the Deferred
                //              object returned from this function.
                //      errHandler:
                //              The first error callback to be registered with Deferred. It has the opportunity
                //              to do cleanup on an error. It will receive two arguments: error (the 
                //              Error object) and dfd, the Deferred object returned from this function.

                var ioArgs = {args: args, url: args.url};

                //Get values from form if requestd.
                var formObject = null;
                if(args.form){ 
                        var form = _d.byId(args.form);
                        //IE requires going through getAttributeNode instead of just getAttribute in some form cases, 
                        //so use it for all.  See #2844
                        var actnNode = form.getAttributeNode("action");
                        ioArgs.url = ioArgs.url || (actnNode ? actnNode.value : null); 
                        formObject = _d.formToObject(form);
                }

                // set up the query params
                var miArgs = [{}];
        
                if(formObject){
                        // potentially over-ride url-provided params w/ form values
                        miArgs.push(formObject);
                }
                if(args.content){
                        // stuff in content over-rides what's set by form
                        miArgs.push(args.content);
                }
                if(args.preventCache){
                        miArgs.push({"dojo.preventCache": new Date().valueOf()});
                }
                ioArgs.query = _d.objectToQuery(_d.mixin.apply(null, miArgs));
        
                // .. and the real work of getting the deferred in order, etc.
                ioArgs.handleAs = args.handleAs || "text";
                var d = new _d.Deferred(canceller);
                d.addCallbacks(okHandler, function(error){
                        return errHandler(error, d);
                });

                //Support specifying load, error and handle callback functions from the args.
                //For those callbacks, the "this" object will be the args object.
                //The callbacks will get the deferred result value as the
                //first argument and the ioArgs object as the second argument.
                var ld = args.load;
                if(ld && _d.isFunction(ld)){
                        d.addCallback(function(value){
                                return ld.call(args, value, ioArgs);
                        });
                }
                var err = args.error;
                if(err && _d.isFunction(err)){
                        d.addErrback(function(value){
                                return err.call(args, value, ioArgs);
                        });
                }
                var handle = args.handle;
                if(handle && _d.isFunction(handle)){
                        d.addBoth(function(value){
                                return handle.call(args, value, ioArgs);
                        });
                }
                
                d.ioArgs = ioArgs;
        
                // FIXME: need to wire up the xhr object's abort method to something
                // analagous in the Deferred
                return d;
        }

        var _deferredCancel = function(/*Deferred*/dfd){
                //summary: canceller function for dojo._ioSetArgs call.
                
                dfd.canceled = true;
                var xhr = dfd.ioArgs.xhr;
                var _at = (typeof xhr.abort);
                if((_at == "function")||(_at == "unknown")){
                        xhr.abort();
                }
                var err = new Error("xhr cancelled");
                err.dojoType = "cancel";
                return err;
        }
        var _deferredOk = function(/*Deferred*/dfd){
                //summary: okHandler function for dojo._ioSetArgs call.

                return _d._contentHandlers[dfd.ioArgs.handleAs](dfd.ioArgs.xhr);
        }
        var _deferError = function(/*Error*/error, /*Deferred*/dfd){
                //summary: errHandler function for dojo._ioSetArgs call.
                
                // console.debug("xhr error in:", dfd.ioArgs.xhr);
                console.debug(error);
                return error;
        }

        var _makeXhrDeferred = function(/*dojo.__xhrArgs*/args){
                //summary: makes the Deferred object for this xhr request.
                var dfd = _d._ioSetArgs(args, _deferredCancel, _deferredOk, _deferError);
                //Pass the args to _xhrObj, to allow xhr iframe proxy interceptions.
                dfd.ioArgs.xhr = _d._xhrObj(dfd.ioArgs.args);
                return dfd;
        }

        // avoid setting a timer per request. It degrades performance on IE
        // something fierece if we don't use unified loops.
        var _inFlightIntvl = null;
        var _inFlight = [];
        var _watchInFlight = function(){
                //summary: 
                //              internal method that checks each inflight XMLHttpRequest to see
                //              if it has completed or if the timeout situation applies.
                
                var now = (new Date()).getTime();
                // make sure sync calls stay thread safe, if this callback is called
                // during a sync call and this results in another sync call before the
                // first sync call ends the browser hangs
                if(!_d._blockAsync){
                        // we need manual loop because we often modify _inFlight (and therefore 'i') while iterating
                        // note: the second clause is an assigment on purpose, lint may complain
                        for(var i=0, tif; (i<_inFlight.length)&&(tif=_inFlight[i]); i++){
                                var dfd = tif.dfd;
                                try{
                                        if(!dfd || dfd.canceled || !tif.validCheck(dfd)){
                                                _inFlight.splice(i--, 1); 
                                        }else if(tif.ioCheck(dfd)){
                                                _inFlight.splice(i--, 1);
                                                tif.resHandle(dfd);
                                        }else if(dfd.startTime){
                                                //did we timeout?
                                                if(dfd.startTime + (dfd.ioArgs.args.timeout||0) < now){
                                                        _inFlight.splice(i--, 1);
                                                        var err = new Error("timeout exceeded");
                                                        err.dojoType = "timeout";
                                                        dfd.errback(err);
                                                        //Cancel the request so the io module can do appropriate cleanup.
                                                        dfd.cancel();
                                                }
                                        }
                                }catch(e){
                                        // FIXME: make sure we errback!
                                        console.debug(e);
                                        dfd.errback(new Error("_watchInFlightError!"));
                                }
                        }
                }

                if(!_inFlight.length){
                        clearInterval(_inFlightIntvl);
                        _inFlightIntvl = null;
                        return;
                }

        }

        dojo._ioCancelAll = function(){
                //summary: Cancels all pending IO requests, regardless of IO type
                //(xhr, script, iframe).
                try{
                        _d.forEach(_inFlight, function(i){
                                i.dfd.cancel();
                        });
                }catch(e){/*squelch*/}
        }

        //Automatically call cancel all io calls on unload
        //in IE for trac issue #2357.
        if(_d.isIE){
                _d.addOnUnload(_d._ioCancelAll);
        }

        _d._ioWatch = function(/*Deferred*/dfd,
                /*Function*/validCheck,
                /*Function*/ioCheck,
                /*Function*/resHandle){
                //summary: watches the io request represented by dfd to see if it completes.
                //dfd:
                //              The Deferred object to watch.
                //validCheck:
                //              Function used to check if the IO request is still valid. Gets the dfd
                //              object as its only argument.
                //ioCheck:
                //              Function used to check if basic IO call worked. Gets the dfd
                //              object as its only argument.
                //resHandle:
                //              Function used to process response. Gets the dfd
                //              object as its only argument.
                if(dfd.ioArgs.args.timeout){
                        dfd.startTime = (new Date()).getTime();
                }
                _inFlight.push({dfd: dfd, validCheck: validCheck, ioCheck: ioCheck, resHandle: resHandle});
                if(!_inFlightIntvl){
                        _inFlightIntvl = setInterval(_watchInFlight, 50);
                }
                _watchInFlight(); // handle sync requests
        }

        var _defaultContentType = "application/x-www-form-urlencoded";

        var _validCheck = function(/*Deferred*/dfd){
                return dfd.ioArgs.xhr.readyState; //boolean
        }
        var _ioCheck = function(/*Deferred*/dfd){
                return 4 == dfd.ioArgs.xhr.readyState; //boolean
        }
        var _resHandle = function(/*Deferred*/dfd){
                if(_d._isDocumentOk(dfd.ioArgs.xhr)){
                        dfd.callback(dfd);
                }else{
                        dfd.errback(new Error("bad http response code:" + dfd.ioArgs.xhr.status));
                }
        }

        var _doIt = function(/*String*/type, /*Deferred*/dfd){
                // IE 6 is a steaming pile. It won't let you call apply() on the native function (xhr.open).
                // workaround for IE6's apply() "issues"
                var ioArgs = dfd.ioArgs;
                var args = ioArgs.args;
                ioArgs.xhr.open(type, ioArgs.url, args.sync !== true, args.user || undefined, args.password || undefined);
                if(args.headers){
                        for(var hdr in args.headers){
                                if(hdr.toLowerCase() === "content-type" && !args.contentType){
                                        args.contentType = args.headers[hdr];
                                }else{
                                        ioArgs.xhr.setRequestHeader(hdr, args.headers[hdr]);
                                }
                        }
                }
                // FIXME: is this appropriate for all content types?
                ioArgs.xhr.setRequestHeader("Content-Type", (args.contentType||_defaultContentType));
                // FIXME: set other headers here!
                try{
                        ioArgs.xhr.send(ioArgs.query);
                }catch(e){
                        dfd.cancel();
                }
                _d._ioWatch(dfd, _validCheck, _ioCheck, _resHandle);
                return dfd; //Deferred
        }

        dojo._ioAddQueryToUrl = function(/*dojo.__ioCallbackArgs*/ioArgs){
                //summary: Adds query params discovered by the io deferred construction to the URL.
                //Only use this for operations which are fundamentally GET-type operations.
                if(ioArgs.query.length){
                        ioArgs.url += (ioArgs.url.indexOf("?") == -1 ? "?" : "&") + ioArgs.query;
                        ioArgs.query = null;
                }               
        }

        /*=====
        dojo.__xhrArgs = function(kwArgs){
                //      summary:
                //              In addition to the properties listed for the dojo.__ioArgs type,
                //              the following properties are allowed for dojo.xhr* methods.
                //      handleAs: 
                //              String. Acceptable values are:
                //                      "text" (default)
                //                      "json"
                //                      "json-comment-optional"
                //                      "json-comment-filtered"
                //                      "javascript"
                //                      "xml"
                //      sync:
                //              Boolean. false is default. Indicates whether the request should
                //              be a synchronous (blocking) request.
                //      headers:
                //              Object. Additional HTTP headers to send in the request.
        }
        =====*/

        dojo.xhrGet = function(/*dojo.__xhrArgs*/ args){
                //      summary: 
                //              Sends an HTTP GET request to the server.
                var dfd = _makeXhrDeferred(args);
                _d._ioAddQueryToUrl(dfd.ioArgs);
                return _doIt("GET", dfd); // dojo.Deferred
        }

        dojo.xhrPost = function(/*dojo.__xhrArgs*/ args){
                //summary: 
                //              Sends an HTTP POST request to the server.
                return _doIt("POST", _makeXhrDeferred(args)); // dojo.Deferred
        }

        dojo.rawXhrPost = function(/*dojo.__xhrArgs*/ args){
                //      summary:
                //              Sends an HTTP POST request to the server. In addtion to the properties
                //              listed for the dojo.__xhrArgs type, the following property is allowed:
                //      postData:
                //              String. The raw data to send in the body of the POST request.
                var dfd = _makeXhrDeferred(args);
                dfd.ioArgs.query = args.postData;
                return _doIt("POST", dfd); // dojo.Deferred
        }

        dojo.xhrPut = function(/*dojo.__xhrArgs*/ args){
                //      summary:
                //              Sends an HTTP PUT request to the server.
                return _doIt("PUT", _makeXhrDeferred(args)); // dojo.Deferred
        }

        dojo.rawXhrPut = function(/*dojo.__xhrArgs*/ args){
                //      summary:
                //              Sends an HTTP PUT request to the server. In addtion to the properties
                //              listed for the dojo.__xhrArgs type, the following property is allowed:
                //      putData:
                //              String. The raw data to send in the body of the PUT request.
                var dfd = _makeXhrDeferred(args);
                var ioArgs = dfd.ioArgs;
                if(args["putData"]){
                        ioArgs.query = args.putData;
                        args.putData = null;
                }
                return _doIt("PUT", dfd); // dojo.Deferred
        }

        dojo.xhrDelete = function(/*dojo.__xhrArgs*/ args){
                //      summary:
                //              Sends an HTTP DELETE request to the server.
                var dfd = _makeXhrDeferred(args);
                _d._ioAddQueryToUrl(dfd.ioArgs);
                return _doIt("DELETE", dfd); // dojo.Deferred
        }

        /*
        dojo.wrapForm = function(formNode){
                //summary:
                //              A replacement for FormBind, but not implemented yet.

                // FIXME: need to think harder about what extensions to this we might
                // want. What should we allow folks to do w/ this? What events to
                // set/send?
                throw new Error("dojo.wrapForm not yet implemented");
        }
        */
})();

}