New build scripts

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@5282 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Gavin Cornwell
2007-03-04 19:05:34 +00:00
parent 04f9a2e7bc
commit 838e7d5381
845 changed files with 121780 additions and 183 deletions

View File

@@ -0,0 +1,680 @@
/*
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.common");
dojo.require("dojo.lang.array");
dojo.require("dojo.lang.func");
dojo.require("dojo.string.extras");
dojo.require("dojo.dom");
dojo.require("dojo.undo.browser");
if(!dj_undef("window")) {
dojo.io.checkChildrenForFile = function(/*DOMNode*/node){
//summary: Checks any child nodes of node for an input type="file" element.
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; //boolean
}
dojo.io.formHasFile = function(/*DOMNode*/formNode){
//summary: Just calls dojo.io.checkChildrenForFile().
return dojo.io.checkChildrenForFile(formNode); //boolean
}
dojo.io.updateNode = function(/*DOMNode*/node, /*String or Object*/urlOrArgs){
//summary: Updates a DOMnode with the result of a dojo.io.bind() call.
//node: DOMNode
//urlOrArgs: String or Object
// Either a String that has an URL, or an object containing dojo.io.bind()
// arguments.
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){
dojo.dom.destroyNode(node.firstChild);
}
node.innerHTML = d;
};
dojo.io.bind(args);
}
dojo.io.formFilter = function(/*DOMNode*/node) {
//summary: Returns true if the node is an input element that is enabled, has
//a name, and whose type is one of the following values: ["file", "submit", "image", "reset", "button"]
var type = (node.type||"").toLowerCase();
return !node.disabled && node.name
&& !dojo.lang.inArray(["file", "submit", "image", "reset", "button"], type); //boolean
}
// TODO: Move to htmlUtils
dojo.io.encodeForm = function(/*DOMNode*/formNode, /*String?*/encoding, /*Function?*/formFilter){
//summary: Converts the names and values of form elements into an URL-encoded
//string (name=value&name=value...).
//formNode: DOMNode
//encoding: String?
// The encoding to use for the values. Specify a string that starts with
// "utf" (for instance, "utf8"), to use encodeURIComponent() as the encoding
// function. Otherwise, dojo.string.encodeAscii will be used.
//formFilter: Function?
// A function used to filter out form elements. The element node will be passed
// to the formFilter function, and a boolean result is expected (true indicating
// indicating that the element should have its name/value included in the output).
// If no formFilter is specified, then dojo.io.formFilter() will be used.
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(["radio", "checkbox"], type)){
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("&") + "&"; //String
}
dojo.io.FormBind = function(/*DOMNode or Object*/args) {
//summary: constructor for a dojo.io.FormBind object. See the Dojo Book for
//some information on usage: http://manual.dojotoolkit.org/WikiHome/DojoDotBook/Book23
//args: DOMNode or Object
// args can either be the DOMNode for a form element, or an object containing
// dojo.io.bind() arguments, one of which should be formNode with the value of
// a form element DOMNode.
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(/*DOMNode or Object*/args) {
//summary: Internal function called by the dojo.io.FormBind() constructor
//do not call this method directly.
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(["submit", "button"], node.type.toLowerCase())) {
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(/*DOMNode*/form) {
//summary: Function used to verify that the form is OK to submit.
//Override this function if you want specific form validation done.
return true; //boolean
},
submit: function(/*Event*/e) {
//summary: internal function that is connected as a listener to the
//form's onsubmit event.
e.preventDefault();
if(this.onSubmit(this.form)) {
dojo.io.bind(dojo.lang.mixin(this.bindArgs, {
formFilter: dojo.lang.hitch(this, "formFilter")
}));
}
},
click: function(/*Event*/e) {
//summary: internal method that is connected as a listener to the
//form's elements whose click event can submit a form.
var node = e.currentTarget;
if(node.disabled) { return; }
this.clickedButton = node;
},
formFilter: function(/*DOMNode*/node) {
//summary: internal function used to know which form element values to include
// in the dojo.io.bind() request.
var type = (node.type||"").toLowerCase();
var accept = false;
if(node.disabled || !node.name) {
accept = false;
} else if(dojo.lang.inArray(["submit", "button", "image"], type)) {
if(!this.clickedButton) { this.clickedButton = node; }
accept = node == this.clickedButton;
} else {
accept = !dojo.lang.inArray(["file", "submit", "reset", "button"], type);
}
return accept; //boolean
},
// in case you don't have dojo.event.* pulled in
connect: function(/*Object*/srcObj, /*Function*/srcFcn, /*Function*/targetFcn) {
//summary: internal function used to connect event listeners to form elements
//that trigger events. Used in case dojo.event is not loaded.
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(){
//summary: The object that implements the dojo.io.bind transport for XMLHttpRequest.
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" || kwArgs.mimetype == "application/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(){
//summary: internal method used to trigger a timer to watch all inflight
//XMLHttpRequests.
if(!this.inFlightTimer){
// setInterval broken in mozilla x86_64 in some circumstances, see
// https://bugzilla.mozilla.org/show_bug.cgi?id=344439
// using setTimeout instead
this.inFlightTimer = setTimeout("dojo.io.XMLHTTPTransport.watchInFlight();", 10);
}
}
this.watchInFlight = function(){
//summary: internal method that checks each inflight XMLHttpRequest to see
//if it has completed or if the timeout situation applies.
var now = null;
// 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(!dojo.hostenv._blockAsync && !_this._blockAsync){
for(var x=this.inFlight.length-1; x>=0; x--){
try{
var tif = this.inFlight[x];
if(!tif || tif.http._aborted || !tif.http.readyState){
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);
}
}
}catch(e){
try{
var errObj = new dojo.io.Error("XMLHttpTransport.watchInFlight Error: " + e);
tif.req[(typeof tif.req.error == "function") ? "error" : "handle"]("error", errObj, tif.http, tif.req);
}catch(e2){
dojo.debug("XMLHttpTransport error callback failed: " + e2);
}
}
}
}
clearTimeout(this.inFlightTimer);
if(this.inFlight.length == 0){
this.inFlightTimer = null;
return;
}
this.inFlightTimer = setTimeout("dojo.io.XMLHTTPTransport.watchInFlight();", 10);
}
var hasXmlHttp = dojo.hostenv.getXmlhttpObject() ? true : false;
this.canHandle = function(/*dojo.io.Request*/kwArgs){
//summary: Tells dojo.io.bind() if this is a good transport to
//use for the particular type of request. This type of transport cannot
//handle forms that have an input type="file" element.
// 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(["text/plain", "text/html", "application/xml", "text/xml", "text/javascript", "text/json", "application/json"], (kwArgs["mimetype"].toLowerCase()||""))
&& !( kwArgs["formNode"] && dojo.io.formHasFile(kwArgs["formNode"]) ); //boolean
}
this.multipartBoundary = "45309FFF-BD65-4d50-99C9-36986896A96F"; // unique guid as a boundary value for multipart posts
this.bind = function(/*dojo.io.Request*/kwArgs){
//summary: function that sends the request to the server.
//This function will attach an abort() function to the kwArgs dojo.io.Request object,
//so if you need to abort the request, you can call that method on the request object.
//The following are acceptable properties in kwArgs (in addition to the
//normal dojo.io.Request object properties).
//url: String: URL the server URL to use for the request.
//method: String: the HTTP method to use (GET, POST, etc...).
//mimetype: Specifies what format the result data should be given to the load/handle callback. Valid values are:
// text/javascript, text/json, application/json, application/xml, text/xml. Any other mimetype will give back a text
// string.
//transport: String: specify "XMLHTTPTransport" to force the use of this XMLHttpRequest transport.
//headers: Object: The object property names and values will be sent as HTTP request header
// names and values.
//sendTransport: boolean: If true, then dojo.transport=xmlhttp will be added to the request.
//encoding: String: The type of encoding to use when dealing with the content kwArgs property.
//content: Object: The content object is converted into a name=value&name=value string, by
// using dojo.io.argsFromMap(). The encoding kwArgs property is passed to dojo.io.argsFromMap()
// for use in encoding the names and values. The resulting string is added to the request.
//formNode: DOMNode: a form element node. This should not normally be used. Use new dojo.io.FormBind() instead.
// If formNode is used, then the names and values of the form elements will be converted
// to a name=value&name=value string and added to the request. The encoding kwArgs property is used
// to encode the names and values.
//postContent: String: Raw name=value&name=value string to be included as part of the request.
//back or backButton: Function: A function to be called if the back button is pressed. If this kwArgs property
// is used, then back button support via dojo.undo.browser will be used. See notes for dojo.undo.browser on usage.
// You need to set djConfig.preventBackButtonFix = false to enable back button support.
//changeUrl: boolean or String: Used as part of back button support. See notes for dojo.undo.browser on usage.
//user: String: The user name. Used in conjuction with password. Passed to XMLHttpRequest.open().
//password: String: The user's password. Used in conjuction with user. Passed to XMLHttpRequest.open().
//file: Object or Array of Objects: an object simulating a file to be uploaded. file objects should have the following properties:
// name or fileName: the name of the file
// contentType: the MIME content type for the file.
// content: the actual content of the file.
//multipart: boolean: indicates whether this should be a multipart mime request. If kwArgs.file exists, then this
// property is set to true automatically.
//sync: boolean: if true, then a synchronous XMLHttpRequest call is done,
// if false (the default), then an asynchronous call is used.
//preventCache: boolean: If true, then a cache busting parameter is added to the request URL.
// default value is false.
//useCache: boolean: If true, then XMLHttpTransport will keep an internal cache of the server
// response and use that response if a similar request is done again.
// A similar request is one that has the same URL, query string and HTTP method value.
// default is false.
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();
}else{
// block async callbacks until sync is in, needed in khtml, others?
_this._blockAsync = true;
}
if(kwArgs.method.toLowerCase() == "post"){
// FIXME: need to hack in more flexible Content-Type setting here!
if (!kwArgs.user) {
http.open("POST", url, async);
}else{
http.open("POST", url, async, kwArgs.user, kwArgs.password);
}
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();
}
if (!kwArgs.user) {
http.open(kwArgs.method.toUpperCase(), tmpUrl, async);
}else{
http.open(kwArgs.method.toUpperCase(), tmpUrl, async, kwArgs.user, kwArgs.password);
}
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);
_this._blockAsync = false;
}
kwArgs.abort = function(){
try{// khtml doesent reset readyState on abort, need this workaround
http._aborted = true;
}catch(e){/*squelsh*/}
return http.abort();
}
return;
}
dojo.io.transports.addTransport("XMLHTTPTransport");
}
}

View File

@@ -0,0 +1,298 @@
/*
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.IframeIO");
dojo.require("dojo.io.BrowserIO");
dojo.require("dojo.uri.*");
// FIXME: is it possible to use the Google htmlfile hack to prevent the
// background click with this transport?
dojo.io.createIFrame = function(/*String*/fname, /*String*/onloadstr, /*String?*/uri){
//summary: Creates a hidden iframe in the page. Used mostly for data transports.
//fname: String
// The name of the iframe. Used for the name attribute on the iframe.
//onloadstr: String
// A string of Javascript that will be executed when the content in the iframe loads.
//uri: String
// The value of the src attribute on the iframe element. If a value is not
// given, then iframe_history.html will be used.
if(window[fname]){ return window[fname]; }
if(window.frames[fname]){ return window.frames[fname]; }
var r = dojo.render.html;
var cframe = null;
var turi = uri||dojo.uri.dojoUri("iframe_history.html?noInit=true");
var ifrstr = ((r.ie)&&(dojo.render.os.win)) ? '<iframe name="'+fname+'" src="'+turi+'" onload="'+onloadstr+'">' : 'iframe';
cframe = document.createElement(ifrstr);
with(cframe){
name = fname;
setAttribute("name", fname);
id = fname;
}
dojo.body().appendChild(cframe);
window[fname] = cframe;
with(cframe.style){
if(!r.safari){
//We can't change the src in Safari 2.0.3 if absolute position. Bizarro.
position = "absolute";
}
left = top = "0px";
height = width = "1px";
visibility = "hidden";
/*
if(djConfig.isDebug){
position = "relative";
height = "300px";
width = "600px";
visibility = "visible";
}
*/
}
if(!r.ie){
dojo.io.setIFrameSrc(cframe, turi, true);
cframe.onload = new Function(onloadstr);
}
return cframe;
}
dojo.io.IframeTransport = new function(){
//summary: The object that implements the dojo.io.bind transport that
//uses an iframe to communicate to the server.
var _this = this;
this.currentRequest = null;
this.requestQueue = [];
this.iframeName = "dojoIoIframe";
this.fireNextRequest = function(){
//summary: Internal method used to fire the next request in the bind queue.
try{
if((this.currentRequest)||(this.requestQueue.length == 0)){ return; }
// dojo.debug("fireNextRequest");
var cr = this.currentRequest = this.requestQueue.shift();
cr._contentToClean = [];
var fn = cr["formNode"];
var content = cr["content"] || {};
if(cr.sendTransport) {
content["dojo.transport"] = "iframe";
}
if(fn){
if(content){
// if we have things in content, we need to add them to the form
// before submission
for(var x in content){
if(!fn[x]){
var tn;
if(dojo.render.html.ie){
tn = document.createElement("<input type='hidden' name='"+x+"' value='"+content[x]+"'>");
fn.appendChild(tn);
}else{
tn = document.createElement("input");
fn.appendChild(tn);
tn.type = "hidden";
tn.name = x;
tn.value = content[x];
}
cr._contentToClean.push(x);
}else{
fn[x].value = content[x];
}
}
}
if(cr["url"]){
cr._originalAction = fn.getAttribute("action");
fn.setAttribute("action", cr.url);
}
if(!fn.getAttribute("method")){
fn.setAttribute("method", (cr["method"]) ? cr["method"] : "post");
}
cr._originalTarget = fn.getAttribute("target");
fn.setAttribute("target", this.iframeName);
fn.target = this.iframeName;
fn.submit();
}else{
// otherwise we post a GET string by changing URL location for the
// iframe
var query = dojo.io.argsFromMap(this.currentRequest.content);
var tmpUrl = cr.url + (cr.url.indexOf("?") > -1 ? "&" : "?") + query;
dojo.io.setIFrameSrc(this.iframe, tmpUrl, true);
}
}catch(e){
this.iframeOnload(e);
}
}
this.canHandle = function(/*dojo.io.Request*/kwArgs){
//summary: Tells dojo.io.bind() if this is a good transport to
//use for the particular type of request.
//description: This type of transport cannot
//handle text/xml or application/xml return types, is limited to GET
//or POST requests, and cannot do synchronous binds.
return (
(
dojo.lang.inArray([ "text/plain", "text/html", "text/javascript", "text/json", "application/json"], kwArgs["mimetype"])
)&&(
dojo.lang.inArray(["post", "get"], kwArgs["method"].toLowerCase())
)&&(
// never handle a sync request
! ((kwArgs["sync"])&&(kwArgs["sync"] == true))
)
); //boolean
}
this.bind = function(/*dojo.io.Request*/kwArgs){
//summary: function that sends the request to the server.
//This transport can only process one bind request at a time, so if bind is called
//multiple times, it will queue up the calls and only process one at a time.
//The following are acceptable properties in kwArgs (in addition to the
//normal dojo.io.Request object properties).
//url: String: URL the server URL to use for the request.
//transport: String: specify "IframeTransport" to force the use of this transport.
//sendTransport: boolean: If true, then dojo.transport=iframe will be added to the request.
//formNode: DOMNode: a form element node. The form elements' names and values will be used in
// the request. This makes it possible to upload files using this transport.
//method: String: the HTTP method to use. Must be GET or POST. Default is POST.
//mimetype: Specifies what format the result data should be given to the load/handle callback. Valid values are:
// text/plain, text/html, text/javascript, text/json, application/json. IMPORTANT: For all values EXCEPT text/html,
// The server response should be an HTML file with a textarea element. The response data should be inside the textarea
// element. Using an HTML document the only reliable, cross-browser way this transport can know
// when the response has loaded. For the text/html mimetype, just return a normal HTML document.
//content: Object: If a formNode is one of the other kwArgs properties, then the content
// object properties become hidden form form elements. For instance, a content
// object of {name1 : "value1"} is converted to a hidden form element with a name
// of "name1" and a value of "value1". If there is not a formNode property, then
// the content object is converted into a name=value&name=value string, by
// using dojo.io.argsFromMap(). No encoding is passed to that method, so the
// names and values will be encoded using dojo.string.encodeAscii().
if(!this["iframe"]){ this.setUpIframe(); }
this.requestQueue.push(kwArgs);
this.fireNextRequest();
return;
}
this.setUpIframe = function(){
// NOTE: IE 5.0 and earlier Mozilla's don't support an onload event for
// iframes. OTOH, we don't care.
this.iframe = dojo.io.createIFrame(this.iframeName, "dojo.io.IframeTransport.iframeOnload();");
}
this.iframeOnload = function(errorObject /* Object */){
if(!_this.currentRequest){
_this.fireNextRequest();
return;
}
var req = _this.currentRequest;
if(req.formNode){
// remove all the hidden content inputs
var toClean = req._contentToClean;
for(var i = 0; i < toClean.length; i++) {
var key = toClean[i];
if(dojo.render.html.safari){
//In Safari (at least 2.0.3), can't use formNode[key] syntax to find the node,
//for nodes that were dynamically added.
var fNode = req.formNode;
for(var j = 0; j < fNode.childNodes.length; j++){
var chNode = fNode.childNodes[j];
if(chNode.name == key){
var pNode = chNode.parentNode;
pNode.removeChild(chNode);
break;
}
}
}else{
var input = req.formNode[key];
req.formNode.removeChild(input);
req.formNode[key] = null;
}
}
// restore original action + target
if(req["_originalAction"]){
req.formNode.setAttribute("action", req._originalAction);
}
if(req["_originalTarget"]){
req.formNode.setAttribute("target", req._originalTarget);
req.formNode.target = req._originalTarget;
}
}
var contentDoc = function(iframe_el){
var doc = iframe_el.contentDocument || // W3
(
(iframe_el.contentWindow)&&(iframe_el.contentWindow.document)
) || // IE
(
(iframe_el.name)&&(document.frames[iframe_el.name])&&
(document.frames[iframe_el.name].document)
) || null;
return doc;
};
var value;
var success = false;
if (errorObject){
this._callError(req, "IframeTransport Request Error: " + errorObject);
}else{
var ifd = contentDoc(_this.iframe);
// handle successful returns
// FIXME: how do we determine success for iframes? Is there an equiv of
// the "status" property?
try{
var cmt = req.mimetype;
if((cmt == "text/javascript")||(cmt == "text/json")||(cmt == "application/json")){
// FIXME: not sure what to do here? try to pull some evalulable
// text from a textarea or cdata section?
// how should we set up the contract for that?
var js = ifd.getElementsByTagName("textarea")[0].value;
if(cmt == "text/json" || cmt == "application/json") { js = "(" + js + ")"; }
value = dj_eval(js);
}else if(cmt == "text/html"){
value = ifd;
}else{ // text/plain
value = ifd.getElementsByTagName("textarea")[0].value;
}
success = true;
}catch(e){
// looks like we didn't get what we wanted!
this._callError(req, "IframeTransport Error: " + e);
}
}
// don't want to mix load function errors with processing errors, thus
// a separate try..catch
try {
if(success && dojo.lang.isFunction(req["load"])){
req.load("load", value, req);
}
} catch(e) {
throw e;
} finally {
_this.currentRequest = null;
_this.fireNextRequest();
}
}
this._callError = function(req /* Object */, message /* String */){
var errObj = new dojo.io.Error(message);
if(dojo.lang.isFunction(req["error"])){
req.error("error", errObj, req);
}
}
dojo.io.transports.addTransport("IframeTransport");
}

View File

@@ -0,0 +1,520 @@
// Copyright (c) 2004 Friendster Inc., Licensed under the Academic Free
// License version 2.0 or later
dojo.require("dojo.event.*");
dojo.require("dojo.io.BrowserIO");
dojo.provide("dojo.io.RepubsubIO");
dojo.io.repubsubTranport = new function(){
var rps = dojo.io.repubsub;
this.canHandle = function(/*dojo.io.Request*/kwArgs){
//summary: Tells dojo.io.bind() if this is a good transport to
//use for the particular type of request. This is a legacy transport
//and should not be used unless you are dealing with repubsub.
//Consider a comet transport instead.
if((kwArgs["mimetype"] == "text/javascript")&&(kwArgs["method"] == "repubsub")){
return true;
}
return false;
}
this.bind = function(/*dojo.io.Request*/kwArgs){
//summary: This is a legacy transport and should not be used unless you are dealing with repubsub.
//Consider a comet transport instead.
if(!rps.isInitialized){
// open up our tunnel, queue up requests anyway
rps.init();
}
// FIXME: we need to turn this into a topic subscription
// var tgtURL = kwArgs.url+"?"+dojo.io.argsFromMap(kwArgs.content);
// sampleTransport.sendRequest(tgtURL, hdlrFunc);
// a normal "bind()" call in a request-response transport layer is
// something that (usually) encodes most of it's payload with the
// request. Multi-event systems like repubsub are a bit more complex,
// and repubsub in particular distinguishes the publish and subscribe
// portions of thep rocess with different method calls to handle each.
// Therefore, a "bind" in the sense of repubsub must first determine if
// we have an open subscription to a channel provided by the server,
// and then "publish" the request payload if there is any. We therefore
// must take care not to incorrectly or too agressively register or
// file event handlers which are provided with the kwArgs method.
// NOTE: we ONLY pay attention to those event handlers that are
// registered with the bind request that subscribes to the channel. If
// event handlers are provided with subsequent requests, we might in
// the future support some additive or replacement syntax, but for now
// they get dropped on the floor.
// NOTE: in this case, url MUST be the "topic" to which we
// subscribe/publish for this channel
if(!rps.topics[kwArgs.url]){
kwArgs.rpsLoad = function(evt){
kwArgs.load("load", evt);
}
rps.subscribe(kwArgs.url, kwArgs, "rpsLoad");
}
if(kwArgs["content"]){
// what we wanted to send
var cEvt = dojo.io.repubsubEvent.initFromProperties(kwArgs.content);
rps.publish(kwArgs.url, cEvt);
}
}
dojo.io.transports.addTransport("repubsubTranport");
}
dojo.io.repubsub = new function(){
this.initDoc = "init.html";
this.isInitialized = false;
this.subscriptionBacklog = [];
this.debug = true;
this.rcvNodeName = null;
this.sndNodeName = null;
this.rcvNode = null;
this.sndNode = null;
this.canRcv = false;
this.canSnd = false;
this.canLog = false;
this.sndTimer = null;
this.windowRef = window;
this.backlog = [];
this.tunnelInitCount = 0;
this.tunnelFrameKey = "tunnel_frame";
this.serverBaseURL = location.protocol+"//"+location.host+location.pathname;
this.logBacklog = [];
this.getRandStr = function(){
return Math.random().toString().substring(2, 10);
}
this.userid = "guest";
this.tunnelID = this.getRandStr();
this.attachPathList = [];
this.topics = []; // list of topics we have listeners to
// actually, now that I think about it a little bit more, it would sure be
// useful to parse out the <script> src attributes. We're looking for
// something with a "do_method=lib", since that's what would have included
// us in the first place (in the common case).
this.parseGetStr = function(){
var baseUrl = document.location.toString();
var params = baseUrl.split("?", 2);
if(params.length > 1){
var paramStr = params[1];
var pairs = paramStr.split("&");
var opts = [];
for(var x in pairs){
var sp = pairs[x].split("=");
// FIXME: is this eval dangerous?
try{
opts[sp[0]]=eval(sp[1]);
}catch(e){
opts[sp[0]]=sp[1];
}
}
return opts;
}else{
return [];
}
}
// parse URL params and use them as default vals
var getOpts = this.parseGetStr();
for(var x in getOpts){
// FIXME: should I be checking for undefined here before setting? Does
// that buy me anything?
this[x] = getOpts[x];
}
if(!this["tunnelURI"]){
this.tunnelURI = [ "/who/", escape(this.userid), "/s/",
this.getRandStr(), "/kn_journal"].join("");
// this.tunnelURI = this.absoluteTopicURI(this.tunnelURI);
}
/*
if (self.kn_tunnelID) kn.tunnelID = self.kn_tunnelID; // the server says
if (kn._argv.kn_tunnelID) kn.tunnelID = kn._argv.kn_tunnelID; // the url says
*/
// check the options object if it exists and use its properties as an
// over-ride
if(window["repubsubOpts"]||window["rpsOpts"]){
var optObj = window["repubsubOpts"]||window["rpsOpts"];
for(var x in optObj){
this[x] = optObj[x]; // copy the option object properties
}
}
// things that get called directly from our iframe to inform us of events
this.tunnelCloseCallback = function(){
// when we get this callback, we should immediately attempt to re-start
// our tunnel connection
dojo.io.setIFrameSrc(this.rcvNode, this.initDoc+"?callback=repubsub.rcvNodeReady&domain="+document.domain);
}
this.receiveEventFromTunnel = function(evt, srcWindow){
// we should never be getting events from windows we didn't create
// NOTE: events sourced from the local window are also supported for
// debugging purposes
// any event object MUST have a an "elements" property
if(!evt["elements"]){
this.log("bailing! event received without elements!", "error");
return;
}
// if the event passes some minimal sanity tests, we need to attempt to
// dispatch it!
// first, it seems we have to munge the event object a bit
var e = {};
for(var i=0; i<evt.elements.length; i++){
var ee = evt.elements[i];
e[ee.name||ee.nameU] = (ee.value||ee.valueU);
// FIXME: need to enable this only in some extreme debugging mode!
this.log("[event]: "+(ee.name||ee.nameU)+": "+e[ee.name||ee.nameU]);
}
// NOTE: the previous version of this library put a bunch of code here
// to manage state that tried to make sure that we never, ever, lost
// any info about an event. If we unload RIGHT HERE, I don't think it's
// going to make a huge difference one way or another. Time will tell.
// and with THAT out of the way, dispatch it!
this.dispatch(e);
// TODO: remove the script block that created the event obj to save
// memory, etc.
}
this.widenDomain = function(domainStr){
// the purpose of this is to set the most liberal domain policy
// available
var cd = domainStr||document.domain;
if(cd.indexOf(".")==-1){ return; } // probably file:/// or localhost
var dps = cd.split(".");
if(dps.length<=2){ return; } // probably file:/// or an RFC 1918 address
dps = dps.slice(dps.length-2);
document.domain = dps.join(".");
}
// FIXME: parseCookie and setCookie should be methods that are more broadly
// available. Perhaps in htmlUtils?
this.parseCookie = function(){
var cs = document.cookie;
var keypairs = cs.split(";");
for(var x=0; x<keypairs.length; x++){
keypairs[x] = keypairs[x].split("=");
if(x!=keypairs.length-1){ cs+=";"; }
}
return keypairs;
}
this.setCookie = function(keypairs, clobber){
// NOTE: we want to only ever set session cookies, so never provide an
// expires date
if((clobber)&&(clobber==true)){ document.cookie = ""; }
var cs = "";
for(var x=0; x<keypairs.length; x++){
cs += keypairs[x][0]+"="+keypairs[x][1];
if(x!=keypairs.length-1){ cs+=";"; }
}
document.cookie = cs;
}
// FIXME: need to replace w/ dojo.log.*
this.log = function(str, lvl){
if(!this.debug){ return; } // we of course only care if we're in debug mode
while(this.logBacklog.length>0){
if(!this.canLog){ break; }
var blo = this.logBacklog.shift();
this.writeLog("["+blo[0]+"]: "+blo[1], blo[2]);
}
this.writeLog(str, lvl);
}
this.writeLog = function(str, lvl){
dojo.debug(((new Date()).toLocaleTimeString())+": "+str);
}
this.init = function(){
this.widenDomain();
// this.findPeers();
this.openTunnel();
this.isInitialized = true;
// FIXME: this seems like entirely the wrong place to replay the backlog
while(this.subscriptionBacklog.length){
this.subscribe.apply(this, this.subscriptionBacklog.shift());
}
}
this.clobber = function(){
if(this.rcvNode){
this.setCookie( [
[this.tunnelFrameKey,"closed"],
["path","/"]
], false
);
}
}
this.openTunnel = function(){
// We create two iframes here:
// one for getting data
this.rcvNodeName = "rcvIFrame_"+this.getRandStr();
// set cookie that can be used to find the receiving iframe
this.setCookie( [
[this.tunnelFrameKey,this.rcvNodeName],
["path","/"]
], false
);
this.rcvNode = dojo.io.createIFrame(this.rcvNodeName);
// FIXME: set the src attribute here to the initialization URL
dojo.io.setIFrameSrc(this.rcvNode, this.initDoc+"?callback=repubsub.rcvNodeReady&domain="+document.domain);
// the other for posting data in reply
this.sndNodeName = "sndIFrame_"+this.getRandStr();
this.sndNode = dojo.io.createIFrame(this.sndNodeName);
// FIXME: set the src attribute here to the initialization URL
dojo.io.setIFrameSrc(this.sndNode, this.initDoc+"?callback=repubsub.sndNodeReady&domain="+document.domain);
}
this.rcvNodeReady = function(){
// FIXME: why is this sequence number needed? Why isn't the UID gen
// function enough?
var statusURI = [this.tunnelURI, '/kn_status/', this.getRandStr(), '_',
String(this.tunnelInitCount++)].join("");
// (kn._seqNum++); // FIXME: !!!!
// this.canRcv = true;
this.log("rcvNodeReady");
// FIXME: initialize receiver and request the base topic
// dojo.io.setIFrameSrc(this.rcvNode, this.serverBaseURL+"/kn?do_method=blank");
var initURIArr = [ this.serverBaseURL, "/kn?kn_from=", escape(this.tunnelURI),
"&kn_id=", escape(this.tunnelID), "&kn_status_from=",
escape(statusURI)];
// FIXME: does the above really need a kn_response_flush? won't the
// server already know? If not, what good is it anyway?
dojo.io.setIFrameSrc(this.rcvNode, initURIArr.join(""));
// setup a status path listener, but don't tell the server about it,
// since it already knows we're itnerested in our own tunnel status
this.subscribe(statusURI, this, "statusListener", true);
this.log(initURIArr.join(""));
}
this.sndNodeReady = function(){
this.canSnd = true;
this.log("sndNodeReady");
this.log(this.backlog.length);
// FIXME: handle any pent-up send commands
if(this.backlog.length > 0){
this.dequeueEvent();
}
}
this.statusListener = function(evt){
this.log("status listener called");
this.log(evt.status, "info");
}
// this handles local event propigation
this.dispatch = function(evt){
// figure out what topic it came from
if(evt["to"]||evt["kn_routed_from"]){
var rf = evt["to"]||evt["kn_routed_from"];
// split off the base server URL
var topic = rf.split(this.serverBaseURL, 2)[1];
if(!topic){
// FIXME: how do we recover when we don't get a sane "from"? Do
// we try to route to it anyway?
topic = rf;
}
this.log("[topic] "+topic);
if(topic.length>3){
if(topic.slice(0, 3)=="/kn"){
topic = topic.slice(3);
}
}
if(this.attachPathList[topic]){
this.attachPathList[topic](evt);
}
}
}
this.subscribe = function( topic /* kn_from in the old terminilogy */,
toObj, toFunc, dontTellServer){
if(!this.isInitialized){
this.subscriptionBacklog.push([topic, toObj, toFunc, dontTellServer]);
return;
}
if(!this.attachPathList[topic]){
this.attachPathList[topic] = function(){ return true; }
this.log("subscribing to: "+topic);
this.topics.push(topic);
}
var revt = new dojo.io.repubsubEvent(this.tunnelURI, topic, "route");
var rstr = [this.serverBaseURL+"/kn", revt.toGetString()].join("");
dojo.event.kwConnect({
once: true,
srcObj: this.attachPathList,
srcFunc: topic,
adviceObj: toObj,
adviceFunc: toFunc
});
// NOTE: the above is a local mapping, if we're not the leader, we
// should connect our mapping to the topic handler of the peer
// leader, this ensures that not matter what happens to the
// leader, we don't really loose our heads if/when the leader
// goes away.
if(!this.rcvNode){ /* this should be an error! */ }
if(dontTellServer){
return;
}
this.log("sending subscription to: "+topic);
// create a subscription event object and give it all the props we need
// to updates on the specified topic
// FIXME: we should only enqueue if this is our first subscription!
this.sendTopicSubToServer(topic, rstr);
}
this.sendTopicSubToServer = function(topic, str){
if(!this.attachPathList[topic]["subscriptions"]){
this.enqueueEventStr(str);
this.attachPathList[topic].subscriptions = 0;
}
this.attachPathList[topic].subscriptions++;
}
this.unSubscribe = function(topic, toObj, toFunc){
// first, locally disconnect
dojo.event.kwDisconnect({
srcObj: this.attachPathList,
srcFunc: topic,
adviceObj: toObj,
adviceFunc: toFunc
});
// FIXME: figure out if there are any remaining listeners to the topic,
// and if not, inform the server of our desire not to be
// notified of updates to the topic
}
// the "publish" method is really a misnomer, since it really means "take
// this event and send it to the server". Note that the "dispatch" method
// handles local event promigulation, and therefore we emulate both sides
// of a real event router without having to swallow all of the complexity.
this.publish = function(topic, event){
var evt = dojo.io.repubsubEvent.initFromProperties(event);
// FIXME: need to make sure we have from and to set correctly
// before we serialize and send off to the great blue
// younder.
evt.to = topic;
// evt.from = this.tunnelURI;
var evtURLParts = [];
evtURLParts.push(this.serverBaseURL+"/kn");
// serialize the event to a string and then post it to the correct
// topic
evtURLParts.push(evt.toGetString());
this.enqueueEventStr(evtURLParts.join(""));
}
this.enqueueEventStr = function(evtStr){
this.log("enqueueEventStr");
this.backlog.push(evtStr);
this.dequeueEvent();
}
this.dequeueEvent = function(force){
this.log("dequeueEvent");
if(this.backlog.length <= 0){ return; }
if((this.canSnd)||(force)){
dojo.io.setIFrameSrc(this.sndNode, this.backlog.shift()+"&callback=repubsub.sndNodeReady");
this.canSnd = false;
}else{
this.log("sndNode not available yet!", "debug");
}
}
}
dojo.io.repubsubEvent = function(to, from, method, id, routeURI, payload, dispname, uid){
this.to = to;
this.from = from;
this.method = method||"route";
this.id = id||repubsub.getRandStr();
this.uri = routeURI;
this.displayname = dispname||repubsub.displayname;
this.userid = uid||repubsub.userid;
this.payload = payload||"";
this.flushChars = 4096;
this.initFromProperties = function(evt){
if(evt.constructor = dojo.io.repubsubEvent){
for(var x in evt){
this[x] = evt[x];
}
}else{
// we want to copy all the properties of the evt object, and transform
// those that are "stock" properties of dojo.io.repubsubEvent. All others should
// be copied as-is
for(var x in evt){
if(typeof this.forwardPropertiesMap[x] == "string"){
this[this.forwardPropertiesMap[x]] = evt[x];
}else{
this[x] = evt[x];
}
}
}
}
this.toGetString = function(noQmark){
var qs = [ ((noQmark) ? "" : "?") ];
for(var x=0; x<this.properties.length; x++){
var tp = this.properties[x];
if(this[tp[0]]){
qs.push(tp[1]+"="+encodeURIComponent(String(this[tp[0]])));
}
// FIXME: we need to be able to serialize non-stock properties!!!
}
return qs.join("&");
}
}
dojo.io.repubsubEvent.prototype.properties = [["from", "kn_from"], ["to", "kn_to"],
["method", "do_method"], ["id", "kn_id"],
["uri", "kn_uri"],
["displayname", "kn_displayname"],
["userid", "kn_userid"],
["payload", "kn_payload"],
["flushChars", "kn_response_flush"],
["responseFormat", "kn_response_format"] ];
// maps properties from their old names to their new names...
dojo.io.repubsubEvent.prototype.forwardPropertiesMap = {};
// ...and vice versa...
dojo.io.repubsubEvent.prototype.reversePropertiesMap = {};
// and we then populate them both from the properties list
for(var x=0; x<dojo.io.repubsubEvent.prototype.properties.length; x++){
var tp = dojo.io.repubsubEvent.prototype.properties[x];
dojo.io.repubsubEvent.prototype.reversePropertiesMap[tp[0]] = tp[1];
dojo.io.repubsubEvent.prototype.forwardPropertiesMap[tp[1]] = tp[0];
}
// static version of initFromProperties, creates new event and object and
// returns it after init
dojo.io.repubsubEvent.initFromProperties = function(evt){
var eventObj = new dojo.io.repubsubEvent();
eventObj.initFromProperties(evt);
return eventObj;
}

View File

@@ -0,0 +1,172 @@
/*
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.RhinoIO");
dojo.require("dojo.io.common");
dojo.require("dojo.lang.func");
dojo.require("dojo.lang.array");
dojo.require("dojo.string.extras");
dojo.io.RhinoHTTPTransport = new function(){
this.canHandle = function(/*dojo.io.Request*/req){
//summary: Tells dojo.io.bind() if this is a good transport to
//use for the particular type of request. This type of transport can
//only be used inside the Rhino JavaScript engine.
// We have to limit to text types because Rhino doesnt support
// a W3C dom implementation out of the box. In the future we
// should provide some kind of hook to inject your own, because
// in all my projects I use XML for Script to provide a W3C DOM.
if(dojo.lang.find(["text/plain", "text/html", "text/xml", "text/javascript", "text/json", "application/json"],
(req.mimetype.toLowerCase() || "")) < 0){
return false;
}
// We only handle http requests! Unfortunately, because the method is
// protected, I can't directly create a java.net.HttpURLConnection, so
// this is the only way to test.
if(req.url.substr(0, 7) != "http://"){
return false;
}
return true;
}
function doLoad(req, conn){
var ret;
if (req.method.toLowerCase() == "head"){
// TODO: return the headers
}else{
var stream = conn.getContent();
var reader = new java.io.BufferedReader(new java.io.InputStreamReader(stream));
// read line-by-line because why not?
var text = "";
var line = null;
while((line = reader.readLine()) != null){
text += line;
}
if(req.mimetype == "text/javascript"){
try{
ret = dj_eval(text);
}catch(e){
dojo.debug(e);
dojo.debug(text);
ret = null;
}
}else if(req.mimetype == "text/json" || req.mimetype == "application/json"){
try{
ret = dj_eval("("+text+")");
}catch(e){
dojo.debug(e);
dojo.debug(text);
ret = false;
}
}else{
ret = text;
}
}
req.load("load", ret, req);
}
function connect(req){
var content = req.content || {};
var query;
if (req.sendTransport){
content["dojo.transport"] = "rhinohttp";
}
if(req.postContent){
query = req.postContent;
}else{
query = dojo.io.argsFromMap(content, req.encoding);
}
var url_text = req.url;
if(req.method.toLowerCase() == "get" && query != ""){
url_text = url_text + "?" + query;
}
var url = new java.net.URL(url_text);
var conn = url.openConnection();
//
// configure the connection
//
conn.setRequestMethod(req.method.toUpperCase());
if(req.headers){
for(var header in req.headers){
if(header.toLowerCase() == "content-type" && !req.contentType){
req.contentType = req.headers[header];
}else{
conn.setRequestProperty(header, req.headers[header]);
}
}
}
if(req.contentType){
conn.setRequestProperty("Content-Type", req.contentType);
}
if(req.method.toLowerCase() == "post"){
conn.setDoOutput(true);
// write the post data
var output_stream = conn.getOutputStream();
var byte_array = (new java.lang.String(query)).getBytes();
output_stream.write(byte_array, 0, byte_array.length);
}
// do it to it!
conn.connect();
// perform the load
doLoad(req, conn);
}
this.bind = function(req){
//summary: function that sends the request to the server.
//The following are acceptable properties in kwArgs (in addition to the
//normal dojo.io.Request object properties).
//url: String: URL the server URL to use for the request.
//method: String: the HTTP method to use (GET, POST, etc...).
//mimetype: Specifies what format the result data should be given to the load/handle callback. Values of
// text/javascript, text/json, and application/json will cause the transport
// to evaluate the response as JavaScript/JSON. Any other mimetype will give back a text
// string.
//transport: String: specify "RhinoHTTPTransport" to force the use of this transport.
//sync: boolean: if true, then a synchronous XMLHttpRequest call is done,
// if false (the default), then an asynchronous call is used.
//headers: Object: The object property names and values will be sent as HTTP request header
// names and values.
//encoding: String: The type of encoding to use when dealing with the content kwArgs property.
//content: Object: The content object is converted into a name=value&name=value string, by
// using dojo.io.argsFromMap(). The encoding kwArgs property is passed to dojo.io.argsFromMap()
// for use in encoding the names and values. The resulting string is added to the request.
//postContent: String: Raw name=value&name=value string to be included as part of the request.
var async = req["sync"] ? false : true;
if (async){
setTimeout(dojo.lang.hitch(this, function(){
connect(req);
}), 1);
} else {
connect(req);
}
}
dojo.io.transports.addTransport("RhinoHTTPTransport");
}

View File

@@ -0,0 +1,469 @@
/*
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.ScriptSrcIO");
dojo.require("dojo.io.BrowserIO");
dojo.require("dojo.undo.browser");
//FIXME: should constantParams be JS object?
//FIXME: check dojo.io calls. Can we move the BrowserIO defined calls somewhere
// else so that we don't depend on BrowserIO at all? The dependent calls
// have to do with dealing with forms and making query params from JS object.
/**
* See test_ScriptSrcIO.html for usage information.
* Notes:
* - The watchInFlight timer is set to 100 ms instead of 10ms (which is what BrowserIO.js uses).
*/
dojo.io.ScriptSrcTransport = new function(){
this.preventCache = false; // if this is true, we'll always force GET requests to not cache
this.maxUrlLength = 1000; //Used to calculate if script request should be multipart.
this.inFlightTimer = null;
this.DsrStatusCodes = {
Continue: 100,
Ok: 200,
Error: 500
};
this.startWatchingInFlight = function(){
//summary: Internal method to start the process of watching for in-flight requests.
if(!this.inFlightTimer){
this.inFlightTimer = setInterval("dojo.io.ScriptSrcTransport.watchInFlight();", 100);
}
}
this.watchInFlight = function(){
//summary: Internal method to watch for in-flight requests.
var totalCount = 0;
var doneCount = 0;
for(var param in this._state){
totalCount++;
var currentState = this._state[param];
if(currentState.isDone){
doneCount++;
delete this._state[param];
}else if(!currentState.isFinishing){
var listener = currentState.kwArgs;
try{
if(currentState.checkString && eval("typeof(" + currentState.checkString + ") != 'undefined'")){
currentState.isFinishing = true;
this._finish(currentState, "load");
doneCount++;
delete this._state[param];
}else if(listener.timeoutSeconds && listener.timeout){
if(currentState.startTime + (listener.timeoutSeconds * 1000) < (new Date()).getTime()){
currentState.isFinishing = true;
this._finish(currentState, "timeout");
doneCount++;
delete this._state[param];
}
}else if(!listener.timeoutSeconds){
//Increment the done count if no timeout is specified, so
//that we turn off the timer if all that is left in the state
//list are things we can't clean up because they fail without
//getting a callback.
doneCount++;
}
}catch(e){
currentState.isFinishing = true;
this._finish(currentState, "error", {status: this.DsrStatusCodes.Error, response: e});
}
}
}
if(doneCount >= totalCount){
clearInterval(this.inFlightTimer);
this.inFlightTimer = null;
}
}
this.canHandle = function(/*dojo.io.Request*/kwArgs){
//summary: Tells dojo.io.bind() if this is a good transport to
//use for the particular type of request. This type of transport can only
//handle responses that are JavaScript or JSON that is passed to a JavaScript
//callback. It can only do asynchronous binds, is limited to GET HTTP method
//requests, and cannot handle formNodes. However, it has the advantage of being
//able to do cross-domain requests.
return dojo.lang.inArray(["text/javascript", "text/json", "application/json"], (kwArgs["mimetype"].toLowerCase()))
&& (kwArgs["method"].toLowerCase() == "get")
&& !(kwArgs["formNode"] && dojo.io.formHasFile(kwArgs["formNode"]))
&& (!kwArgs["sync"] || kwArgs["sync"] == false)
&& !kwArgs["file"]
&& !kwArgs["multipart"];
}
this.removeScripts = function(){
//summary: Removes any script tags from the DOM that may have been added by ScriptSrcTransport.
//description: Be careful though, by removing them from the script, you may invalidate some
//script objects that were defined by the js file that was pulled in as the
//src of the script tag. Test carefully if you decide to call this method.
//In MSIE 6 (and probably 5.x), if you remove the script element while
//part of the response script is still executing, the browser might crash.
var scripts = document.getElementsByTagName("script");
for(var i = 0; scripts && i < scripts.length; i++){
var scriptTag = scripts[i];
if(scriptTag.className == "ScriptSrcTransport"){
var parent = scriptTag.parentNode;
parent.removeChild(scriptTag);
i--; //Set the index back one since we removed an item.
}
}
}
this.bind = function(/*dojo.io.Request*/kwArgs){
//summary: function that sends the request to the server.
//description: See the Dojo Book page on this transport for a full
//description of supported kwArgs properties and usage:
//http://manual.dojotoolkit.org/WikiHome/DojoDotBook/Book25
//START duplication from BrowserIO.js (some changes made)
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];
}
//Break off the domain/path of the URL.
var urlParts = url.split("?");
if(urlParts && urlParts.length == 2){
url = urlParts[0];
query += (query ? "&" : "") + urlParts[1];
}
if(kwArgs["backButton"] || kwArgs["back"] || kwArgs["changeUrl"]){
dojo.undo.browser.addToHistory(kwArgs);
}
//Create an ID for the request.
var id = kwArgs["apiId"] ? kwArgs["apiId"] : "id" + this._counter++;
//Fill out any other content pieces.
var content = kwArgs["content"];
var jsonpName = kwArgs.jsonParamName;
if(kwArgs.sendTransport || jsonpName) {
if (!content){
content = {};
}
if(kwArgs.sendTransport){
content["dojo.transport"] = "scriptsrc";
}
if(jsonpName){
content[jsonpName] = "dojo.io.ScriptSrcTransport._state." + id + ".jsonpCall";
}
}
if(kwArgs.postContent){
query = kwArgs.postContent;
}else if(content){
query += ((query) ? "&" : "") + dojo.io.argsFromMap(content, kwArgs.encoding, jsonpName);
}
//END duplication from BrowserIO.js
//START DSR
//If an apiId is specified, then we want to make sure useRequestId is true.
if(kwArgs["apiId"]){
kwArgs["useRequestId"] = true;
}
//Set up the state for this request.
var state = {
"id": id,
"idParam": "_dsrid=" + id,
"url": url,
"query": query,
"kwArgs": kwArgs,
"startTime": (new Date()).getTime(),
"isFinishing": false
};
if(!url){
//Error. An URL is needed.
this._finish(state, "error", {status: this.DsrStatusCodes.Error, statusText: "url.none"});
return;
}
//If this is a jsonp request, intercept the jsonp callback
if(content && content[jsonpName]){
state.jsonp = content[jsonpName];
state.jsonpCall = function(data){
if(data["Error"]||data["error"]){
if(dojo["json"] && dojo["json"]["serialize"]){
dojo.debug(dojo.json.serialize(data));
}
dojo.io.ScriptSrcTransport._finish(this, "error", data);
}else{
dojo.io.ScriptSrcTransport._finish(this, "load", data);
}
};
}
//Only store the request state on the state tracking object if a callback
//is expected or if polling on a checkString will be done.
if(kwArgs["useRequestId"] || kwArgs["checkString"] || state["jsonp"]){
this._state[id] = state;
}
//A checkstring is a string that if evaled will not be undefined once the
//script src loads. Used as an alternative to depending on a callback from
//the script file. If this is set, then multipart is not assumed to be used,
//since multipart requires a specific callback. With checkString we will be doing
//polling.
if(kwArgs["checkString"]){
state.checkString = kwArgs["checkString"];
}
//Constant params are parameters that should always be sent with each
//part of a multipart URL.
state.constantParams = (kwArgs["constantParams"] == null ? "" : kwArgs["constantParams"]);
if(kwArgs["preventCache"] ||
(this.preventCache == true && kwArgs["preventCache"] != false)){
state.nocacheParam = "dojo.preventCache=" + new Date().valueOf();
}else{
state.nocacheParam = "";
}
//Get total length URL, if we were to do it as one URL.
//Add some padding, extra & separators.
var urlLength = state.url.length + state.query.length + state.constantParams.length
+ state.nocacheParam.length + this._extraPaddingLength;
if(kwArgs["useRequestId"]){
urlLength += state.idParam.length;
}
if(!kwArgs["checkString"] && kwArgs["useRequestId"]
&& !state["jsonp"] && !kwArgs["forceSingleRequest"]
&& urlLength > this.maxUrlLength){
if(url > this.maxUrlLength){
//Error. The URL domain and path are too long. We can't
//segment that, so return an error.
this._finish(state, "error", {status: this.DsrStatusCodes.Error, statusText: "url.tooBig"});
return;
}else{
//Start the multiple requests.
this._multiAttach(state, 1);
}
}else{
//Send one URL.
var queryParams = [state.constantParams, state.nocacheParam, state.query];
if(kwArgs["useRequestId"] && !state["jsonp"]){
queryParams.unshift(state.idParam);
}
var finalUrl = this._buildUrl(state.url, queryParams);
//Track the final URL in case we need to use that instead of api ID when receiving
//the load callback.
state.finalUrl = finalUrl;
this._attach(state.id, finalUrl);
}
//END DSR
this.startWatchingInFlight();
}
//Private properties/methods
this._counter = 1;
this._state = {};
this._extraPaddingLength = 16;
//Is there a dojo function for this already?
this._buildUrl = function(url, nameValueArray){
var finalUrl = url;
var joiner = "?";
for(var i = 0; i < nameValueArray.length; i++){
if(nameValueArray[i]){
finalUrl += joiner + nameValueArray[i];
joiner = "&";
}
}
return finalUrl;
}
this._attach = function(id, url){
//Attach the script to the DOM.
var element = document.createElement("script");
element.type = "text/javascript";
element.src = url;
element.id = id;
element.className = "ScriptSrcTransport";
document.getElementsByTagName("head")[0].appendChild(element);
}
this._multiAttach = function(state, part){
//Check to make sure we still have a query to send up. This is mostly
//a protection from a goof on the server side when it sends a part OK
//response instead of a final response.
if(state.query == null){
this._finish(state, "error", {status: this.DsrStatusCodes.Error, statusText: "query.null"});
return;
}
if(!state.constantParams){
state.constantParams = "";
}
//How much of the query can we take?
//Add a padding constant to account for _part and a couple extra amperstands.
//Also add space for id since we'll need it now.
var queryMax = this.maxUrlLength - state.idParam.length
- state.constantParams.length - state.url.length
- state.nocacheParam.length - this._extraPaddingLength;
//Figure out if this is the last part.
var isDone = state.query.length < queryMax;
//Break up the query string if necessary.
var currentQuery;
if(isDone){
currentQuery = state.query;
state.query = null;
}else{
//Find the & or = nearest the max url length.
var ampEnd = state.query.lastIndexOf("&", queryMax - 1);
var eqEnd = state.query.lastIndexOf("=", queryMax - 1);
//See if & is closer, or if = is right at the edge,
//which means we should put it on the next URL.
if(ampEnd > eqEnd || eqEnd == queryMax - 1){
//& is nearer the end. So just chop off from there.
currentQuery = state.query.substring(0, ampEnd);
state.query = state.query.substring(ampEnd + 1, state.query.length) //strip off amperstand with the + 1.
}else{
//= is nearer the end. Take the max amount possible.
currentQuery = state.query.substring(0, queryMax);
//Find the last query name in the currentQuery so we can prepend it to
//ampEnd. Could be -1 (not there), so account for that.
var queryName = currentQuery.substring((ampEnd == -1 ? 0 : ampEnd + 1), eqEnd);
state.query = queryName + "=" + state.query.substring(queryMax, state.query.length);
}
}
//Now send a part of the script
var queryParams = [currentQuery, state.idParam, state.constantParams, state.nocacheParam];
if(!isDone){
queryParams.push("_part=" + part);
}
var url = this._buildUrl(state.url, queryParams);
this._attach(state.id + "_" + part, url);
}
this._finish = function(state, callback, event){
if(callback != "partOk" && !state.kwArgs[callback] && !state.kwArgs["handle"]){
//Ignore "partOk" because that is an internal callback.
if(callback == "error"){
state.isDone = true;
throw event;
}
}else{
switch(callback){
case "load":
var response = event ? event.response : null;
if(!response){
response = event;
}
state.kwArgs[(typeof state.kwArgs.load == "function") ? "load" : "handle"]("load", response, event, state.kwArgs);
state.isDone = true;
break;
case "partOk":
var part = parseInt(event.response.part, 10) + 1;
//Update the constant params, if any.
if(event.response.constantParams){
state.constantParams = event.response.constantParams;
}
this._multiAttach(state, part);
state.isDone = false;
break;
case "error":
state.kwArgs[(typeof state.kwArgs.error == "function") ? "error" : "handle"]("error", event.response, event, state.kwArgs);
state.isDone = true;
break;
default:
state.kwArgs[(typeof state.kwArgs[callback] == "function") ? callback : "handle"](callback, event, event, state.kwArgs);
state.isDone = true;
}
}
}
dojo.io.transports.addTransport("ScriptSrcTransport");
}
//Define callback handler.
window.onscriptload = function(event){
var state = null;
var transport = dojo.io.ScriptSrcTransport;
//Find the matching state object for event ID.
if(transport._state[event.id]){
state = transport._state[event.id];
}else{
//The ID did not match directly to an entry in the state list.
//Try searching the state objects for a matching original URL.
var tempState;
for(var param in transport._state){
tempState = transport._state[param];
if(tempState.finalUrl && tempState.finalUrl == event.id){
state = tempState;
break;
}
}
//If no matching original URL is found, then use the URL that was actually used
//in the SCRIPT SRC attribute.
if(state == null){
var scripts = document.getElementsByTagName("script");
for(var i = 0; scripts && i < scripts.length; i++){
var scriptTag = scripts[i];
if(scriptTag.getAttribute("class") == "ScriptSrcTransport"
&& scriptTag.src == event.id){
state = transport._state[scriptTag.id];
break;
}
}
}
//If state is still null, then throw an error.
if(state == null){
throw "No matching state for onscriptload event.id: " + event.id;
}
}
var callbackName = "error";
switch(event.status){
case dojo.io.ScriptSrcTransport.DsrStatusCodes.Continue:
//A part of a multipart request.
callbackName = "partOk";
break;
case dojo.io.ScriptSrcTransport.DsrStatusCodes.Ok:
//Successful reponse.
callbackName = "load";
break;
}
transport._finish(state, callbackName, event);
};

View File

@@ -0,0 +1,239 @@
/*
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.XhrIframeProxy");
dojo.require("dojo.experimental");
dojo.experimental("dojo.io.XhrIframeProxy");
dojo.require("dojo.io.IframeIO");
dojo.require("dojo.dom");
dojo.require("dojo.uri.Uri");
dojo.io.XhrIframeProxy = {
//summary: Object that implements the iframe handling for XMLHttpRequest
//IFrame Proxying.
//description: Do not use this object directly. See the Dojo Book page
//on XMLHttpRequest IFrame Proxying:
//http://manual.dojotoolkit.org/WikiHome/DojoDotBook/Book75
//Usage of XHR IFrame Proxying does not work from local disk in Safari.
xipClientUrl: djConfig["xipClientUrl"] || dojo.uri.dojoUri("src/io/xip_client.html"),
_state: {},
_stateIdCounter: 0,
needFrameRecursion: function(){
return (true == dojo.render.html.ie70);
},
send: function(facade){
var stateId = "XhrIframeProxy" + (this._stateIdCounter++);
facade._stateId = stateId;
var frameUrl = this.xipClientUrl + "#0:init:id=" + stateId + "&server="
+ encodeURIComponent(facade._ifpServerUrl) + "&fr=false";
if(this.needFrameRecursion()){
//IE7 hack. Need to load server URL, and have that load the xip_client.html.
//Also, this server URL needs to different from the one eventually loaded by xip_client.html
//Otherwise, IE7 will not load it. Funky.
var fullClientUrl = window.location.href.substring(0, window.location.href.lastIndexOf("/") + 1);
fullClientUrl += this.xipClientUrl;
var serverUrl = facade._ifpServerUrl
+ (facade._ifpServerUrl.indexOf("?") == -1 ? "?" : "&") + "dojo.fr=1";
frameUrl = serverUrl + "#0:init:id=" + stateId + "&client="
+ encodeURIComponent(fullClientUrl) + "&fr=" + this.needFrameRecursion(); //fr is for Frame Recursion
}
this._state[stateId] = {
facade: facade,
stateId: stateId,
clientFrame: dojo.io.createIFrame(stateId, "", frameUrl)
};
return stateId;
},
receive: function(/*String*/stateId, /*String*/urlEncodedData){
/* urlEncodedData should have the following params:
- responseHeaders
- status
- statusText
- responseText
*/
//Decode response data.
var response = {};
var nvPairs = urlEncodedData.split("&");
for(var i = 0; i < nvPairs.length; i++){
if(nvPairs[i]){
var nameValue = nvPairs[i].split("=");
response[decodeURIComponent(nameValue[0])] = decodeURIComponent(nameValue[1]);
}
}
//Set data on facade object.
var state = this._state[stateId];
var facade = state.facade;
facade._setResponseHeaders(response.responseHeaders);
if(response.status == 0 || response.status){
facade.status = parseInt(response.status, 10);
}
if(response.statusText){
facade.statusText = response.statusText;
}
if(response.responseText){
facade.responseText = response.responseText;
//Fix responseXML.
var contentType = facade.getResponseHeader("Content-Type");
if(contentType && (contentType == "application/xml" || contentType == "text/xml")){
facade.responseXML = dojo.dom.createDocumentFromText(response.responseText, contentType);
}
}
facade.readyState = 4;
this.destroyState(stateId);
},
clientFrameLoaded: function(/*String*/stateId){
var state = this._state[stateId];
var facade = state.facade;
if(this.needFrameRecursion()){
var clientWindow = window.open("", state.stateId + "_clientEndPoint");
}else{
var clientWindow = state.clientFrame.contentWindow;
}
var reqHeaders = [];
for(var param in facade._requestHeaders){
reqHeaders.push(param + ": " + facade._requestHeaders[param]);
}
var requestData = {
uri: facade._uri
};
if(reqHeaders.length > 0){
requestData.requestHeaders = reqHeaders.join("\r\n");
}
if(facade._method){
requestData.method = facade._method;
}
if(facade._bodyData){
requestData.data = facade._bodyData;
}
clientWindow.send(dojo.io.argsFromMap(requestData, "utf8"));
},
destroyState: function(/*String*/stateId){
var state = this._state[stateId];
if(state){
delete this._state[stateId];
var parentNode = state.clientFrame.parentNode;
parentNode.removeChild(state.clientFrame);
state.clientFrame = null;
state = null;
}
},
createFacade: function(){
if(arguments && arguments[0] && arguments[0]["iframeProxyUrl"]){
return new dojo.io.XhrIframeFacade(arguments[0]["iframeProxyUrl"]);
}else{
return dojo.io.XhrIframeProxy.oldGetXmlhttpObject.apply(dojo.hostenv, arguments);
}
}
}
//Replace the normal XHR factory with the proxy one.
dojo.io.XhrIframeProxy.oldGetXmlhttpObject = dojo.hostenv.getXmlhttpObject;
dojo.hostenv.getXmlhttpObject = dojo.io.XhrIframeProxy.createFacade;
/**
Using this a reference: http://www.w3.org/TR/XMLHttpRequest/
Does not implement the onreadystate callback since dojo.io.BrowserIO does
not use it.
*/
dojo.io.XhrIframeFacade = function(ifpServerUrl){
//summary: XMLHttpRequest facade object used by dojo.io.XhrIframeProxy.
//description: Do not use this object directly. See the Dojo Book page
//on XMLHttpRequest IFrame Proxying:
//http://manual.dojotoolkit.org/WikiHome/DojoDotBook/Book75
this._requestHeaders = {};
this._allResponseHeaders = null;
this._responseHeaders = {};
this._method = null;
this._uri = null;
this._bodyData = null;
this.responseText = null;
this.responseXML = null;
this.status = null;
this.statusText = null;
this.readyState = 0;
this._ifpServerUrl = ifpServerUrl;
this._stateId = null;
}
dojo.lang.extend(dojo.io.XhrIframeFacade, {
//The open method does not properly reset since Dojo does not reuse XHR objects.
open: function(/*String*/method, /*String*/uri){
this._method = method;
this._uri = uri;
this.readyState = 1;
},
setRequestHeader: function(/*String*/header, /*String*/value){
this._requestHeaders[header] = value;
},
send: function(/*String*/stringData){
this._bodyData = stringData;
this._stateId = dojo.io.XhrIframeProxy.send(this);
this.readyState = 2;
},
abort: function(){
dojo.io.XhrIframeProxy.destroyState(this._stateId);
},
getAllResponseHeaders: function(){
return this._allResponseHeaders; //String
},
getResponseHeader: function(/*String*/header){
return this._responseHeaders[header]; //String
},
_setResponseHeaders: function(/*String*/allHeaders){
if(allHeaders){
this._allResponseHeaders = allHeaders;
//Make sure ther are now CR characters in the headers.
allHeaders = allHeaders.replace(/\r/g, "");
var nvPairs = allHeaders.split("\n");
for(var i = 0; i < nvPairs.length; i++){
if(nvPairs[i]){
var nameValue = nvPairs[i].split(": ");
this._responseHeaders[nameValue[0]] = nameValue[1];
}
}
}
}
});

View File

@@ -0,0 +1,17 @@
/*
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.kwCompoundRequire({
common: ["dojo.io.common"],
rhino: ["dojo.io.RhinoIO"],
browser: ["dojo.io.BrowserIO", "dojo.io.cookie"],
dashboard: ["dojo.io.BrowserIO", "dojo.io.cookie"]
});
dojo.provide("dojo.io.*");

View File

@@ -0,0 +1,929 @@
/*
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.require("dojo.io.common"); // io/common.js provides setIFrameSrc and the IO module
dojo.provide("dojo.io.cometd");
dojo.require("dojo.AdapterRegistry");
dojo.require("dojo.json");
dojo.require("dojo.io.BrowserIO"); // we need XHR for the handshake, etc.
// FIXME: determine if we can use XMLHTTP to make x-domain posts despite not
// being able to hear back about the result
dojo.require("dojo.io.IframeIO");
dojo.require("dojo.io.ScriptSrcIO"); // for x-domain long polling
dojo.require("dojo.io.cookie"); // for peering
dojo.require("dojo.event.*");
dojo.require("dojo.lang.common");
dojo.require("dojo.lang.func");
/*
* this file defines Comet protocol client. Actual message transport is
* deferred to one of several connection type implementations. The default is a
* forever-frame implementation. A single global object named "cometd" is
* used to mediate for these connection types in order to provide a stable
* interface.
*/
// TODO: the auth handling in this file is a *mess*. It should probably live in
// the cometd object with the ability to mix in or call down to an auth-handler
// object, the prototypical variant of which is a no-op
cometd = new function(){
this.initialized = false;
this.connected = false;
this.connectionTypes = new dojo.AdapterRegistry(true);
this.version = 0.1;
this.minimumVersion = 0.1;
this.clientId = null;
this._isXD = false;
this.handshakeReturn = null;
this.currentTransport = null;
this.url = null;
this.lastMessage = null;
this.globalTopicChannels = {};
this.backlog = [];
this.tunnelInit = function(childLocation, childDomain){
// placeholder
}
this.tunnelCollapse = function(){
dojo.debug("tunnel collapsed!");
// placeholder
}
this.init = function(props, root, bargs){
// FIXME: if the root isn't from the same host, we should automatically
// try to select an XD-capable transport
props = props||{};
// go ask the short bus server what we can support
props.version = this.version;
props.minimumVersion = this.minimumVersion;
props.channel = "/meta/handshake";
// FIXME: do we just assume that the props knows
// everything we care about WRT to auth? Should we be trying to
// call back into it for subsequent auth actions? Should we fire
// local auth functions to ask for/get auth data?
// FIXME: what about ScriptSrcIO for x-domain comet?
this.url = root||djConfig["cometdRoot"];
if(!this.url){
dojo.debug("no cometd root specified in djConfig and no root passed");
return;
}
// FIXME: we need to select a way to handle JSONP-style stuff
// generically here. We already know if the server is gonna be on
// another domain (or can know it), so we should select appropriate
// negotiation methods here as well as in final transport type
// selection.
var bindArgs = {
url: this.url,
method: "POST",
mimetype: "text/json",
load: dojo.lang.hitch(this, "finishInit"),
content: { "message": dojo.json.serialize([props]) }
};
// borrowed from dojo.uri.Uri in lieu of fixed host and port properties
var regexp = "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?$";
var r = (""+window.location).match(new RegExp(regexp));
if(r[4]){
var tmp = r[4].split(":");
var thisHost = tmp[0];
var thisPort = tmp[1]||"80"; // FIXME: match 443
r = this.url.match(new RegExp(regexp));
if(r[4]){
tmp = r[4].split(":");
var urlHost = tmp[0];
var urlPort = tmp[1]||"80";
if( (urlHost != thisHost)||
(urlPort != thisPort) ){
dojo.debug(thisHost, urlHost);
dojo.debug(thisPort, urlPort);
this._isXD = true;
bindArgs.transport = "ScriptSrcTransport";
bindArgs.jsonParamName = "jsonp";
bindArgs.method = "GET";
}
}
}
if(bargs){
dojo.lang.mixin(bindArgs, bargs);
}
return dojo.io.bind(bindArgs);
}
this.finishInit = function(type, data, evt, request){
data = data[0];
this.handshakeReturn = data;
// pick a transport
if(data["authSuccessful"] == false){
dojo.debug("cometd authentication failed");
return;
}
if(data.version < this.minimumVersion){
dojo.debug("cometd protocol version mismatch. We wanted", this.minimumVersion, "but got", data.version);
return;
}
this.currentTransport = this.connectionTypes.match(
data.supportedConnectionTypes,
data.version,
this._isXD
);
this.currentTransport.version = data.version;
this.clientId = data.clientId;
this.tunnelInit = dojo.lang.hitch(this.currentTransport, "tunnelInit");
this.tunnelCollapse = dojo.lang.hitch(this.currentTransport, "tunnelCollapse");
this.initialized = true;
this.currentTransport.startup(data);
while(this.backlog.length != 0){
var cur = this.backlog.shift();
var fn = cur.shift();
this[fn].apply(this, cur);
}
}
this._getRandStr = function(){
return Math.random().toString().substring(2, 10);
}
// public API functions called by cometd or by the transport classes
this.deliver = function(messages){
dojo.lang.forEach(messages, this._deliver, this);
}
this._deliver = function(message){
// dipatch events along the specified path
if(!message["channel"]){
dojo.debug("cometd error: no channel for message!");
return;
}
if(!this.currentTransport){
this.backlog.push(["deliver", message]);
return;
}
this.lastMessage = message;
// check to see if we got a /meta channel message that we care about
if( (message.channel.length > 5)&&
(message.channel.substr(0, 5) == "/meta")){
// check for various meta topic actions that we need to respond to
switch(message.channel){
case "/meta/subscribe":
if(!message.successful){
dojo.debug("cometd subscription error for channel", message.channel, ":", message.error);
return;
}
this.subscribed(message.subscription, message);
break;
case "/meta/unsubscribe":
if(!message.successful){
dojo.debug("cometd unsubscription error for channel", message.channel, ":", message.error);
return;
}
this.unsubscribed(message.subscription, message);
break;
}
}
// send the message down for processing by the transport
this.currentTransport.deliver(message);
// dispatch the message to any locally subscribed listeners
var tname = (this.globalTopicChannels[message.channel]) ? message.channel : "/cometd"+message.channel;
dojo.event.topic.publish(tname, message);
}
this.disconnect = function(){
if(!this.currentTransport){
dojo.debug("no current transport to disconnect from");
return;
}
this.currentTransport.disconnect();
}
// public API functions called by end users
this.publish = function(/*string*/channel, /*object*/data, /*object*/properties){
// summary:
// publishes the passed message to the cometd server for delivery
// on the specified topic
// channel:
// the destination channel for the message
// data:
// a JSON object containing the message "payload"
// properties:
// Optional. Other meta-data to be mixed into the top-level of the
// message
if(!this.currentTransport){
this.backlog.push(["publish", channel, data, properties]);
return;
}
var message = {
data: data,
channel: channel
};
if(properties){
dojo.lang.mixin(message, properties);
}
return this.currentTransport.sendMessage(message);
}
this.subscribe = function( /*string*/ channel,
/*boolean, optional*/ useLocalTopics,
/*object, optional*/ objOrFunc,
/*string, optional*/ funcName){ // return: boolean
// summary:
// inform the server of this client's interest in channel
// channel:
// name of the cometd channel to subscribe to
// useLocalTopics:
// Determines if up a local event topic subscription to the passed
// function using the channel name that was passed is constructed,
// or if the topic name will be prefixed with some other
// identifier for local message distribution. Setting this to
// "true" is a good way to hook up server-sent message delivery to
// pre-existing local topics.
// objOrFunc:
// an object scope for funcName or the name or reference to a
// function to be called when messages are delivered to the
// channel
// funcName:
// the second half of the objOrFunc/funcName pair for identifying
// a callback function to notifiy upon channel message delivery
if(!this.currentTransport){
this.backlog.push(["subscribe", channel, useLocalTopics, objOrFunc, funcName]);
return;
}
if(objOrFunc){
var tname = (useLocalTopics) ? channel : "/cometd"+channel;
if(useLocalTopics){
this.globalTopicChannels[channel] = true;
}
dojo.event.topic.subscribe(tname, objOrFunc, funcName);
}
// FIXME: would we handle queuing of the subscription if not connected?
// Or should the transport object?
return this.currentTransport.sendMessage({
channel: "/meta/subscribe",
subscription: channel
});
}
this.subscribed = function( /*string*/ channel,
/*obj*/ message){
dojo.debug(channel);
dojo.debugShallow(message);
}
this.unsubscribe = function(/*string*/ channel,
/*boolean, optional*/ useLocalTopics,
/*object, optional*/ objOrFunc,
/*string, optional*/ funcName){ // return: boolean
// summary:
// inform the server of this client's disinterest in channel
// channel:
// name of the cometd channel to subscribe to
// useLocalTopics:
// Determines if up a local event topic subscription to the passed
// function using the channel name that was passed is destroyed,
// or if the topic name will be prefixed with some other
// identifier for stopping message distribution.
// objOrFunc:
// an object scope for funcName or the name or reference to a
// function to be called when messages are delivered to the
// channel
// funcName:
// the second half of the objOrFunc/funcName pair for identifying
if(!this.currentTransport){
this.backlog.push(["unsubscribe", channel, useLocalTopics, objOrFunc, funcName]);
return;
}
// a callback function to notifiy upon channel message delivery
if(objOrFunc){
// FIXME: should actual local topic unsubscription be delayed for
// successful unsubcribe notices from the other end? (guessing "no")
// FIXME: if useLocalTopics is false, should we go ahead and
// destroy the local topic?
var tname = (useLocalTopics) ? channel : "/cometd"+channel;
dojo.event.topic.unsubscribe(tname, objOrFunc, funcName);
}
return this.currentTransport.sendMessage({
channel: "/meta/unsubscribe",
subscription: channel
});
}
this.unsubscribed = function(/*string*/ channel,
/*obj*/ message){
dojo.debug(channel);
dojo.debugShallow(message);
}
// FIXME: add an "addPublisher" function
}
/*
transport objects MUST expose the following methods:
- check
- startup
- sendMessage
- deliver
- disconnect
optional, standard but transport dependent methods are:
- tunnelCollapse
- tunnelInit
Transports SHOULD be namespaced under the cometd object and transports MUST
register themselves with cometd.connectionTypes
here's a stub transport defintion:
cometd.blahTransport = new function(){
this.connected = false;
this.connectionId = null;
this.authToken = null;
this.lastTimestamp = null;
this.lastId = null;
this.check = function(types, version, xdomain){
// summary:
// determines whether or not this transport is suitable given a
// list of transport types that the server supports
return dojo.lang.inArray(types, "blah");
}
this.startup = function(){
if(this.connected){ return; }
// FIXME: fill in startup routine here
this.connected = true;
}
this.sendMessage = function(message){
// FIXME: fill in message sending logic
}
this.deliver = function(message){
if(message["timestamp"]){
this.lastTimestamp = message.timestamp;
}
if(message["id"]){
this.lastId = message.id;
}
if( (message.channel.length > 5)&&
(message.channel.substr(0, 5) == "/meta")){
// check for various meta topic actions that we need to respond to
// switch(message.channel){
// case "/meta/connect":
// // FIXME: fill in logic here
// break;
// // case ...: ...
// }
}
}
this.disconnect = function(){
if(!this.connected){ return; }
// FIXME: fill in shutdown routine here
this.connected = false;
}
}
cometd.connectionTypes.register("blah", cometd.blahTransport.check, cometd.blahTransport);
*/
cometd.iframeTransport = new function(){
this.connected = false;
this.connectionId = null;
this.rcvNode = null;
this.rcvNodeName = "";
this.phonyForm = null;
this.authToken = null;
this.lastTimestamp = null;
this.lastId = null;
this.backlog = [];
this.check = function(types, version, xdomain){
return ((!xdomain)&&
(!dojo.render.html.safari)&&
(dojo.lang.inArray(types, "iframe")));
}
this.tunnelInit = function(){
// we've gotten our initialization document back in the iframe, so
// now open up a connection and start passing data!
this.postToIframe({
message: dojo.json.serialize([
{
channel: "/meta/connect",
clientId: cometd.clientId,
connectionType: "iframe"
// FIXME: auth not passed here!
// "authToken": this.authToken
}
])
});
}
this.tunnelCollapse = function(){
if(this.connected){
// try to restart the tunnel
this.connected = false;
this.postToIframe({
message: dojo.json.serialize([
{
channel: "/meta/reconnect",
clientId: cometd.clientId,
connectionId: this.connectionId,
timestamp: this.lastTimestamp,
id: this.lastId
// FIXME: no authToken provision!
}
])
});
}
}
this.deliver = function(message){
// handle delivery details that this transport particularly cares
// about. Most functions of should be handled by the main cometd object
// with only transport-specific details and state being tracked here.
if(message["timestamp"]){
this.lastTimestamp = message.timestamp;
}
if(message["id"]){
this.lastId = message.id;
}
// check to see if we got a /meta channel message that we care about
if( (message.channel.length > 5)&&
(message.channel.substr(0, 5) == "/meta")){
// check for various meta topic actions that we need to respond to
switch(message.channel){
case "/meta/connect":
if(!message.successful){
dojo.debug("cometd connection error:", message.error);
return;
}
this.connectionId = message.connectionId;
this.connected = true;
this.processBacklog();
break;
case "/meta/reconnect":
if(!message.successful){
dojo.debug("cometd reconnection error:", message.error);
return;
}
this.connected = true;
break;
case "/meta/subscribe":
if(!message.successful){
dojo.debug("cometd subscription error for channel", message.channel, ":", message.error);
return;
}
// this.subscribed(message.channel);
dojo.debug(message.channel);
break;
}
}
}
this.widenDomain = function(domainStr){
// allow us to make reqests to the TLD
var cd = domainStr||document.domain;
if(cd.indexOf(".")==-1){ return; } // probably file:/// or localhost
var dps = cd.split(".");
if(dps.length<=2){ return; } // probably file:/// or an RFC 1918 address
dps = dps.slice(dps.length-2);
document.domain = dps.join(".");
return document.domain;
}
this.postToIframe = function(content, url){
if(!this.phonyForm){
if(dojo.render.html.ie){
this.phonyForm = document.createElement("<form enctype='application/x-www-form-urlencoded' method='POST' style='display: none;'>");
dojo.body().appendChild(this.phonyForm);
}else{
this.phonyForm = document.createElement("form");
this.phonyForm.style.display = "none"; // FIXME: will this still work?
dojo.body().appendChild(this.phonyForm);
this.phonyForm.enctype = "application/x-www-form-urlencoded";
this.phonyForm.method = "POST";
}
}
this.phonyForm.action = url||cometd.url;
this.phonyForm.target = this.rcvNodeName;
this.phonyForm.setAttribute("target", this.rcvNodeName);
while(this.phonyForm.firstChild){
this.phonyForm.removeChild(this.phonyForm.firstChild);
}
for(var x in content){
var tn;
if(dojo.render.html.ie){
tn = document.createElement("<input type='hidden' name='"+x+"' value='"+content[x]+"'>");
this.phonyForm.appendChild(tn);
}else{
tn = document.createElement("input");
this.phonyForm.appendChild(tn);
tn.type = "hidden";
tn.name = x;
tn.value = content[x];
}
}
this.phonyForm.submit();
}
this.processBacklog = function(){
while(this.backlog.length > 0){
this.sendMessage(this.backlog.shift(), true);
}
}
this.sendMessage = function(message, bypassBacklog){
// FIXME: what about auth fields?
if((bypassBacklog)||(this.connected)){
message.connectionId = this.connectionId;
message.clientId = cometd.clientId;
var bindArgs = {
url: cometd.url||djConfig["cometdRoot"],
method: "POST",
mimetype: "text/json",
// FIXME: we should be able to do better than this given that we're sending an array!
content: { message: dojo.json.serialize([ message ]) }
};
return dojo.io.bind(bindArgs);
}else{
this.backlog.push(message);
}
}
this.startup = function(handshakeData){
dojo.debug("startup!");
dojo.debug(dojo.json.serialize(handshakeData));
if(this.connected){ return; }
// this.widenDomain();
// NOTE: we require the server to cooperate by hosting
// cometdInit.html at the designated endpoint
this.rcvNodeName = "cometdRcv_"+cometd._getRandStr();
// the "forever frame" approach
var initUrl = cometd.url+"/?tunnelInit=iframe"; // &domain="+document.domain;
if(false && dojo.render.html.ie){ // FIXME: DISALBED FOR NOW
// use the "htmlfile hack" to prevent the background click junk
this.rcvNode = new ActiveXObject("htmlfile");
this.rcvNode.open();
this.rcvNode.write("<html>");
this.rcvNode.write("<script>document.domain = '"+document.domain+"'");
this.rcvNode.write("</html>");
this.rcvNode.close();
var ifrDiv = this.rcvNode.createElement("div");
this.rcvNode.appendChild(ifrDiv);
this.rcvNode.parentWindow.dojo = dojo;
ifrDiv.innerHTML = "<iframe src='"+initUrl+"'></iframe>"
}else{
this.rcvNode = dojo.io.createIFrame(this.rcvNodeName, "", initUrl);
// dojo.io.setIFrameSrc(this.rcvNode, initUrl);
// we're still waiting on the iframe to call back up to use and
// advertise that it's been initialized via tunnelInit
}
}
}
cometd.mimeReplaceTransport = new function(){
this.connected = false;
this.connectionId = null;
this.xhr = null;
this.authToken = null;
this.lastTimestamp = null;
this.lastId = null;
this.backlog = [];
this.check = function(types, version, xdomain){
return ((!xdomain)&&
(dojo.render.html.mozilla)&& // seems only Moz really supports this right now = (
(dojo.lang.inArray(types, "mime-message-block")));
}
this.tunnelInit = function(){
if(this.connected){ return; }
// FIXME: open up the connection here
this.openTunnelWith({
message: dojo.json.serialize([
{
channel: "/meta/connect",
clientId: cometd.clientId,
connectionType: "mime-message-block"
// FIXME: auth not passed here!
// "authToken": this.authToken
}
])
});
this.connected = true;
}
this.tunnelCollapse = function(){
if(this.connected){
// try to restart the tunnel
this.connected = false;
this.openTunnelWith({
message: dojo.json.serialize([
{
channel: "/meta/reconnect",
clientId: cometd.clientId,
connectionId: this.connectionId,
timestamp: this.lastTimestamp,
id: this.lastId
// FIXME: no authToken provision!
}
])
});
}
}
this.deliver = cometd.iframeTransport.deliver;
// the logic appears to be the same
this.handleOnLoad = function(resp){
cometd.deliver(dojo.json.evalJson(this.xhr.responseText));
}
this.openTunnelWith = function(content, url){
// set up the XHR object and register the multipart callbacks
this.xhr = dojo.hostenv.getXmlhttpObject();
this.xhr.multipart = true; // FIXME: do Opera and Safari support this flag?
if(dojo.render.html.mozilla){
this.xhr.addEventListener("load", dojo.lang.hitch(this, "handleOnLoad"), false);
}else if(dojo.render.html.safari){
// Blah. WebKit doesn't actually populate responseText and/or responseXML. Useless.
dojo.debug("Webkit is broken with multipart responses over XHR = (");
this.xhr.onreadystatechange = dojo.lang.hitch(this, "handleOnLoad");
}else{
this.xhr.onload = dojo.lang.hitch(this, "handleOnLoad");
}
this.xhr.open("POST", (url||cometd.url), true); // async post
this.xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
dojo.debug(dojo.json.serialize(content));
this.xhr.send(dojo.io.argsFromMap(content, "utf8"));
}
this.processBacklog = function(){
while(this.backlog.length > 0){
this.sendMessage(this.backlog.shift(), true);
}
}
this.sendMessage = function(message, bypassBacklog){
// FIXME: what about auth fields?
if((bypassBacklog)||(this.connected)){
message.connectionId = this.connectionId;
message.clientId = cometd.clientId;
var bindArgs = {
url: cometd.url||djConfig["cometdRoot"],
method: "POST",
mimetype: "text/json",
content: { message: dojo.json.serialize([ message ]) }
};
return dojo.io.bind(bindArgs);
}else{
this.backlog.push(message);
}
}
this.startup = function(handshakeData){
dojo.debugShallow(handshakeData);
if(this.connected){ return; }
this.tunnelInit();
}
}
cometd.longPollTransport = new function(){
this.connected = false;
this.connectionId = null;
this.authToken = null;
this.lastTimestamp = null;
this.lastId = null;
this.backlog = [];
this.check = function(types, version, xdomain){
return ((!xdomain)&&(dojo.lang.inArray(types, "long-polling")));
}
this.tunnelInit = function(){
if(this.connected){ return; }
// FIXME: open up the connection here
this.openTunnelWith({
message: dojo.json.serialize([
{
channel: "/meta/connect",
clientId: cometd.clientId,
connectionType: "long-polling"
// FIXME: auth not passed here!
// "authToken": this.authToken
}
])
});
this.connected = true;
}
this.tunnelCollapse = function(){
if(!this.connected){
// try to restart the tunnel
this.connected = false;
dojo.debug("clientId:", cometd.clientId);
this.openTunnelWith({
message: dojo.json.serialize([
{
channel: "/meta/reconnect",
connectionType: "long-polling",
clientId: cometd.clientId,
connectionId: this.connectionId,
timestamp: this.lastTimestamp,
id: this.lastId
// FIXME: no authToken provision!
}
])
});
}
}
this.deliver = cometd.iframeTransport.deliver;
// the logic appears to be the same
this.openTunnelWith = function(content, url){
dojo.io.bind({
url: (url||cometd.url),
method: "post",
content: content,
mimetype: "text/json",
load: dojo.lang.hitch(this, function(type, data, evt, args){
// dojo.debug(evt.responseText);
cometd.deliver(data);
this.connected = false;
this.tunnelCollapse();
}),
error: function(){ dojo.debug("tunnel opening failed"); }
});
this.connected = true;
}
this.processBacklog = function(){
while(this.backlog.length > 0){
this.sendMessage(this.backlog.shift(), true);
}
}
this.sendMessage = function(message, bypassBacklog){
// FIXME: what about auth fields?
if((bypassBacklog)||(this.connected)){
message.connectionId = this.connectionId;
message.clientId = cometd.clientId;
var bindArgs = {
url: cometd.url||djConfig["cometdRoot"],
method: "post",
mimetype: "text/json",
content: { message: dojo.json.serialize([ message ]) }
};
return dojo.io.bind(bindArgs);
}else{
this.backlog.push(message);
}
}
this.startup = function(handshakeData){
if(this.connected){ return; }
this.tunnelInit();
}
}
cometd.callbackPollTransport = new function(){
this.connected = false;
this.connectionId = null;
this.authToken = null;
this.lastTimestamp = null;
this.lastId = null;
this.backlog = [];
this.check = function(types, version, xdomain){
// we handle x-domain!
return dojo.lang.inArray(types, "callback-polling");
}
this.tunnelInit = function(){
if(this.connected){ return; }
// FIXME: open up the connection here
this.openTunnelWith({
message: dojo.json.serialize([
{
channel: "/meta/connect",
clientId: cometd.clientId,
connectionType: "callback-polling"
// FIXME: auth not passed here!
// "authToken": this.authToken
}
])
});
this.connected = true;
}
this.tunnelCollapse = function(){
if(!this.connected){
// try to restart the tunnel
this.connected = false;
this.openTunnelWith({
message: dojo.json.serialize([
{
channel: "/meta/reconnect",
connectionType: "long-polling",
clientId: cometd.clientId,
connectionId: this.connectionId,
timestamp: this.lastTimestamp,
id: this.lastId
// FIXME: no authToken provision!
}
])
});
}
}
this.deliver = cometd.iframeTransport.deliver;
// the logic appears to be the same
this.openTunnelWith = function(content, url){
// create a <script> element to generate the request
var req = dojo.io.bind({
url: (url||cometd.url),
content: content,
mimetype: "text/json",
transport: "ScriptSrcTransport",
jsonParamName: "jsonp",
load: dojo.lang.hitch(this, function(type, data, evt, args){
dojo.debug(dojo.json.serialize(data));
cometd.deliver(data);
this.connected = false;
this.tunnelCollapse();
}),
error: function(){ dojo.debug("tunnel opening failed"); }
});
this.connected = true;
}
this.processBacklog = function(){
while(this.backlog.length > 0){
this.sendMessage(this.backlog.shift(), true);
}
}
this.sendMessage = function(message, bypassBacklog){
// FIXME: what about auth fields?
if((bypassBacklog)||(this.connected)){
message.connectionId = this.connectionId;
message.clientId = cometd.clientId;
var bindArgs = {
url: cometd.url||djConfig["cometdRoot"],
mimetype: "text/json",
transport: "ScriptSrcTransport",
jsonParamName: "jsonp",
content: { message: dojo.json.serialize([ message ]) }
};
return dojo.io.bind(bindArgs);
}else{
this.backlog.push(message);
}
}
this.startup = function(handshakeData){
if(this.connected){ return; }
this.tunnelInit();
}
}
cometd.connectionTypes.register("mime-message-block", cometd.mimeReplaceTransport.check, cometd.mimeReplaceTransport);
cometd.connectionTypes.register("long-polling", cometd.longPollTransport.check, cometd.longPollTransport);
cometd.connectionTypes.register("callback-polling", cometd.callbackPollTransport.check, cometd.callbackPollTransport);
cometd.connectionTypes.register("iframe", cometd.iframeTransport.check, cometd.iframeTransport);
// FIXME: need to implement fallback-polling, IE XML block
dojo.io.cometd = cometd;

View File

@@ -0,0 +1,517 @@
/*
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.common");
dojo.require("dojo.string");
dojo.require("dojo.lang.extras");
/******************************************************************************
* Notes about dojo.io design:
*
* The dojo.io.* package has the unenviable task of making a lot of different
* types of I/O feel natural, despite a universal lack of good (or even
* reasonable!) I/O capability in the host environment. So lets pin this down
* a little bit further.
*
* Rhino:
* perhaps the best situation anywhere. Access to Java classes allows you
* to do anything one might want in terms of I/O, both synchronously and
* async. Can open TCP sockets and perform low-latency client/server
* interactions. HTTP transport is available through Java HTTP client and
* server classes. Wish it were always this easy.
*
* xpcshell:
* XPCOM for I/O.
*
* spidermonkey:
* S.O.L.
*
* Browsers:
* Browsers generally do not provide any useable filesystem access. We are
* therefore limited to HTTP for moving information to and from Dojo
* instances living in a browser.
*
* XMLHTTP:
* Sync or async, allows reading of arbitrary text files (including
* JS, which can then be eval()'d), writing requires server
* cooperation and is limited to HTTP mechanisms (POST and GET).
*
* <iframe> hacks:
* iframe document hacks allow browsers to communicate asynchronously
* with a server via HTTP POST and GET operations. With significant
* effort and server cooperation, low-latency data transit between
* client and server can be acheived via iframe mechanisms (repubsub).
*
* SVG:
* Adobe's SVG viewer implements helpful primitives for XML-based
* requests, but receipt of arbitrary text data seems unlikely w/o
* <![CDATA[]]> sections.
*
*
* A discussion between Dylan, Mark, Tom, and Alex helped to lay down a lot
* the IO API interface. A transcript of it can be found at:
* http://dojotoolkit.org/viewcvs/viewcvs.py/documents/irc/irc_io_api_log.txt?rev=307&view=auto
*
* Also referenced in the design of the API was the DOM 3 L&S spec:
* http://www.w3.org/TR/2004/REC-DOM-Level-3-LS-20040407/load-save.html
******************************************************************************/
// a map of the available transport options. Transports should add themselves
// by calling add(name)
dojo.io.transports = [];
dojo.io.hdlrFuncNames = [ "load", "error", "timeout" ]; // we're omitting a progress() event for now
dojo.io.Request = function(/*String*/ url, /*String*/ mimetype, /*String*/ transport, /*String or Boolean*/ changeUrl){
// summary:
// Constructs a Request object that is used by dojo.io.bind().
// description:
// dojo.io.bind() will create one of these for you if
// you call dojo.io.bind() with an plain object containing the bind parameters.
// This method can either take the arguments specified, or an Object containing all of the parameters that you
// want to use to create the dojo.io.Request (similar to how dojo.io.bind() is called.
// The named parameters to this constructor represent the minimum set of parameters need
if((arguments.length == 1)&&(arguments[0].constructor == Object)){
this.fromKwArgs(arguments[0]);
}else{
this.url = url;
if(mimetype){ this.mimetype = mimetype; }
if(transport){ this.transport = transport; }
if(arguments.length >= 4){ this.changeUrl = changeUrl; }
}
}
dojo.lang.extend(dojo.io.Request, {
/** The URL to hit */
url: "",
/** The mime type used to interrpret the response body */
mimetype: "text/plain",
/** The HTTP method to use */
method: "GET",
/** An Object containing key-value pairs to be included with the request */
content: undefined, // Object
/** The transport medium to use */
transport: undefined, // String
/** If defined the URL of the page is physically changed */
changeUrl: undefined, // String
/** A form node to use in the request */
formNode: undefined, // HTMLFormElement
/** Whether the request should be made synchronously */
sync: false,
bindSuccess: false,
/** Cache/look for the request in the cache before attempting to request?
* NOTE: this isn't a browser cache, this is internal and would only cache in-page
*/
useCache: false,
/** Prevent the browser from caching this by adding a query string argument to the URL */
preventCache: false,
// events stuff
load: function(/*String*/type, /*Object*/data, /*Object*/transportImplementation, /*Object*/kwArgs){
// summary:
// Called on successful completion of a bind.
// type: String
// A string with value "load"
// data: Object
// The object representing the result of the bind. The actual structure
// of the data object will depend on the mimetype that was given to bind
// in the bind arguments.
// transportImplementation: Object
// The object that implements a particular transport. Structure is depedent
// on the transport. For XMLHTTPTransport (dojo.io.BrowserIO), it will be the
// XMLHttpRequest object from the browser.
// kwArgs: Object
// Object that contains the request parameters that were given to the
// bind call. Useful for storing and retrieving state from when bind
// was called.
},
error: function(/*String*/type, /*Object*/error, /*Object*/transportImplementation, /*Object*/kwArgs){
// summary:
// Called when there is an error with a bind.
// type: String
// A string with value "error"
// error: Object
// The error object. Should be a dojo.io.Error object, but not guaranteed.
// transportImplementation: Object
// The object that implements a particular transport. Structure is depedent
// on the transport. For XMLHTTPTransport (dojo.io.BrowserIO), it will be the
// XMLHttpRequest object from the browser.
// kwArgs: Object
// Object that contains the request parameters that were given to the
// bind call. Useful for storing and retrieving state from when bind
// was called.
},
timeout: function(/*String*/type, /*Object*/empty, /*Object*/transportImplementation, /*Object*/kwArgs){
// summary:
// Called when there is an error with a bind. Only implemented in certain transports at this time.
// type: String
// A string with value "timeout"
// empty: Object
// Should be null. Just a spacer argument so that load, error, timeout and handle have the
// same signatures.
// transportImplementation: Object
// The object that implements a particular transport. Structure is depedent
// on the transport. For XMLHTTPTransport (dojo.io.BrowserIO), it will be the
// XMLHttpRequest object from the browser. May be null for the timeout case for
// some transports.
// kwArgs: Object
// Object that contains the request parameters that were given to the
// bind call. Useful for storing and retrieving state from when bind
// was called.
},
handle: function(/*String*/type, /*Object*/data, /*Object*/transportImplementation, /*Object*/kwArgs){
// summary:
// The handle method can be defined instead of defining separate load, error and timeout
// callbacks.
// type: String
// A string with the type of callback: "load", "error", or "timeout".
// data: Object
// See the above callbacks for what this parameter could be.
// transportImplementation: Object
// The object that implements a particular transport. Structure is depedent
// on the transport. For XMLHTTPTransport (dojo.io.BrowserIO), it will be the
// XMLHttpRequest object from the browser.
// kwArgs: Object
// Object that contains the request parameters that were given to the
// bind call. Useful for storing and retrieving state from when bind
// was called.
},
//FIXME: change IframeIO.js to use timeouts?
// The number of seconds to wait until firing a timeout callback.
// If it is zero, that means, don't do a timeout check.
timeoutSeconds: 0,
// the abort method needs to be filled in by the transport that accepts the
// bind() request
abort: function(){ },
// backButton: function(){ },
// forwardButton: function(){ },
fromKwArgs: function(/*Object*/ kwArgs){
// summary:
// Creates a dojo.io.Request from a simple object (kwArgs object).
// normalize args
if(kwArgs["url"]){ kwArgs.url = kwArgs.url.toString(); }
if(kwArgs["formNode"]) { kwArgs.formNode = dojo.byId(kwArgs.formNode); }
if(!kwArgs["method"] && kwArgs["formNode"] && kwArgs["formNode"].method) {
kwArgs.method = kwArgs["formNode"].method;
}
// backwards compatibility
if(!kwArgs["handle"] && kwArgs["handler"]){ kwArgs.handle = kwArgs.handler; }
if(!kwArgs["load"] && kwArgs["loaded"]){ kwArgs.load = kwArgs.loaded; }
if(!kwArgs["changeUrl"] && kwArgs["changeURL"]) { kwArgs.changeUrl = kwArgs.changeURL; }
// encoding fun!
kwArgs.encoding = dojo.lang.firstValued(kwArgs["encoding"], djConfig["bindEncoding"], "");
kwArgs.sendTransport = dojo.lang.firstValued(kwArgs["sendTransport"], djConfig["ioSendTransport"], false);
var isFunction = dojo.lang.isFunction;
for(var x=0; x<dojo.io.hdlrFuncNames.length; x++){
var fn = dojo.io.hdlrFuncNames[x];
if(kwArgs[fn] && isFunction(kwArgs[fn])){ continue; }
if(kwArgs["handle"] && isFunction(kwArgs["handle"])){
kwArgs[fn] = kwArgs.handle;
}
// handler is aliased above, shouldn't need this check
/* else if(dojo.lang.isObject(kwArgs.handler)){
if(isFunction(kwArgs.handler[fn])){
kwArgs[fn] = kwArgs.handler[fn]||kwArgs.handler["handle"]||function(){};
}
}*/
}
dojo.lang.mixin(this, kwArgs);
}
});
dojo.io.Error = function(/*String*/ msg, /*String*/ type, /*Number*/num){
// summary:
// Constructs an object representing a bind error.
this.message = msg;
this.type = type || "unknown"; // must be one of "io", "parse", "unknown"
this.number = num || 0; // per-substrate error number, not normalized
}
dojo.io.transports.addTransport = function(/*String*/name){
// summary:
// Used to register transports that can support bind calls.
this.push(name);
// FIXME: do we need to handle things that aren't direct children of the
// dojo.io module? (say, dojo.io.foo.fooTransport?)
this[name] = dojo.io[name];
}
// binding interface, the various implementations register their capabilities
// and the bind() method dispatches
dojo.io.bind = function(/*dojo.io.Request or Object*/request){
// summary:
// Binding interface for IO. Loading different IO transports, like
// dojo.io.BrowserIO or dojo.io.IframeIO, will register with bind
// to handle particular types of bind calls.
// request: Object
// Object containing bind arguments. This object is converted to
// a dojo.io.Request object, and that request object is the return
// value for this method.
if(!(request instanceof dojo.io.Request)){
try{
request = new dojo.io.Request(request);
}catch(e){ dojo.debug(e); }
}
// if the request asks for a particular implementation, use it
var tsName = "";
if(request["transport"]){
tsName = request["transport"];
if(!this[tsName]){
dojo.io.sendBindError(request, "No dojo.io.bind() transport with name '"
+ request["transport"] + "'.");
return request; //dojo.io.Request
}
if(!this[tsName].canHandle(request)){
dojo.io.sendBindError(request, "dojo.io.bind() transport with name '"
+ request["transport"] + "' cannot handle this type of request.");
return request; //dojo.io.Request
}
}else{
// otherwise we do our best to auto-detect what available transports
// will handle
for(var x=0; x<dojo.io.transports.length; x++){
var tmp = dojo.io.transports[x];
if((this[tmp])&&(this[tmp].canHandle(request))){
tsName = tmp;
break;
}
}
if(tsName == ""){
dojo.io.sendBindError(request, "None of the loaded transports for dojo.io.bind()"
+ " can handle the request.");
return request; //dojo.io.Request
}
}
this[tsName].bind(request);
request.bindSuccess = true;
return request; //dojo.io.Request
}
dojo.io.sendBindError = function(/* Object */request, /* String */message){
// summary:
// Used internally by dojo.io.bind() to return/raise a bind error.
//Need to be careful since not all hostenvs support setTimeout.
if((typeof request.error == "function" || typeof request.handle == "function")
&& (typeof setTimeout == "function" || typeof setTimeout == "object")){
var errorObject = new dojo.io.Error(message);
setTimeout(function(){
request[(typeof request.error == "function") ? "error" : "handle"]("error", errorObject, null, request);
}, 50);
}else{
dojo.raise(message);
}
}
dojo.io.queueBind = function(/*dojo.io.Request or Object*/request){
// summary:
// queueBind will use dojo.io.bind() but guarantee that only one bind
// call is handled at a time.
// description:
// If queueBind is called while a bind call
// is in process, it will queue up the other calls to bind and call them
// in order as bind calls complete.
// request: Object
// Same sort of request object as used for dojo.io.bind().
if(!(request instanceof dojo.io.Request)){
try{
request = new dojo.io.Request(request);
}catch(e){ dojo.debug(e); }
}
// make sure we get called if/when we get a response
var oldLoad = request.load;
request.load = function(){
dojo.io._queueBindInFlight = false;
var ret = oldLoad.apply(this, arguments);
dojo.io._dispatchNextQueueBind();
return ret;
}
var oldErr = request.error;
request.error = function(){
dojo.io._queueBindInFlight = false;
var ret = oldErr.apply(this, arguments);
dojo.io._dispatchNextQueueBind();
return ret;
}
dojo.io._bindQueue.push(request);
dojo.io._dispatchNextQueueBind();
return request; //dojo.io.Request
}
dojo.io._dispatchNextQueueBind = function(){
// summary:
// Private method used by dojo.io.queueBind().
if(!dojo.io._queueBindInFlight){
dojo.io._queueBindInFlight = true;
if(dojo.io._bindQueue.length > 0){
dojo.io.bind(dojo.io._bindQueue.shift());
}else{
dojo.io._queueBindInFlight = false;
}
}
}
dojo.io._bindQueue = [];
dojo.io._queueBindInFlight = false;
dojo.io.argsFromMap = function(/*Object*/map, /*String?*/encoding, /*String?*/last){
// summary:
// Converts name/values pairs in the map object to an URL-encoded string
// with format of name1=value1&name2=value2...
// map: Object
// Object that has the contains the names and values.
// encoding: String?
// String to specify how to encode the name and value. If the encoding string
// contains "utf" (case-insensitive), then encodeURIComponent is used. Otherwise
// dojo.string.encodeAscii is used.
// last: String?
// The last parameter in the list. Helps with final string formatting?
var enc = /utf/i.test(encoding||"") ? encodeURIComponent : dojo.string.encodeAscii;
var mapped = [];
var control = new Object();
for(var name in map){
var domap = function(elt){
var val = enc(name)+"="+enc(elt);
mapped[(last == name) ? "push" : "unshift"](val);
}
if(!control[name]){
var value = map[name];
// FIXME: should be isArrayLike?
if (dojo.lang.isArray(value)){
dojo.lang.forEach(value, domap);
}else{
domap(value);
}
}
}
return mapped.join("&"); //String
}
dojo.io.setIFrameSrc = function(/*DOMNode*/ iframe, /*String*/ src, /*Boolean*/ replace){
//summary:
// Sets the URL that is loaded in an IFrame. The replace parameter indicates whether
// location.replace() should be used when changing the location of the iframe.
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.safari){
idoc = iframe.document;
}else{ // if(r.moz){
idoc = iframe.contentWindow;
}
//For Safari (at least 2.0.3) and Opera, if the iframe
//has just been created but it doesn't have content
//yet, then iframe.document may be null. In that case,
//use iframe.location and return.
if(!idoc){
iframe.location = src;
return;
}else{
idoc.location.replace(src);
}
}
}catch(e){
dojo.debug(e);
dojo.debug("setIFrameSrc: "+e);
}
}
/*
dojo.io.sampleTranport = new function(){
this.canHandle = function(kwArgs){
// canHandle just tells dojo.io.bind() if this is a good transport to
// use for the particular type of request.
if(
(
(kwArgs["mimetype"] == "text/plain") ||
(kwArgs["mimetype"] == "text/html") ||
(kwArgs["mimetype"] == "text/javascript")
)&&(
(kwArgs["method"] == "get") ||
( (kwArgs["method"] == "post") && (!kwArgs["formNode"]) )
)
){
return true;
}
return false;
}
this.bind = function(kwArgs){
var hdlrObj = {};
// set up a handler object
for(var x=0; x<dojo.io.hdlrFuncNames.length; x++){
var fn = dojo.io.hdlrFuncNames[x];
if(typeof kwArgs.handler == "object"){
if(typeof kwArgs.handler[fn] == "function"){
hdlrObj[fn] = kwArgs.handler[fn]||kwArgs.handler["handle"];
}
}else if(typeof kwArgs[fn] == "function"){
hdlrObj[fn] = kwArgs[fn];
}else{
hdlrObj[fn] = kwArgs["handle"]||function(){};
}
}
// build a handler function that calls back to the handler obj
var hdlrFunc = function(evt){
if(evt.type == "onload"){
hdlrObj.load("load", evt.data, evt);
}else if(evt.type == "onerr"){
var errObj = new dojo.io.Error("sampleTransport Error: "+evt.msg);
hdlrObj.error("error", errObj);
}
}
// the sample transport would attach the hdlrFunc() when sending the
// request down the pipe at this point
var tgtURL = kwArgs.url+"?"+dojo.io.argsFromMap(kwArgs.content);
// sampleTransport.sendRequest(tgtURL, hdlrFunc);
}
dojo.io.transports.addTransport("sampleTranport");
}
*/

View File

@@ -0,0 +1,130 @@
/*
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.cookie");
dojo.io.cookie.setCookie = function(/*String*/name, /*String*/value,
/*Number?*/days, /*String?*/path,
/*String?*/domain, /*boolean?*/secure){
//summary: sets a cookie.
var expires = -1;
if((typeof days == "number")&&(days >= 0)){
var d = new Date();
d.setTime(d.getTime()+(days*24*60*60*1000));
expires = d.toGMTString();
}
value = escape(value);
document.cookie = name + "=" + value + ";"
+ (expires != -1 ? " expires=" + expires + ";" : "")
+ (path ? "path=" + path : "")
+ (domain ? "; domain=" + domain : "")
+ (secure ? "; secure" : "");
}
dojo.io.cookie.set = dojo.io.cookie.setCookie;
dojo.io.cookie.getCookie = function(/*String*/name){
//summary: Gets a cookie with the given name.
// FIXME: Which cookie should we return?
// If there are cookies set for different sub domains in the current
// scope there could be more than one cookie with the same name.
// I think taking the last one in the list takes the one from the
// deepest subdomain, which is what we're doing here.
var idx = document.cookie.lastIndexOf(name+'=');
if(idx == -1) { return null; }
var value = document.cookie.substring(idx+name.length+1);
var end = value.indexOf(';');
if(end == -1) { end = value.length; }
value = value.substring(0, end);
value = unescape(value);
return value; //String
}
dojo.io.cookie.get = dojo.io.cookie.getCookie;
dojo.io.cookie.deleteCookie = function(/*String*/name){
//summary: Deletes a cookie with the given name.
dojo.io.cookie.setCookie(name, "-", 0);
}
dojo.io.cookie.setObjectCookie = function( /*String*/name, /*Object*/obj,
/*Number?*/days, /*String?*/path,
/*String?*/domain, /*boolean?*/secure,
/*boolean?*/clearCurrent){
//summary: Takes an object, serializes it to a cookie value, and either
//sets a cookie with the serialized value.
//description: If clearCurrent is true, then any current cookie value
//for this object will be replaced with the the new serialized object value.
//If clearCurrent is false, then the existing cookie value will be modified
//with any changes from the new object value.
//Objects must be simple name/value pairs where the value is either a string
//or a number. Any other value will be ignored.
if(arguments.length == 5){ // for backwards compat
clearCurrent = domain;
domain = null;
secure = null;
}
var pairs = [], cookie, value = "";
if(!clearCurrent){
cookie = dojo.io.cookie.getObjectCookie(name);
}
if(days >= 0){
if(!cookie){ cookie = {}; }
for(var prop in obj){
if(obj[prop] == null){
delete cookie[prop];
}else if((typeof obj[prop] == "string")||(typeof obj[prop] == "number")){
cookie[prop] = obj[prop];
}
}
prop = null;
for(var prop in cookie){
pairs.push(escape(prop) + "=" + escape(cookie[prop]));
}
value = pairs.join("&");
}
dojo.io.cookie.setCookie(name, value, days, path, domain, secure);
}
dojo.io.cookie.getObjectCookie = function(/*String*/name){
//summary: Gets an object value for the given cookie name. The complement of
//dojo.io.cookie.setObjectCookie().
var values = null, cookie = dojo.io.cookie.getCookie(name);
if(cookie){
values = {};
var pairs = cookie.split("&");
for(var i = 0; i < pairs.length; i++){
var pair = pairs[i].split("=");
var value = pair[1];
if( isNaN(value) ){ value = unescape(pair[1]); }
values[ unescape(pair[0]) ] = value;
}
}
return values;
}
dojo.io.cookie.isSupported = function(){
//summary: Tests the browser to see if cookies are enabled.
if(typeof navigator.cookieEnabled != "boolean"){
dojo.io.cookie.setCookie("__TestingYourBrowserForCookieSupport__",
"CookiesAllowed", 90, null);
var cookieVal = dojo.io.cookie.getCookie("__TestingYourBrowserForCookieSupport__");
navigator.cookieEnabled = (cookieVal == "CookiesAllowed");
if(navigator.cookieEnabled){
// FIXME: should we leave this around?
this.deleteCookie("__TestingYourBrowserForCookieSupport__");
}
}
return navigator.cookieEnabled; //boolean
}
// need to leave this in for backwards-compat from 0.1 for when it gets pulled in by dojo.io.*
if(!dojo.io.cookies){ dojo.io.cookies = dojo.io.cookie; }

View File

@@ -0,0 +1,254 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta>
<script type="text/javascript">
// <!--
/*
This file is really focused on just sending one message to the server, and
receiving one response. The code does not expect to be re-used for multiple messages.
This might be reworked later if performance indicates a need for it.
xip fragment identifier/hash values have the form:
#id:cmd:realEncodedMessage
id: some ID that should be unique among messages. No inherent meaning,
just something to make sure the hash value is unique so the message
receiver knows a new message is available.
cmd: command to the receiver. Valid values are:
- init: message used to init the frame. Sent as the first URL when loading
the page. Contains some config parameters.
- loaded: the remote frame is loaded. Only sent from server to client.
- ok: the message that this page sent was received OK. The next message may
now be sent.
- start: the start message of a block of messages (a complete message may
need to be segmented into many messages to get around the limitiations
of the size of an URL that a browser accepts.
- part: indicates this is a part of a message.
- end: the end message of a block of messages. The message can now be acted upon.
If the message is small enough that it doesn't need to be segmented, then
just one hash value message can be sent with "end" as the command.
To reassemble a segmented message, the realEncodedMessage parts just have to be concatenated
together.
*/
//MSIE has the lowest limit for URLs with fragment identifiers,
//at around 4K. Choosing a slightly smaller number for good measure.
xipUrlLimit = 4000;
xipIdCounter = 1;
function xipInit(){
xipStateId = "";
xipIsSending = false;
xipServerUrl = null;
xipStateId = null;
xipRequestData = null;
xipCurrentHash = "";
xipResponseMessage = "";
xipRequestParts = [];
xipPartIndex = 0;
xipServerWindow = null;
xipUseFrameRecursion = false;
}
xipInit();
function send(encodedData){
if(xipUseFrameRecursion == "true"){
var clientEndPoint = window.open(xipStateId + "_clientEndPoint");
clientEndPoint.send(encodedData);
}else{
if(!xipIsSending){
xipIsSending = true;
xipRequestData = encodedData || "";
//Get a handle to the server iframe.
xipServerWindow = frames[xipStateId + "_frame"];
if (!xipServerWindow){
xipServerWindow = document.getElementById(xipStateId + "_frame").contentWindow;
}
sendRequestStart();
}
}
}
//Modify the server URL if it is a local path and
//This is done for local/same domain testing.
function fixServerUrl(ifpServerUrl){
if(ifpServerUrl.indexOf("..") == 0){
var parts = ifpServerUrl.split("/");
ifpServerUrl = parts[parts.length - 1];
}
return ifpServerUrl;
}
function pollHash(){
//Can't use location.hash because at least Firefox does a decodeURIComponent on it.
var urlParts = window.location.href.split("#");
if(urlParts.length == 2){
var newHash = urlParts[1];
if(newHash != xipCurrentHash){
try{
messageReceived(newHash);
}catch(e){
//Make sure to not keep processing the error hash value.
xipCurrentHash = newHash;
throw e;
}
xipCurrentHash = newHash;
}
}
}
function messageReceived(encodedData){
var msg = unpackMessage(encodedData);
switch(msg.command){
case "loaded":
xipMasterFrame.dojo.io.XhrIframeProxy.clientFrameLoaded(xipStateId);
break;
case "ok":
sendRequestPart();
break;
case "start":
xipResponseMessage = "";
xipResponseMessage += msg.message;
setServerUrl("ok");
break;
case "part":
xipResponseMessage += msg.message;
setServerUrl("ok");
break;
case "end":
setServerUrl("ok");
xipResponseMessage += msg.message;
xipMasterFrame.dojo.io.XhrIframeProxy.receive(xipStateId, xipResponseMessage);
break;
}
}
function sendRequestStart(){
//Break the message into parts, if necessary.
xipRequestParts = [];
var reqData = xipRequestData;
var urlLength = xipServerUrl.length;
var partLength = xipUrlLimit - urlLength;
var reqIndex = 0;
while((reqData.length - reqIndex) + urlLength > xipUrlLimit){
xipRequestParts.push(reqData.substring(reqIndex, reqIndex + partLength));
reqIndex += partLength;
}
xipRequestParts.push(reqData.substring(reqIndex, reqData.length));
xipPartIndex = 0;
sendRequestPart();
}
function sendRequestPart(){
if(xipPartIndex < xipRequestParts.length){
//Get the message part.
var partData = xipRequestParts[xipPartIndex];
//Get the command.
var cmd = "part";
if(xipPartIndex + 1 == xipRequestParts.length){
cmd = "end";
}else if (xipPartIndex == 0){
cmd = "start";
}
setServerUrl(cmd, partData);
xipPartIndex++;
}
}
function setServerUrl(cmd, message){
var serverUrl = makeServerUrl(cmd, message);
//Safari won't let us replace across domains.
if(navigator.userAgent.indexOf("Safari") == -1){
xipServerWindow.location.replace(serverUrl);
}else{
xipServerWindow.location = serverUrl;
}
}
function makeServerUrl(cmd, message){
var serverUrl = xipServerUrl + "#" + (xipIdCounter++) + ":" + cmd;
if(message){
serverUrl += ":" + message;
}
return serverUrl;
}
function unpackMessage(encodedMessage){
var parts = encodedMessage.split(":");
var command = parts[1];
encodedMessage = parts[2] || "";
var config = null;
if(command == "init"){
var configParts = encodedMessage.split("&");
config = {};
for(var i = 0; i < configParts.length; i++){
var nameValue = configParts[i].split("=");
config[decodeURIComponent(nameValue[0])] = decodeURIComponent(nameValue[1]);
}
}
return {command: command, message: encodedMessage, config: config};
}
function onClientLoad(){
//Decode the init params
var config = unpackMessage(window.location.href.split("#")[1]).config;
xipStateId = config.id;
//Remove the query param for the IE7 recursive case.
xipServerUrl = fixServerUrl(config.server).replace(/(\?|\&)dojo\.fr\=1/, "");
xipUseFrameRecursion = config["fr"];
if(xipUseFrameRecursion == "endpoint"){
xipMasterFrame = parent.parent;
}else{
xipMasterFrame = parent;
}
//Start counter to inspect hash value.
setInterval(pollHash, 10);
var clientUrl = window.location.href.split("#")[0];
document.getElementById("iframeHolder").innerHTML = '<iframe src="'
+ makeServerUrl("init", 'id=' + xipStateId + '&client=' + encodeURIComponent(clientUrl)
+ '&fr=' + xipUseFrameRecursion) + '" id="' + xipStateId + '_frame"></iframe>';
}
if(typeof(window.addEventListener) == "undefined"){
window.attachEvent("onload", onClientLoad);
}else{
window.addEventListener('load', onClientLoad, false);
}
// -->
</script>
</head>
<body>
<h4>The Dojo Toolkit -- xip_client.html</h4>
<p>This file is used for Dojo's XMLHttpRequest Iframe Proxy. This is the "client" file used
internally by dojo.io.XhrIframeProxy.</p>
<span id="iframeHolder"></span>
</body>
</html>

View File

@@ -0,0 +1,371 @@
<!--
/*
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
*/
Pieces taken from Dojo source to make this file stand-alone
-->
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta>
<script type="text/javascript" src="isAllowed.js"></script>
<!--
BY DEFAULT THIS FILE DOES NOT WORK SO THAT YOU DON'T ACCIDENTALLY EXPOSE
ALL OF YOUR XHR-ENABLED SERVICES ON YOUR SITE.
In order for this file to work, you should define a function with the following signature:
function isAllowedRequest(request){
return false;
}
Return true out of the function if you want to allow the cross-domain request.
DON'T DEFINE THIS FUNCTION IN THIS FILE! Define it in a separate file called isAllowed.js
and include it in this page with a script tag that has a src attribute pointing to the file.
See the very first script tag in this file for an example. You do not have to place the
script file in the same directory as this file, just update the path above if you move it
somewhere else.
Customize the isAllowedRequest function to restrict what types of requests are allowed
for this server. The request object has the following properties:
- requestHeaders: an object with the request headers that are to be added to
the XHR request.
- method: the HTTP method (GET, POST, etc...)
- uri: The URI for the request.
- data: The URL-encoded data for the request. For a GET request, this would
be the querystring parameters. For a POST request, it wll be the
body data.
See xip_client.html for more info on the xip fragment identifier protocol.
-->
<script type="text/javascript">
// <!--
djConfig = {
parseWidgets: false,
baseScriptUri: "./"
}
// -->
</script>
<script type="text/javascript">
// <!--
//Core XHR handling taken from Dojo IO code.
dojo = {};
dojo.hostenv = {};
// These are in order of decreasing likelihood; this will change in time.
dojo.hostenv._XMLHTTP_PROGIDS = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'];
dojo.hostenv.getXmlhttpObject = function(){
var http = null;
var last_e = null;
try{ http = new XMLHttpRequest(); }catch(e){}
if(!http){
for(var i=0; i<3; ++i){
var progid = dojo.hostenv._XMLHTTP_PROGIDS[i];
try{
http = new ActiveXObject(progid);
}catch(e){
last_e = e;
}
if(http){
dojo.hostenv._XMLHTTP_PROGIDS = [progid]; // so faster next time
break;
}
}
/*if(http && !http.toString) {
http.toString = function() { "[object XMLHttpRequest]"; }
}*/
}
if(!http){
throw "xip_server.html: XMLHTTP not available: " + last_e;
}
return http;
}
dojo.setHeaders = function(http, headers){
if(headers) {
for(var header in headers) {
var headerValue = headers[header];
http.setRequestHeader(header, headerValue);
}
}
}
//MSIE has the lowest limit for URLs with fragment identifiers,
//at around 4K. Choosing a slightly smaller number for good measure.
xipUrlLimit = 4000;
xipIdCounter = 1;
function xipServerInit(){
xipStateId = "";
xipCurrentHash = "";
xipRequestMessage = "";
xipResponseParts = [];
xipPartIndex = 0;
}
function pollHash(){
//Can't use location.hash because at least Firefox does a decodeURIComponent on it.
var urlParts = window.location.href.split("#");
if(urlParts.length == 2){
var newHash = urlParts[1];
if(newHash != xipCurrentHash){
try{
messageReceived(newHash);
}catch(e){
//Make sure to not keep processing the error hash value.
xipCurrentHash = newHash;
throw e;
}
xipCurrentHash = newHash;
}
}
}
function messageReceived(encodedData){
var msg = unpackMessage(encodedData);
switch(msg.command){
case "ok":
sendResponsePart();
break;
case "start":
xipRequestMessage = "";
xipRequestMessage += msg.message;
setClientUrl("ok");
break;
case "part":
xipRequestMessage += msg.message;
setClientUrl("ok");
break;
case "end":
setClientUrl("ok");
xipRequestMessage += msg.message;
sendXhr();
break;
}
}
function sendResponse(encodedData){
//Break the message into parts, if necessary.
xipResponseParts = [];
var resData = encodedData;
var urlLength = xipClientUrl.length;
var partLength = xipUrlLimit - urlLength;
var resIndex = 0;
while((resData.length - resIndex) + urlLength > xipUrlLimit){
xipResponseParts.push(resData.substring(resIndex, resIndex + partLength));
resIndex += partLength;
}
xipResponseParts.push(resData.substring(resIndex, resData.length));
xipPartIndex = 0;
sendResponsePart();
}
function sendResponsePart(){
if(xipPartIndex < xipResponseParts.length){
//Get the message part.
var partData = xipResponseParts[xipPartIndex];
//Get the command.
var cmd = "part";
if(xipPartIndex + 1 == xipResponseParts.length){
cmd = "end";
}else if (xipPartIndex == 0){
cmd = "start";
}
setClientUrl(cmd, partData);
xipPartIndex++;
}else{
xipServerInit();
}
}
function setClientUrl(cmd, message){
var clientUrl = makeClientUrl(cmd, message);
//Safari won't let us replace across domains.
if(navigator.userAgent.indexOf("Safari") == -1){
parent.location.replace(clientUrl);
}else{
parent.location = clientUrl;
}
}
function makeClientUrl(cmd, message){
var clientUrl = xipClientUrl + "#" + (xipIdCounter++) + ":" + cmd;
if(message){
clientUrl += ":" + message;
}
return clientUrl
}
function xhrDone(xhr){
/* Need to pull off and return the following data:
- responseHeaders
- status
- statusText
- responseText
*/
var response = {};
if(typeof(xhr.getAllResponseHeaders) != "undefined"){
var allHeaders = xhr.getAllResponseHeaders();
if(allHeaders){
response.responseHeaders = allHeaders;
}
}
if(xhr.status == 0 || xhr.status){
response.status = xhr.status;
}
if(xhr.statusText){
response.statusText = xhr.statusText;
}
if(xhr.responseText){
response.responseText = xhr.responseText;
}
//Build a string of the response object.
var result = "";
var isFirst = true;
for (var param in response){
if(isFirst){
isFirst = false;
}else{
result += "&";
}
result += param + "=" + encodeURIComponent(response[param]);
}
sendResponse(result);
}
function sendXhr(){
var request = {};
var nvPairs = xipRequestMessage.split("&");
var i = 0;
var nameValue = null;
for(i = 0; i < nvPairs.length; i++){
if(nvPairs[i]){
var nameValue = nvPairs[i].split("=");
request[decodeURIComponent(nameValue[0])] = decodeURIComponent(nameValue[1]);
}
}
//Split up the request headers, if any.
var headers = {};
if(request.requestHeaders){
nvPairs = request.requestHeaders.split("\r\n");
for(i = 0; i < nvPairs.length; i++){
if(nvPairs[i]){
nameValue = nvPairs[i].split(": ");
headers[decodeURIComponent(nameValue[0])] = decodeURIComponent(nameValue[1]);
}
}
request.requestHeaders = headers;
}
if(isAllowedRequest(request)){
//The request is allowed, so set up the XHR object.
var xhr = dojo.hostenv.getXmlhttpObject();
//Start timer to look for readyState.
var xhrIntervalId = setInterval(function(){
if(xhr.readyState == 4){
clearInterval(xhrIntervalId);
xhrDone(xhr);
}
}, 10);
//Actually start up the XHR request.
xhr.open(request.method, request.uri, true);
dojo.setHeaders(xhr, request.requestHeaders);
var content = "";
if(request.data){
content = request.data;
}
try{
xhr.send(content);
}catch(e){
if(typeof xhr.abort == "function"){
xhr.abort();
xhrDone({status: 404, statusText: "xip_server.html error: " + e});
}
}
}
}
function unpackMessage(encodedMessage){
var parts = encodedMessage.split(":");
var command = parts[1];
encodedMessage = parts[2] || "";
var config = null;
if(command == "init"){
var configParts = encodedMessage.split("&");
config = {};
for(var i = 0; i < configParts.length; i++){
var nameValue = configParts[i].split("=");
config[decodeURIComponent(nameValue[0])] = decodeURIComponent(nameValue[1]);
}
}
return {command: command, message: encodedMessage, config: config};
}
function onServerLoad(){
xipServerInit();
//Decode the init params
var config = unpackMessage(window.location.href.split("#")[1]).config;
xipStateId = config.id;
xipClientUrl = config.client;
xipUseFrameRecursion = config["fr"];
setInterval(pollHash, 10);
if(xipUseFrameRecursion == "true"){
var serverUrl = window.location.href.split("#")[0];
document.getElementById("iframeHolder").innerHTML = '<iframe src="'
+ makeClientUrl("init", 'id=' + xipStateId + '&server=' + encodeURIComponent(serverUrl)
+ '&fr=endpoint') + '" name="' + xipStateId + '_clientEndPoint"></iframe>';
}else{
setClientUrl("loaded");
}
}
if(typeof(window.addEventListener) == "undefined"){
window.attachEvent("onload", onServerLoad);
}else{
window.addEventListener('load', onServerLoad, false);
}
// -->
</script>
</head>
<body>
<h4>The Dojo Toolkit -- xip_server.html</h4>
<p>This file is used for Dojo's XMLHttpRequest Iframe Proxy. This is the the file
that should go on the server that will actually be doing the XHR request.</p>
<div id="iframeHolder"></div>
</body>
</html>