1 if(!dojo._hasResource["dojo._base.xhr"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2 dojo._hasResource["dojo._base.xhr"] = true;
3 dojo.provide("dojo._base.xhr");
4 dojo.require("dojo._base.Deferred");
5 dojo.require("dojo._base.json");
6 dojo.require("dojo._base.lang");
7 dojo.require("dojo._base.query");
11 function setValue(/*Object*/obj, /*String*/name, /*String*/value){
13 // For the nameed property in object, set the value. If a value
14 // already exists and it is a string, convert the value to be an
18 obj[name] = [val, value];
19 }else if(_d.isArray(val)){
26 dojo.formToObject = function(/*DOMNode||String*/ formNode){
28 // dojo.formToObject returns the values encoded in an HTML form as
29 // string properties in an object which it then returns. Disabled form
30 // elements, buttons, and other non-value form elements are skipped.
31 // Multi-select elements are returned as an array of string values.
35 // | <form id="test_form">
36 // | <input type="text" name="blah" value="blah">
37 // | <input type="text" name="no_value" value="blah" disabled>
38 // | <input type="button" name="no_value2" value="blah">
39 // | <select type="select" multiple name="multi" size="5">
40 // | <option value="blah">blah</option>
41 // | <option value="thud" selected>thud</option>
42 // | <option value="thonk" selected>thonk</option>
46 // yields this object structure as the result of a call to
58 var iq = "input:not([type=file]):not([type=submit]):not([type=image]):not([type=reset]):not([type=button]), select, textarea";
59 _d.query(iq, formNode).filter(function(node){
60 return !node.disabled && node.name;
61 }).forEach(function(item){
63 var type = (item.type||"").toLowerCase();
64 if(type == "radio" || type == "checkbox"){
65 if(item.checked){ setValue(ret, _in, item.value); }
66 }else if(item.multiple){
68 _d.query("option", item).forEach(function(opt){
70 setValue(ret, _in, opt.value);
74 setValue(ret, _in, item.value);
76 ret[_in+".x"] = ret[_in+".y"] = ret[_in].x = ret[_in].y = 0;
83 dojo.objectToQuery = function(/*Object*/ map){
85 // takes a name/value mapping object and returns a string representing
86 // a URL-encoded version of that object.
98 // yields the following query string:
100 // | "blah=blah&multi=thud&multi=thonk"
102 // FIXME: need to implement encodeAscii!!
103 var enc = encodeURIComponent;
106 for(var name in map){
107 var value = map[name];
108 if(value != backstop[name]){
109 var assign = enc(name) + "=";
110 if(_d.isArray(value)){
111 for(var i=0; i < value.length; i++){
112 pairs.push(assign + enc(value[i]));
115 pairs.push(assign + enc(value));
119 return pairs.join("&"); // String
122 dojo.formToQuery = function(/*DOMNode||String*/ formNode){
124 // Returns a URL-encoded string representing the form passed as either a
125 // node or string ID identifying the form to serialize
126 return _d.objectToQuery(_d.formToObject(formNode)); // String
129 dojo.formToJson = function(/*DOMNode||String*/ formNode, /*Boolean?*/prettyPrint){
131 // return a serialized JSON string from a form node or string
132 // ID identifying the form to serialize
133 return _d.toJson(_d.formToObject(formNode), prettyPrint); // String
136 dojo.queryToObject = function(/*String*/ str){
138 // returns an object representing a de-serialized query section of a
139 // URL. Query keys with multiple values are returned in an array.
143 // | "foo=bar&foo=baz&thinger=%20spaces%20=blah&zonk=blarg&"
145 // results in this object structure:
148 // | foo: [ "bar", "baz" ],
149 // | thinger: " spaces =blah",
153 // Note that spaces and other urlencoded entities are correctly
156 // FIXME: should we grab the URL string if we're not passed one?
158 var qp = str.split("&");
159 var dec = decodeURIComponent;
160 _d.forEach(qp, function(item){
162 var parts = item.split("=");
163 var name = dec(parts.shift());
164 var val = dec(parts.join("="));
165 if(_d.isString(ret[name])){
166 ret[name] = [ret[name]];
168 if(_d.isArray(ret[name])){
175 return ret; // Object
181 all bind() replacement APIs take the following argument structure:
186 // all below are optional, but must be supported in some form by
188 timeout: 1000, // milliseconds
189 handleAs: "text", // replaces the always-wrong "mimetype"
194 // browser-specific, MAY be unsupported
195 sync: true, // defaults to false
196 form: dojo.byId("someForm")
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()
204 dojo._blockAsync = false;
206 dojo._contentHandlers = {
207 "text": function(xhr){ return xhr.responseText; },
208 "json": function(xhr){
209 if(!dojo.config.usePlainJson){
210 console.warn("Consider using mimetype:text/json-comment-filtered"
211 + " to avoid potential security issues with JSON endpoints"
212 + " (use djConfig.usePlainJson=true to turn off this message)");
214 return (xhr.status == 204) ? undefined : _d.fromJson(xhr.responseText);
216 "json-comment-filtered": function(xhr){
217 // NOTE: we provide the json-comment-filtered option as one solution to
218 // the "JavaScript Hijacking" issue noted by Fortify and others. It is
219 // not appropriate for all circumstances.
221 var value = xhr.responseText;
222 var cStartIdx = value.indexOf("\/*");
223 var cEndIdx = value.lastIndexOf("*\/");
224 if(cStartIdx == -1 || cEndIdx == -1){
225 throw new Error("JSON was not comment filtered");
227 return (xhr.status == 204) ? undefined :
228 _d.fromJson(value.substring(cStartIdx+2, cEndIdx));
230 "javascript": function(xhr){
231 // FIXME: try Moz and IE specific eval variants?
232 return _d.eval(xhr.responseText);
234 "xml": function(xhr){
235 var result = xhr.responseXML;
236 if(_d.isIE && (!result || window.location.protocol == "file:")){
237 _d.forEach(["MSXML2", "Microsoft", "MSXML", "MSXML3"], function(prefix){
239 var dom = new ActiveXObject(prefix + ".XMLDOM");
241 dom.loadXML(xhr.responseText);
243 }catch(e){ /* Not available. Squelch and try next one. */ }
246 return result; // DOMDocument
250 dojo._contentHandlers["json-comment-optional"] = function(xhr){
251 var handlers = _d._contentHandlers;
253 return handlers["json-comment-filtered"](xhr);
255 return handlers["json"](xhr);
260 dojo.__IoArgs = function(){
262 // URL to server endpoint.
264 // Contains properties with string values. These
265 // properties will be serialized as name1=value2 and
266 // passed in the request.
268 // Milliseconds to wait for the response. If this time
269 // passes, the then error callbacks are called.
271 // DOM node for a form. Used to extract the form values
272 // and send to the server.
273 // preventCache: Boolean?
274 // Default is false. If true, then a
275 // "dojo.preventCache" parameter is sent in the request
276 // with a value that changes with each request
277 // (timestamp). Useful only with GET-type requests.
279 // Acceptable values depend on the type of IO
280 // transport (see specific IO calls for more information).
282 // function(response, ioArgs){}. response is an Object, ioArgs
283 // is of type dojo.__IoCallbackArgs. The load function will be
284 // called on a successful response.
286 // function(response, ioArgs){}. response is an Object, ioArgs
287 // is of type dojo.__IoCallbackArgs. The error function will
288 // be called in an error case.
290 // function(response, ioArgs){}. response is an Object, ioArgs
291 // is of type dojo.__IoCallbackArgs. The handle function will
292 // be called in either the successful or error case. For
293 // the load, error and handle functions, the ioArgs object
294 // will contain the following properties:
296 this.content = content;
297 this.timeout = timeout;
299 this.preventCache = preventCache;
300 this.handleAs = handleAs;
303 this.handle = handle;
308 dojo.__IoCallbackArgs = function(args, xhr, url, query, handleAs, id, canDelete, json){
310 // the original object argument to the IO call.
311 // xhr: XMLHttpRequest
312 // For XMLHttpRequest calls only, the
313 // XMLHttpRequest object that was used for the
316 // The final URL used for the call. Many times it
317 // will be different than the original args.url
320 // For non-GET requests, the
321 // name1=value1&name2=value2 parameters sent up in
324 // The final indicator on how the response will be
327 // For dojo.io.script calls only, the internal
328 // script ID used for the request.
329 // canDelete: Boolean
330 // For dojo.io.script calls only, indicates
331 // whether the script tag that represents the
332 // request can be deleted after callbacks have
333 // been called. Used internally to know when
334 // cleanup can happen on JSONP-type requests.
336 // For dojo.io.script calls only: holds the JSON
337 // response for JSONP-type requests. Used
338 // internally to hold on to the JSON responses.
339 // You should not need to access it directly --
340 // the same object should be passed to the success
341 // callbacks directly.
346 this.handleAs = handleAs;
348 this.canDelete = canDelete;
355 dojo._ioSetArgs = function(/*dojo.__IoArgs*/args,
356 /*Function*/canceller,
357 /*Function*/okHandler,
358 /*Function*/errHandler){
360 // sets up the Deferred and ioArgs property on the Deferred so it
361 // can be used in an io call.
363 // The args object passed into the public io call. Recognized properties on
364 // the args object are:
366 // The canceller function used for the Deferred object. The function
367 // will receive one argument, the Deferred object that is related to the
370 // The first OK callback to be registered with Deferred. It has the opportunity
371 // to transform the OK response. It will receive one argument -- the Deferred
372 // object returned from this function.
374 // The first error callback to be registered with Deferred. It has the opportunity
375 // to do cleanup on an error. It will receive two arguments: error (the
376 // Error object) and dfd, the Deferred object returned from this function.
378 var ioArgs = {args: args, url: args.url};
380 //Get values from form if requestd.
381 var formObject = null;
383 var form = _d.byId(args.form);
384 //IE requires going through getAttributeNode instead of just getAttribute in some form cases,
385 //so use it for all. See #2844
386 var actnNode = form.getAttributeNode("action");
387 ioArgs.url = ioArgs.url || (actnNode ? actnNode.value : null);
388 formObject = _d.formToObject(form);
391 // set up the query params
395 // potentially over-ride url-provided params w/ form values
396 miArgs.push(formObject);
399 // stuff in content over-rides what's set by form
400 miArgs.push(args.content);
402 if(args.preventCache){
403 miArgs.push({"dojo.preventCache": new Date().valueOf()});
405 ioArgs.query = _d.objectToQuery(_d.mixin.apply(null, miArgs));
407 // .. and the real work of getting the deferred in order, etc.
408 ioArgs.handleAs = args.handleAs || "text";
409 var d = new _d.Deferred(canceller);
410 d.addCallbacks(okHandler, function(error){
411 return errHandler(error, d);
414 //Support specifying load, error and handle callback functions from the args.
415 //For those callbacks, the "this" object will be the args object.
416 //The callbacks will get the deferred result value as the
417 //first argument and the ioArgs object as the second argument.
419 if(ld && _d.isFunction(ld)){
420 d.addCallback(function(value){
421 return ld.call(args, value, ioArgs);
424 var err = args.error;
425 if(err && _d.isFunction(err)){
426 d.addErrback(function(value){
427 return err.call(args, value, ioArgs);
430 var handle = args.handle;
431 if(handle && _d.isFunction(handle)){
432 d.addBoth(function(value){
433 return handle.call(args, value, ioArgs);
439 // FIXME: need to wire up the xhr object's abort method to something
440 // analagous in the Deferred
444 var _deferredCancel = function(/*Deferred*/dfd){
445 //summary: canceller function for dojo._ioSetArgs call.
448 var xhr = dfd.ioArgs.xhr;
449 var _at = typeof xhr.abort;
450 if(_at == "function" || _at == "unknown"){
453 var err = new Error("xhr cancelled");
454 err.dojoType = "cancel";
457 var _deferredOk = function(/*Deferred*/dfd){
458 //summary: okHandler function for dojo._ioSetArgs call.
460 return _d._contentHandlers[dfd.ioArgs.handleAs](dfd.ioArgs.xhr);
462 var _deferError = function(/*Error*/error, /*Deferred*/dfd){
463 //summary: errHandler function for dojo._ioSetArgs call.
465 // console.debug("xhr error in:", dfd.ioArgs.xhr);
466 console.debug(error);
470 var _makeXhrDeferred = function(/*dojo.__XhrArgs*/args){
471 //summary: makes the Deferred object for this xhr request.
472 var dfd = _d._ioSetArgs(args, _deferredCancel, _deferredOk, _deferError);
473 //Pass the args to _xhrObj, to allow xhr iframe proxy interceptions.
474 dfd.ioArgs.xhr = _d._xhrObj(dfd.ioArgs.args);
478 // avoid setting a timer per request. It degrades performance on IE
479 // something fierece if we don't use unified loops.
480 var _inFlightIntvl = null;
482 var _watchInFlight = function(){
484 // internal method that checks each inflight XMLHttpRequest to see
485 // if it has completed or if the timeout situation applies.
487 var now = (new Date()).getTime();
488 // make sure sync calls stay thread safe, if this callback is called
489 // during a sync call and this results in another sync call before the
490 // first sync call ends the browser hangs
492 // we need manual loop because we often modify _inFlight (and therefore 'i') while iterating
493 // note: the second clause is an assigment on purpose, lint may complain
494 for(var i = 0, tif; i < _inFlight.length && (tif = _inFlight[i]); i++){
497 if(!dfd || dfd.canceled || !tif.validCheck(dfd)){
498 _inFlight.splice(i--, 1);
499 }else if(tif.ioCheck(dfd)){
500 _inFlight.splice(i--, 1);
502 }else if(dfd.startTime){
504 if(dfd.startTime + (dfd.ioArgs.args.timeout || 0) < now){
505 _inFlight.splice(i--, 1);
506 var err = new Error("timeout exceeded");
507 err.dojoType = "timeout";
509 //Cancel the request so the io module can do appropriate cleanup.
514 // FIXME: make sure we errback! (fixed? remove console.debug?)
516 dfd.errback(new Error("_watchInFlightError!"));
521 if(!_inFlight.length){
522 clearInterval(_inFlightIntvl);
523 _inFlightIntvl = null;
529 dojo._ioCancelAll = function(){
530 //summary: Cancels all pending IO requests, regardless of IO type
531 //(xhr, script, iframe).
533 _d.forEach(_inFlight, function(i){
536 }catch(e){/*squelch*/}
539 //Automatically call cancel all io calls on unload
540 //in IE for trac issue #2357.
542 _d.addOnUnload(_d._ioCancelAll);
545 _d._ioWatch = function(/*Deferred*/dfd,
546 /*Function*/validCheck,
548 /*Function*/resHandle){
549 //summary: watches the io request represented by dfd to see if it completes.
551 // The Deferred object to watch.
553 // Function used to check if the IO request is still valid. Gets the dfd
554 // object as its only argument.
556 // Function used to check if basic IO call worked. Gets the dfd
557 // object as its only argument.
559 // Function used to process response. Gets the dfd
560 // object as its only argument.
561 if(dfd.ioArgs.args.timeout){
562 dfd.startTime = (new Date()).getTime();
564 _inFlight.push({dfd: dfd, validCheck: validCheck, ioCheck: ioCheck, resHandle: resHandle});
566 _inFlightIntvl = setInterval(_watchInFlight, 50);
568 _watchInFlight(); // handle sync requests
571 var _defaultContentType = "application/x-www-form-urlencoded";
573 var _validCheck = function(/*Deferred*/dfd){
574 return dfd.ioArgs.xhr.readyState; //boolean
576 var _ioCheck = function(/*Deferred*/dfd){
577 return 4 == dfd.ioArgs.xhr.readyState; //boolean
579 var _resHandle = function(/*Deferred*/dfd){
580 var xhr = dfd.ioArgs.xhr;
581 if(_d._isDocumentOk(xhr)){
584 var err = new Error("Unable to load " + dfd.ioArgs.url + " status:" + xhr.status);
585 err.status = xhr.status;
586 err.responseText = xhr.responseText;
591 var _doIt = function(/*String*/type, /*Deferred*/dfd){
592 // IE 6 is a steaming pile. It won't let you call apply() on the native function (xhr.open).
593 // workaround for IE6's apply() "issues"
594 var ioArgs = dfd.ioArgs;
595 var args = ioArgs.args;
596 var xhr = ioArgs.xhr;
597 xhr.open(type, ioArgs.url, args.sync !== true, args.user || undefined, args.password || undefined);
599 for(var hdr in args.headers){
600 if(hdr.toLowerCase() === "content-type" && !args.contentType){
601 args.contentType = args.headers[hdr];
603 xhr.setRequestHeader(hdr, args.headers[hdr]);
607 // FIXME: is this appropriate for all content types?
608 xhr.setRequestHeader("Content-Type", args.contentType || _defaultContentType);
609 if(!args.headers || !args.headers["X-Requested-With"]){
610 xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
612 // FIXME: set other headers here!
614 xhr.send(ioArgs.query);
618 _d._ioWatch(dfd, _validCheck, _ioCheck, _resHandle);
620 return dfd; //Deferred
623 dojo._ioAddQueryToUrl = function(/*dojo.__IoCallbackArgs*/ioArgs){
624 //summary: Adds query params discovered by the io deferred construction to the URL.
625 //Only use this for operations which are fundamentally GET-type operations.
626 if(ioArgs.query.length){
627 ioArgs.url += (ioArgs.url.indexOf("?") == -1 ? "?" : "&") + ioArgs.query;
633 dojo.declare("dojo.__XhrArgs", dojo.__IoArgs, {
634 constructor: function(){
636 // In addition to the properties listed for the dojo._IoArgs type,
637 // the following properties are allowed for dojo.xhr* methods.
639 // Acceptable values are: text (default), json, json-comment-optional,
640 // json-comment-filtered, javascript, xml
642 // false is default. Indicates whether the request should
643 // be a synchronous (blocking) request.
645 // Additional HTTP headers to send in the request.
646 this.handleAs = handleAs;
648 this.headers = headers;
653 dojo.xhr = function(/*String*/ method, /*dojo.__XhrArgs*/ args, /*Boolean?*/ hasBody){
655 // Sends an HTTP request with the given method. If the request has an
656 // HTTP body, then pass true for hasBody. The method argument should be uppercase.
657 // Also look at dojo.xhrGet(), xhrPost(), xhrPut() and dojo.xhrDelete() for shortcuts
658 // for those HTTP methods. There are also methods for "raw" PUT and POST methods
659 // via dojo.rawXhrPut() and dojo.rawXhrPost() respectively.
660 var dfd = _makeXhrDeferred(args);
662 _d._ioAddQueryToUrl(dfd.ioArgs);
664 return _doIt(method, dfd); // dojo.Deferred
667 dojo.xhrGet = function(/*dojo.__XhrArgs*/ args){
669 // Sends an HTTP GET request to the server.
670 return _d.xhr("GET", args); //dojo.Deferred
673 dojo.xhrPost = function(/*dojo.__XhrArgs*/ args){
675 // Sends an HTTP POST request to the server.
676 return _d.xhr("POST", args, true); // dojo.Deferred
679 dojo.rawXhrPost = function(/*dojo.__XhrArgs*/ args){
681 // Sends an HTTP POST request to the server. In addtion to the properties
682 // listed for the dojo.__XhrArgs type, the following property is allowed:
684 // String. The raw data to send in the body of the POST request.
685 var dfd = _makeXhrDeferred(args);
686 dfd.ioArgs.query = args.postData;
687 return _doIt("POST", dfd); // dojo.Deferred
690 dojo.xhrPut = function(/*dojo.__XhrArgs*/ args){
692 // Sends an HTTP PUT request to the server.
693 return _d.xhr("PUT", args, true); // dojo.Deferred
696 dojo.rawXhrPut = function(/*dojo.__XhrArgs*/ args){
698 // Sends an HTTP PUT request to the server. In addtion to the properties
699 // listed for the dojo.__XhrArgs type, the following property is allowed:
701 // String. The raw data to send in the body of the PUT request.
702 var dfd = _makeXhrDeferred(args);
703 var ioArgs = dfd.ioArgs;
705 ioArgs.query = args.putData;
708 return _doIt("PUT", dfd); // dojo.Deferred
711 dojo.xhrDelete = function(/*dojo.__XhrArgs*/ args){
713 // Sends an HTTP DELETE request to the server.
714 return _d.xhr("DELETE", args); //dojo.Deferred
718 dojo.wrapForm = function(formNode){
720 // A replacement for FormBind, but not implemented yet.
722 // FIXME: need to think harder about what extensions to this we might
723 // want. What should we allow folks to do w/ this? What events to
725 throw new Error("dojo.wrapForm not yet implemented");