diff options
author | Andrew Dolgov <[email protected]> | 2011-03-04 19:02:28 +0300 |
---|---|---|
committer | Andrew Dolgov <[email protected]> | 2011-03-04 19:02:59 +0300 |
commit | a089699c8915636ba4f158d77dba9b012bc93208 (patch) | |
tree | b2d7d051f1f55d44a6be07d3ee137e5a7ccfcefb /lib/dojo/_base/xhr.js | |
parent | cfad9259a6feacfa8194b1312770ae6db1ecce50 (diff) |
build custom layer of Dojo to speed up loading of tt-rss (refs #293)
Diffstat (limited to 'lib/dojo/_base/xhr.js')
-rw-r--r-- | lib/dojo/_base/xhr.js | 1348 |
1 files changed, 926 insertions, 422 deletions
diff --git a/lib/dojo/_base/xhr.js b/lib/dojo/_base/xhr.js index 5823371cf..818f8e418 100644 --- a/lib/dojo/_base/xhr.js +++ b/lib/dojo/_base/xhr.js @@ -5,433 +5,937 @@ */ -if(!dojo._hasResource["dojo._base.xhr"]){ -dojo._hasResource["dojo._base.xhr"]=true; +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 _1=dojo,_2=_1.config; -function _3(_4,_5,_6){ -if(_6===null){ -return; -} -var _7=_4[_5]; -if(typeof _7=="string"){ -_4[_5]=[_7,_6]; -}else{ -if(_1.isArray(_7)){ -_7.push(_6); -}else{ -_4[_5]=_6; -} -} -}; -dojo.fieldToObject=function(_8){ -var _9=null; -var _a=_1.byId(_8); -if(_a){ -var _b=_a.name; -var _c=(_a.type||"").toLowerCase(); -if(_b&&_c&&!_a.disabled){ -if(_c=="radio"||_c=="checkbox"){ -if(_a.checked){ -_9=_a.value; -} -}else{ -if(_a.multiple){ -_9=[]; -_1.query("option",_a).forEach(function(_d){ -if(_d.selected){ -_9.push(_d.value); -} -}); -}else{ -_9=_a.value; -} -} -} -} -return _9; -}; -dojo.formToObject=function(_e){ -var _f={}; -var _10="file|submit|image|reset|button|"; -_1.forEach(dojo.byId(_e).elements,function(_11){ -var _12=_11.name; -var _13=(_11.type||"").toLowerCase(); -if(_12&&_13&&_10.indexOf(_13)==-1&&!_11.disabled){ -_3(_f,_12,_1.fieldToObject(_11)); -if(_13=="image"){ -_f[_12+".x"]=_f[_12+".y"]=_f[_12].x=_f[_12].y=0; -} -} -}); -return _f; -}; -dojo.objectToQuery=function(map){ -var enc=encodeURIComponent; -var _14=[]; -var _15={}; -for(var _16 in map){ -var _17=map[_16]; -if(_17!=_15[_16]){ -var _18=enc(_16)+"="; -if(_1.isArray(_17)){ -for(var i=0;i<_17.length;i++){ -_14.push(_18+enc(_17[i])); -} -}else{ -_14.push(_18+enc(_17)); -} -} -} -return _14.join("&"); -}; -dojo.formToQuery=function(_19){ -return _1.objectToQuery(_1.formToObject(_19)); -}; -dojo.formToJson=function(_1a,_1b){ -return _1.toJson(_1.formToObject(_1a),_1b); -}; -dojo.queryToObject=function(str){ -var ret={}; -var qp=str.split("&"); -var dec=decodeURIComponent; -_1.forEach(qp,function(_1c){ -if(_1c.length){ -var _1d=_1c.split("="); -var _1e=dec(_1d.shift()); -var val=dec(_1d.join("=")); -if(typeof ret[_1e]=="string"){ -ret[_1e]=[ret[_1e]]; -} -if(_1.isArray(ret[_1e])){ -ret[_1e].push(val); -}else{ -ret[_1e]=val; -} -} -}); -return ret; -}; -dojo._blockAsync=false; -var _1f=_1._contentHandlers=dojo.contentHandlers={text:function(xhr){ -return xhr.responseText; -},json:function(xhr){ -return _1.fromJson(xhr.responseText||null); -},"json-comment-filtered":function(xhr){ -if(!dojo.config.useCommentedJson){ -console.warn("Consider using the standard mimetype:application/json."+" json-commenting can introduce security issues. To"+" decrease the chances of hijacking, use the standard the 'json' handler and"+" prefix your json with: {}&&\n"+"Use djConfig.useCommentedJson=true to turn off this message."); -} -var _20=xhr.responseText; -var _21=_20.indexOf("/*"); -var _22=_20.lastIndexOf("*/"); -if(_21==-1||_22==-1){ -throw new Error("JSON was not comment filtered"); -} -return _1.fromJson(_20.substring(_21+2,_22)); -},javascript:function(xhr){ -return _1.eval(xhr.responseText); -},xml:function(xhr){ -var _23=xhr.responseXML; -if(_1.isIE&&(!_23||!_23.documentElement)){ -var ms=function(n){ -return "MSXML"+n+".DOMDocument"; -}; -var dp=["Microsoft.XMLDOM",ms(6),ms(4),ms(3),ms(2)]; -_1.some(dp,function(p){ -try{ -var dom=new ActiveXObject(p); -dom.async=false; -dom.loadXML(xhr.responseText); -_23=dom; -} -catch(e){ -return false; -} -return true; -}); -} -return _23; -},"json-comment-optional":function(xhr){ -if(xhr.responseText&&/^[^{\[]*\/\*/.test(xhr.responseText)){ -return _1f["json-comment-filtered"](xhr); -}else{ -return _1f["json"](xhr); -} -}}; -dojo._ioSetArgs=function(_24,_25,_26,_27){ -var _28={args:_24,url:_24.url}; -var _29=null; -if(_24.form){ -var _2a=_1.byId(_24.form); -var _2b=_2a.getAttributeNode("action"); -_28.url=_28.url||(_2b?_2b.value:null); -_29=_1.formToObject(_2a); -} -var _2c=[{}]; -if(_29){ -_2c.push(_29); -} -if(_24.content){ -_2c.push(_24.content); -} -if(_24.preventCache){ -_2c.push({"dojo.preventCache":new Date().valueOf()}); -} -_28.query=_1.objectToQuery(_1.mixin.apply(null,_2c)); -_28.handleAs=_24.handleAs||"text"; -var d=new _1.Deferred(_25); -d.addCallbacks(_26,function(_2d){ -return _27(_2d,d); -}); -var ld=_24.load; -if(ld&&_1.isFunction(ld)){ -d.addCallback(function(_2e){ -return ld.call(_24,_2e,_28); -}); -} -var err=_24.error; -if(err&&_1.isFunction(err)){ -d.addErrback(function(_2f){ -return err.call(_24,_2f,_28); -}); -} -var _30=_24.handle; -if(_30&&_1.isFunction(_30)){ -d.addBoth(function(_31){ -return _30.call(_24,_31,_28); -}); -} -if(_2.ioPublish&&_1.publish&&_28.args.ioPublish!==false){ -d.addCallbacks(function(res){ -_1.publish("/dojo/io/load",[d,res]); -return res; -},function(res){ -_1.publish("/dojo/io/error",[d,res]); -return res; -}); -d.addBoth(function(res){ -_1.publish("/dojo/io/done",[d,res]); -return res; -}); -} -d.ioArgs=_28; -return d; -}; -var _32=function(dfd){ -dfd.canceled=true; -var xhr=dfd.ioArgs.xhr; -var _33=typeof xhr.abort; -if(_33=="function"||_33=="object"||_33=="unknown"){ -xhr.abort(); -} -var err=dfd.ioArgs.error; -if(!err){ -err=new Error("xhr cancelled"); -err.dojoType="cancel"; -} -return err; -}; -var _34=function(dfd){ -var ret=_1f[dfd.ioArgs.handleAs](dfd.ioArgs.xhr); -return ret===undefined?null:ret; -}; -var _35=function(_36,dfd){ -if(!dfd.ioArgs.args.failOk){ -console.error(_36); -} -return _36; -}; -var _37=null; -var _38=[]; -var _39=0; -var _3a=function(dfd){ -if(_39<=0){ -_39=0; -if(_2.ioPublish&&_1.publish&&(!dfd||dfd&&dfd.ioArgs.args.ioPublish!==false)){ -_1.publish("/dojo/io/stop"); -} -} -}; -var _3b=function(){ -var now=(new Date()).getTime(); -if(!_1._blockAsync){ -for(var i=0,tif;i<_38.length&&(tif=_38[i]);i++){ -var dfd=tif.dfd; -var _3c=function(){ -if(!dfd||dfd.canceled||!tif.validCheck(dfd)){ -_38.splice(i--,1); -_39-=1; -}else{ -if(tif.ioCheck(dfd)){ -_38.splice(i--,1); -tif.resHandle(dfd); -_39-=1; -}else{ -if(dfd.startTime){ -if(dfd.startTime+(dfd.ioArgs.args.timeout||0)<now){ -_38.splice(i--,1); -var err=new Error("timeout exceeded"); -err.dojoType="timeout"; -dfd.errback(err); -dfd.cancel(); -_39-=1; -} -} -} -} -}; -if(dojo.config.debugAtAllCosts){ -_3c.call(this); -}else{ -try{ -_3c.call(this); -} -catch(e){ -dfd.errback(e); -} -} -} -} -_3a(dfd); -if(!_38.length){ -clearInterval(_37); -_37=null; -return; -} -}; -dojo._ioCancelAll=function(){ -try{ -_1.forEach(_38,function(i){ -try{ -i.dfd.cancel(); -} -catch(e){ -} -}); -} -catch(e){ -} -}; -if(_1.isIE){ -_1.addOnWindowUnload(_1._ioCancelAll); -} -_1._ioNotifyStart=function(dfd){ -if(_2.ioPublish&&_1.publish&&dfd.ioArgs.args.ioPublish!==false){ -if(!_39){ -_1.publish("/dojo/io/start"); -} -_39+=1; -_1.publish("/dojo/io/send",[dfd]); -} -}; -_1._ioWatch=function(dfd,_3d,_3e,_3f){ -var _40=dfd.ioArgs.args; -if(_40.timeout){ -dfd.startTime=(new Date()).getTime(); -} -_38.push({dfd:dfd,validCheck:_3d,ioCheck:_3e,resHandle:_3f}); -if(!_37){ -_37=setInterval(_3b,50); -} -if(_40.sync){ -_3b(); -} -}; -var _41="application/x-www-form-urlencoded"; -var _42=function(dfd){ -return dfd.ioArgs.xhr.readyState; -}; -var _43=function(dfd){ -return 4==dfd.ioArgs.xhr.readyState; -}; -var _44=function(dfd){ -var xhr=dfd.ioArgs.xhr; -if(_1._isDocumentOk(xhr)){ -dfd.callback(dfd); -}else{ -var err=new Error("Unable to load "+dfd.ioArgs.url+" status:"+xhr.status); -err.status=xhr.status; -err.responseText=xhr.responseText; -dfd.errback(err); -} -}; -dojo._ioAddQueryToUrl=function(_45){ -if(_45.query.length){ -_45.url+=(_45.url.indexOf("?")==-1?"?":"&")+_45.query; -_45.query=null; -} -}; -dojo.xhr=function(_46,_47,_48){ -var dfd=_1._ioSetArgs(_47,_32,_34,_35); -var _49=dfd.ioArgs; -var xhr=_49.xhr=_1._xhrObj(_49.args); -if(!xhr){ -dfd.cancel(); -return dfd; -} -if("postData" in _47){ -_49.query=_47.postData; -}else{ -if("putData" in _47){ -_49.query=_47.putData; -}else{ -if("rawBody" in _47){ -_49.query=_47.rawBody; -}else{ -if((arguments.length>2&&!_48)||"POST|PUT".indexOf(_46.toUpperCase())==-1){ -_1._ioAddQueryToUrl(_49); -} -} -} -} -xhr.open(_46,_49.url,_47.sync!==true,_47.user||undefined,_47.password||undefined); -if(_47.headers){ -for(var hdr in _47.headers){ -if(hdr.toLowerCase()==="content-type"&&!_47.contentType){ -_47.contentType=_47.headers[hdr]; -}else{ -if(_47.headers[hdr]){ -xhr.setRequestHeader(hdr,_47.headers[hdr]); -} -} -} -} -xhr.setRequestHeader("Content-Type",_47.contentType||_41); -if(!_47.headers||!("X-Requested-With" in _47.headers)){ -xhr.setRequestHeader("X-Requested-With","XMLHttpRequest"); -} -_1._ioNotifyStart(dfd); -if(dojo.config.debugAtAllCosts){ -xhr.send(_49.query); -}else{ -try{ -xhr.send(_49.query); -} -catch(e){ -_49.error=e; -dfd.cancel(); -} -} -_1._ioWatch(dfd,_42,_43,_44); -xhr=null; -return dfd; -}; -dojo.xhrGet=function(_4a){ -return _1.xhr("GET",_4a); -}; -dojo.rawXhrPost=dojo.xhrPost=function(_4b){ -return _1.xhr("POST",_4b,true); -}; -dojo.rawXhrPut=dojo.xhrPut=function(_4c){ -return _1.xhr("PUT",_4c,true); -}; -dojo.xhrDelete=function(_4d){ -return _1.xhr("DELETE",_4d); -}; + var _d = dojo, cfg = _d.config; + + function setValue(/*Object*/obj, /*String*/name, /*String*/value){ + //summary: + // For the named 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. + + //Skip it if there is no value + if(value === null){ + return; + } + + var val = obj[name]; + if(typeof val == "string"){ // inline'd type check + obj[name] = [val, value]; + }else if(_d.isArray(val)){ + val.push(value); + }else{ + obj[name] = value; + } + } + + dojo.fieldToObject = function(/*DOMNode||String*/ inputNode){ + // summary: + // Serialize a form field to a JavaScript object. + // + // description: + // Returns the value encoded in a form field as + // as a string or an array of strings. Disabled form elements + // and unchecked radio and checkboxes are skipped. Multi-select + // elements are returned as an array of string values. + var ret = null; + var item = _d.byId(inputNode); + if(item){ + var _in = item.name; + var type = (item.type||"").toLowerCase(); + if(_in && type && !item.disabled){ + if(type == "radio" || type == "checkbox"){ + if(item.checked){ ret = item.value } + }else if(item.multiple){ + ret = []; + _d.query("option", item).forEach(function(opt){ + if(opt.selected){ + ret.push(opt.value); + } + }); + }else{ + ret = item.value; + } + } + } + return ret; // Object + } + + dojo.formToObject = function(/*DOMNode||String*/ formNode){ + // summary: + // Serialize a form node to a JavaScript object. + // description: + // 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. + // + // example: + // 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 exclude = "file|submit|image|reset|button|"; + _d.forEach(dojo.byId(formNode).elements, function(item){ + var _in = item.name; + var type = (item.type||"").toLowerCase(); + if(_in && type && exclude.indexOf(type) == -1 && !item.disabled){ + setValue(ret, _in, _d.fieldToObject(item)); + 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 name/value mapping object and returns a string representing + // a URL-encoded version of that object. + // example: + // this object: + // + // | { + // | blah: "blah", + // | multi: [ + // | "thud", + // | "thonk" + // | ] + // | }; + // + // yields the following query string: + // + // | "blah=blah&multi=thud&multi=thonk" + + // FIXME: need to implement encodeAscii!! + var enc = encodeURIComponent; + var pairs = []; + var backstop = {}; + for(var name in map){ + var value = map[name]; + if(value != backstop[name]){ + var assign = enc(name) + "="; + if(_d.isArray(value)){ + for(var i=0; i < value.length; i++){ + pairs.push(assign + enc(value[i])); + } + }else{ + pairs.push(assign + enc(value)); + } + } + } + return pairs.join("&"); // String + } + + dojo.formToQuery = function(/*DOMNode||String*/ formNode){ + // summary: + // Returns a 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: + // Create 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: + // Create an object representing a de-serialized query section of a + // URL. Query keys with multiple values are returned in an array. + // + // example: + // This string: + // + // | "foo=bar&foo=baz&thinger=%20spaces%20=blah&zonk=blarg&" + // + // results in 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 dec = decodeURIComponent; + _d.forEach(qp, function(item){ + if(item.length){ + var parts = item.split("="); + var name = dec(parts.shift()); + var val = dec(parts.join("=")); + if(typeof ret[name] == "string"){ // inline'd type check + ret[name] = [ret[name]]; + } + + if(_d.isArray(ret[name])){ + ret[name].push(val); + }else{ + ret[name] = val; + } + } + }); + return ret; // Object + } + + // 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; + + // MOW: remove dojo._contentHandlers alias in 2.0 + var handlers = _d._contentHandlers = dojo.contentHandlers = { + // summary: + // A map of availble XHR transport handle types. Name matches the + // `handleAs` attribute passed to XHR calls. + // + // description: + // A map of availble XHR transport handle types. Name matches the + // `handleAs` attribute passed to XHR calls. Each contentHandler is + // called, passing the xhr object for manipulation. The return value + // from the contentHandler will be passed to the `load` or `handle` + // functions defined in the original xhr call. + // + // example: + // Creating a custom content-handler: + // | dojo.contentHandlers.makeCaps = function(xhr){ + // | return xhr.responseText.toUpperCase(); + // | } + // | // and later: + // | dojo.xhrGet({ + // | url:"foo.txt", + // | handleAs:"makeCaps", + // | load: function(data){ /* data is a toUpper version of foo.txt */ } + // | }); + + text: function(xhr){ + // summary: A contentHandler which simply returns the plaintext response data + return xhr.responseText; + }, + json: function(xhr){ + // summary: A contentHandler which returns a JavaScript object created from the response data + return _d.fromJson(xhr.responseText || null); + }, + "json-comment-filtered": function(xhr){ + // summary: A contentHandler which expects comment-filtered JSON. + // description: + // A contentHandler which expects comment-filtered JSON. + // the json-comment-filtered option was implemented to prevent + // "JavaScript Hijacking", but it is less secure than standard JSON. Use + // standard JSON instead. JSON prefixing can be used to subvert hijacking. + // + // Will throw a notice suggesting to use application/json mimetype, as + // json-commenting can introduce security issues. To decrease the chances of hijacking, + // use the standard `json` contentHandler, and prefix your "JSON" with: {}&& + // + // use djConfig.useCommentedJson = true to turn off the notice + if(!dojo.config.useCommentedJson){ + console.warn("Consider using the standard mimetype:application/json." + + " json-commenting can introduce security issues. To" + + " decrease the chances of hijacking, use the standard the 'json' handler and" + + " prefix your json with: {}&&\n" + + "Use djConfig.useCommentedJson=true to turn off this message."); + } + + 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){ + // summary: A contentHandler which evaluates the response data, expecting it to be valid JavaScript + + // FIXME: try Moz and IE specific eval variants? + return _d.eval(xhr.responseText); + }, + xml: function(xhr){ + // summary: A contentHandler returning an XML Document parsed from the response data + var result = xhr.responseXML; + if(_d.isIE && (!result || !result.documentElement)){ + //WARNING: this branch used by the xml handling in dojo.io.iframe, + //so be sure to test dojo.io.iframe if making changes below. + var ms = function(n){ return "MSXML" + n + ".DOMDocument"; } + var dp = ["Microsoft.XMLDOM", ms(6), ms(4), ms(3), ms(2)]; + _d.some(dp, function(p){ + try{ + var dom = new ActiveXObject(p); + dom.async = false; + dom.loadXML(xhr.responseText); + result = dom; + }catch(e){ return false; } + return true; + }); + } + return result; // DOMDocument + }, + "json-comment-optional": function(xhr){ + // summary: A contentHandler which checks the presence of comment-filtered JSON and + // alternates between the `json` and `json-comment-filtered` contentHandlers. + if(xhr.responseText && /^[^{\[]*\/\*/.test(xhr.responseText)){ + return handlers["json-comment-filtered"](xhr); + }else{ + return handlers["json"](xhr); + } + } + }; + + /*===== + dojo.__IoArgs = function(){ + // 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). + // rawBody: String? + // Sets the raw body for an HTTP request. If this is used, then the content + // property is ignored. This is mostly useful for HTTP methods that have + // a body to their requests, like PUT or POST. This property can be used instead + // of postData and putData for dojo.rawXhrPost and dojo.rawXhrPut respectively. + // ioPublish: Boolean? + // Set this explicitly to false to prevent publishing of topics related to + // IO operations. Otherwise, if djConfig.ioPublish is set to true, topics + // will be published via dojo.publish for different phases of an IO operation. + // See dojo.__IoPublish for a list of topics that are published. + // load: Function? + // This function will be + // called on a successful HTTP response code. + // error: Function? + // This function will + // be called when the request fails due to a network or server error, the url + // is invalid, etc. It will also be called if the load or handle callback throws an + // exception, unless djConfig.debugAtAllCosts is true. This allows deployed applications + // to continue to run even when a logic error happens in the callback, while making + // it easier to troubleshoot while in debug mode. + // handle: Function? + // This function will + // be called at the end of every request, whether or not an error occurs. + this.url = url; + this.content = content; + this.timeout = timeout; + this.form = form; + this.preventCache = preventCache; + this.handleAs = handleAs; + this.ioPublish = ioPublish; + this.load = function(response, ioArgs){ + // ioArgs: dojo.__IoCallbackArgs + // Provides additional information about the request. + // response: Object + // The response in the format as defined with handleAs. + } + this.error = function(response, ioArgs){ + // ioArgs: dojo.__IoCallbackArgs + // Provides additional information about the request. + // response: Object + // The response in the format as defined with handleAs. + } + this.handle = function(loadOrError, response, ioArgs){ + // loadOrError: String + // Provides a string that tells you whether this function + // was called because of success (load) or failure (error). + // response: Object + // The response in the format as defined with handleAs. + // ioArgs: dojo.__IoCallbackArgs + // Provides additional information about the request. + } + } + =====*/ + + /*===== + dojo.__IoCallbackArgs = function(args, xhr, url, query, handleAs, id, canDelete, json){ + // 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. + this.args = args; + this.xhr = xhr; + this.url = url; + this.query = query; + this.handleAs = handleAs; + this.id = id; + this.canDelete = canDelete; + this.json = json; + } + =====*/ + + + /*===== + dojo.__IoPublish = function(){ + // summary: + // This is a list of IO topics that can be published + // if djConfig.ioPublish is set to true. IO topics can be + // published for any Input/Output, network operation. So, + // dojo.xhr, dojo.io.script and dojo.io.iframe can all + // trigger these topics to be published. + // start: String + // "/dojo/io/start" is sent when there are no outstanding IO + // requests, and a new IO request is started. No arguments + // are passed with this topic. + // send: String + // "/dojo/io/send" is sent whenever a new IO request is started. + // It passes the dojo.Deferred for the request with the topic. + // load: String + // "/dojo/io/load" is sent whenever an IO request has loaded + // successfully. It passes the response and the dojo.Deferred + // for the request with the topic. + // error: String + // "/dojo/io/error" is sent whenever an IO request has errored. + // It passes the error and the dojo.Deferred + // for the request with the topic. + // done: String + // "/dojo/io/done" is sent whenever an IO request has completed, + // either by loading or by erroring. It passes the error and + // the dojo.Deferred for the request with the topic. + // stop: String + // "/dojo/io/stop" is sent when all outstanding IO requests have + // finished. No arguments are passed with this topic. + this.start = "/dojo/io/start"; + this.send = "/dojo/io/send"; + this.load = "/dojo/io/load"; + this.error = "/dojo/io/error"; + this.done = "/dojo/io/done"; + this.stop = "/dojo/io/stop"; + } + =====*/ + + + 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); + }); + } + + //Plug in topic publishing, if dojo.publish is loaded. + if(cfg.ioPublish && _d.publish && ioArgs.args.ioPublish !== false){ + d.addCallbacks( + function(res){ + _d.publish("/dojo/io/load", [d, res]); + return res; + }, + function(res){ + _d.publish("/dojo/io/error", [d, res]); + return res; + } + ); + d.addBoth(function(res){ + _d.publish("/dojo/io/done", [d, res]); + return res; + }); + } + + 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 == "object" || _at == "unknown"){ + xhr.abort(); + } + var err = dfd.ioArgs.error; + if(!err){ + err = new Error("xhr cancelled"); + err.dojoType="cancel"; + } + return err; + } + var _deferredOk = function(/*Deferred*/dfd){ + // summary: okHandler function for dojo._ioSetArgs call. + + var ret = handlers[dfd.ioArgs.handleAs](dfd.ioArgs.xhr); + return ret === undefined ? null : ret; + } + var _deferError = function(/*Error*/error, /*Deferred*/dfd){ + // summary: errHandler function for dojo._ioSetArgs call. + + if(!dfd.ioArgs.args.failOk){ + console.error(error); + } + return error; + } + + // 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 = []; + + + //Use a separate count for knowing if we are starting/stopping io calls. + //Cannot use _inFlight.length since it can change at a different time than + //when we want to do this kind of test. We only want to decrement the count + //after a callback/errback has finished, since the callback/errback should be + //considered as part of finishing a request. + var _pubCount = 0; + var _checkPubCount = function(dfd){ + if(_pubCount <= 0){ + _pubCount = 0; + if(cfg.ioPublish && _d.publish && (!dfd || dfd && dfd.ioArgs.args.ioPublish !== false)){ + _d.publish("/dojo/io/stop"); + } + } + }; + + 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; + var func = function(){ + if(!dfd || dfd.canceled || !tif.validCheck(dfd)){ + _inFlight.splice(i--, 1); + _pubCount -= 1; + }else if(tif.ioCheck(dfd)){ + _inFlight.splice(i--, 1); + tif.resHandle(dfd); + _pubCount -= 1; + }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(); + _pubCount -= 1; + } + } + }; + if(dojo.config.debugAtAllCosts){ + func.call(this); + }else{ + try{ + func.call(this); + }catch(e){ + dfd.errback(e); + } + } + } + } + + _checkPubCount(dfd); + + 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){ + try{ + i.dfd.cancel(); + }catch(e){/*squelch*/} + }); + }catch(e){/*squelch*/} + } + + //Automatically call cancel all io calls on unload + //in IE for trac issue #2357. + if(_d.isIE){ + _d.addOnWindowUnload(_d._ioCancelAll); + } + + _d._ioNotifyStart = function(/*Deferred*/dfd){ + // summary: + // If dojo.publish is available, publish topics + // about the start of a request queue and/or the + // the beginning of request. + // description: + // Used by IO transports. An IO transport should + // call this method before making the network connection. + if(cfg.ioPublish && _d.publish && dfd.ioArgs.args.ioPublish !== false){ + if(!_pubCount){ + _d.publish("/dojo/io/start"); + } + _pubCount += 1; + _d.publish("/dojo/io/send", [dfd]); + } + } + + _d._ioWatch = function(dfd, validCheck, ioCheck, resHandle){ + // summary: + // Watches the io request represented by dfd to see if it completes. + // dfd: Deferred + // The Deferred object to watch. + // validCheck: Function + // Function used to check if the IO request is still valid. Gets the dfd + // object as its only argument. + // ioCheck: Function + // Function used to check if basic IO call worked. Gets the dfd + // object as its only argument. + // resHandle: Function + // Function used to process response. Gets the dfd + // object as its only argument. + var args = dfd.ioArgs.args; + if(args.timeout){ + dfd.startTime = (new Date()).getTime(); + } + + _inFlight.push({dfd: dfd, validCheck: validCheck, ioCheck: ioCheck, resHandle: resHandle}); + if(!_inFlightIntvl){ + _inFlightIntvl = setInterval(_watchInFlight, 50); + } + // handle sync requests + //A weakness: async calls in flight + //could have their handlers called as part of the + //_watchInFlight call, before the sync's callbacks + // are called. + if(args.sync){ + _watchInFlight(); + } + } + + 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){ + var xhr = dfd.ioArgs.xhr; + if(_d._isDocumentOk(xhr)){ + dfd.callback(dfd); + }else{ + var err = new Error("Unable to load " + dfd.ioArgs.url + " status:" + xhr.status); + err.status = xhr.status; + err.responseText = xhr.responseText; + dfd.errback(err); + } + } + + 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.declare("dojo.__XhrArgs", dojo.__IoArgs, { + constructor: function(){ + // 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. See `dojo.contentHandlers` + // 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. + // failOk: Boolean? + // false is default. Indicates whether a request should be + // allowed to fail (and therefore no console error message in + // the event of a failure) + this.handleAs = handleAs; + this.sync = sync; + this.headers = headers; + this.failOk = failOk; + } + }); + =====*/ + + dojo.xhr = function(/*String*/ method, /*dojo.__XhrArgs*/ args, /*Boolean?*/ hasBody){ + // summary: + // Sends an HTTP request with the given method. + // description: + // Sends an HTTP request with the given method. + // See also dojo.xhrGet(), xhrPost(), xhrPut() and dojo.xhrDelete() for shortcuts + // for those HTTP methods. There are also methods for "raw" PUT and POST methods + // via dojo.rawXhrPut() and dojo.rawXhrPost() respectively. + // method: + // HTTP method to be used, such as GET, POST, PUT, DELETE. Should be uppercase. + // hasBody: + // If the request has an HTTP body, then pass true for hasBody. + + //Make the Deferred object for this xhr request. + var dfd = _d._ioSetArgs(args, _deferredCancel, _deferredOk, _deferError); + var ioArgs = dfd.ioArgs; + + //Pass the args to _xhrObj, to allow alternate XHR calls based specific calls, like + //the one used for iframe proxies. + var xhr = ioArgs.xhr = _d._xhrObj(ioArgs.args); + //If XHR factory fails, cancel the deferred. + if(!xhr){ + dfd.cancel(); + return dfd; + } + + //Allow for specifying the HTTP body completely. + if("postData" in args){ + ioArgs.query = args.postData; + }else if("putData" in args){ + ioArgs.query = args.putData; + }else if("rawBody" in args){ + ioArgs.query = args.rawBody; + }else if((arguments.length > 2 && !hasBody) || "POST|PUT".indexOf(method.toUpperCase()) == -1){ + //Check for hasBody being passed. If no hasBody, + //then only append query string if not a POST or PUT request. + _d._ioAddQueryToUrl(ioArgs); + } + + // 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" + xhr.open(method, 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 if(args.headers[hdr]){ + //Only add header if it has a value. This allows for instnace, skipping + //insertion of X-Requested-With by specifying empty value. + xhr.setRequestHeader(hdr, args.headers[hdr]); + } + } + } + // FIXME: is this appropriate for all content types? + xhr.setRequestHeader("Content-Type", args.contentType || _defaultContentType); + if(!args.headers || !("X-Requested-With" in args.headers)){ + xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + } + // FIXME: set other headers here! + _d._ioNotifyStart(dfd); + if(dojo.config.debugAtAllCosts){ + xhr.send(ioArgs.query); + }else{ + try{ + xhr.send(ioArgs.query); + }catch(e){ + ioArgs.error = e; + dfd.cancel(); + } + } + _d._ioWatch(dfd, _validCheck, _ioCheck, _resHandle); + xhr = null; + return dfd; // dojo.Deferred + } + + dojo.xhrGet = function(/*dojo.__XhrArgs*/ args){ + // summary: + // Sends an HTTP GET request to the server. + return _d.xhr("GET", args); // dojo.Deferred + } + + dojo.rawXhrPost = dojo.xhrPost = 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. Send raw data in the body of the POST request. + return _d.xhr("POST", args, true); // dojo.Deferred + } + + dojo.rawXhrPut = dojo.xhrPut = 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. Send raw data in the body of the PUT request. + return _d.xhr("PUT", args, true); // dojo.Deferred + } + + dojo.xhrDelete = function(/*dojo.__XhrArgs*/ args){ + // summary: + // Sends an HTTP DELETE request to the server. + return _d.xhr("DELETE", args); //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"); + } + */ })(); + } |