From e44a7e37b6c7b5961adaffc62b9042b8d442938e Mon Sep 17 00:00:00 2001 From: mensonge Date: Thu, 13 Nov 2008 09:49:11 +0000 Subject: New feature: basic Ajax suggestion for tags and implementation of Dojo toolkit git-svn-id: https://semanticscuttle.svn.sourceforge.net/svnroot/semanticscuttle/trunk@151 b3834d28-1941-0410-a4f8-b48e95affb8f --- includes/js/dojo/_base/xhr.js | 730 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 730 insertions(+) create mode 100644 includes/js/dojo/_base/xhr.js (limited to 'includes/js/dojo/_base/xhr.js') diff --git a/includes/js/dojo/_base/xhr.js b/includes/js/dojo/_base/xhr.js new file mode 100644 index 0000000..f6e7f1a --- /dev/null +++ b/includes/js/dojo/_base/xhr.js @@ -0,0 +1,730 @@ +if(!dojo._hasResource["dojo._base.xhr"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo._base.xhr"] = true; +dojo.provide("dojo._base.xhr"); +dojo.require("dojo._base.Deferred"); +dojo.require("dojo._base.json"); +dojo.require("dojo._base.lang"); +dojo.require("dojo._base.query"); + +(function(){ + var _d = dojo; + function setValue(/*Object*/obj, /*String*/name, /*String*/value){ + //summary: + // For the nameed property in object, set the value. If a value + // already exists and it is a string, convert the value to be an + // array of values. + var val = obj[name]; + if(_d.isString(val)){ + obj[name] = [val, value]; + }else if(_d.isArray(val)){ + val.push(value); + }else{ + obj[name] = value; + } + } + + dojo.formToObject = function(/*DOMNode||String*/ formNode){ + // summary: + // dojo.formToObject returns the values encoded in an HTML form as + // string properties in an object which it then returns. Disabled form + // elements, buttons, and other non-value form elements are skipped. + // Multi-select elements are returned as an array of string values. + // description: + // This form: + // + // |
+ // | + // | + // | + // | + // |
+ // + // yields this object structure as the result of a call to + // formToObject(): + // + // | { + // | blah: "blah", + // | multi: [ + // | "thud", + // | "thonk" + // | ] + // | }; + + var ret = {}; + var iq = "input:not([type=file]):not([type=submit]):not([type=image]):not([type=reset]):not([type=button]), select, textarea"; + _d.query(iq, formNode).filter(function(node){ + return !node.disabled && node.name; + }).forEach(function(item){ + var _in = item.name; + var type = (item.type||"").toLowerCase(); + if(type == "radio" || type == "checkbox"){ + if(item.checked){ setValue(ret, _in, item.value); } + }else if(item.multiple){ + ret[_in] = []; + _d.query("option", item).forEach(function(opt){ + if(opt.selected){ + setValue(ret, _in, opt.value); + } + }); + }else{ + setValue(ret, _in, item.value); + if(type == "image"){ + ret[_in+".x"] = ret[_in+".y"] = ret[_in].x = ret[_in].y = 0; + } + } + }); + return ret; // Object + } + + dojo.objectToQuery = function(/*Object*/ map){ + // summary: + // takes a name/value mapping object and returns a string representing + // a URL-encoded version of that object. + // example: + // this object: + // + // | { + // | blah: "blah", + // | multi: [ + // | "thud", + // | "thonk" + // | ] + // | }; + // + // yields the following query string: + // + // | "blah=blah&multi=thud&multi=thonk" + + // FIXME: need to implement encodeAscii!! + var enc = encodeURIComponent; + var pairs = []; + var backstop = {}; + for(var name in map){ + var value = map[name]; + if(value != backstop[name]){ + var assign = enc(name) + "="; + if(_d.isArray(value)){ + for(var i=0; i < value.length; i++){ + pairs.push(assign + enc(value[i])); + } + }else{ + pairs.push(assign + enc(value)); + } + } + } + return pairs.join("&"); // String + } + + dojo.formToQuery = function(/*DOMNode||String*/ formNode){ + // summary: + // Returns a URL-encoded string representing the form passed as either a + // node or string ID identifying the form to serialize + return _d.objectToQuery(_d.formToObject(formNode)); // String + } + + dojo.formToJson = function(/*DOMNode||String*/ formNode, /*Boolean?*/prettyPrint){ + // summary: + // return a serialized JSON string from a form node or string + // ID identifying the form to serialize + return _d.toJson(_d.formToObject(formNode), prettyPrint); // String + } + + dojo.queryToObject = function(/*String*/ str){ + // summary: + // returns an object representing a de-serialized query section of a + // URL. Query keys with multiple values are returned in an array. + // description: + // This string: + // + // | "foo=bar&foo=baz&thinger=%20spaces%20=blah&zonk=blarg&" + // + // results in this object structure: + // + // | { + // | foo: [ "bar", "baz" ], + // | thinger: " spaces =blah", + // | zonk: "blarg" + // | } + // + // Note that spaces and other urlencoded entities are correctly + // handled. + + // FIXME: should we grab the URL string if we're not passed one? + var ret = {}; + var qp = str.split("&"); + var dec = decodeURIComponent; + _d.forEach(qp, function(item){ + if(item.length){ + var parts = item.split("="); + var name = dec(parts.shift()); + var val = dec(parts.join("=")); + if(_d.isString(ret[name])){ + ret[name] = [ret[name]]; + } + if(_d.isArray(ret[name])){ + ret[name].push(val); + }else{ + ret[name] = val; + } + } + }); + return ret; // Object + } + + /* + from refactor.txt: + + all bind() replacement APIs take the following argument structure: + + { + url: "blah.html", + + // all below are optional, but must be supported in some form by + // every IO API + timeout: 1000, // milliseconds + handleAs: "text", // replaces the always-wrong "mimetype" + content: { + key: "value" + }, + + // browser-specific, MAY be unsupported + sync: true, // defaults to false + form: dojo.byId("someForm") + } + */ + + // need to block async callbacks from snatching this thread as the result + // of an async callback might call another sync XHR, this hangs khtml forever + // must checked by watchInFlight() + + dojo._blockAsync = false; + + dojo._contentHandlers = { + "text": function(xhr){ return xhr.responseText; }, + "json": function(xhr){ + if(!dojo.config.usePlainJson){ + console.warn("Consider using mimetype:text/json-comment-filtered" + + " to avoid potential security issues with JSON endpoints" + + " (use djConfig.usePlainJson=true to turn off this message)"); + } + return (xhr.status == 204) ? undefined : _d.fromJson(xhr.responseText); + }, + "json-comment-filtered": function(xhr){ + // NOTE: we provide the json-comment-filtered option as one solution to + // the "JavaScript Hijacking" issue noted by Fortify and others. It is + // not appropriate for all circumstances. + + var value = xhr.responseText; + var cStartIdx = value.indexOf("\/*"); + var cEndIdx = value.lastIndexOf("*\/"); + if(cStartIdx == -1 || cEndIdx == -1){ + throw new Error("JSON was not comment filtered"); + } + return (xhr.status == 204) ? undefined : + _d.fromJson(value.substring(cStartIdx+2, cEndIdx)); + }, + "javascript": function(xhr){ + // FIXME: try Moz and IE specific eval variants? + return _d.eval(xhr.responseText); + }, + "xml": function(xhr){ + var result = xhr.responseXML; + if(_d.isIE && (!result || window.location.protocol == "file:")){ + _d.forEach(["MSXML2", "Microsoft", "MSXML", "MSXML3"], function(prefix){ + try{ + var dom = new ActiveXObject(prefix + ".XMLDOM"); + dom.async = false; + dom.loadXML(xhr.responseText); + result = dom; + }catch(e){ /* Not available. Squelch and try next one. */ } + }); + } + return result; // DOMDocument + } + }; + + dojo._contentHandlers["json-comment-optional"] = function(xhr){ + var handlers = _d._contentHandlers; + try{ + return handlers["json-comment-filtered"](xhr); + }catch(e){ + return handlers["json"](xhr); + } + }; + + /*===== + dojo.__IoArgs = function(){ + // url: String + // URL to server endpoint. + // content: Object? + // Contains properties with string values. These + // properties will be serialized as name1=value2 and + // passed in the request. + // timeout: Integer? + // Milliseconds to wait for the response. If this time + // passes, the then error callbacks are called. + // form: DOMNode? + // DOM node for a form. Used to extract the form values + // and send to the server. + // preventCache: Boolean? + // Default is false. If true, then a + // "dojo.preventCache" parameter is sent in the request + // with a value that changes with each request + // (timestamp). Useful only with GET-type requests. + // handleAs: String? + // Acceptable values depend on the type of IO + // transport (see specific IO calls for more information). + // load: Function? + // function(response, ioArgs){}. response is an Object, ioArgs + // is of type dojo.__IoCallbackArgs. The load function will be + // called on a successful response. + // error: Function? + // function(response, ioArgs){}. response is an Object, ioArgs + // is of type dojo.__IoCallbackArgs. The error function will + // be called in an error case. + // handle: Function? + // function(response, ioArgs){}. response is an Object, ioArgs + // is of type dojo.__IoCallbackArgs. The handle function will + // be called in either the successful or error case. For + // the load, error and handle functions, the ioArgs object + // will contain the following properties: + this.url = url; + this.content = content; + this.timeout = timeout; + this.form = form; + this.preventCache = preventCache; + this.handleAs = handleAs; + this.load = load; + this.error = error; + this.handle = handle; + } + =====*/ + + /*===== + dojo.__IoCallbackArgs = function(args, xhr, url, query, handleAs, id, canDelete, json){ + // args: Object + // the original object argument to the IO call. + // xhr: XMLHttpRequest + // For XMLHttpRequest calls only, the + // XMLHttpRequest object that was used for the + // request. + // url: String + // The final URL used for the call. Many times it + // will be different than the original args.url + // value. + // query: String + // For non-GET requests, the + // name1=value1&name2=value2 parameters sent up in + // the request. + // handleAs: String + // The final indicator on how the response will be + // handled. + // id: String + // For dojo.io.script calls only, the internal + // script ID used for the request. + // canDelete: Boolean + // For dojo.io.script calls only, indicates + // whether the script tag that represents the + // request can be deleted after callbacks have + // been called. Used internally to know when + // cleanup can happen on JSONP-type requests. + // json: Object + // For dojo.io.script calls only: holds the JSON + // response for JSONP-type requests. Used + // internally to hold on to the JSON responses. + // You should not need to access it directly -- + // the same object should be passed to the success + // callbacks directly. + this.args = args; + this.xhr = xhr; + this.url = url; + this.query = query; + this.handleAs = handleAs; + this.id = id; + this.canDelete = canDelete; + this.json = json; + } + =====*/ + + + + dojo._ioSetArgs = function(/*dojo.__IoArgs*/args, + /*Function*/canceller, + /*Function*/okHandler, + /*Function*/errHandler){ + // summary: + // sets up the Deferred and ioArgs property on the Deferred so it + // can be used in an io call. + // args: + // The args object passed into the public io call. Recognized properties on + // the args object are: + // canceller: + // The canceller function used for the Deferred object. The function + // will receive one argument, the Deferred object that is related to the + // canceller. + // okHandler: + // The first OK callback to be registered with Deferred. It has the opportunity + // to transform the OK response. It will receive one argument -- the Deferred + // object returned from this function. + // errHandler: + // The first error callback to be registered with Deferred. It has the opportunity + // to do cleanup on an error. It will receive two arguments: error (the + // Error object) and dfd, the Deferred object returned from this function. + + var ioArgs = {args: args, url: args.url}; + + //Get values from form if requestd. + var formObject = null; + if(args.form){ + var form = _d.byId(args.form); + //IE requires going through getAttributeNode instead of just getAttribute in some form cases, + //so use it for all. See #2844 + var actnNode = form.getAttributeNode("action"); + ioArgs.url = ioArgs.url || (actnNode ? actnNode.value : null); + formObject = _d.formToObject(form); + } + + // set up the query params + var miArgs = [{}]; + + if(formObject){ + // potentially over-ride url-provided params w/ form values + miArgs.push(formObject); + } + if(args.content){ + // stuff in content over-rides what's set by form + miArgs.push(args.content); + } + if(args.preventCache){ + miArgs.push({"dojo.preventCache": new Date().valueOf()}); + } + ioArgs.query = _d.objectToQuery(_d.mixin.apply(null, miArgs)); + + // .. and the real work of getting the deferred in order, etc. + ioArgs.handleAs = args.handleAs || "text"; + var d = new _d.Deferred(canceller); + d.addCallbacks(okHandler, function(error){ + return errHandler(error, d); + }); + + //Support specifying load, error and handle callback functions from the args. + //For those callbacks, the "this" object will be the args object. + //The callbacks will get the deferred result value as the + //first argument and the ioArgs object as the second argument. + var ld = args.load; + if(ld && _d.isFunction(ld)){ + d.addCallback(function(value){ + return ld.call(args, value, ioArgs); + }); + } + var err = args.error; + if(err && _d.isFunction(err)){ + d.addErrback(function(value){ + return err.call(args, value, ioArgs); + }); + } + var handle = args.handle; + if(handle && _d.isFunction(handle)){ + d.addBoth(function(value){ + return handle.call(args, value, ioArgs); + }); + } + + d.ioArgs = ioArgs; + + // FIXME: need to wire up the xhr object's abort method to something + // analagous in the Deferred + return d; + } + + var _deferredCancel = function(/*Deferred*/dfd){ + //summary: canceller function for dojo._ioSetArgs call. + + dfd.canceled = true; + var xhr = dfd.ioArgs.xhr; + var _at = typeof xhr.abort; + if(_at == "function" || _at == "unknown"){ + xhr.abort(); + } + var err = new Error("xhr cancelled"); + err.dojoType = "cancel"; + return err; + } + var _deferredOk = function(/*Deferred*/dfd){ + //summary: okHandler function for dojo._ioSetArgs call. + + return _d._contentHandlers[dfd.ioArgs.handleAs](dfd.ioArgs.xhr); + } + var _deferError = function(/*Error*/error, /*Deferred*/dfd){ + //summary: errHandler function for dojo._ioSetArgs call. + + // console.debug("xhr error in:", dfd.ioArgs.xhr); + console.debug(error); + return error; + } + + var _makeXhrDeferred = function(/*dojo.__XhrArgs*/args){ + //summary: makes the Deferred object for this xhr request. + var dfd = _d._ioSetArgs(args, _deferredCancel, _deferredOk, _deferError); + //Pass the args to _xhrObj, to allow xhr iframe proxy interceptions. + dfd.ioArgs.xhr = _d._xhrObj(dfd.ioArgs.args); + return dfd; + } + + // avoid setting a timer per request. It degrades performance on IE + // something fierece if we don't use unified loops. + var _inFlightIntvl = null; + var _inFlight = []; + var _watchInFlight = function(){ + //summary: + // internal method that checks each inflight XMLHttpRequest to see + // if it has completed or if the timeout situation applies. + + var now = (new Date()).getTime(); + // make sure sync calls stay thread safe, if this callback is called + // during a sync call and this results in another sync call before the + // first sync call ends the browser hangs + if(!_d._blockAsync){ + // we need manual loop because we often modify _inFlight (and therefore 'i') while iterating + // note: the second clause is an assigment on purpose, lint may complain + for(var i = 0, tif; i < _inFlight.length && (tif = _inFlight[i]); i++){ + var dfd = tif.dfd; + try{ + if(!dfd || dfd.canceled || !tif.validCheck(dfd)){ + _inFlight.splice(i--, 1); + }else if(tif.ioCheck(dfd)){ + _inFlight.splice(i--, 1); + tif.resHandle(dfd); + }else if(dfd.startTime){ + //did we timeout? + if(dfd.startTime + (dfd.ioArgs.args.timeout || 0) < now){ + _inFlight.splice(i--, 1); + var err = new Error("timeout exceeded"); + err.dojoType = "timeout"; + dfd.errback(err); + //Cancel the request so the io module can do appropriate cleanup. + dfd.cancel(); + } + } + }catch(e){ + // FIXME: make sure we errback! (fixed? remove console.debug?) + console.debug(e); + dfd.errback(new Error("_watchInFlightError!")); + } + } + } + + if(!_inFlight.length){ + clearInterval(_inFlightIntvl); + _inFlightIntvl = null; + return; + } + + } + + dojo._ioCancelAll = function(){ + //summary: Cancels all pending IO requests, regardless of IO type + //(xhr, script, iframe). + try{ + _d.forEach(_inFlight, function(i){ + i.dfd.cancel(); + }); + }catch(e){/*squelch*/} + } + + //Automatically call cancel all io calls on unload + //in IE for trac issue #2357. + if(_d.isIE){ + _d.addOnUnload(_d._ioCancelAll); + } + + _d._ioWatch = function(/*Deferred*/dfd, + /*Function*/validCheck, + /*Function*/ioCheck, + /*Function*/resHandle){ + //summary: watches the io request represented by dfd to see if it completes. + //dfd: + // The Deferred object to watch. + //validCheck: + // Function used to check if the IO request is still valid. Gets the dfd + // object as its only argument. + //ioCheck: + // Function used to check if basic IO call worked. Gets the dfd + // object as its only argument. + //resHandle: + // Function used to process response. Gets the dfd + // object as its only argument. + if(dfd.ioArgs.args.timeout){ + dfd.startTime = (new Date()).getTime(); + } + _inFlight.push({dfd: dfd, validCheck: validCheck, ioCheck: ioCheck, resHandle: resHandle}); + if(!_inFlightIntvl){ + _inFlightIntvl = setInterval(_watchInFlight, 50); + } + _watchInFlight(); // handle sync requests + } + + var _defaultContentType = "application/x-www-form-urlencoded"; + + var _validCheck = function(/*Deferred*/dfd){ + return dfd.ioArgs.xhr.readyState; //boolean + } + var _ioCheck = function(/*Deferred*/dfd){ + return 4 == dfd.ioArgs.xhr.readyState; //boolean + } + var _resHandle = function(/*Deferred*/dfd){ + var xhr = dfd.ioArgs.xhr; + if(_d._isDocumentOk(xhr)){ + dfd.callback(dfd); + }else{ + var err = new Error("Unable to load " + dfd.ioArgs.url + " status:" + xhr.status); + err.status = xhr.status; + err.responseText = xhr.responseText; + dfd.errback(err); + } + } + + var _doIt = function(/*String*/type, /*Deferred*/dfd){ + // IE 6 is a steaming pile. It won't let you call apply() on the native function (xhr.open). + // workaround for IE6's apply() "issues" + var ioArgs = dfd.ioArgs; + var args = ioArgs.args; + var xhr = ioArgs.xhr; + xhr.open(type, ioArgs.url, args.sync !== true, args.user || undefined, args.password || undefined); + if(args.headers){ + for(var hdr in args.headers){ + if(hdr.toLowerCase() === "content-type" && !args.contentType){ + args.contentType = args.headers[hdr]; + }else{ + xhr.setRequestHeader(hdr, args.headers[hdr]); + } + } + } + // FIXME: is this appropriate for all content types? + xhr.setRequestHeader("Content-Type", args.contentType || _defaultContentType); + if(!args.headers || !args.headers["X-Requested-With"]){ + xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + } + // FIXME: set other headers here! + try{ + xhr.send(ioArgs.query); + }catch(e){ + dfd.cancel(); + } + _d._ioWatch(dfd, _validCheck, _ioCheck, _resHandle); + xhr = null; + return dfd; //Deferred + } + + dojo._ioAddQueryToUrl = function(/*dojo.__IoCallbackArgs*/ioArgs){ + //summary: Adds query params discovered by the io deferred construction to the URL. + //Only use this for operations which are fundamentally GET-type operations. + if(ioArgs.query.length){ + ioArgs.url += (ioArgs.url.indexOf("?") == -1 ? "?" : "&") + ioArgs.query; + ioArgs.query = null; + } + } + + /*===== + dojo.declare("dojo.__XhrArgs", dojo.__IoArgs, { + constructor: function(){ + // summary: + // In addition to the properties listed for the dojo._IoArgs type, + // the following properties are allowed for dojo.xhr* methods. + // handleAs: String? + // Acceptable values are: text (default), json, json-comment-optional, + // json-comment-filtered, javascript, xml + // sync: Boolean? + // false is default. Indicates whether the request should + // be a synchronous (blocking) request. + // headers: Object? + // Additional HTTP headers to send in the request. + this.handleAs = handleAs; + this.sync = sync; + this.headers = headers; + } + }); + =====*/ + + dojo.xhr = function(/*String*/ method, /*dojo.__XhrArgs*/ args, /*Boolean?*/ hasBody){ + // summary: + // Sends an HTTP request with the given method. If the request has an + // HTTP body, then pass true for hasBody. The method argument should be uppercase. + // Also look at dojo.xhrGet(), xhrPost(), xhrPut() and dojo.xhrDelete() for shortcuts + // for those HTTP methods. There are also methods for "raw" PUT and POST methods + // via dojo.rawXhrPut() and dojo.rawXhrPost() respectively. + var dfd = _makeXhrDeferred(args); + if(!hasBody){ + _d._ioAddQueryToUrl(dfd.ioArgs); + } + return _doIt(method, dfd); // dojo.Deferred + } + + dojo.xhrGet = function(/*dojo.__XhrArgs*/ args){ + // summary: + // Sends an HTTP GET request to the server. + return _d.xhr("GET", args); //dojo.Deferred + } + + dojo.xhrPost = function(/*dojo.__XhrArgs*/ args){ + //summary: + // Sends an HTTP POST request to the server. + return _d.xhr("POST", args, true); // dojo.Deferred + } + + dojo.rawXhrPost = function(/*dojo.__XhrArgs*/ args){ + // summary: + // Sends an HTTP POST request to the server. In addtion to the properties + // listed for the dojo.__XhrArgs type, the following property is allowed: + // postData: + // String. The raw data to send in the body of the POST request. + var dfd = _makeXhrDeferred(args); + dfd.ioArgs.query = args.postData; + return _doIt("POST", dfd); // dojo.Deferred + } + + dojo.xhrPut = function(/*dojo.__XhrArgs*/ args){ + // summary: + // Sends an HTTP PUT request to the server. + return _d.xhr("PUT", args, true); // dojo.Deferred + } + + dojo.rawXhrPut = function(/*dojo.__XhrArgs*/ args){ + // summary: + // Sends an HTTP PUT request to the server. In addtion to the properties + // listed for the dojo.__XhrArgs type, the following property is allowed: + // putData: + // String. The raw data to send in the body of the PUT request. + var dfd = _makeXhrDeferred(args); + var ioArgs = dfd.ioArgs; + if(args.putData){ + ioArgs.query = args.putData; + args.putData = null; + } + return _doIt("PUT", dfd); // dojo.Deferred + } + + dojo.xhrDelete = function(/*dojo.__XhrArgs*/ args){ + // summary: + // Sends an HTTP DELETE request to the server. + return _d.xhr("DELETE", args); //dojo.Deferred + } + + /* + dojo.wrapForm = function(formNode){ + //summary: + // A replacement for FormBind, but not implemented yet. + + // FIXME: need to think harder about what extensions to this we might + // want. What should we allow folks to do w/ this? What events to + // set/send? + throw new Error("dojo.wrapForm not yet implemented"); + } + */ +})(); + +} -- cgit v1.2.3-54-g00ecf