xhr.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dojo._base.xhr"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojo._base.xhr"] = true;
  8. dojo.provide("dojo._base.xhr");
  9. dojo.require("dojo._base.Deferred");
  10. dojo.require("dojo._base.json");
  11. dojo.require("dojo._base.lang");
  12. dojo.require("dojo._base.query");
  13. (function(){
  14. var _d = dojo, cfg = _d.config;
  15. function setValue(/*Object*/obj, /*String*/name, /*String*/value){
  16. //summary:
  17. // For the named property in object, set the value. If a value
  18. // already exists and it is a string, convert the value to be an
  19. // array of values.
  20. //Skip it if there is no value
  21. if(value === null){
  22. return;
  23. }
  24. var val = obj[name];
  25. if(typeof val == "string"){ // inline'd type check
  26. obj[name] = [val, value];
  27. }else if(_d.isArray(val)){
  28. val.push(value);
  29. }else{
  30. obj[name] = value;
  31. }
  32. }
  33. dojo.fieldToObject = function(/*DOMNode||String*/ inputNode){
  34. // summary:
  35. // Serialize a form field to a JavaScript object.
  36. //
  37. // description:
  38. // Returns the value encoded in a form field as
  39. // as a string or an array of strings. Disabled form elements
  40. // and unchecked radio and checkboxes are skipped. Multi-select
  41. // elements are returned as an array of string values.
  42. var ret = null;
  43. var item = _d.byId(inputNode);
  44. if(item){
  45. var _in = item.name;
  46. var type = (item.type||"").toLowerCase();
  47. if(_in && type && !item.disabled){
  48. if(type == "radio" || type == "checkbox"){
  49. if(item.checked){ ret = item.value; }
  50. }else if(item.multiple){
  51. ret = [];
  52. _d.query("option", item).forEach(function(opt){
  53. if(opt.selected){
  54. ret.push(opt.value);
  55. }
  56. });
  57. }else{
  58. ret = item.value;
  59. }
  60. }
  61. }
  62. return ret; // Object
  63. };
  64. dojo.formToObject = function(/*DOMNode||String*/ formNode){
  65. // summary:
  66. // Serialize a form node to a JavaScript object.
  67. // description:
  68. // Returns the values encoded in an HTML form as
  69. // string properties in an object which it then returns. Disabled form
  70. // elements, buttons, and other non-value form elements are skipped.
  71. // Multi-select elements are returned as an array of string values.
  72. //
  73. // example:
  74. // This form:
  75. // | <form id="test_form">
  76. // | <input type="text" name="blah" value="blah">
  77. // | <input type="text" name="no_value" value="blah" disabled>
  78. // | <input type="button" name="no_value2" value="blah">
  79. // | <select type="select" multiple name="multi" size="5">
  80. // | <option value="blah">blah</option>
  81. // | <option value="thud" selected>thud</option>
  82. // | <option value="thonk" selected>thonk</option>
  83. // | </select>
  84. // | </form>
  85. //
  86. // yields this object structure as the result of a call to
  87. // formToObject():
  88. //
  89. // | {
  90. // | blah: "blah",
  91. // | multi: [
  92. // | "thud",
  93. // | "thonk"
  94. // | ]
  95. // | };
  96. var ret = {};
  97. var exclude = "file|submit|image|reset|button|";
  98. _d.forEach(dojo.byId(formNode).elements, function(item){
  99. var _in = item.name;
  100. var type = (item.type||"").toLowerCase();
  101. if(_in && type && exclude.indexOf(type) == -1 && !item.disabled){
  102. setValue(ret, _in, _d.fieldToObject(item));
  103. if(type == "image"){
  104. ret[_in+".x"] = ret[_in+".y"] = ret[_in].x = ret[_in].y = 0;
  105. }
  106. }
  107. });
  108. return ret; // Object
  109. };
  110. dojo.objectToQuery = function(/*Object*/ map){
  111. // summary:
  112. // takes a name/value mapping object and returns a string representing
  113. // a URL-encoded version of that object.
  114. // example:
  115. // this object:
  116. //
  117. // | {
  118. // | blah: "blah",
  119. // | multi: [
  120. // | "thud",
  121. // | "thonk"
  122. // | ]
  123. // | };
  124. //
  125. // yields the following query string:
  126. //
  127. // | "blah=blah&multi=thud&multi=thonk"
  128. // FIXME: need to implement encodeAscii!!
  129. var enc = encodeURIComponent;
  130. var pairs = [];
  131. var backstop = {};
  132. for(var name in map){
  133. var value = map[name];
  134. if(value != backstop[name]){
  135. var assign = enc(name) + "=";
  136. if(_d.isArray(value)){
  137. for(var i=0; i < value.length; i++){
  138. pairs.push(assign + enc(value[i]));
  139. }
  140. }else{
  141. pairs.push(assign + enc(value));
  142. }
  143. }
  144. }
  145. return pairs.join("&"); // String
  146. };
  147. dojo.formToQuery = function(/*DOMNode||String*/ formNode){
  148. // summary:
  149. // Returns a URL-encoded string representing the form passed as either a
  150. // node or string ID identifying the form to serialize
  151. return _d.objectToQuery(_d.formToObject(formNode)); // String
  152. };
  153. dojo.formToJson = function(/*DOMNode||String*/ formNode, /*Boolean?*/prettyPrint){
  154. // summary:
  155. // Create a serialized JSON string from a form node or string
  156. // ID identifying the form to serialize
  157. return _d.toJson(_d.formToObject(formNode), prettyPrint); // String
  158. };
  159. dojo.queryToObject = function(/*String*/ str){
  160. // summary:
  161. // Create an object representing a de-serialized query section of a
  162. // URL. Query keys with multiple values are returned in an array.
  163. //
  164. // example:
  165. // This string:
  166. //
  167. // | "foo=bar&foo=baz&thinger=%20spaces%20=blah&zonk=blarg&"
  168. //
  169. // results in this object structure:
  170. //
  171. // | {
  172. // | foo: [ "bar", "baz" ],
  173. // | thinger: " spaces =blah",
  174. // | zonk: "blarg"
  175. // | }
  176. //
  177. // Note that spaces and other urlencoded entities are correctly
  178. // handled.
  179. // FIXME: should we grab the URL string if we're not passed one?
  180. var ret = {};
  181. var qp = str.split("&");
  182. var dec = decodeURIComponent;
  183. _d.forEach(qp, function(item){
  184. if(item.length){
  185. var parts = item.split("=");
  186. var name = dec(parts.shift());
  187. var val = dec(parts.join("="));
  188. if(typeof ret[name] == "string"){ // inline'd type check
  189. ret[name] = [ret[name]];
  190. }
  191. if(_d.isArray(ret[name])){
  192. ret[name].push(val);
  193. }else{
  194. ret[name] = val;
  195. }
  196. }
  197. });
  198. return ret; // Object
  199. };
  200. // need to block async callbacks from snatching this thread as the result
  201. // of an async callback might call another sync XHR, this hangs khtml forever
  202. // must checked by watchInFlight()
  203. dojo._blockAsync = false;
  204. // MOW: remove dojo._contentHandlers alias in 2.0
  205. var handlers = _d._contentHandlers = dojo.contentHandlers = {
  206. // summary:
  207. // A map of availble XHR transport handle types. Name matches the
  208. // `handleAs` attribute passed to XHR calls.
  209. //
  210. // description:
  211. // A map of availble XHR transport handle types. Name matches the
  212. // `handleAs` attribute passed to XHR calls. Each contentHandler is
  213. // called, passing the xhr object for manipulation. The return value
  214. // from the contentHandler will be passed to the `load` or `handle`
  215. // functions defined in the original xhr call.
  216. //
  217. // example:
  218. // Creating a custom content-handler:
  219. // | dojo.contentHandlers.makeCaps = function(xhr){
  220. // | return xhr.responseText.toUpperCase();
  221. // | }
  222. // | // and later:
  223. // | dojo.xhrGet({
  224. // | url:"foo.txt",
  225. // | handleAs:"makeCaps",
  226. // | load: function(data){ /* data is a toUpper version of foo.txt */ }
  227. // | });
  228. text: function(xhr){
  229. // summary: A contentHandler which simply returns the plaintext response data
  230. return xhr.responseText;
  231. },
  232. json: function(xhr){
  233. // summary: A contentHandler which returns a JavaScript object created from the response data
  234. return _d.fromJson(xhr.responseText || null);
  235. },
  236. "json-comment-filtered": function(xhr){
  237. // summary: A contentHandler which expects comment-filtered JSON.
  238. // description:
  239. // A contentHandler which expects comment-filtered JSON.
  240. // the json-comment-filtered option was implemented to prevent
  241. // "JavaScript Hijacking", but it is less secure than standard JSON. Use
  242. // standard JSON instead. JSON prefixing can be used to subvert hijacking.
  243. //
  244. // Will throw a notice suggesting to use application/json mimetype, as
  245. // json-commenting can introduce security issues. To decrease the chances of hijacking,
  246. // use the standard `json` contentHandler, and prefix your "JSON" with: {}&&
  247. //
  248. // use djConfig.useCommentedJson = true to turn off the notice
  249. if(!dojo.config.useCommentedJson){
  250. console.warn("Consider using the standard mimetype:application/json."
  251. + " json-commenting can introduce security issues. To"
  252. + " decrease the chances of hijacking, use the standard the 'json' handler and"
  253. + " prefix your json with: {}&&\n"
  254. + "Use djConfig.useCommentedJson=true to turn off this message.");
  255. }
  256. var value = xhr.responseText;
  257. var cStartIdx = value.indexOf("\/*");
  258. var cEndIdx = value.lastIndexOf("*\/");
  259. if(cStartIdx == -1 || cEndIdx == -1){
  260. throw new Error("JSON was not comment filtered");
  261. }
  262. return _d.fromJson(value.substring(cStartIdx+2, cEndIdx));
  263. },
  264. javascript: function(xhr){
  265. // summary: A contentHandler which evaluates the response data, expecting it to be valid JavaScript
  266. // FIXME: try Moz and IE specific eval variants?
  267. return _d.eval(xhr.responseText);
  268. },
  269. xml: function(xhr){
  270. // summary: A contentHandler returning an XML Document parsed from the response data
  271. var result = xhr.responseXML;
  272. if(_d.isIE && (!result || !result.documentElement)){
  273. //WARNING: this branch used by the xml handling in dojo.io.iframe,
  274. //so be sure to test dojo.io.iframe if making changes below.
  275. var ms = function(n){ return "MSXML" + n + ".DOMDocument"; };
  276. var dp = ["Microsoft.XMLDOM", ms(6), ms(4), ms(3), ms(2)];
  277. _d.some(dp, function(p){
  278. try{
  279. var dom = new ActiveXObject(p);
  280. dom.async = false;
  281. dom.loadXML(xhr.responseText);
  282. result = dom;
  283. }catch(e){ return false; }
  284. return true;
  285. });
  286. }
  287. return result; // DOMDocument
  288. },
  289. "json-comment-optional": function(xhr){
  290. // summary: A contentHandler which checks the presence of comment-filtered JSON and
  291. // alternates between the `json` and `json-comment-filtered` contentHandlers.
  292. if(xhr.responseText && /^[^{\[]*\/\*/.test(xhr.responseText)){
  293. return handlers["json-comment-filtered"](xhr);
  294. }else{
  295. return handlers["json"](xhr);
  296. }
  297. }
  298. };
  299. /*=====
  300. dojo.__IoArgs = function(){
  301. // url: String
  302. // URL to server endpoint.
  303. // content: Object?
  304. // Contains properties with string values. These
  305. // properties will be serialized as name1=value2 and
  306. // passed in the request.
  307. // timeout: Integer?
  308. // Milliseconds to wait for the response. If this time
  309. // passes, the then error callbacks are called.
  310. // form: DOMNode?
  311. // DOM node for a form. Used to extract the form values
  312. // and send to the server.
  313. // preventCache: Boolean?
  314. // Default is false. If true, then a
  315. // "dojo.preventCache" parameter is sent in the request
  316. // with a value that changes with each request
  317. // (timestamp). Useful only with GET-type requests.
  318. // handleAs: String?
  319. // Acceptable values depend on the type of IO
  320. // transport (see specific IO calls for more information).
  321. // rawBody: String?
  322. // Sets the raw body for an HTTP request. If this is used, then the content
  323. // property is ignored. This is mostly useful for HTTP methods that have
  324. // a body to their requests, like PUT or POST. This property can be used instead
  325. // of postData and putData for dojo.rawXhrPost and dojo.rawXhrPut respectively.
  326. // ioPublish: Boolean?
  327. // Set this explicitly to false to prevent publishing of topics related to
  328. // IO operations. Otherwise, if djConfig.ioPublish is set to true, topics
  329. // will be published via dojo.publish for different phases of an IO operation.
  330. // See dojo.__IoPublish for a list of topics that are published.
  331. // load: Function?
  332. // This function will be
  333. // called on a successful HTTP response code.
  334. // error: Function?
  335. // This function will
  336. // be called when the request fails due to a network or server error, the url
  337. // is invalid, etc. It will also be called if the load or handle callback throws an
  338. // exception, unless djConfig.debugAtAllCosts is true. This allows deployed applications
  339. // to continue to run even when a logic error happens in the callback, while making
  340. // it easier to troubleshoot while in debug mode.
  341. // handle: Function?
  342. // This function will
  343. // be called at the end of every request, whether or not an error occurs.
  344. this.url = url;
  345. this.content = content;
  346. this.timeout = timeout;
  347. this.form = form;
  348. this.preventCache = preventCache;
  349. this.handleAs = handleAs;
  350. this.ioPublish = ioPublish;
  351. this.load = function(response, ioArgs){
  352. // ioArgs: dojo.__IoCallbackArgs
  353. // Provides additional information about the request.
  354. // response: Object
  355. // The response in the format as defined with handleAs.
  356. }
  357. this.error = function(response, ioArgs){
  358. // ioArgs: dojo.__IoCallbackArgs
  359. // Provides additional information about the request.
  360. // response: Object
  361. // The response in the format as defined with handleAs.
  362. }
  363. this.handle = function(loadOrError, response, ioArgs){
  364. // loadOrError: String
  365. // Provides a string that tells you whether this function
  366. // was called because of success (load) or failure (error).
  367. // response: Object
  368. // The response in the format as defined with handleAs.
  369. // ioArgs: dojo.__IoCallbackArgs
  370. // Provides additional information about the request.
  371. }
  372. }
  373. =====*/
  374. /*=====
  375. dojo.__IoCallbackArgs = function(args, xhr, url, query, handleAs, id, canDelete, json){
  376. // args: Object
  377. // the original object argument to the IO call.
  378. // xhr: XMLHttpRequest
  379. // For XMLHttpRequest calls only, the
  380. // XMLHttpRequest object that was used for the
  381. // request.
  382. // url: String
  383. // The final URL used for the call. Many times it
  384. // will be different than the original args.url
  385. // value.
  386. // query: String
  387. // For non-GET requests, the
  388. // name1=value1&name2=value2 parameters sent up in
  389. // the request.
  390. // handleAs: String
  391. // The final indicator on how the response will be
  392. // handled.
  393. // id: String
  394. // For dojo.io.script calls only, the internal
  395. // script ID used for the request.
  396. // canDelete: Boolean
  397. // For dojo.io.script calls only, indicates
  398. // whether the script tag that represents the
  399. // request can be deleted after callbacks have
  400. // been called. Used internally to know when
  401. // cleanup can happen on JSONP-type requests.
  402. // json: Object
  403. // For dojo.io.script calls only: holds the JSON
  404. // response for JSONP-type requests. Used
  405. // internally to hold on to the JSON responses.
  406. // You should not need to access it directly --
  407. // the same object should be passed to the success
  408. // callbacks directly.
  409. this.args = args;
  410. this.xhr = xhr;
  411. this.url = url;
  412. this.query = query;
  413. this.handleAs = handleAs;
  414. this.id = id;
  415. this.canDelete = canDelete;
  416. this.json = json;
  417. }
  418. =====*/
  419. /*=====
  420. dojo.__IoPublish = function(){
  421. // summary:
  422. // This is a list of IO topics that can be published
  423. // if djConfig.ioPublish is set to true. IO topics can be
  424. // published for any Input/Output, network operation. So,
  425. // dojo.xhr, dojo.io.script and dojo.io.iframe can all
  426. // trigger these topics to be published.
  427. // start: String
  428. // "/dojo/io/start" is sent when there are no outstanding IO
  429. // requests, and a new IO request is started. No arguments
  430. // are passed with this topic.
  431. // send: String
  432. // "/dojo/io/send" is sent whenever a new IO request is started.
  433. // It passes the dojo.Deferred for the request with the topic.
  434. // load: String
  435. // "/dojo/io/load" is sent whenever an IO request has loaded
  436. // successfully. It passes the response and the dojo.Deferred
  437. // for the request with the topic.
  438. // error: String
  439. // "/dojo/io/error" is sent whenever an IO request has errored.
  440. // It passes the error and the dojo.Deferred
  441. // for the request with the topic.
  442. // done: String
  443. // "/dojo/io/done" is sent whenever an IO request has completed,
  444. // either by loading or by erroring. It passes the error and
  445. // the dojo.Deferred for the request with the topic.
  446. // stop: String
  447. // "/dojo/io/stop" is sent when all outstanding IO requests have
  448. // finished. No arguments are passed with this topic.
  449. this.start = "/dojo/io/start";
  450. this.send = "/dojo/io/send";
  451. this.load = "/dojo/io/load";
  452. this.error = "/dojo/io/error";
  453. this.done = "/dojo/io/done";
  454. this.stop = "/dojo/io/stop";
  455. }
  456. =====*/
  457. dojo._ioSetArgs = function(/*dojo.__IoArgs*/args,
  458. /*Function*/canceller,
  459. /*Function*/okHandler,
  460. /*Function*/errHandler){
  461. // summary:
  462. // sets up the Deferred and ioArgs property on the Deferred so it
  463. // can be used in an io call.
  464. // args:
  465. // The args object passed into the public io call. Recognized properties on
  466. // the args object are:
  467. // canceller:
  468. // The canceller function used for the Deferred object. The function
  469. // will receive one argument, the Deferred object that is related to the
  470. // canceller.
  471. // okHandler:
  472. // The first OK callback to be registered with Deferred. It has the opportunity
  473. // to transform the OK response. It will receive one argument -- the Deferred
  474. // object returned from this function.
  475. // errHandler:
  476. // The first error callback to be registered with Deferred. It has the opportunity
  477. // to do cleanup on an error. It will receive two arguments: error (the
  478. // Error object) and dfd, the Deferred object returned from this function.
  479. var ioArgs = {args: args, url: args.url};
  480. //Get values from form if requestd.
  481. var formObject = null;
  482. if(args.form){
  483. var form = _d.byId(args.form);
  484. //IE requires going through getAttributeNode instead of just getAttribute in some form cases,
  485. //so use it for all. See #2844
  486. var actnNode = form.getAttributeNode("action");
  487. ioArgs.url = ioArgs.url || (actnNode ? actnNode.value : null);
  488. formObject = _d.formToObject(form);
  489. }
  490. // set up the query params
  491. var miArgs = [{}];
  492. if(formObject){
  493. // potentially over-ride url-provided params w/ form values
  494. miArgs.push(formObject);
  495. }
  496. if(args.content){
  497. // stuff in content over-rides what's set by form
  498. miArgs.push(args.content);
  499. }
  500. if(args.preventCache){
  501. miArgs.push({"dojo.preventCache": new Date().valueOf()});
  502. }
  503. ioArgs.query = _d.objectToQuery(_d.mixin.apply(null, miArgs));
  504. // .. and the real work of getting the deferred in order, etc.
  505. ioArgs.handleAs = args.handleAs || "text";
  506. var d = new _d.Deferred(canceller);
  507. d.addCallbacks(okHandler, function(error){
  508. return errHandler(error, d);
  509. });
  510. //Support specifying load, error and handle callback functions from the args.
  511. //For those callbacks, the "this" object will be the args object.
  512. //The callbacks will get the deferred result value as the
  513. //first argument and the ioArgs object as the second argument.
  514. var ld = args.load;
  515. if(ld && _d.isFunction(ld)){
  516. d.addCallback(function(value){
  517. return ld.call(args, value, ioArgs);
  518. });
  519. }
  520. var err = args.error;
  521. if(err && _d.isFunction(err)){
  522. d.addErrback(function(value){
  523. return err.call(args, value, ioArgs);
  524. });
  525. }
  526. var handle = args.handle;
  527. if(handle && _d.isFunction(handle)){
  528. d.addBoth(function(value){
  529. return handle.call(args, value, ioArgs);
  530. });
  531. }
  532. //Plug in topic publishing, if dojo.publish is loaded.
  533. if(cfg.ioPublish && _d.publish && ioArgs.args.ioPublish !== false){
  534. d.addCallbacks(
  535. function(res){
  536. _d.publish("/dojo/io/load", [d, res]);
  537. return res;
  538. },
  539. function(res){
  540. _d.publish("/dojo/io/error", [d, res]);
  541. return res;
  542. }
  543. );
  544. d.addBoth(function(res){
  545. _d.publish("/dojo/io/done", [d, res]);
  546. return res;
  547. });
  548. }
  549. d.ioArgs = ioArgs;
  550. // FIXME: need to wire up the xhr object's abort method to something
  551. // analagous in the Deferred
  552. return d;
  553. };
  554. var _deferredCancel = function(/*Deferred*/dfd){
  555. // summary: canceller function for dojo._ioSetArgs call.
  556. dfd.canceled = true;
  557. var xhr = dfd.ioArgs.xhr;
  558. var _at = typeof xhr.abort;
  559. if(_at == "function" || _at == "object" || _at == "unknown"){
  560. xhr.abort();
  561. }
  562. var err = dfd.ioArgs.error;
  563. if(!err){
  564. err = new Error("xhr cancelled");
  565. err.dojoType="cancel";
  566. }
  567. return err;
  568. };
  569. var _deferredOk = function(/*Deferred*/dfd){
  570. // summary: okHandler function for dojo._ioSetArgs call.
  571. var ret = handlers[dfd.ioArgs.handleAs](dfd.ioArgs.xhr);
  572. return ret === undefined ? null : ret;
  573. };
  574. var _deferError = function(/*Error*/error, /*Deferred*/dfd){
  575. // summary: errHandler function for dojo._ioSetArgs call.
  576. if(!dfd.ioArgs.args.failOk){
  577. console.error(error);
  578. }
  579. return error;
  580. };
  581. // avoid setting a timer per request. It degrades performance on IE
  582. // something fierece if we don't use unified loops.
  583. var _inFlightIntvl = null;
  584. var _inFlight = [];
  585. //Use a separate count for knowing if we are starting/stopping io calls.
  586. //Cannot use _inFlight.length since it can change at a different time than
  587. //when we want to do this kind of test. We only want to decrement the count
  588. //after a callback/errback has finished, since the callback/errback should be
  589. //considered as part of finishing a request.
  590. var _pubCount = 0;
  591. var _checkPubCount = function(dfd){
  592. if(_pubCount <= 0){
  593. _pubCount = 0;
  594. if(cfg.ioPublish && _d.publish && (!dfd || dfd && dfd.ioArgs.args.ioPublish !== false)){
  595. _d.publish("/dojo/io/stop");
  596. }
  597. }
  598. };
  599. var _watchInFlight = function(){
  600. //summary:
  601. // internal method that checks each inflight XMLHttpRequest to see
  602. // if it has completed or if the timeout situation applies.
  603. var now = (new Date()).getTime();
  604. // make sure sync calls stay thread safe, if this callback is called
  605. // during a sync call and this results in another sync call before the
  606. // first sync call ends the browser hangs
  607. if(!_d._blockAsync){
  608. // we need manual loop because we often modify _inFlight (and therefore 'i') while iterating
  609. // note: the second clause is an assigment on purpose, lint may complain
  610. for(var i = 0, tif; i < _inFlight.length && (tif = _inFlight[i]); i++){
  611. var dfd = tif.dfd;
  612. var func = function(){
  613. if(!dfd || dfd.canceled || !tif.validCheck(dfd)){
  614. _inFlight.splice(i--, 1);
  615. _pubCount -= 1;
  616. }else if(tif.ioCheck(dfd)){
  617. _inFlight.splice(i--, 1);
  618. tif.resHandle(dfd);
  619. _pubCount -= 1;
  620. }else if(dfd.startTime){
  621. //did we timeout?
  622. if(dfd.startTime + (dfd.ioArgs.args.timeout || 0) < now){
  623. _inFlight.splice(i--, 1);
  624. var err = new Error("timeout exceeded");
  625. err.dojoType = "timeout";
  626. dfd.errback(err);
  627. //Cancel the request so the io module can do appropriate cleanup.
  628. dfd.cancel();
  629. _pubCount -= 1;
  630. }
  631. }
  632. };
  633. if(dojo.config.debugAtAllCosts){
  634. func.call(this);
  635. }else{
  636. try{
  637. func.call(this);
  638. }catch(e){
  639. dfd.errback(e);
  640. }
  641. }
  642. }
  643. }
  644. _checkPubCount(dfd);
  645. if(!_inFlight.length){
  646. clearInterval(_inFlightIntvl);
  647. _inFlightIntvl = null;
  648. return;
  649. }
  650. };
  651. dojo._ioCancelAll = function(){
  652. //summary: Cancels all pending IO requests, regardless of IO type
  653. //(xhr, script, iframe).
  654. try{
  655. _d.forEach(_inFlight, function(i){
  656. try{
  657. i.dfd.cancel();
  658. }catch(e){/*squelch*/}
  659. });
  660. }catch(e){/*squelch*/}
  661. };
  662. //Automatically call cancel all io calls on unload
  663. //in IE for trac issue #2357.
  664. if(_d.isIE){
  665. _d.addOnWindowUnload(_d._ioCancelAll);
  666. }
  667. _d._ioNotifyStart = function(/*Deferred*/dfd){
  668. // summary:
  669. // If dojo.publish is available, publish topics
  670. // about the start of a request queue and/or the
  671. // the beginning of request.
  672. // description:
  673. // Used by IO transports. An IO transport should
  674. // call this method before making the network connection.
  675. if(cfg.ioPublish && _d.publish && dfd.ioArgs.args.ioPublish !== false){
  676. if(!_pubCount){
  677. _d.publish("/dojo/io/start");
  678. }
  679. _pubCount += 1;
  680. _d.publish("/dojo/io/send", [dfd]);
  681. }
  682. };
  683. _d._ioWatch = function(dfd, validCheck, ioCheck, resHandle){
  684. // summary:
  685. // Watches the io request represented by dfd to see if it completes.
  686. // dfd: Deferred
  687. // The Deferred object to watch.
  688. // validCheck: Function
  689. // Function used to check if the IO request is still valid. Gets the dfd
  690. // object as its only argument.
  691. // ioCheck: Function
  692. // Function used to check if basic IO call worked. Gets the dfd
  693. // object as its only argument.
  694. // resHandle: Function
  695. // Function used to process response. Gets the dfd
  696. // object as its only argument.
  697. var args = dfd.ioArgs.args;
  698. if(args.timeout){
  699. dfd.startTime = (new Date()).getTime();
  700. }
  701. _inFlight.push({dfd: dfd, validCheck: validCheck, ioCheck: ioCheck, resHandle: resHandle});
  702. if(!_inFlightIntvl){
  703. _inFlightIntvl = setInterval(_watchInFlight, 50);
  704. }
  705. // handle sync requests
  706. //A weakness: async calls in flight
  707. //could have their handlers called as part of the
  708. //_watchInFlight call, before the sync's callbacks
  709. // are called.
  710. if(args.sync){
  711. _watchInFlight();
  712. }
  713. };
  714. var _defaultContentType = "application/x-www-form-urlencoded";
  715. var _validCheck = function(/*Deferred*/dfd){
  716. return dfd.ioArgs.xhr.readyState; //boolean
  717. };
  718. var _ioCheck = function(/*Deferred*/dfd){
  719. return 4 == dfd.ioArgs.xhr.readyState; //boolean
  720. };
  721. var _resHandle = function(/*Deferred*/dfd){
  722. var xhr = dfd.ioArgs.xhr;
  723. if(_d._isDocumentOk(xhr)){
  724. dfd.callback(dfd);
  725. }else{
  726. var err = new Error("Unable to load " + dfd.ioArgs.url + " status:" + xhr.status);
  727. err.status = xhr.status;
  728. err.responseText = xhr.responseText;
  729. dfd.errback(err);
  730. }
  731. };
  732. dojo._ioAddQueryToUrl = function(/*dojo.__IoCallbackArgs*/ioArgs){
  733. //summary: Adds query params discovered by the io deferred construction to the URL.
  734. //Only use this for operations which are fundamentally GET-type operations.
  735. if(ioArgs.query.length){
  736. ioArgs.url += (ioArgs.url.indexOf("?") == -1 ? "?" : "&") + ioArgs.query;
  737. ioArgs.query = null;
  738. }
  739. };
  740. /*=====
  741. dojo.declare("dojo.__XhrArgs", dojo.__IoArgs, {
  742. constructor: function(){
  743. // summary:
  744. // In addition to the properties listed for the dojo._IoArgs type,
  745. // the following properties are allowed for dojo.xhr* methods.
  746. // handleAs: String?
  747. // Acceptable values are: text (default), json, json-comment-optional,
  748. // json-comment-filtered, javascript, xml. See `dojo.contentHandlers`
  749. // sync: Boolean?
  750. // false is default. Indicates whether the request should
  751. // be a synchronous (blocking) request.
  752. // headers: Object?
  753. // Additional HTTP headers to send in the request.
  754. // failOk: Boolean?
  755. // false is default. Indicates whether a request should be
  756. // allowed to fail (and therefore no console error message in
  757. // the event of a failure)
  758. // contentType: String|Boolean
  759. // "application/x-www-form-urlencoded" is default. Set to false to
  760. // prevent a Content-Type header from being sent, or to a string
  761. // to send a different Content-Type.
  762. this.handleAs = handleAs;
  763. this.sync = sync;
  764. this.headers = headers;
  765. this.failOk = failOk;
  766. }
  767. });
  768. =====*/
  769. dojo.xhr = function(/*String*/ method, /*dojo.__XhrArgs*/ args, /*Boolean?*/ hasBody){
  770. // summary:
  771. // Sends an HTTP request with the given method.
  772. // description:
  773. // Sends an HTTP request with the given method.
  774. // See also dojo.xhrGet(), xhrPost(), xhrPut() and dojo.xhrDelete() for shortcuts
  775. // for those HTTP methods. There are also methods for "raw" PUT and POST methods
  776. // via dojo.rawXhrPut() and dojo.rawXhrPost() respectively.
  777. // method:
  778. // HTTP method to be used, such as GET, POST, PUT, DELETE. Should be uppercase.
  779. // hasBody:
  780. // If the request has an HTTP body, then pass true for hasBody.
  781. //Make the Deferred object for this xhr request.
  782. var dfd = _d._ioSetArgs(args, _deferredCancel, _deferredOk, _deferError);
  783. var ioArgs = dfd.ioArgs;
  784. //Pass the args to _xhrObj, to allow alternate XHR calls based specific calls, like
  785. //the one used for iframe proxies.
  786. var xhr = ioArgs.xhr = _d._xhrObj(ioArgs.args);
  787. //If XHR factory fails, cancel the deferred.
  788. if(!xhr){
  789. dfd.cancel();
  790. return dfd;
  791. }
  792. //Allow for specifying the HTTP body completely.
  793. if("postData" in args){
  794. ioArgs.query = args.postData;
  795. }else if("putData" in args){
  796. ioArgs.query = args.putData;
  797. }else if("rawBody" in args){
  798. ioArgs.query = args.rawBody;
  799. }else if((arguments.length > 2 && !hasBody) || "POST|PUT".indexOf(method.toUpperCase()) == -1){
  800. //Check for hasBody being passed. If no hasBody,
  801. //then only append query string if not a POST or PUT request.
  802. _d._ioAddQueryToUrl(ioArgs);
  803. }
  804. // IE 6 is a steaming pile. It won't let you call apply() on the native function (xhr.open).
  805. // workaround for IE6's apply() "issues"
  806. xhr.open(method, ioArgs.url, args.sync !== true, args.user || undefined, args.password || undefined);
  807. if(args.headers){
  808. for(var hdr in args.headers){
  809. if(hdr.toLowerCase() === "content-type"){
  810. if(!args.contentType){
  811. args.contentType = args.headers[hdr];
  812. }
  813. }else if(args.headers[hdr]){
  814. //Only add header if it has a value. This allows for instnace, skipping
  815. //insertion of X-Requested-With by specifying empty value.
  816. xhr.setRequestHeader(hdr, args.headers[hdr]);
  817. }
  818. }
  819. }
  820. // FIXME: is this appropriate for all content types?
  821. if(args.contentType !== false){
  822. xhr.setRequestHeader("Content-Type", args.contentType || _defaultContentType);
  823. }
  824. if(!args.headers || !("X-Requested-With" in args.headers)){
  825. xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
  826. }
  827. // FIXME: set other headers here!
  828. _d._ioNotifyStart(dfd);
  829. if(dojo.config.debugAtAllCosts){
  830. xhr.send(ioArgs.query);
  831. }else{
  832. try{
  833. xhr.send(ioArgs.query);
  834. }catch(e){
  835. ioArgs.error = e;
  836. dfd.cancel();
  837. }
  838. }
  839. _d._ioWatch(dfd, _validCheck, _ioCheck, _resHandle);
  840. xhr = null;
  841. return dfd; // dojo.Deferred
  842. };
  843. dojo.xhrGet = function(/*dojo.__XhrArgs*/ args){
  844. // summary:
  845. // Sends an HTTP GET request to the server.
  846. return _d.xhr("GET", args); // dojo.Deferred
  847. };
  848. dojo.rawXhrPost = dojo.xhrPost = function(/*dojo.__XhrArgs*/ args){
  849. // summary:
  850. // Sends an HTTP POST request to the server. In addtion to the properties
  851. // listed for the dojo.__XhrArgs type, the following property is allowed:
  852. // postData:
  853. // String. Send raw data in the body of the POST request.
  854. return _d.xhr("POST", args, true); // dojo.Deferred
  855. };
  856. dojo.rawXhrPut = dojo.xhrPut = function(/*dojo.__XhrArgs*/ args){
  857. // summary:
  858. // Sends an HTTP PUT request to the server. In addtion to the properties
  859. // listed for the dojo.__XhrArgs type, the following property is allowed:
  860. // putData:
  861. // String. Send raw data in the body of the PUT request.
  862. return _d.xhr("PUT", args, true); // dojo.Deferred
  863. };
  864. dojo.xhrDelete = function(/*dojo.__XhrArgs*/ args){
  865. // summary:
  866. // Sends an HTTP DELETE request to the server.
  867. return _d.xhr("DELETE", args); //dojo.Deferred
  868. };
  869. /*
  870. dojo.wrapForm = function(formNode){
  871. //summary:
  872. // A replacement for FormBind, but not implemented yet.
  873. // FIXME: need to think harder about what extensions to this we might
  874. // want. What should we allow folks to do w/ this? What events to
  875. // set/send?
  876. throw new Error("dojo.wrapForm not yet implemented");
  877. }
  878. */
  879. })();
  880. }