557 lines
16 KiB
JavaScript
557 lines
16 KiB
JavaScript
/*
|
|
Copyright (c) 2004-2006, The Dojo Foundation
|
|
All Rights Reserved.
|
|
|
|
Licensed under the Academic Free License version 2.1 or above OR the
|
|
modified BSD license. For more information on Dojo licensing, see:
|
|
|
|
http://dojotoolkit.org/community/licensing.shtml
|
|
*/
|
|
|
|
dojo.provide("dojo.io.BrowserIO");
|
|
|
|
dojo.require("dojo.io");
|
|
dojo.require("dojo.lang.array");
|
|
dojo.require("dojo.lang.func");
|
|
dojo.require("dojo.string.extras");
|
|
dojo.require("dojo.dom");
|
|
dojo.require("dojo.undo.browser");
|
|
|
|
dojo.io.checkChildrenForFile = function(node){
|
|
var hasFile = false;
|
|
var inputs = node.getElementsByTagName("input");
|
|
dojo.lang.forEach(inputs, function(input){
|
|
if(hasFile){ return; }
|
|
if(input.getAttribute("type")=="file"){
|
|
hasFile = true;
|
|
}
|
|
});
|
|
return hasFile;
|
|
}
|
|
|
|
dojo.io.formHasFile = function(formNode){
|
|
return dojo.io.checkChildrenForFile(formNode);
|
|
}
|
|
|
|
dojo.io.updateNode = function(node, urlOrArgs){
|
|
node = dojo.byId(node);
|
|
var args = urlOrArgs;
|
|
if(dojo.lang.isString(urlOrArgs)){
|
|
args = { url: urlOrArgs };
|
|
}
|
|
args.mimetype = "text/html";
|
|
args.load = function(t, d, e){
|
|
while(node.firstChild){
|
|
if(dojo["event"]){
|
|
try{
|
|
dojo.event.browser.clean(node.firstChild);
|
|
}catch(e){}
|
|
}
|
|
node.removeChild(node.firstChild);
|
|
}
|
|
node.innerHTML = d;
|
|
};
|
|
dojo.io.bind(args);
|
|
}
|
|
|
|
dojo.io.formFilter = function(node) {
|
|
var type = (node.type||"").toLowerCase();
|
|
return !node.disabled && node.name
|
|
&& !dojo.lang.inArray(type, ["file", "submit", "image", "reset", "button"]);
|
|
}
|
|
|
|
// TODO: Move to htmlUtils
|
|
dojo.io.encodeForm = function(formNode, encoding, formFilter){
|
|
if((!formNode)||(!formNode.tagName)||(!formNode.tagName.toLowerCase() == "form")){
|
|
dojo.raise("Attempted to encode a non-form element.");
|
|
}
|
|
if(!formFilter) { formFilter = dojo.io.formFilter; }
|
|
var enc = /utf/i.test(encoding||"") ? encodeURIComponent : dojo.string.encodeAscii;
|
|
var values = [];
|
|
|
|
for(var i = 0; i < formNode.elements.length; i++){
|
|
var elm = formNode.elements[i];
|
|
if(!elm || elm.tagName.toLowerCase() == "fieldset" || !formFilter(elm)) { continue; }
|
|
var name = enc(elm.name);
|
|
var type = elm.type.toLowerCase();
|
|
|
|
if(type == "select-multiple"){
|
|
for(var j = 0; j < elm.options.length; j++){
|
|
if(elm.options[j].selected) {
|
|
values.push(name + "=" + enc(elm.options[j].value));
|
|
}
|
|
}
|
|
}else if(dojo.lang.inArray(type, ["radio", "checkbox"])){
|
|
if(elm.checked){
|
|
values.push(name + "=" + enc(elm.value));
|
|
}
|
|
}else{
|
|
values.push(name + "=" + enc(elm.value));
|
|
}
|
|
}
|
|
|
|
// now collect input type="image", which doesn't show up in the elements array
|
|
var inputs = formNode.getElementsByTagName("input");
|
|
for(var i = 0; i < inputs.length; i++) {
|
|
var input = inputs[i];
|
|
if(input.type.toLowerCase() == "image" && input.form == formNode
|
|
&& formFilter(input)) {
|
|
var name = enc(input.name);
|
|
values.push(name + "=" + enc(input.value));
|
|
values.push(name + ".x=0");
|
|
values.push(name + ".y=0");
|
|
}
|
|
}
|
|
return values.join("&") + "&";
|
|
}
|
|
|
|
dojo.io.FormBind = function(args) {
|
|
this.bindArgs = {};
|
|
|
|
if(args && args.formNode) {
|
|
this.init(args);
|
|
} else if(args) {
|
|
this.init({formNode: args});
|
|
}
|
|
}
|
|
dojo.lang.extend(dojo.io.FormBind, {
|
|
form: null,
|
|
|
|
bindArgs: null,
|
|
|
|
clickedButton: null,
|
|
|
|
init: function(args) {
|
|
var form = dojo.byId(args.formNode);
|
|
|
|
if(!form || !form.tagName || form.tagName.toLowerCase() != "form") {
|
|
throw new Error("FormBind: Couldn't apply, invalid form");
|
|
} else if(this.form == form) {
|
|
return;
|
|
} else if(this.form) {
|
|
throw new Error("FormBind: Already applied to a form");
|
|
}
|
|
|
|
dojo.lang.mixin(this.bindArgs, args);
|
|
this.form = form;
|
|
|
|
this.connect(form, "onsubmit", "submit");
|
|
|
|
for(var i = 0; i < form.elements.length; i++) {
|
|
var node = form.elements[i];
|
|
if(node && node.type && dojo.lang.inArray(node.type.toLowerCase(), ["submit", "button"])) {
|
|
this.connect(node, "onclick", "click");
|
|
}
|
|
}
|
|
|
|
var inputs = form.getElementsByTagName("input");
|
|
for(var i = 0; i < inputs.length; i++) {
|
|
var input = inputs[i];
|
|
if(input.type.toLowerCase() == "image" && input.form == form) {
|
|
this.connect(input, "onclick", "click");
|
|
}
|
|
}
|
|
},
|
|
|
|
onSubmit: function(form) {
|
|
return true;
|
|
},
|
|
|
|
submit: function(e) {
|
|
e.preventDefault();
|
|
if(this.onSubmit(this.form)) {
|
|
dojo.io.bind(dojo.lang.mixin(this.bindArgs, {
|
|
formFilter: dojo.lang.hitch(this, "formFilter")
|
|
}));
|
|
}
|
|
},
|
|
|
|
click: function(e) {
|
|
var node = e.currentTarget;
|
|
if(node.disabled) { return; }
|
|
this.clickedButton = node;
|
|
},
|
|
|
|
formFilter: function(node) {
|
|
var type = (node.type||"").toLowerCase();
|
|
var accept = false;
|
|
if(node.disabled || !node.name) {
|
|
accept = false;
|
|
} else if(dojo.lang.inArray(type, ["submit", "button", "image"])) {
|
|
if(!this.clickedButton) { this.clickedButton = node; }
|
|
accept = node == this.clickedButton;
|
|
} else {
|
|
accept = !dojo.lang.inArray(type, ["file", "submit", "reset", "button"]);
|
|
}
|
|
return accept;
|
|
},
|
|
|
|
// in case you don't have dojo.event.* pulled in
|
|
connect: function(srcObj, srcFcn, targetFcn) {
|
|
if(dojo.evalObjPath("dojo.event.connect")) {
|
|
dojo.event.connect(srcObj, srcFcn, this, targetFcn);
|
|
} else {
|
|
var fcn = dojo.lang.hitch(this, targetFcn);
|
|
srcObj[srcFcn] = function(e) {
|
|
if(!e) { e = window.event; }
|
|
if(!e.currentTarget) { e.currentTarget = e.srcElement; }
|
|
if(!e.preventDefault) { e.preventDefault = function() { window.event.returnValue = false; } }
|
|
fcn(e);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
dojo.io.XMLHTTPTransport = new function(){
|
|
var _this = this;
|
|
|
|
var _cache = {}; // FIXME: make this public? do we even need to?
|
|
this.useCache = false; // if this is true, we'll cache unless kwArgs.useCache = false
|
|
this.preventCache = false; // if this is true, we'll always force GET requests to cache
|
|
|
|
// FIXME: Should this even be a function? or do we just hard code it in the next 2 functions?
|
|
function getCacheKey(url, query, method) {
|
|
return url + "|" + query + "|" + method.toLowerCase();
|
|
}
|
|
|
|
function addToCache(url, query, method, http) {
|
|
_cache[getCacheKey(url, query, method)] = http;
|
|
}
|
|
|
|
function getFromCache(url, query, method) {
|
|
return _cache[getCacheKey(url, query, method)];
|
|
}
|
|
|
|
this.clearCache = function() {
|
|
_cache = {};
|
|
}
|
|
|
|
// moved successful load stuff here
|
|
function doLoad(kwArgs, http, url, query, useCache) {
|
|
if( ((http.status>=200)&&(http.status<300))|| // allow any 2XX response code
|
|
(http.status==304)|| // get it out of the cache
|
|
(location.protocol=="file:" && (http.status==0 || http.status==undefined))||
|
|
(location.protocol=="chrome:" && (http.status==0 || http.status==undefined))
|
|
){
|
|
var ret;
|
|
if(kwArgs.method.toLowerCase() == "head"){
|
|
var headers = http.getAllResponseHeaders();
|
|
ret = {};
|
|
ret.toString = function(){ return headers; }
|
|
var values = headers.split(/[\r\n]+/g);
|
|
for(var i = 0; i < values.length; i++) {
|
|
var pair = values[i].match(/^([^:]+)\s*:\s*(.+)$/i);
|
|
if(pair) {
|
|
ret[pair[1]] = pair[2];
|
|
}
|
|
}
|
|
}else if(kwArgs.mimetype == "text/javascript"){
|
|
try{
|
|
ret = dj_eval(http.responseText);
|
|
}catch(e){
|
|
dojo.debug(e);
|
|
dojo.debug(http.responseText);
|
|
ret = null;
|
|
}
|
|
}else if(kwArgs.mimetype == "text/json"){
|
|
try{
|
|
ret = dj_eval("("+http.responseText+")");
|
|
}catch(e){
|
|
dojo.debug(e);
|
|
dojo.debug(http.responseText);
|
|
ret = false;
|
|
}
|
|
}else if((kwArgs.mimetype == "application/xml")||
|
|
(kwArgs.mimetype == "text/xml")){
|
|
ret = http.responseXML;
|
|
if(!ret || typeof ret == "string" || !http.getResponseHeader("Content-Type")) {
|
|
ret = dojo.dom.createDocumentFromText(http.responseText);
|
|
}
|
|
}else{
|
|
ret = http.responseText;
|
|
}
|
|
|
|
if(useCache){ // only cache successful responses
|
|
addToCache(url, query, kwArgs.method, http);
|
|
}
|
|
kwArgs[(typeof kwArgs.load == "function") ? "load" : "handle"]("load", ret, http, kwArgs);
|
|
}else{
|
|
var errObj = new dojo.io.Error("XMLHttpTransport Error: "+http.status+" "+http.statusText);
|
|
kwArgs[(typeof kwArgs.error == "function") ? "error" : "handle"]("error", errObj, http, kwArgs);
|
|
}
|
|
}
|
|
|
|
// set headers (note: Content-Type will get overriden if kwArgs.contentType is set)
|
|
function setHeaders(http, kwArgs){
|
|
if(kwArgs["headers"]) {
|
|
for(var header in kwArgs["headers"]) {
|
|
if(header.toLowerCase() == "content-type" && !kwArgs["contentType"]) {
|
|
kwArgs["contentType"] = kwArgs["headers"][header];
|
|
} else {
|
|
http.setRequestHeader(header, kwArgs["headers"][header]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this.inFlight = [];
|
|
this.inFlightTimer = null;
|
|
|
|
this.startWatchingInFlight = function(){
|
|
if(!this.inFlightTimer){
|
|
this.inFlightTimer = setInterval("dojo.io.XMLHTTPTransport.watchInFlight();", 10);
|
|
}
|
|
}
|
|
|
|
this.watchInFlight = function(){
|
|
var now = null;
|
|
for(var x=this.inFlight.length-1; x>=0; x--){
|
|
var tif = this.inFlight[x];
|
|
if(!tif){ this.inFlight.splice(x, 1); continue; }
|
|
if(4==tif.http.readyState){
|
|
// remove it so we can clean refs
|
|
this.inFlight.splice(x, 1);
|
|
doLoad(tif.req, tif.http, tif.url, tif.query, tif.useCache);
|
|
}else if (tif.startTime){
|
|
//See if this is a timeout case.
|
|
if(!now){
|
|
now = (new Date()).getTime();
|
|
}
|
|
if(tif.startTime + (tif.req.timeoutSeconds * 1000) < now){
|
|
//Stop the request.
|
|
if(typeof tif.http.abort == "function"){
|
|
tif.http.abort();
|
|
}
|
|
|
|
// remove it so we can clean refs
|
|
this.inFlight.splice(x, 1);
|
|
tif.req[(typeof tif.req.timeout == "function") ? "timeout" : "handle"]("timeout", null, tif.http, tif.req);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(this.inFlight.length == 0){
|
|
clearInterval(this.inFlightTimer);
|
|
this.inFlightTimer = null;
|
|
}
|
|
}
|
|
|
|
var hasXmlHttp = dojo.hostenv.getXmlhttpObject() ? true : false;
|
|
this.canHandle = function(kwArgs){
|
|
// canHandle just tells dojo.io.bind() if this is a good transport to
|
|
// use for the particular type of request.
|
|
|
|
// FIXME: we need to determine when form values need to be
|
|
// multi-part mime encoded and avoid using this transport for those
|
|
// requests.
|
|
return hasXmlHttp
|
|
&& dojo.lang.inArray((kwArgs["mimetype"].toLowerCase()||""), ["text/plain", "text/html", "application/xml", "text/xml", "text/javascript", "text/json"])
|
|
&& !( kwArgs["formNode"] && dojo.io.formHasFile(kwArgs["formNode"]) );
|
|
}
|
|
|
|
this.multipartBoundary = "45309FFF-BD65-4d50-99C9-36986896A96F"; // unique guid as a boundary value for multipart posts
|
|
|
|
this.bind = function(kwArgs){
|
|
if(!kwArgs["url"]){
|
|
// are we performing a history action?
|
|
if( !kwArgs["formNode"]
|
|
&& (kwArgs["backButton"] || kwArgs["back"] || kwArgs["changeUrl"] || kwArgs["watchForURL"])
|
|
&& (!djConfig.preventBackButtonFix)) {
|
|
dojo.deprecated("Using dojo.io.XMLHTTPTransport.bind() to add to browser history without doing an IO request",
|
|
"Use dojo.undo.browser.addToHistory() instead.", "0.4");
|
|
dojo.undo.browser.addToHistory(kwArgs);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// build this first for cache purposes
|
|
var url = kwArgs.url;
|
|
var query = "";
|
|
if(kwArgs["formNode"]){
|
|
var ta = kwArgs.formNode.getAttribute("action");
|
|
if((ta)&&(!kwArgs["url"])){ url = ta; }
|
|
var tp = kwArgs.formNode.getAttribute("method");
|
|
if((tp)&&(!kwArgs["method"])){ kwArgs.method = tp; }
|
|
query += dojo.io.encodeForm(kwArgs.formNode, kwArgs.encoding, kwArgs["formFilter"]);
|
|
}
|
|
|
|
if(url.indexOf("#") > -1) {
|
|
dojo.debug("Warning: dojo.io.bind: stripping hash values from url:", url);
|
|
url = url.split("#")[0];
|
|
}
|
|
|
|
if(kwArgs["file"]){
|
|
// force post for file transfer
|
|
kwArgs.method = "post";
|
|
}
|
|
|
|
if(!kwArgs["method"]){
|
|
kwArgs.method = "get";
|
|
}
|
|
|
|
// guess the multipart value
|
|
if(kwArgs.method.toLowerCase() == "get"){
|
|
// GET cannot use multipart
|
|
kwArgs.multipart = false;
|
|
}else{
|
|
if(kwArgs["file"]){
|
|
// enforce multipart when sending files
|
|
kwArgs.multipart = true;
|
|
}else if(!kwArgs["multipart"]){
|
|
// default
|
|
kwArgs.multipart = false;
|
|
}
|
|
}
|
|
|
|
if(kwArgs["backButton"] || kwArgs["back"] || kwArgs["changeUrl"]){
|
|
dojo.undo.browser.addToHistory(kwArgs);
|
|
}
|
|
|
|
var content = kwArgs["content"] || {};
|
|
|
|
if(kwArgs.sendTransport) {
|
|
content["dojo.transport"] = "xmlhttp";
|
|
}
|
|
|
|
do { // break-block
|
|
if(kwArgs.postContent){
|
|
query = kwArgs.postContent;
|
|
break;
|
|
}
|
|
|
|
if(content) {
|
|
query += dojo.io.argsFromMap(content, kwArgs.encoding);
|
|
}
|
|
|
|
if(kwArgs.method.toLowerCase() == "get" || !kwArgs.multipart){
|
|
break;
|
|
}
|
|
|
|
var t = [];
|
|
if(query.length){
|
|
var q = query.split("&");
|
|
for(var i = 0; i < q.length; ++i){
|
|
if(q[i].length){
|
|
var p = q[i].split("=");
|
|
t.push( "--" + this.multipartBoundary,
|
|
"Content-Disposition: form-data; name=\"" + p[0] + "\"",
|
|
"",
|
|
p[1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(kwArgs.file){
|
|
if(dojo.lang.isArray(kwArgs.file)){
|
|
for(var i = 0; i < kwArgs.file.length; ++i){
|
|
var o = kwArgs.file[i];
|
|
t.push( "--" + this.multipartBoundary,
|
|
"Content-Disposition: form-data; name=\"" + o.name + "\"; filename=\"" + ("fileName" in o ? o.fileName : o.name) + "\"",
|
|
"Content-Type: " + ("contentType" in o ? o.contentType : "application/octet-stream"),
|
|
"",
|
|
o.content);
|
|
}
|
|
}else{
|
|
var o = kwArgs.file;
|
|
t.push( "--" + this.multipartBoundary,
|
|
"Content-Disposition: form-data; name=\"" + o.name + "\"; filename=\"" + ("fileName" in o ? o.fileName : o.name) + "\"",
|
|
"Content-Type: " + ("contentType" in o ? o.contentType : "application/octet-stream"),
|
|
"",
|
|
o.content);
|
|
}
|
|
}
|
|
|
|
if(t.length){
|
|
t.push("--"+this.multipartBoundary+"--", "");
|
|
query = t.join("\r\n");
|
|
}
|
|
}while(false);
|
|
|
|
// kwArgs.Connection = "close";
|
|
|
|
var async = kwArgs["sync"] ? false : true;
|
|
|
|
var preventCache = kwArgs["preventCache"] ||
|
|
(this.preventCache == true && kwArgs["preventCache"] != false);
|
|
var useCache = kwArgs["useCache"] == true ||
|
|
(this.useCache == true && kwArgs["useCache"] != false );
|
|
|
|
// preventCache is browser-level (add query string junk), useCache
|
|
// is for the local cache. If we say preventCache, then don't attempt
|
|
// to look in the cache, but if useCache is true, we still want to cache
|
|
// the response
|
|
if(!preventCache && useCache){
|
|
var cachedHttp = getFromCache(url, query, kwArgs.method);
|
|
if(cachedHttp){
|
|
doLoad(kwArgs, cachedHttp, url, query, false);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// much of this is from getText, but reproduced here because we need
|
|
// more flexibility
|
|
var http = dojo.hostenv.getXmlhttpObject(kwArgs);
|
|
var received = false;
|
|
|
|
// build a handler function that calls back to the handler obj
|
|
if(async){
|
|
var startTime =
|
|
// FIXME: setting up this callback handler leaks on IE!!!
|
|
this.inFlight.push({
|
|
"req": kwArgs,
|
|
"http": http,
|
|
"url": url,
|
|
"query": query,
|
|
"useCache": useCache,
|
|
"startTime": kwArgs.timeoutSeconds ? (new Date()).getTime() : 0
|
|
});
|
|
this.startWatchingInFlight();
|
|
}
|
|
|
|
if(kwArgs.method.toLowerCase() == "post"){
|
|
// FIXME: need to hack in more flexible Content-Type setting here!
|
|
http.open("POST", url, async);
|
|
setHeaders(http, kwArgs);
|
|
http.setRequestHeader("Content-Type", kwArgs.multipart ? ("multipart/form-data; boundary=" + this.multipartBoundary) :
|
|
(kwArgs.contentType || "application/x-www-form-urlencoded"));
|
|
try{
|
|
http.send(query);
|
|
}catch(e){
|
|
if(typeof http.abort == "function"){
|
|
http.abort();
|
|
}
|
|
doLoad(kwArgs, {status: 404}, url, query, useCache);
|
|
}
|
|
}else{
|
|
var tmpUrl = url;
|
|
if(query != "") {
|
|
tmpUrl += (tmpUrl.indexOf("?") > -1 ? "&" : "?") + query;
|
|
}
|
|
if(preventCache) {
|
|
tmpUrl += (dojo.string.endsWithAny(tmpUrl, "?", "&")
|
|
? "" : (tmpUrl.indexOf("?") > -1 ? "&" : "?")) + "dojo.preventCache=" + new Date().valueOf();
|
|
}
|
|
http.open(kwArgs.method.toUpperCase(), tmpUrl, async);
|
|
setHeaders(http, kwArgs);
|
|
try {
|
|
http.send(null);
|
|
}catch(e) {
|
|
if(typeof http.abort == "function"){
|
|
http.abort();
|
|
}
|
|
doLoad(kwArgs, {status: 404}, url, query, useCache);
|
|
}
|
|
}
|
|
|
|
if( !async ) {
|
|
doLoad(kwArgs, http, url, query, useCache);
|
|
}
|
|
|
|
kwArgs.abort = function(){
|
|
return http.abort();
|
|
}
|
|
|
|
return;
|
|
}
|
|
dojo.io.transports.addTransport("XMLHTTPTransport");
|
|
}
|