| /* |
| Copyright (c) 2004-2005, 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"); |
| dojo.require("dojo.dom"); |
| |
| try { |
| if((!djConfig["preventBackButtonFix"])&&(!dojo.hostenv.post_load_)){ |
| document.write("<iframe style='border: 0px; width: 1px; height: 1px; position: absolute; bottom: 0px; right: 0px; visibility: visible;' name='djhistory' id='djhistory' src='"+(dojo.hostenv.getBaseScriptUri()+'iframe_history.html')+"'></iframe>"); |
| } |
| }catch(e){/* squelch */} |
| |
| 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); |
| } |
| |
| // TODO: Move to htmlUtils |
| dojo.io.encodeForm = function(formNode, encoding){ |
| if((!formNode)||(!formNode.tagName)||(!formNode.tagName.toLowerCase() == "form")){ |
| dojo.raise("Attempted to encode a non-form element."); |
| } |
| 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.disabled || elm.tagName.toLowerCase() == "fieldset" || !elm.name){ |
| 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 if(!dojo.lang.inArray(type, ["file", "submit", "reset", "button"])) { |
| 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) { |
| 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.setIFrameSrc = function(iframe, src, replace){ |
| try{ |
| var r = dojo.render.html; |
| // dojo.debug(iframe); |
| if(!replace){ |
| if(r.safari){ |
| iframe.location = src; |
| }else{ |
| frames[iframe.name].location = src; |
| } |
| }else{ |
| // Fun with DOM 0 incompatibilities! |
| var idoc; |
| if(r.ie){ |
| idoc = iframe.contentWindow.document; |
| }else if(r.moz){ |
| idoc = iframe.contentWindow; |
| }else if(r.safari){ |
| idoc = iframe.document; |
| } |
| idoc.location.replace(src); |
| } |
| }catch(e){ |
| dojo.debug(e); |
| dojo.debug("setIFrameSrc: "+e); |
| } |
| } |
| |
| dojo.io.XMLHTTPTransport = new function(){ |
| var _this = this; |
| |
| this.initialHref = window.location.href; |
| this.initialHash = window.location.hash; |
| |
| this.moveForward = false; |
| |
| 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 |
| this.historyStack = []; |
| this.forwardStack = []; |
| this.historyIframe = null; |
| this.bookmarkAnchor = null; |
| this.locationTimer = null; |
| |
| /* NOTES: |
| * Safari 1.2: |
| * back button "works" fine, however it's not possible to actually |
| * DETECT that you've moved backwards by inspecting window.location. |
| * Unless there is some other means of locating. |
| * FIXME: perhaps we can poll on history.length? |
| * IE 5.5 SP2: |
| * back button behavior is macro. It does not move back to the |
| * previous hash value, but to the last full page load. This suggests |
| * that the iframe is the correct way to capture the back button in |
| * these cases. |
| * IE 6.0: |
| * same behavior as IE 5.5 SP2 |
| * Firefox 1.0: |
| * the back button will return us to the previous hash on the same |
| * page, thereby not requiring an iframe hack, although we do then |
| * need to run a timer to detect inter-page movement. |
| */ |
| |
| // 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)||(location.protocol=="file:" && http.status==0)) { |
| 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") { |
| 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); |
| }else{ |
| var errObj = new dojo.io.Error("XMLHttpTransport Error: "+http.status+" "+http.statusText); |
| kwArgs[(typeof kwArgs.error == "function") ? "error" : "handle"]("error", errObj, http); |
| } |
| } |
| |
| // 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.addToHistory = function(args){ |
| var callback = args["back"]||args["backButton"]||args["handle"]; |
| var hash = null; |
| if(!this.historyIframe){ |
| this.historyIframe = window.frames["djhistory"]; |
| } |
| if(!this.bookmarkAnchor){ |
| this.bookmarkAnchor = document.createElement("a"); |
| (document.body||document.getElementsByTagName("body")[0]).appendChild(this.bookmarkAnchor); |
| this.bookmarkAnchor.style.display = "none"; |
| } |
| if((!args["changeUrl"])||(dojo.render.html.ie)){ |
| var url = dojo.hostenv.getBaseScriptUri()+"iframe_history.html?"+(new Date()).getTime(); |
| this.moveForward = true; |
| dojo.io.setIFrameSrc(this.historyIframe, url, false); |
| } |
| if(args["changeUrl"]){ |
| hash = "#"+ ((args["changeUrl"]!==true) ? args["changeUrl"] : (new Date()).getTime()); |
| setTimeout("window.location.href = '"+hash+"';", 1); |
| this.bookmarkAnchor.href = hash; |
| if(dojo.render.html.ie){ |
| // IE requires manual setting of the hash since we are catching |
| // events from the iframe |
| var oldCB = callback; |
| var lh = null; |
| var hsl = this.historyStack.length-1; |
| if(hsl>=0){ |
| while(!this.historyStack[hsl]["urlHash"]){ |
| hsl--; |
| } |
| lh = this.historyStack[hsl]["urlHash"]; |
| } |
| if(lh){ |
| callback = function(){ |
| if(window.location.hash != ""){ |
| setTimeout("window.location.href = '"+lh+"';", 1); |
| } |
| oldCB(); |
| } |
| } |
| // when we issue a new bind(), we clobber the forward |
| // FIXME: is this always a good idea? |
| this.forwardStack = []; |
| var oldFW = args["forward"]||args["forwardButton"];; |
| var tfw = function(){ |
| if(window.location.hash != ""){ |
| window.location.href = hash; |
| } |
| if(oldFW){ // we might not actually have one |
| oldFW(); |
| } |
| } |
| if(args["forward"]){ |
| args.forward = tfw; |
| }else if(args["forwardButton"]){ |
| args.forwardButton = tfw; |
| } |
| }else if(dojo.render.html.moz){ |
| // start the timer |
| if(!this.locationTimer){ |
| this.locationTimer = setInterval("dojo.io.XMLHTTPTransport.checkLocation();", 200); |
| } |
| } |
| } |
| |
| this.historyStack.push({"url": url, "callback": callback, "kwArgs": args, "urlHash": hash}); |
| } |
| |
| this.checkLocation = function(){ |
| var hsl = this.historyStack.length; |
| |
| if((window.location.hash == this.initialHash)||(window.location.href == this.initialHref)&&(hsl == 1)){ |
| // FIXME: could this ever be a forward button? |
| // we can't clear it because we still need to check for forwards. Ugg. |
| // clearInterval(this.locationTimer); |
| this.handleBackButton(); |
| return; |
| } |
| // first check to see if we could have gone forward. We always halt on |
| // a no-hash item. |
| if(this.forwardStack.length > 0){ |
| if(this.forwardStack[this.forwardStack.length-1].urlHash == window.location.hash){ |
| this.handleForwardButton(); |
| return; |
| } |
| } |
| // ok, that didn't work, try someplace back in the history stack |
| if((hsl >= 2)&&(this.historyStack[hsl-2])){ |
| if(this.historyStack[hsl-2].urlHash==window.location.hash){ |
| this.handleBackButton(); |
| return; |
| } |
| } |
| } |
| |
| this.iframeLoaded = function(evt, ifrLoc){ |
| var isp = ifrLoc.href.split("?"); |
| if(isp.length < 2){ |
| // alert("iframeLoaded"); |
| // we hit the end of the history, so we should go back |
| if(this.historyStack.length == 1){ |
| this.handleBackButton(); |
| } |
| return; |
| } |
| var query = isp[1]; |
| if(this.moveForward){ |
| // we were expecting it, so it's not either a forward or backward |
| // movement |
| this.moveForward = false; |
| return; |
| } |
| |
| var last = this.historyStack.pop(); |
| // we don't have anything in history, so it could be a forward button |
| if(!last){ |
| if(this.forwardStack.length > 0){ |
| var next = this.forwardStack[this.forwardStack.length-1]; |
| if(query == next.url.split("?")[1]){ |
| this.handleForwardButton(); |
| } |
| } |
| // regardless, we didnt' have any history, so it can't be a back button |
| return; |
| } |
| // put it back on the stack so we can do something useful with it when |
| // we call handleBackButton() |
| this.historyStack.push(last); |
| if(this.historyStack.length >= 2){ |
| if(isp[1] == this.historyStack[this.historyStack.length-2].url.split("?")[1]){ |
| // looks like it IS a back button press, so handle it |
| this.handleBackButton(); |
| } |
| }else{ |
| this.handleBackButton(); |
| } |
| } |
| |
| this.handleBackButton = function(){ |
| var last = this.historyStack.pop(); |
| if(!last){ return; } |
| if(last["callback"]){ |
| last.callback(); |
| }else if(last.kwArgs["backButton"]){ |
| last.kwArgs["backButton"](); |
| }else if(last.kwArgs["back"]){ |
| last.kwArgs["back"](); |
| }else if(last.kwArgs["handle"]){ |
| last.kwArgs.handle("back"); |
| } |
| this.forwardStack.push(last); |
| } |
| |
| this.handleForwardButton = function(){ |
| // FIXME: should we build in support for re-issuing the bind() call here? |
| // alert("alert we found a forward button call"); |
| var last = this.forwardStack.pop(); |
| if(!last){ return; } |
| if(last.kwArgs["forward"]){ |
| last.kwArgs.forward(); |
| }else if(last.kwArgs["forwardButton"]){ |
| last.kwArgs.forwardButton(); |
| }else if(last.kwArgs["handle"]){ |
| last.kwArgs.handle("forward"); |
| } |
| this.historyStack.push(last); |
| } |
| |
| this.inFlight = []; |
| this.inFlightTimer = null; |
| |
| this.startWatchingInFlight = function(){ |
| if(!this.inFlightTimer){ |
| this.inFlightTimer = setInterval("dojo.io.XMLHTTPTransport.watchInFlight();", 10); |
| } |
| } |
| |
| this.watchInFlight = function(){ |
| 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); |
| if(this.inFlight.length == 0){ |
| clearInterval(this.inFlightTimer); |
| this.inFlightTimer = null; |
| } |
| } // FIXME: need to implement a timeout param here! |
| } |
| } |
| |
| 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"]) |
| && dojo.lang.inArray(kwArgs["method"].toLowerCase(), ["post", "get", "head"]) |
| && !( 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)) { |
| this.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); |
| } |
| |
| 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"]){ |
| this.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(); |
| var received = false; |
| |
| // build a handler function that calls back to the handler obj |
| if(async){ |
| // FIXME: setting up this callback handler leaks on IE!!! |
| this.inFlight.push({ |
| "req": kwArgs, |
| "http": http, |
| "url": url, |
| "query": query, |
| "useCache": useCache |
| }); |
| 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")); |
| http.send(query); |
| }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); |
| http.send(null); |
| } |
| |
| if( !async ) { |
| doLoad(kwArgs, http, url, query, useCache); |
| } |
| |
| kwArgs.abort = function(){ |
| return http.abort(); |
| } |
| |
| return; |
| } |
| dojo.io.transports.addTransport("XMLHTTPTransport"); |
| } |