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