Subversion Repositories Applications.papyrus

Compare Revisions

No changes between revisions

Ignore whitespace Rev 2149 → Rev 2150

/trunk/api/js/dojo1.0/dojox/off/README
New file
0,0 → 1,0
See http://docs.google.com/View?docid=dhkhksk4_8gdp9gr for documentation and a tutorial on using Dojo Offline.
/trunk/api/js/dojo1.0/dojox/off/files.js
New file
0,0 → 1,449
if(!dojo._hasResource["dojox.off.files"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.off.files"] = true;
dojo.provide("dojox.off.files");
 
// Author: Brad Neuberg, bkn3@columbia.edu, http://codinginparadise.org
 
// summary:
// Helps maintain resources that should be
// available offline, such as CSS files.
// description:
// dojox.off.files makes it easy to indicate
// what resources should be available offline,
// such as CSS files, JavaScript, HTML, etc.
dojox.off.files = {
// versionURL: String
// An optional file, that if present, records the version
// of our bundle of files to make available offline. If this
// file is present, and we are not currently debugging,
// then we only refresh our offline files if the version has
// changed.
versionURL: "version.js",
// listOfURLs: Array
// For advanced usage; most developers can ignore this.
// Our list of URLs that will be cached and made available
// offline.
listOfURLs: [],
// refreshing: boolean
// For advanced usage; most developers can ignore this.
// Whether we are currently in the middle
// of refreshing our list of offline files.
refreshing: false,
 
_cancelID: null,
_error: false,
_errorMessages: [],
_currentFileIndex: 0,
_store: null,
_doSlurp: false,
slurp: function(){
// summary:
// Autoscans the page to find all resources to
// cache. This includes scripts, images, CSS, and hyperlinks
// to pages that are in the same scheme/port/host as this
// page. We also scan the embedded CSS of any stylesheets
// to find @import statements and url()'s.
// You should call this method from the top-level, outside of
// any functions and before the page loads:
//
// <script>
// dojo.require("dojox.sql");
// dojo.require("dojox.off");
// dojo.require("dojox.off.ui");
// dojo.require("dojox.off.sync");
//
// // configure how we should work offline
//
// // set our application name
// dojox.off.ui.appName = "Moxie";
//
// // automatically "slurp" the page and
// // capture the resources we need offline
// dojox.off.files.slurp();
//
// // tell Dojo Offline we are ready for it to initialize itself now
// // that we have finished configuring it for our application
// dojox.off.initialize();
// </script>
//
// Note that inline styles on elements are not handled (i.e.
// if you somehow have an inline style that uses a URL);
// object and embed tags are not scanned since their format
// differs based on type; and elements created by JavaScript
// after page load are not found. For these you must manually
// add them with a dojox.off.files.cache() method call.
// just schedule the slurp once the page is loaded and
// Dojo Offline is ready to slurp; dojox.off will call
// our _slurp() method before indicating it is finished
// loading
this._doSlurp = true;
},
cache: function(urlOrList){ /* void */
// summary:
// Caches a file or list of files to be available offline. This
// can either be a full URL, such as http://foobar.com/index.html,
// or a relative URL, such as ../index.html. This URL is not
// actually cached until dojox.off.sync.synchronize() is called.
// urlOrList: String or Array[]
// A URL of a file to cache or an Array of Strings of files to
// cache
//console.debug("dojox.off.files.cache, urlOrList="+urlOrList);
if(dojo.isString(urlOrList)){
var url = this._trimAnchor(urlOrList+"");
if(!this.isAvailable(url)){
this.listOfURLs.push(url);
}
}else if(urlOrList instanceof dojo._Url){
var url = this._trimAnchor(urlOrList.uri);
if(!this.isAvailable(url)){
this.listOfURLs.push(url);
}
}else{
dojo.forEach(urlOrList, function(url){
url = this._trimAnchor(url);
if(!this.isAvailable(url)){
this.listOfURLs.push(url);
}
}, this);
}
},
printURLs: function(){
// summary:
// A helper function that will dump and print out
// all of the URLs that are cached for offline
// availability. This can help with debugging if you
// are trying to make sure that all of your URLs are
// available offline
console.debug("The following URLs are cached for offline use:");
dojo.forEach(this.listOfURLs, function(i){
console.debug(i);
});
},
remove: function(url){ /* void */
// summary:
// Removes a URL from the list of files to cache.
// description:
// Removes a URL from the list of URLs to cache. Note that this
// does not actually remove the file from the offline cache;
// instead, it just prevents us from refreshing this file at a
// later time, so that it will naturally time out and be removed
// from the offline cache
// url: String
// The URL to remove
for(var i = 0; i < this.listOfURLs.length; i++){
if(this.listOfURLs[i] == url){
this.listOfURLs = this.listOfURLs.splice(i, 1);
break;
}
}
},
isAvailable: function(url){ /* boolean */
// summary:
// Determines whether the given resource is available offline.
// url: String
// The URL to check
for(var i = 0; i < this.listOfURLs.length; i++){
if(this.listOfURLs[i] == url){
return true;
}
}
return false;
},
refresh: function(callback){ /* void */
//console.debug("dojox.off.files.refresh");
// summary:
// For advanced usage; most developers can ignore this.
// Refreshes our list of offline resources,
// making them available offline.
// callback: Function
// A callback that receives two arguments: whether an error
// occurred, which is a boolean; and an array of error message strings
// with details on errors encountered. If no error occured then message is
// empty array with length 0.
try{
if(djConfig.isDebug){
this.printURLs();
}
this.refreshing = true;
if(this.versionURL){
this._getVersionInfo(function(oldVersion, newVersion, justDebugged){
//console.warn("getVersionInfo, oldVersion="+oldVersion+", newVersion="+newVersion
// + ", justDebugged="+justDebugged+", isDebug="+djConfig.isDebug);
if(djConfig.isDebug || !newVersion || justDebugged
|| !oldVersion || oldVersion != newVersion){
console.warn("Refreshing offline file list");
this._doRefresh(callback, newVersion);
}else{
console.warn("No need to refresh offline file list");
callback(false, []);
}
});
}else{
console.warn("Refreshing offline file list");
this._doRefresh(callback);
}
}catch(e){
this.refreshing = false;
// can't refresh files -- core operation --
// fail fast
dojox.off.coreOpFailed = true;
dojox.off.enabled = false;
dojox.off.onFrameworkEvent("coreOperationFailed");
}
},
abortRefresh: function(){
// summary:
// For advanced usage; most developers can ignore this.
// Aborts and cancels a refresh.
if(!this.refreshing){
return;
}
this._store.abortCapture(this._cancelID);
this.refreshing = false;
},
_slurp: function(){
if(!this._doSlurp){
return;
}
var handleUrl = dojo.hitch(this, function(url){
if(this._sameLocation(url)){
this.cache(url);
}
});
handleUrl(window.location.href);
dojo.query("script").forEach(function(i){
try{
handleUrl(i.getAttribute("src"));
}catch(exp){
//console.debug("dojox.off.files.slurp 'script' error: "
// + exp.message||exp);
}
});
dojo.query("link").forEach(function(i){
try{
if(!i.getAttribute("rel")
|| i.getAttribute("rel").toLowerCase() != "stylesheet"){
return;
}
handleUrl(i.getAttribute("href"));
}catch(exp){
//console.debug("dojox.off.files.slurp 'link' error: "
// + exp.message||exp);
}
});
dojo.query("img").forEach(function(i){
try{
handleUrl(i.getAttribute("src"));
}catch(exp){
//console.debug("dojox.off.files.slurp 'img' error: "
// + exp.message||exp);
}
});
dojo.query("a").forEach(function(i){
try{
handleUrl(i.getAttribute("href"));
}catch(exp){
//console.debug("dojox.off.files.slurp 'a' error: "
// + exp.message||exp);
}
});
// FIXME: handle 'object' and 'embed' tag
// parse our style sheets for inline URLs and imports
dojo.forEach(document.styleSheets, function(sheet){
try{
if(sheet.cssRules){ // Firefox
dojo.forEach(sheet.cssRules, function(rule){
var text = rule.cssText;
if(text){
var matches = text.match(/url\(\s*([^\) ]*)\s*\)/i);
if(!matches){
return;
}
for(var i = 1; i < matches.length; i++){
handleUrl(matches[i])
}
}
});
}else if(sheet.cssText){ // IE
var matches;
var text = sheet.cssText.toString();
// unfortunately, using RegExp.exec seems to be flakey
// for looping across multiple lines on IE using the
// global flag, so we have to simulate it
var lines = text.split(/\f|\r|\n/);
for(var i = 0; i < lines.length; i++){
matches = lines[i].match(/url\(\s*([^\) ]*)\s*\)/i);
if(matches && matches.length){
handleUrl(matches[1]);
}
}
}
}catch(exp){
//console.debug("dojox.off.files.slurp stylesheet parse error: "
// + exp.message||exp);
}
});
//this.printURLs();
},
_sameLocation: function(url){
if(!url){ return false; }
// filter out anchors
if(url.length && url.charAt(0) == "#"){
return false;
}
// FIXME: dojo._Url should be made public;
// it's functionality is very useful for
// parsing URLs correctly, which is hard to
// do right
url = new dojo._Url(url);
// totally relative -- ../../someFile.html
if(!url.scheme && !url.port && !url.host){
return true;
}
// scheme relative with port specified -- brad.com:8080
if(!url.scheme && url.host && url.port
&& window.location.hostname == url.host
&& window.location.port == url.port){
return true;
}
// scheme relative with no-port specified -- brad.com
if(!url.scheme && url.host && !url.port
&& window.location.hostname == url.host
&& window.location.port == 80){
return true;
}
// else we have everything
return window.location.protocol == (url.scheme + ":")
&& window.location.hostname == url.host
&& (window.location.port == url.port || !window.location.port && !url.port);
},
_trimAnchor: function(url){
return url.replace(/\#.*$/, "");
},
_doRefresh: function(callback, newVersion){
// get our local server
var localServer;
try{
localServer = google.gears.factory.create("beta.localserver", "1.0");
}catch(exp){
dojo.setObject("google.gears.denied", true);
dojox.off.onFrameworkEvent("coreOperationFailed");
throw "Google Gears must be allowed to run";
}
var storeName = "dot_store_"
+ window.location.href.replace(/[^0-9A-Za-z_]/g, "_");
// refresh everything by simply removing
// any older stores
localServer.removeStore(storeName);
// open/create the resource store
localServer.openStore(storeName);
var store = localServer.createStore(storeName);
this._store = store;
 
// add our list of files to capture
var self = this;
this._currentFileIndex = 0;
this._cancelID = store.capture(this.listOfURLs, function(url, success, captureId){
//console.debug("store.capture, url="+url+", success="+success);
if(!success && self.refreshing){
self._cancelID = null;
self.refreshing = false;
var errorMsgs = [];
errorMsgs.push("Unable to capture: " + url);
callback(true, errorMsgs);
return;
}else if(success){
self._currentFileIndex++;
}
if(success && self._currentFileIndex >= self.listOfURLs.length){
self._cancelID = null;
self.refreshing = false;
if(newVersion){
dojox.storage.put("oldVersion", newVersion, null,
dojox.off.STORAGE_NAMESPACE);
}
dojox.storage.put("justDebugged", djConfig.isDebug, null,
dojox.off.STORAGE_NAMESPACE);
callback(false, []);
}
});
},
_getVersionInfo: function(callback){
var justDebugged = dojox.storage.get("justDebugged",
dojox.off.STORAGE_NAMESPACE);
var oldVersion = dojox.storage.get("oldVersion",
dojox.off.STORAGE_NAMESPACE);
var newVersion = null;
callback = dojo.hitch(this, callback);
dojo.xhrGet({
url: this.versionURL + "?browserbust=" + new Date().getTime(),
timeout: 5 * 1000,
handleAs: "javascript",
error: function(err){
//console.warn("dojox.off.files._getVersionInfo, err=",err);
dojox.storage.remove("oldVersion", dojox.off.STORAGE_NAMESPACE);
dojox.storage.remove("justDebugged", dojox.off.STORAGE_NAMESPACE);
callback(oldVersion, newVersion, justDebugged);
},
load: function(data){
//console.warn("dojox.off.files._getVersionInfo, load=",data);
// some servers incorrectly return 404's
// as a real page
if(data){
newVersion = data;
}
callback(oldVersion, newVersion, justDebugged);
}
});
}
}
 
}
/trunk/api/js/dojo1.0/dojox/off/offline.js
New file
0,0 → 1,20
/*
Copyright (c) 2004-2007, 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/book/dojo-book-0-9/introduction/licensing
*/
 
/*
This is a compiled version of Dojo, built for deployment and not for
development. To get an editable version, please visit:
 
http://dojotoolkit.org
 
for documentation and information on getting the source.
*/
 
if(!dojo._hasResource["dojox.storage.Provider"]){dojo._hasResource["dojox.storage.Provider"]=true;dojo.provide("dojox.storage.Provider");dojo.declare("dojox.storage.Provider",null,{constructor:function(){},SUCCESS:"success",FAILED:"failed",PENDING:"pending",SIZE_NOT_AVAILABLE:"Size not available",SIZE_NO_LIMIT:"No size limit",DEFAULT_NAMESPACE:"default",onHideSettingsUI:null,initialize:function(){console.warn("dojox.storage.initialize not implemented");},isAvailable:function(){console.warn("dojox.storage.isAvailable not implemented");},put:function(_1,_2,_3,_4){console.warn("dojox.storage.put not implemented");},get:function(_5,_6){console.warn("dojox.storage.get not implemented");},hasKey:function(_7,_8){return (this.get(_7)!=null);},getKeys:function(_9){console.warn("dojox.storage.getKeys not implemented");},clear:function(_a){console.warn("dojox.storage.clear not implemented");},remove:function(_b,_c){console.warn("dojox.storage.remove not implemented");},getNamespaces:function(){console.warn("dojox.storage.getNamespaces not implemented");},isPermanent:function(){console.warn("dojox.storage.isPermanent not implemented");},getMaximumSize:function(){console.warn("dojox.storage.getMaximumSize not implemented");},putMultiple:function(_d,_e,_f,_10){console.warn("dojox.storage.putMultiple not implemented");},getMultiple:function(_11,_12){console.warn("dojox.storage.getMultiple not implemented");},removeMultiple:function(_13,_14){console.warn("dojox.storage.remove not implemented");},isValidKeyArray:function(_15){if(_15===null||typeof _15==="undefined"||!_15 instanceof Array){return false;}for(var k=0;k<_15.length;k++){if(!this.isValidKey(_15[k])){return false;}}return true;},hasSettingsUI:function(){return false;},showSettingsUI:function(){console.warn("dojox.storage.showSettingsUI not implemented");},hideSettingsUI:function(){console.warn("dojox.storage.hideSettingsUI not implemented");},isValidKey:function(_17){if((_17==null)||(typeof _17=="undefined")){return false;}return /^[0-9A-Za-z_]*$/.test(_17);},getResourceList:function(){return [];}});}if(!dojo._hasResource["dojox.storage.manager"]){dojo._hasResource["dojox.storage.manager"]=true;dojo.provide("dojox.storage.manager");dojox.storage.manager=new function(){this.currentProvider=null;this.available=false;this._initialized=false;this._providers=[];this._onLoadListeners=[];this.initialize=function(){this.autodetect();};this.register=function(_18,_19){this._providers[this._providers.length]=_19;this._providers[_18]=_19;};this.setProvider=function(_1a){};this.autodetect=function(){if(this._initialized){return;}var _1b=djConfig["forceStorageProvider"]||false;var _1c;for(var i=0;i<this._providers.length;i++){_1c=this._providers[i];if(_1b==_1c.declaredClass){_1c.isAvailable();break;}else{if(_1c.isAvailable()){break;}}}if(!_1c){this._initialized=true;this.available=false;this.currentProvider=null;console.warn("No storage provider found for this platform");this.loaded();return;}this.currentProvider=_1c;dojo.mixin(dojox.storage,this.currentProvider);dojox.storage.initialize();this._initialized=true;this.available=true;};this.isAvailable=function(){return this.available;};this.addOnLoad=function(_1e){this._onLoadListeners.push(_1e);if(this.isInitialized()){this._fireLoaded();}};this.removeOnLoad=function(_1f){for(var i=0;i<this._onLoadListeners.length;i++){if(_1f==this._onLoadListeners[i]){this._onLoadListeners=this._onLoadListeners.splice(i,1);break;}}};this.isInitialized=function(){if(this.currentProvider!=null&&this.currentProvider.declaredClass=="dojox.storage.FlashStorageProvider"&&dojox.flash.ready==false){return false;}else{return this._initialized;}};this.supportsProvider=function(_21){try{var _22=eval("new "+_21+"()");var _23=_22.isAvailable();if(!_23){return false;}return _23;}catch(e){return false;}};this.getProvider=function(){return this.currentProvider;};this.loaded=function(){this._fireLoaded();};this._fireLoaded=function(){dojo.forEach(this._onLoadListeners,function(i){try{i();}catch(e){console.debug(e);}});};this.getResourceList=function(){var _25=[];dojo.forEach(dojox.storage.manager._providers,function(_26){_25=_25.concat(_26.getResourceList());});return _25;};};}if(!dojo._hasResource["dojox._sql._crypto"]){dojo._hasResource["dojox._sql._crypto"]=true;dojo.provide("dojox._sql._crypto");dojo.mixin(dojox._sql._crypto,{_POOL_SIZE:100,encrypt:function(_27,_28,_29){this._initWorkerPool();var msg={plaintext:_27,password:_28};msg=dojo.toJson(msg);msg="encr:"+String(msg);this._assignWork(msg,_29);},decrypt:function(_2b,_2c,_2d){this._initWorkerPool();var msg={ciphertext:_2b,password:_2c};msg=dojo.toJson(msg);msg="decr:"+String(msg);this._assignWork(msg,_2d);},_initWorkerPool:function(){if(!this._manager){try{this._manager=google.gears.factory.create("beta.workerpool","1.0");this._unemployed=[];this._employed={};this._handleMessage=[];var _2f=this;this._manager.onmessage=function(msg,_31){var _32=_2f._employed["_"+_31];_2f._employed["_"+_31]=undefined;_2f._unemployed.push("_"+_31);if(_2f._handleMessage.length){var _33=_2f._handleMessage.shift();_2f._assignWork(_33.msg,_33.callback);}_32(msg);};var _34="function _workerInit(){"+"gearsWorkerPool.onmessage = "+String(this._workerHandler)+";"+"}";var _35=_34+" _workerInit();";for(var i=0;i<this._POOL_SIZE;i++){this._unemployed.push("_"+this._manager.createWorker(_35));}}catch(exp){throw exp.message||exp;}}},_assignWork:function(msg,_38){if(!this._handleMessage.length&&this._unemployed.length){var _39=this._unemployed.shift().substring(1);this._employed["_"+_39]=_38;this._manager.sendMessage(msg,_39);}else{this._handleMessage={msg:msg,callback:_38};}},_workerHandler:function(msg,_3b){var _3c=[99,124,119,123,242,107,111,197,48,1,103,43,254,215,171,118,202,130,201,125,250,89,71,240,173,212,162,175,156,164,114,192,183,253,147,38,54,63,247,204,52,165,229,241,113,216,49,21,4,199,35,195,24,150,5,154,7,18,128,226,235,39,178,117,9,131,44,26,27,110,90,160,82,59,214,179,41,227,47,132,83,209,0,237,32,252,177,91,106,203,190,57,74,76,88,207,208,239,170,251,67,77,51,133,69,249,2,127,80,60,159,168,81,163,64,143,146,157,56,245,188,182,218,33,16,255,243,210,205,12,19,236,95,151,68,23,196,167,126,61,100,93,25,115,96,129,79,220,34,42,144,136,70,238,184,20,222,94,11,219,224,50,58,10,73,6,36,92,194,211,172,98,145,149,228,121,231,200,55,109,141,213,78,169,108,86,244,234,101,122,174,8,186,120,37,46,28,166,180,198,232,221,116,31,75,189,139,138,112,62,181,102,72,3,246,14,97,53,87,185,134,193,29,158,225,248,152,17,105,217,142,148,155,30,135,233,206,85,40,223,140,161,137,13,191,230,66,104,65,153,45,15,176,84,187,22];var _3d=[[0,0,0,0],[1,0,0,0],[2,0,0,0],[4,0,0,0],[8,0,0,0],[16,0,0,0],[32,0,0,0],[64,0,0,0],[128,0,0,0],[27,0,0,0],[54,0,0,0]];function Cipher(_3e,w){var Nb=4;var Nr=w.length/Nb-1;var _42=[[],[],[],[]];for(var i=0;i<4*Nb;i++){_42[i%4][Math.floor(i/4)]=_3e[i];}_42=AddRoundKey(_42,w,0,Nb);for(var _44=1;_44<Nr;_44++){_42=SubBytes(_42,Nb);_42=ShiftRows(_42,Nb);_42=MixColumns(_42,Nb);_42=AddRoundKey(_42,w,_44,Nb);}_42=SubBytes(_42,Nb);_42=ShiftRows(_42,Nb);_42=AddRoundKey(_42,w,Nr,Nb);var _45=new Array(4*Nb);for(var i=0;i<4*Nb;i++){_45[i]=_42[i%4][Math.floor(i/4)];}return _45;};function SubBytes(s,Nb){for(var r=0;r<4;r++){for(var c=0;c<Nb;c++){s[r][c]=_3c[s[r][c]];}}return s;};function ShiftRows(s,Nb){var t=new Array(4);for(var r=1;r<4;r++){for(var c=0;c<4;c++){t[c]=s[r][(c+r)%Nb];}for(var c=0;c<4;c++){s[r][c]=t[c];}}return s;};function MixColumns(s,Nb){for(var c=0;c<4;c++){var a=new Array(4);var b=new Array(4);for(var i=0;i<4;i++){a[i]=s[i][c];b[i]=s[i][c]&128?s[i][c]<<1^283:s[i][c]<<1;}s[0][c]=b[0]^a[1]^b[1]^a[2]^a[3];s[1][c]=a[0]^b[1]^a[2]^b[2]^a[3];s[2][c]=a[0]^a[1]^b[2]^a[3]^b[3];s[3][c]=a[0]^b[0]^a[1]^a[2]^b[3];}return s;};function AddRoundKey(_55,w,rnd,Nb){for(var r=0;r<4;r++){for(var c=0;c<Nb;c++){_55[r][c]^=w[rnd*4+c][r];}}return _55;};function KeyExpansion(key){var Nb=4;var Nk=key.length/4;var Nr=Nk+6;var w=new Array(Nb*(Nr+1));var _60=new Array(4);for(var i=0;i<Nk;i++){var r=[key[4*i],key[4*i+1],key[4*i+2],key[4*i+3]];w[i]=r;}for(var i=Nk;i<(Nb*(Nr+1));i++){w[i]=new Array(4);for(var t=0;t<4;t++){_60[t]=w[i-1][t];}if(i%Nk==0){_60=SubWord(RotWord(_60));for(var t=0;t<4;t++){_60[t]^=_3d[i/Nk][t];}}else{if(Nk>6&&i%Nk==4){_60=SubWord(_60);}}for(var t=0;t<4;t++){w[i][t]=w[i-Nk][t]^_60[t];}}return w;};function SubWord(w){for(var i=0;i<4;i++){w[i]=_3c[w[i]];}return w;};function RotWord(w){w[4]=w[0];for(var i=0;i<4;i++){w[i]=w[i+1];}return w;};function AESEncryptCtr(_68,_69,_6a){if(!(_6a==128||_6a==192||_6a==256)){return "";}var _6b=_6a/8;var _6c=new Array(_6b);for(var i=0;i<_6b;i++){_6c[i]=_69.charCodeAt(i)&255;}var key=Cipher(_6c,KeyExpansion(_6c));key=key.concat(key.slice(0,_6b-16));var _6f=16;var _70=new Array(_6f);var _71=(new Date()).getTime();for(var i=0;i<4;i++){_70[i]=(_71>>>i*8)&255;}for(var i=0;i<4;i++){_70[i+4]=(_71/4294967296>>>i*8)&255;}var _72=KeyExpansion(key);var _73=Math.ceil(_68.length/_6f);var _74=new Array(_73);for(var b=0;b<_73;b++){for(var c=0;c<4;c++){_70[15-c]=(b>>>c*8)&255;}for(var c=0;c<4;c++){_70[15-c-4]=(b/4294967296>>>c*8);}var _77=Cipher(_70,_72);var _78=b<_73-1?_6f:(_68.length-1)%_6f+1;var ct="";for(var i=0;i<_78;i++){var _7a=_68.charCodeAt(b*_6f+i);var _7b=_7a^_77[i];ct+=String.fromCharCode(_7b);}_74[b]=escCtrlChars(ct);}var _7c="";for(var i=0;i<8;i++){_7c+=String.fromCharCode(_70[i]);}_7c=escCtrlChars(_7c);return _7c+"-"+_74.join("-");};function AESDecryptCtr(_7d,_7e,_7f){if(!(_7f==128||_7f==192||_7f==256)){return "";}var _80=_7f/8;var _81=new Array(_80);for(var i=0;i<_80;i++){_81[i]=_7e.charCodeAt(i)&255;}var _83=KeyExpansion(_81);var key=Cipher(_81,_83);key=key.concat(key.slice(0,_80-16));var _85=KeyExpansion(key);_7d=_7d.split("-");var _86=16;var _87=new Array(_86);var _88=unescCtrlChars(_7d[0]);for(var i=0;i<8;i++){_87[i]=_88.charCodeAt(i);}var _89=new Array(_7d.length-1);for(var b=1;b<_7d.length;b++){for(var c=0;c<4;c++){_87[15-c]=((b-1)>>>c*8)&255;}for(var c=0;c<4;c++){_87[15-c-4]=((b/4294967296-1)>>>c*8)&255;}var _8c=Cipher(_87,_85);_7d[b]=unescCtrlChars(_7d[b]);var pt="";for(var i=0;i<_7d[b].length;i++){var _8e=_7d[b].charCodeAt(i);var _8f=_8e^_8c[i];pt+=String.fromCharCode(_8f);}_89[b-1]=pt;}return _89.join("");};function escCtrlChars(str){return str.replace(/[\0\t\n\v\f\r\xa0!-]/g,function(c){return "!"+c.charCodeAt(0)+"!";});};function unescCtrlChars(str){return str.replace(/!\d\d?\d?!/g,function(c){return String.fromCharCode(c.slice(1,-1));});};function encrypt(_94,_95){return AESEncryptCtr(_94,_95,256);};function decrypt(_96,_97){return AESDecryptCtr(_96,_97,256);};var cmd=msg.substr(0,4);var arg=msg.substr(5);if(cmd=="encr"){arg=eval("("+arg+")");var _9a=arg.plaintext;var _9b=arg.password;var _9c=encrypt(_9a,_9b);gearsWorkerPool.sendMessage(String(_9c),_3b);}else{if(cmd=="decr"){arg=eval("("+arg+")");var _9d=arg.ciphertext;var _9b=arg.password;var _9c=decrypt(_9d,_9b);gearsWorkerPool.sendMessage(String(_9c),_3b);}}}});}if(!dojo._hasResource["dojox._sql.common"]){dojo._hasResource["dojox._sql.common"]=true;dojo.provide("dojox._sql.common");dojox.sql=new Function("return dojox.sql._exec(arguments);");dojo.mixin(dojox.sql,{dbName:null,debug:(dojo.exists("dojox.sql.debug")?dojox.sql.debug:false),open:function(_9e){if(this._dbOpen&&(!_9e||_9e==this.dbName)){return;}if(!this.dbName){this.dbName="dot_store_"+window.location.href.replace(/[^0-9A-Za-z_]/g,"_");}if(!_9e){_9e=this.dbName;}try{this._initDb();this.db.open(_9e);this._dbOpen=true;}catch(exp){throw exp.message||exp;}},close:function(_9f){if(dojo.isIE){return;}if(!this._dbOpen&&(!_9f||_9f==this.dbName)){return;}if(!_9f){_9f=this.dbName;}try{this.db.close(_9f);this._dbOpen=false;}catch(exp){throw exp.message||exp;}},_exec:function(_a0){try{this._initDb();if(!this._dbOpen){this.open();this._autoClose=true;}var sql=null;var _a2=null;var _a3=null;var _a4=dojo._toArray(_a0);sql=_a4.splice(0,1)[0];if(this._needsEncrypt(sql)||this._needsDecrypt(sql)){_a2=_a4.splice(_a4.length-1,1)[0];_a3=_a4.splice(_a4.length-1,1)[0];}if(this.debug){this._printDebugSQL(sql,_a4);}if(this._needsEncrypt(sql)){var _a5=new dojox.sql._SQLCrypto("encrypt",sql,_a3,_a4,_a2);return;}else{if(this._needsDecrypt(sql)){var _a5=new dojox.sql._SQLCrypto("decrypt",sql,_a3,_a4,_a2);return;}}var rs=this.db.execute(sql,_a4);rs=this._normalizeResults(rs);if(this._autoClose){this.close();}return rs;}catch(exp){exp=exp.message||exp;console.debug("SQL Exception: "+exp);if(this._autoClose){try{this.close();}catch(e){console.debug("Error closing database: "+e.message||e);}}throw exp;}},_initDb:function(){if(!this.db){try{this.db=google.gears.factory.create("beta.database","1.0");}catch(exp){dojo.setObject("google.gears.denied",true);dojox.off.onFrameworkEvent("coreOperationFailed");throw "Google Gears must be allowed to run";}}},_printDebugSQL:function(sql,_a8){var msg="dojox.sql(\""+sql+"\"";for(var i=0;i<_a8.length;i++){if(typeof _a8[i]=="string"){msg+=", \""+_a8[i]+"\"";}else{msg+=", "+_a8[i];}}msg+=")";console.debug(msg);},_normalizeResults:function(rs){var _ac=[];if(!rs){return [];}while(rs.isValidRow()){var row={};for(var i=0;i<rs.fieldCount();i++){var _af=rs.fieldName(i);var _b0=rs.field(i);row[_af]=_b0;}_ac.push(row);rs.next();}rs.close();return _ac;},_needsEncrypt:function(sql){return /encrypt\([^\)]*\)/i.test(sql);},_needsDecrypt:function(sql){return /decrypt\([^\)]*\)/i.test(sql);}});dojo.declare("dojox.sql._SQLCrypto",null,{constructor:function(_b3,sql,_b5,_b6,_b7){if(_b3=="encrypt"){this._execEncryptSQL(sql,_b5,_b6,_b7);}else{this._execDecryptSQL(sql,_b5,_b6,_b7);}},_execEncryptSQL:function(sql,_b9,_ba,_bb){var _bc=this._stripCryptoSQL(sql);var _bd=this._flagEncryptedArgs(sql,_ba);var _be=this;this._encrypt(_bc,_b9,_ba,_bd,function(_bf){var _c0=false;var _c1=[];var exp=null;try{_c1=dojox.sql.db.execute(_bc,_bf);}catch(execError){_c0=true;exp=execError.message||execError;}if(exp!=null){if(dojox.sql._autoClose){try{dojox.sql.close();}catch(e){}}_bb(null,true,exp.toString());return;}_c1=dojox.sql._normalizeResults(_c1);if(dojox.sql._autoClose){dojox.sql.close();}if(dojox.sql._needsDecrypt(sql)){var _c3=_be._determineDecryptedColumns(sql);_be._decrypt(_c1,_c3,_b9,function(_c4){_bb(_c4,false,null);});}else{_bb(_c1,false,null);}});},_execDecryptSQL:function(sql,_c6,_c7,_c8){var _c9=this._stripCryptoSQL(sql);var _ca=this._determineDecryptedColumns(sql);var _cb=false;var _cc=[];var exp=null;try{_cc=dojox.sql.db.execute(_c9,_c7);}catch(execError){_cb=true;exp=execError.message||execError;}if(exp!=null){if(dojox.sql._autoClose){try{dojox.sql.close();}catch(e){}}_c8(_cc,true,exp.toString());return;}_cc=dojox.sql._normalizeResults(_cc);if(dojox.sql._autoClose){dojox.sql.close();}this._decrypt(_cc,_ca,_c6,function(_ce){_c8(_ce,false,null);});},_encrypt:function(sql,_d0,_d1,_d2,_d3){this._totalCrypto=0;this._finishedCrypto=0;this._finishedSpawningCrypto=false;this._finalArgs=_d1;for(var i=0;i<_d1.length;i++){if(_d2[i]){var _d5=_d1[i];var _d6=i;this._totalCrypto++;dojox._sql._crypto.encrypt(_d5,_d0,dojo.hitch(this,function(_d7){this._finalArgs[_d6]=_d7;this._finishedCrypto++;if(this._finishedCrypto>=this._totalCrypto&&this._finishedSpawningCrypto){_d3(this._finalArgs);}}));}}this._finishedSpawningCrypto=true;},_decrypt:function(_d8,_d9,_da,_db){this._totalCrypto=0;this._finishedCrypto=0;this._finishedSpawningCrypto=false;this._finalResultSet=_d8;for(var i=0;i<_d8.length;i++){var row=_d8[i];for(var _de in row){if(_d9=="*"||_d9[_de]){this._totalCrypto++;var _df=row[_de];this._decryptSingleColumn(_de,_df,_da,i,function(_e0){_db(_e0);});}}}this._finishedSpawningCrypto=true;},_stripCryptoSQL:function(sql){sql=sql.replace(/DECRYPT\(\*\)/ig,"*");var _e2=sql.match(/ENCRYPT\([^\)]*\)/ig);if(_e2!=null){for(var i=0;i<_e2.length;i++){var _e4=_e2[i];var _e5=_e4.match(/ENCRYPT\(([^\)]*)\)/i)[1];sql=sql.replace(_e4,_e5);}}_e2=sql.match(/DECRYPT\([^\)]*\)/ig);if(_e2!=null){for(var i=0;i<_e2.length;i++){var _e6=_e2[i];var _e7=_e6.match(/DECRYPT\(([^\)]*)\)/i)[1];sql=sql.replace(_e6,_e7);}}return sql;},_flagEncryptedArgs:function(sql,_e9){var _ea=new RegExp(/([\"][^\"]*\?[^\"]*[\"])|([\'][^\']*\?[^\']*[\'])|(\?)/ig);var _eb;var _ec=0;var _ed=[];while((_eb=_ea.exec(sql))!=null){var _ee=RegExp.lastMatch+"";if(/^[\"\']/.test(_ee)){continue;}var _ef=false;if(/ENCRYPT\([^\)]*$/i.test(RegExp.leftContext)){_ef=true;}_ed[_ec]=_ef;_ec++;}return _ed;},_determineDecryptedColumns:function(sql){var _f1={};if(/DECRYPT\(\*\)/i.test(sql)){_f1="*";}else{var _f2=/DECRYPT\((?:\s*\w*\s*\,?)*\)/ig;var _f3;while(_f3=_f2.exec(sql)){var _f4=new String(RegExp.lastMatch);var _f5=_f4.replace(/DECRYPT\(/i,"");_f5=_f5.replace(/\)/,"");_f5=_f5.split(/\s*,\s*/);dojo.forEach(_f5,function(_f6){if(/\s*\w* AS (\w*)/i.test(_f6)){_f6=_f6.match(/\s*\w* AS (\w*)/i)[1];}_f1[_f6]=true;});}}return _f1;},_decryptSingleColumn:function(_f7,_f8,_f9,_fa,_fb){dojox._sql._crypto.decrypt(_f8,_f9,dojo.hitch(this,function(_fc){this._finalResultSet[_fa][_f7]=_fc;this._finishedCrypto++;if(this._finishedCrypto>=this._totalCrypto&&this._finishedSpawningCrypto){_fb(this._finalResultSet);}}));}});}if(!dojo._hasResource["dojox.sql"]){dojo._hasResource["dojox.sql"]=true;dojo.provide("dojox.sql");}if(!dojo._hasResource["dojox.storage.GearsStorageProvider"]){dojo._hasResource["dojox.storage.GearsStorageProvider"]=true;dojo.provide("dojox.storage.GearsStorageProvider");if(dojo.isGears){(function(){dojo.declare("dojox.storage.GearsStorageProvider",dojox.storage.Provider,{constructor:function(){},TABLE_NAME:"__DOJO_STORAGE",initialized:false,_available:null,initialize:function(){if(djConfig["disableGearsStorage"]==true){return;}this.TABLE_NAME="__DOJO_STORAGE";try{dojox.sql("CREATE TABLE IF NOT EXISTS "+this.TABLE_NAME+"( "+" namespace TEXT, "+" key TEXT, "+" value TEXT "+")");dojox.sql("CREATE UNIQUE INDEX IF NOT EXISTS namespace_key_index"+" ON "+this.TABLE_NAME+" (namespace, key)");}catch(e){console.debug("dojox.storage.GearsStorageProvider.initialize:",e);this.initialized=false;dojox.storage.manager.loaded();return;}this.initialized=true;dojox.storage.manager.loaded();},isAvailable:function(){return this._available=dojo.isGears;},put:function(key,_fe,_ff,_100){if(this.isValidKey(key)==false){throw new Error("Invalid key given: "+key);}_100=_100||this.DEFAULT_NAMESPACE;if(dojo.isString(_fe)){_fe="string:"+_fe;}else{_fe=dojo.toJson(_fe);}try{dojox.sql("DELETE FROM "+this.TABLE_NAME+" WHERE namespace = ? AND key = ?",_100,key);dojox.sql("INSERT INTO "+this.TABLE_NAME+" VALUES (?, ?, ?)",_100,key,_fe);}catch(e){console.debug("dojox.storage.GearsStorageProvider.put:",e);_ff(this.FAILED,key,e.toString());return;}if(_ff){_ff(dojox.storage.SUCCESS,key,null);}},get:function(key,_102){if(this.isValidKey(key)==false){throw new Error("Invalid key given: "+key);}_102=_102||this.DEFAULT_NAMESPACE;var _103=dojox.sql("SELECT * FROM "+this.TABLE_NAME+" WHERE namespace = ? AND "+" key = ?",_102,key);if(!_103.length){return null;}else{_103=_103[0].value;}if(dojo.isString(_103)&&(/^string:/.test(_103))){_103=_103.substring("string:".length);}else{_103=dojo.fromJson(_103);}return _103;},getNamespaces:function(){var _104=[dojox.storage.DEFAULT_NAMESPACE];var rs=dojox.sql("SELECT namespace FROM "+this.TABLE_NAME+" DESC GROUP BY namespace");for(var i=0;i<rs.length;i++){if(rs[i].namespace!=dojox.storage.DEFAULT_NAMESPACE){_104.push(rs[i].namespace);}}return _104;},getKeys:function(_107){_107=_107||this.DEFAULT_NAMESPACE;if(this.isValidKey(_107)==false){throw new Error("Invalid namespace given: "+_107);}var rs=dojox.sql("SELECT key FROM "+this.TABLE_NAME+" WHERE namespace = ?",_107);var _109=[];for(var i=0;i<rs.length;i++){_109.push(rs[i].key);}return _109;},clear:function(_10b){if(this.isValidKey(_10b)==false){throw new Error("Invalid namespace given: "+_10b);}_10b=_10b||this.DEFAULT_NAMESPACE;dojox.sql("DELETE FROM "+this.TABLE_NAME+" WHERE namespace = ?",_10b);},remove:function(key,_10d){_10d=_10d||this.DEFAULT_NAMESPACE;dojox.sql("DELETE FROM "+this.TABLE_NAME+" WHERE namespace = ? AND"+" key = ?",_10d,key);},putMultiple:function(keys,_10f,_110,_111){if(this.isValidKeyArray(keys)===false||!_10f instanceof Array||keys.length!=_10f.length){throw new Error("Invalid arguments: keys = ["+keys+"], values = ["+_10f+"]");}if(_111==null||typeof _111=="undefined"){_111=dojox.storage.DEFAULT_NAMESPACE;}if(this.isValidKey(_111)==false){throw new Error("Invalid namespace given: "+_111);}this._statusHandler=_110;try{dojox.sql.open();dojox.sql.db.execute("BEGIN TRANSACTION");var _112="REPLACE INTO "+this.TABLE_NAME+" VALUES (?, ?, ?)";for(var i=0;i<keys.length;i++){var _114=_10f[i];if(dojo.isString(_114)){_114="string:"+_114;}else{_114=dojo.toJson(_114);}dojox.sql.db.execute(_112,[_111,keys[i],_114]);}dojox.sql.db.execute("COMMIT TRANSACTION");dojox.sql.close();}catch(e){console.debug("dojox.storage.GearsStorageProvider.putMultiple:",e);if(_110){_110(this.FAILED,keys,e.toString());}return;}if(_110){_110(dojox.storage.SUCCESS,key,null);}},getMultiple:function(keys,_116){if(this.isValidKeyArray(keys)===false){throw new ("Invalid key array given: "+keys);}if(_116==null||typeof _116=="undefined"){_116=dojox.storage.DEFAULT_NAMESPACE;}if(this.isValidKey(_116)==false){throw new Error("Invalid namespace given: "+_116);}var _117="SELECT * FROM "+this.TABLE_NAME+" WHERE namespace = ? AND "+" key = ?";var _118=[];for(var i=0;i<keys.length;i++){var _11a=dojox.sql(_117,_116,keys[i]);if(!_11a.length){_118[i]=null;}else{_11a=_11a[0].value;if(dojo.isString(_11a)&&(/^string:/.test(_11a))){_118[i]=_11a.substring("string:".length);}else{_118[i]=dojo.fromJson(_11a);}}}return _118;},removeMultiple:function(keys,_11c){_11c=_11c||this.DEFAULT_NAMESPACE;dojox.sql.open();dojox.sql.db.execute("BEGIN TRANSACTION");var _11d="DELETE FROM "+this.TABLE_NAME+" WHERE namespace = ? AND key = ?";for(var i=0;i<keys.length;i++){dojox.sql.db.execute(_11d,[_11c,keys[i]]);}dojox.sql.db.execute("COMMIT TRANSACTION");dojox.sql.close();},isPermanent:function(){return true;},getMaximumSize:function(){return this.SIZE_NO_LIMIT;},hasSettingsUI:function(){return false;},showSettingsUI:function(){throw new Error(this.declaredClass+" does not support a storage settings user-interface");},hideSettingsUI:function(){throw new Error(this.declaredClass+" does not support a storage settings user-interface");}});dojox.storage.manager.register("dojox.storage.GearsStorageProvider",new dojox.storage.GearsStorageProvider());dojox.storage.manager.initialize();})();}}if(!dojo._hasResource["dojox.storage._common"]){dojo._hasResource["dojox.storage._common"]=true;dojo.provide("dojox.storage._common");dojox.storage.manager.initialize();}if(!dojo._hasResource["dojox.storage"]){dojo._hasResource["dojox.storage"]=true;dojo.provide("dojox.storage");}if(!dojo._hasResource["dojox.off.files"]){dojo._hasResource["dojox.off.files"]=true;dojo.provide("dojox.off.files");dojox.off.files={versionURL:"version.js",listOfURLs:[],refreshing:false,_cancelID:null,_error:false,_errorMessages:[],_currentFileIndex:0,_store:null,_doSlurp:false,slurp:function(){this._doSlurp=true;},cache:function(_11f){if(dojo.isString(_11f)){var url=this._trimAnchor(_11f+"");if(!this.isAvailable(url)){this.listOfURLs.push(url);}}else{if(_11f instanceof dojo._Url){var url=this._trimAnchor(_11f.uri);if(!this.isAvailable(url)){this.listOfURLs.push(url);}}else{dojo.forEach(_11f,function(url){url=this._trimAnchor(url);if(!this.isAvailable(url)){this.listOfURLs.push(url);}},this);}}},printURLs:function(){console.debug("The following URLs are cached for offline use:");dojo.forEach(this.listOfURLs,function(i){console.debug(i);});},remove:function(url){for(var i=0;i<this.listOfURLs.length;i++){if(this.listOfURLs[i]==url){this.listOfURLs=this.listOfURLs.splice(i,1);break;}}},isAvailable:function(url){for(var i=0;i<this.listOfURLs.length;i++){if(this.listOfURLs[i]==url){return true;}}return false;},refresh:function(_127){try{if(djConfig.isDebug){this.printURLs();}this.refreshing=true;if(this.versionURL){this._getVersionInfo(function(_128,_129,_12a){if(djConfig.isDebug||!_129||_12a||!_128||_128!=_129){console.warn("Refreshing offline file list");this._doRefresh(_127,_129);}else{console.warn("No need to refresh offline file list");_127(false,[]);}});}else{console.warn("Refreshing offline file list");this._doRefresh(_127);}}catch(e){this.refreshing=false;dojox.off.coreOpFailed=true;dojox.off.enabled=false;dojox.off.onFrameworkEvent("coreOperationFailed");}},abortRefresh:function(){if(!this.refreshing){return;}this._store.abortCapture(this._cancelID);this.refreshing=false;},_slurp:function(){if(!this._doSlurp){return;}var _12b=dojo.hitch(this,function(url){if(this._sameLocation(url)){this.cache(url);}});_12b(window.location.href);dojo.query("script").forEach(function(i){try{_12b(i.getAttribute("src"));}catch(exp){}});dojo.query("link").forEach(function(i){try{if(!i.getAttribute("rel")||i.getAttribute("rel").toLowerCase()!="stylesheet"){return;}_12b(i.getAttribute("href"));}catch(exp){}});dojo.query("img").forEach(function(i){try{_12b(i.getAttribute("src"));}catch(exp){}});dojo.query("a").forEach(function(i){try{_12b(i.getAttribute("href"));}catch(exp){}});dojo.forEach(document.styleSheets,function(_131){try{if(_131.cssRules){dojo.forEach(_131.cssRules,function(rule){var text=rule.cssText;if(text){var _134=text.match(/url\(\s*([^\) ]*)\s*\)/i);if(!_134){return;}for(var i=1;i<_134.length;i++){_12b(_134[i]);}}});}else{if(_131.cssText){var _136;var text=_131.cssText.toString();var _138=text.split(/\f|\r|\n/);for(var i=0;i<_138.length;i++){_136=_138[i].match(/url\(\s*([^\) ]*)\s*\)/i);if(_136&&_136.length){_12b(_136[1]);}}}}}catch(exp){}});},_sameLocation:function(url){if(!url){return false;}if(url.length&&url.charAt(0)=="#"){return false;}url=new dojo._Url(url);if(!url.scheme&&!url.port&&!url.host){return true;}if(!url.scheme&&url.host&&url.port&&window.location.hostname==url.host&&window.location.port==url.port){return true;}if(!url.scheme&&url.host&&!url.port&&window.location.hostname==url.host&&window.location.port==80){return true;}return window.location.protocol==(url.scheme+":")&&window.location.hostname==url.host&&(window.location.port==url.port||!window.location.port&&!url.port);},_trimAnchor:function(url){return url.replace(/\#.*$/,"");},_doRefresh:function(_13c,_13d){var _13e;try{_13e=google.gears.factory.create("beta.localserver","1.0");}catch(exp){dojo.setObject("google.gears.denied",true);dojox.off.onFrameworkEvent("coreOperationFailed");throw "Google Gears must be allowed to run";}var _13f="dot_store_"+window.location.href.replace(/[^0-9A-Za-z_]/g,"_");_13e.removeStore(_13f);_13e.openStore(_13f);var _140=_13e.createStore(_13f);this._store=_140;var self=this;this._currentFileIndex=0;this._cancelID=_140.capture(this.listOfURLs,function(url,_143,_144){if(!_143&&self.refreshing){self._cancelID=null;self.refreshing=false;var _145=[];_145.push("Unable to capture: "+url);_13c(true,_145);return;}else{if(_143){self._currentFileIndex++;}}if(_143&&self._currentFileIndex>=self.listOfURLs.length){self._cancelID=null;self.refreshing=false;if(_13d){dojox.storage.put("oldVersion",_13d,null,dojox.off.STORAGE_NAMESPACE);}dojox.storage.put("justDebugged",djConfig.isDebug,null,dojox.off.STORAGE_NAMESPACE);_13c(false,[]);}});},_getVersionInfo:function(_146){var _147=dojox.storage.get("justDebugged",dojox.off.STORAGE_NAMESPACE);var _148=dojox.storage.get("oldVersion",dojox.off.STORAGE_NAMESPACE);var _149=null;_146=dojo.hitch(this,_146);dojo.xhrGet({url:this.versionURL+"?browserbust="+new Date().getTime(),timeout:5*1000,handleAs:"javascript",error:function(err){dojox.storage.remove("oldVersion",dojox.off.STORAGE_NAMESPACE);dojox.storage.remove("justDebugged",dojox.off.STORAGE_NAMESPACE);_146(_148,_149,_147);},load:function(data){if(data){_149=data;}_146(_148,_149,_147);}});}};}if(!dojo._hasResource["dojox.off.sync"]){dojo._hasResource["dojox.off.sync"]=true;dojo.provide("dojox.off.sync");dojo.mixin(dojox.off.sync,{isSyncing:false,cancelled:false,successful:true,details:[],error:false,actions:null,autoSync:true,onSync:function(type){},synchronize:function(){if(this.isSyncing||dojox.off.goingOnline||(!dojox.off.isOnline)){return;}this.isSyncing=true;this.successful=false;this.details=[];this.cancelled=false;this.start();},cancel:function(){if(!this.isSyncing){return;}this.cancelled=true;if(dojox.off.files.refreshing){dojox.off.files.abortRefresh();}this.onSync("cancel");},finishedDownloading:function(_14d,_14e){if(typeof _14d=="undefined"){_14d=true;}if(!_14d){this.successful=false;this.details.push(_14e);this.error=true;}this.finished();},start:function(){if(this.cancelled){this.finished();return;}this.onSync("start");this.refreshFiles();},refreshFiles:function(){if(this.cancelled){this.finished();return;}this.onSync("refreshFiles");dojox.off.files.refresh(dojo.hitch(this,function(_14f,_150){if(_14f){this.error=true;this.successful=false;for(var i=0;i<_150.length;i++){this.details.push(_150[i]);}}this.upload();}));},upload:function(){if(this.cancelled){this.finished();return;}this.onSync("upload");dojo.connect(this.actions,"onReplayFinished",this,this.download);this.actions.replay();},download:function(){if(this.cancelled){this.finished();return;}this.onSync("download");},finished:function(){this.isSyncing=false;this.successful=(!this.cancelled&&!this.error);this.onSync("finished");},_save:function(_152){this.actions._save(function(){_152();});},_load:function(_153){this.actions._load(function(){_153();});}});dojo.declare("dojox.off.sync.ActionLog",null,{entries:[],reasonHalted:null,isReplaying:false,autoSave:true,add:function(_154){if(this.isReplaying){throw "Programming error: you can not call "+"dojox.off.sync.actions.add() while "+"we are replaying an action log";}this.entries.push(_154);if(this.autoSave){this._save();}},onReplay:function(_155,_156){},length:function(){return this.entries.length;},haltReplay:function(_157){if(!this.isReplaying){return;}if(_157){this.reasonHalted=_157.toString();}if(this.autoSave){var self=this;this._save(function(){self.isReplaying=false;self.onReplayFinished();});}else{this.isReplaying=false;this.onReplayFinished();}},continueReplay:function(){if(!this.isReplaying){return;}this.entries.shift();if(!this.entries.length){if(this.autoSave){var self=this;this._save(function(){self.isReplaying=false;self.onReplayFinished();});return;}else{this.isReplaying=false;this.onReplayFinished();return;}}var _15a=this.entries[0];this.onReplay(_15a,this);},clear:function(){if(this.isReplaying){return;}this.entries=[];if(this.autoSave){this._save();}},replay:function(){if(this.isReplaying){return;}this.reasonHalted=null;if(!this.entries.length){this.onReplayFinished();return;}this.isReplaying=true;var _15b=this.entries[0];this.onReplay(_15b,this);},onReplayFinished:function(){},toString:function(){var _15c="";_15c+="[";for(var i=0;i<this.entries.length;i++){_15c+="{";for(var j in this.entries[i]){_15c+=j+": \""+this.entries[i][j]+"\"";_15c+=", ";}_15c+="}, ";}_15c+="]";return _15c;},_save:function(_15f){if(!_15f){_15f=function(){};}try{var self=this;var _161=function(_162,key,_164){if(_162==dojox.storage.FAILED){dojox.off.onFrameworkEvent("save",{status:dojox.storage.FAILED,isCoreSave:true,key:key,value:_164,namespace:dojox.off.STORAGE_NAMESPACE});_15f();}else{if(_162==dojox.storage.SUCCESS){_15f();}}};dojox.storage.put("actionlog",this.entries,_161,dojox.off.STORAGE_NAMESPACE);}catch(exp){console.debug("dojox.off.sync._save: "+exp.message||exp);dojox.off.onFrameworkEvent("save",{status:dojox.storage.FAILED,isCoreSave:true,key:"actionlog",value:this.entries,namespace:dojox.off.STORAGE_NAMESPACE});_15f();}},_load:function(_165){var _166=dojox.storage.get("actionlog",dojox.off.STORAGE_NAMESPACE);if(!_166){_166=[];}this.entries=_166;_165();}});dojox.off.sync.actions=new dojox.off.sync.ActionLog();}if(!dojo._hasResource["dojox.off._common"]){dojo._hasResource["dojox.off._common"]=true;dojo.provide("dojox.off._common");dojo.mixin(dojox.off,{isOnline:false,NET_CHECK:5,STORAGE_NAMESPACE:"_dot",enabled:true,availabilityURL:dojo.moduleUrl("dojox","off/network_check.txt"),goingOnline:false,coreOpFailed:false,doNetChecking:true,hasOfflineCache:null,browserRestart:false,_STORAGE_APP_NAME:window.location.href.replace(/[^0-9A-Za-z_]/g,"_"),_initializeCalled:false,_storageLoaded:false,_pageLoaded:false,onLoad:function(){},onNetwork:function(type){},initialize:function(){this._initializeCalled=true;if(this._storageLoaded&&this._pageLoaded){this._onLoad();}},goOffline:function(){if((dojox.off.sync.isSyncing)||(this.goingOnline)){return;}this.goingOnline=false;this.isOnline=false;},goOnline:function(_168){if(dojox.off.sync.isSyncing||dojox.off.goingOnline){return;}this.goingOnline=true;this.isOnline=false;this._isSiteAvailable(_168);},onFrameworkEvent:function(type,_16a){if(type=="save"){if(_16a.isCoreSave&&(_16a.status==dojox.storage.FAILED)){dojox.off.coreOpFailed=true;dojox.off.enabled=false;dojox.off.onFrameworkEvent("coreOperationFailed");}}else{if(type=="coreOperationFailed"){dojox.off.coreOpFailed=true;dojox.off.enabled=false;}}},_checkOfflineCacheAvailable:function(_16b){this.hasOfflineCache=dojo.isGears;_16b();},_onLoad:function(){dojox.off.files.cache(dojo.moduleUrl("dojo","dojo.js"));this._cacheDojoResources();dojox.off.files.cache(dojox.storage.manager.getResourceList());dojox.off.files._slurp();this._checkOfflineCacheAvailable(dojo.hitch(this,"_onOfflineCacheChecked"));},_onOfflineCacheChecked:function(){if(this.hasOfflineCache&&this.enabled){this._load(dojo.hitch(this,"_finishStartingUp"));}else{if(this.hasOfflineCache&&!this.enabled){this._finishStartingUp();}else{this._keepCheckingUntilInstalled();}}},_keepCheckingUntilInstalled:function(){this._finishStartingUp();},_finishStartingUp:function(){if(!this.hasOfflineCache){this.onLoad();}else{if(this.enabled){this._startNetworkThread();this.goOnline(dojo.hitch(this,function(){dojox.off.onLoad();}));}else{if(this.coreOpFailed){this.onFrameworkEvent("coreOperationFailed");}else{this.onLoad();}}}},_onPageLoad:function(){this._pageLoaded=true;if(this._storageLoaded&&this._initializeCalled){this._onLoad();}},_onStorageLoad:function(){this._storageLoaded=true;if(!dojox.storage.manager.isAvailable()&&dojox.storage.manager.isInitialized()){this.coreOpFailed=true;this.enabled=false;}if(this._pageLoaded&&this._initializeCalled){this._onLoad();}},_isSiteAvailable:function(_16c){dojo.xhrGet({url:this._getAvailabilityURL(),handleAs:"text",timeout:this.NET_CHECK*1000,error:dojo.hitch(this,function(err){this.goingOnline=false;this.isOnline=false;if(_16c){_16c(false);}}),load:dojo.hitch(this,function(data){this.goingOnline=false;this.isOnline=true;if(_16c){_16c(true);}else{this.onNetwork("online");}})});},_startNetworkThread:function(){if(!this.doNetChecking){return;}window.setInterval(dojo.hitch(this,function(){var d=dojo.xhrGet({url:this._getAvailabilityURL(),handleAs:"text",timeout:this.NET_CHECK*1000,error:dojo.hitch(this,function(err){if(this.isOnline){this.isOnline=false;try{if(typeof d.ioArgs.xhr.abort=="function"){d.ioArgs.xhr.abort();}}catch(e){}dojox.off.sync.isSyncing=false;this.onNetwork("offline");}}),load:dojo.hitch(this,function(data){if(!this.isOnline){this.isOnline=true;this.onNetwork("online");}})});}),this.NET_CHECK*1000);},_getAvailabilityURL:function(){var url=this.availabilityURL.toString();if(url.indexOf("?")==-1){url+="?";}else{url+="&";}url+="browserbust="+new Date().getTime();return url;},_onOfflineCacheInstalled:function(){this.onFrameworkEvent("offlineCacheInstalled");},_cacheDojoResources:function(){var _173=true;dojo.forEach(dojo.query("script"),function(i){var src=i.getAttribute("src");if(!src){return;}if(src.indexOf("_base/_loader/bootstrap.js")!=-1){_173=false;}});if(!_173){dojox.off.files.cache(dojo.moduleUrl("dojo","_base.js").uri);dojox.off.files.cache(dojo.moduleUrl("dojo","_base/_loader/loader.js").uri);dojox.off.files.cache(dojo.moduleUrl("dojo","_base/_loader/bootstrap.js").uri);dojox.off.files.cache(dojo.moduleUrl("dojo","_base/_loader/hostenv_browser.js").uri);}for(var i=0;i<dojo._loadedUrls.length;i++){dojox.off.files.cache(dojo._loadedUrls[i]);}},_save:function(){},_load:function(_177){dojox.off.sync._load(_177);}});dojox.storage.manager.addOnLoad(dojo.hitch(dojox.off,"_onStorageLoad"));dojo.addOnLoad(dojox.off,"_onPageLoad");}if(!dojo._hasResource["dojox.off"]){dojo._hasResource["dojox.off"]=true;dojo.provide("dojox.off");}if(!dojo._hasResource["dojox.off.ui"]){dojo._hasResource["dojox.off.ui"]=true;dojo.provide("dojox.off.ui");dojo.mixin(dojox.off.ui,{appName:"setme",autoEmbed:true,autoEmbedID:"dot-widget",runLink:window.location.href,runLinkTitle:"Run Application",learnHowPath:dojo.moduleUrl("dojox","off/resources/learnhow.html"),customLearnHowPath:false,htmlTemplatePath:dojo.moduleUrl("dojox","off/resources/offline-widget.html").uri,cssTemplatePath:dojo.moduleUrl("dojox","off/resources/offline-widget.css").uri,onlineImagePath:dojo.moduleUrl("dojox","off/resources/greenball.png").uri,offlineImagePath:dojo.moduleUrl("dojox","off/resources/redball.png").uri,rollerImagePath:dojo.moduleUrl("dojox","off/resources/roller.gif").uri,checkmarkImagePath:dojo.moduleUrl("dojox","off/resources/checkmark.png").uri,learnHowJSPath:dojo.moduleUrl("dojox","off/resources/learnhow.js").uri,_initialized:false,onLoad:function(){},_initialize:function(){if(this._validateAppName(this.appName)==false){alert("You must set dojox.off.ui.appName; it can only contain "+"letters, numbers, and spaces; right now it "+"is incorrectly set to '"+dojox.off.ui.appName+"'");dojox.off.enabled=false;return;}this.runLinkText="Run "+this.appName;dojo.connect(dojox.off,"onNetwork",this,"_onNetwork");dojo.connect(dojox.off.sync,"onSync",this,"_onSync");dojox.off.files.cache([this.htmlTemplatePath,this.cssTemplatePath,this.onlineImagePath,this.offlineImagePath,this.rollerImagePath,this.checkmarkImagePath]);if(this.autoEmbed){this._doAutoEmbed();}},_doAutoEmbed:function(){dojo.xhrGet({url:this.htmlTemplatePath,handleAs:"text",error:function(err){dojox.off.enabled=false;err=err.message||err;alert("Error loading the Dojo Offline Widget from "+this.htmlTemplatePath+": "+err);},load:dojo.hitch(this,this._templateLoaded)});},_templateLoaded:function(data){var _17a=dojo.byId(this.autoEmbedID);if(_17a){_17a.innerHTML=data;}this._initImages();this._updateNetIndicator();this._initLearnHow();this._initialized=true;if(!dojox.off.hasOfflineCache){this._showNeedsOfflineCache();return;}if(dojox.off.hasOfflineCache&&dojox.off.browserRestart){this._needsBrowserRestart();return;}else{var _17b=dojo.byId("dot-widget-browser-restart");if(_17b){_17b.style.display="none";}}this._updateSyncUI();this._initMainEvtHandlers();this._setOfflineEnabled(dojox.off.enabled);this._onNetwork(dojox.off.isOnline?"online":"offline");this._testNet();},_testNet:function(){dojox.off.goOnline(dojo.hitch(this,function(_17c){this._onNetwork(_17c?"online":"offline");this.onLoad();}));},_updateNetIndicator:function(){var _17d=dojo.byId("dot-widget-network-indicator-online");var _17e=dojo.byId("dot-widget-network-indicator-offline");var _17f=dojo.byId("dot-widget-title-text");if(_17d&&_17e){if(dojox.off.isOnline==true){_17d.style.display="inline";_17e.style.display="none";}else{_17d.style.display="none";_17e.style.display="inline";}}if(_17f){if(dojox.off.isOnline){_17f.innerHTML="Online";}else{_17f.innerHTML="Offline";}}},_initLearnHow:function(){var _180=dojo.byId("dot-widget-learn-how-link");if(!_180){return;}if(!this.customLearnHowPath){var _181=djConfig.baseRelativePath;this.learnHowPath+="?appName="+encodeURIComponent(this.appName)+"&hasOfflineCache="+dojox.off.hasOfflineCache+"&runLink="+encodeURIComponent(this.runLink)+"&runLinkText="+encodeURIComponent(this.runLinkText)+"&baseRelativePath="+encodeURIComponent(_181);dojox.off.files.cache(this.learnHowJSPath);dojox.off.files.cache(this.learnHowPath);}_180.setAttribute("href",this.learnHowPath);var _182=dojo.byId("dot-widget-learn-how-app-name");if(!_182){return;}_182.innerHTML="";_182.appendChild(document.createTextNode(this.appName));},_validateAppName:function(_183){if(!_183){return false;}return (/^[a-z0-9 ]*$/i.test(_183));},_updateSyncUI:function(){var _184=dojo.byId("dot-roller");var _185=dojo.byId("dot-success-checkmark");var _186=dojo.byId("dot-sync-messages");var _187=dojo.byId("dot-sync-details");var _188=dojo.byId("dot-sync-cancel");if(dojox.off.sync.isSyncing){this._clearSyncMessage();if(_184){_184.style.display="inline";}if(_185){_185.style.display="none";}if(_186){dojo.removeClass(_186,"dot-sync-error");}if(_187){_187.style.display="none";}if(_188){_188.style.display="inline";}}else{if(_184){_184.style.display="none";}if(_188){_188.style.display="none";}if(_186){dojo.removeClass(_186,"dot-sync-error");}}},_setSyncMessage:function(_189){var _18a=dojo.byId("dot-sync-messages");if(_18a){while(_18a.firstChild){_18a.removeChild(_18a.firstChild);}_18a.appendChild(document.createTextNode(_189));}},_clearSyncMessage:function(){this._setSyncMessage("");},_initImages:function(){var _18b=dojo.byId("dot-widget-network-indicator-online");if(_18b){_18b.setAttribute("src",this.onlineImagePath);}var _18c=dojo.byId("dot-widget-network-indicator-offline");if(_18c){_18c.setAttribute("src",this.offlineImagePath);}var _18d=dojo.byId("dot-roller");if(_18d){_18d.setAttribute("src",this.rollerImagePath);}var _18e=dojo.byId("dot-success-checkmark");if(_18e){_18e.setAttribute("src",this.checkmarkImagePath);}},_showDetails:function(evt){evt.preventDefault();evt.stopPropagation();if(!dojox.off.sync.details.length){return;}var html="";html+="<html><head><title>Sync Details</title><head><body>";html+="<h1>Sync Details</h1>\n";html+="<ul>\n";for(var i=0;i<dojox.off.sync.details.length;i++){html+="<li>";html+=dojox.off.sync.details[i];html+="</li>";}html+="</ul>\n";html+="<a href='javascript:window.close()' "+"style='text-align: right; padding-right: 2em;'>"+"Close Window"+"</a>\n";html+="</body></html>";var _192="height=400,width=600,resizable=true,"+"scrollbars=true,toolbar=no,menubar=no,"+"location=no,directories=no,dependent=yes";var _193=window.open("","SyncDetails",_192);if(!_193){alert("Please allow popup windows for this domain; can't display sync details window");return;}_193.document.open();_193.document.write(html);_193.document.close();if(_193.focus){_193.focus();}},_cancel:function(evt){evt.preventDefault();evt.stopPropagation();dojox.off.sync.cancel();},_needsBrowserRestart:function(){var _195=dojo.byId("dot-widget-browser-restart");if(_195){dojo.addClass(_195,"dot-needs-browser-restart");}var _196=dojo.byId("dot-widget-browser-restart-app-name");if(_196){_196.innerHTML="";_196.appendChild(document.createTextNode(this.appName));}var _197=dojo.byId("dot-sync-status");if(_197){_197.style.display="none";}},_showNeedsOfflineCache:function(){var _198=dojo.byId("dot-widget-container");if(_198){dojo.addClass(_198,"dot-needs-offline-cache");}},_hideNeedsOfflineCache:function(){var _199=dojo.byId("dot-widget-container");if(_199){dojo.removeClass(_199,"dot-needs-offline-cache");}},_initMainEvtHandlers:function(){var _19a=dojo.byId("dot-sync-details-button");if(_19a){dojo.connect(_19a,"onclick",this,this._showDetails);}var _19b=dojo.byId("dot-sync-cancel-button");if(_19b){dojo.connect(_19b,"onclick",this,this._cancel);}},_setOfflineEnabled:function(_19c){var _19d=[];_19d.push(dojo.byId("dot-sync-status"));for(var i=0;i<_19d.length;i++){if(_19d[i]){_19d[i].style.visibility=(_19c?"visible":"hidden");}}},_syncFinished:function(){this._updateSyncUI();var _19f=dojo.byId("dot-success-checkmark");var _1a0=dojo.byId("dot-sync-details");if(dojox.off.sync.successful==true){this._setSyncMessage("Sync Successful");if(_19f){_19f.style.display="inline";}}else{if(dojox.off.sync.cancelled==true){this._setSyncMessage("Sync Cancelled");if(_19f){_19f.style.display="none";}}else{this._setSyncMessage("Sync Error");var _1a1=dojo.byId("dot-sync-messages");if(_1a1){dojo.addClass(_1a1,"dot-sync-error");}if(_19f){_19f.style.display="none";}}}if(dojox.off.sync.details.length&&_1a0){_1a0.style.display="inline";}},_onFrameworkEvent:function(type,_1a3){if(type=="save"){if(_1a3.status==dojox.storage.FAILED&&!_1a3.isCoreSave){alert("Please increase the amount of local storage available "+"to this application");if(dojox.storage.hasSettingsUI()){dojox.storage.showSettingsUI();}}}else{if(type=="coreOperationFailed"){console.log("Application does not have permission to use Dojo Offline");if(!this._userInformed){alert("This application will not work if Google Gears is not allowed to run");this._userInformed=true;}}else{if(type=="offlineCacheInstalled"){this._hideNeedsOfflineCache();if(dojox.off.hasOfflineCache==true&&dojox.off.browserRestart==true){this._needsBrowserRestart();return;}else{var _1a4=dojo.byId("dot-widget-browser-restart");if(_1a4){_1a4.style.display="none";}}this._updateSyncUI();this._initMainEvtHandlers();this._setOfflineEnabled(dojox.off.enabled);this._testNet();}}}},_onSync:function(type){switch(type){case "start":this._updateSyncUI();break;case "refreshFiles":this._setSyncMessage("Downloading UI...");break;case "upload":this._setSyncMessage("Uploading new data...");break;case "download":this._setSyncMessage("Downloading new data...");break;case "finished":this._syncFinished();break;case "cancel":this._setSyncMessage("Canceling Sync...");break;default:dojo.warn("Programming error: "+"Unknown sync type in dojox.off.ui: "+type);break;}},_onNetwork:function(type){if(!this._initialized){return;}this._updateNetIndicator();if(type=="offline"){this._setSyncMessage("You are working offline");var _1a7=dojo.byId("dot-sync-details");if(_1a7){_1a7.style.display="none";}this._updateSyncUI();}else{if(dojox.off.sync.autoSync){window.setTimeout("dojox.off.sync.synchronize()",1000);}}}});dojo.connect(dojox.off,"onFrameworkEvent",dojox.off.ui,"_onFrameworkEvent");dojo.connect(dojox.off,"onLoad",dojox.off.ui,dojox.off.ui._initialize);}if(!dojo._hasResource["dojox.off.offline"]){dojo._hasResource["dojox.off.offline"]=true;dojo.provide("dojox.off.offline");}
/trunk/api/js/dojo1.0/dojox/off/offline.js.uncompressed.js
New file
0,0 → 1,4276
/*
Copyright (c) 2004-2007, 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/book/dojo-book-0-9/introduction/licensing
*/
 
/*
This is a compiled version of Dojo, built for deployment and not for
development. To get an editable version, please visit:
 
http://dojotoolkit.org
 
for documentation and information on getting the source.
*/
 
if(!dojo._hasResource["dojox.storage.Provider"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.storage.Provider"] = true;
dojo.provide("dojox.storage.Provider");
 
dojo.declare("dojox.storage.Provider", null, {
// summary: A singleton for working with dojox.storage.
// description:
// dojox.storage exposes the current available storage provider on this
// platform. It gives you methods such as dojox.storage.put(),
// dojox.storage.get(), etc.
//
// For more details on dojox.storage, see the primary documentation
// page at
// http://manual.dojotoolkit.org/storage.html
//
// Note for storage provider developers who are creating subclasses-
// This is the base class for all storage providers Specific kinds of
// Storage Providers should subclass this and implement these methods.
// You should avoid initialization in storage provider subclass's
// constructor; instead, perform initialization in your initialize()
// method.
constructor: function(){
},
// SUCCESS: String
// Flag that indicates a put() call to a
// storage provider was succesful.
SUCCESS: "success",
// FAILED: String
// Flag that indicates a put() call to
// a storage provider failed.
FAILED: "failed",
// PENDING: String
// Flag that indicates a put() call to a
// storage provider is pending user approval.
PENDING: "pending",
// SIZE_NOT_AVAILABLE: String
// Returned by getMaximumSize() if this storage provider can not determine
// the maximum amount of data it can support.
SIZE_NOT_AVAILABLE: "Size not available",
// SIZE_NO_LIMIT: String
// Returned by getMaximumSize() if this storage provider has no theoretical
// limit on the amount of data it can store.
SIZE_NO_LIMIT: "No size limit",
 
// DEFAULT_NAMESPACE: String
// The namespace for all storage operations. This is useful if several
// applications want access to the storage system from the same domain but
// want different storage silos.
DEFAULT_NAMESPACE: "default",
// onHideSettingsUI: Function
// If a function is assigned to this property, then when the settings
// provider's UI is closed this function is called. Useful, for example,
// if the user has just cleared out all storage for this provider using
// the settings UI, and you want to update your UI.
onHideSettingsUI: null,
 
initialize: function(){
// summary:
// Allows this storage provider to initialize itself. This is
// called after the page has finished loading, so you can not do
// document.writes(). Storage Provider subclasses should initialize
// themselves inside of here rather than in their function
// constructor.
console.warn("dojox.storage.initialize not implemented");
},
isAvailable: function(){ /*Boolean*/
// summary:
// Returns whether this storage provider is available on this
// platform.
console.warn("dojox.storage.isAvailable not implemented");
},
 
put: function( /*string*/ key,
/*object*/ value,
/*function*/ resultsHandler,
/*string?*/ namespace){
// summary:
// Puts a key and value into this storage system.
// description:
// Example-
// var resultsHandler = function(status, key, message){
// alert("status="+status+", key="+key+", message="+message);
// };
// dojox.storage.put("test", "hello world", resultsHandler);
//
// Important note: if you are using Dojo Storage in conjunction with
// Dojo Offline, then you don't need to provide
// a resultsHandler; this is because for Dojo Offline we
// use Google Gears to persist data, which has unlimited data
// once the user has given permission. If you are using Dojo
// Storage apart from Dojo Offline, then under the covers hidden
// Flash might be used, which is both asychronous and which might
// get denied; in this case you must provide a resultsHandler.
// key:
// A string key to use when retrieving this value in the future.
// value:
// A value to store; this can be any JavaScript type.
// resultsHandler:
// A callback function that will receive three arguments. The
// first argument is one of three values: dojox.storage.SUCCESS,
// dojox.storage.FAILED, or dojox.storage.PENDING; these values
// determine how the put request went. In some storage systems
// users can deny a storage request, resulting in a
// dojox.storage.FAILED, while in other storage systems a storage
// request must wait for user approval, resulting in a
// dojox.storage.PENDING status until the request is either
// approved or denied, resulting in another call back with
// dojox.storage.SUCCESS.
// The second argument in the call back is the key name that was being stored.
// The third argument in the call back is an optional message that
// details possible error messages that might have occurred during
// the storage process.
// namespace:
// Optional string namespace that this value will be placed into;
// if left off, the value will be placed into dojox.storage.DEFAULT_NAMESPACE
console.warn("dojox.storage.put not implemented");
},
 
get: function(/*string*/ key, /*string?*/ namespace){ /*Object*/
// summary:
// Gets the value with the given key. Returns null if this key is
// not in the storage system.
// key:
// A string key to get the value of.
// namespace:
// Optional string namespace that this value will be retrieved from;
// if left off, the value will be retrieved from dojox.storage.DEFAULT_NAMESPACE
// return: Returns any JavaScript object type; null if the key is not present
console.warn("dojox.storage.get not implemented");
},
 
hasKey: function(/*string*/ key, /*string?*/ namespace){ /*Boolean*/
// summary: Determines whether the storage has the given key.
return (this.get(key) != null);
},
 
getKeys: function(/*string?*/ namespace){ /*Array*/
// summary: Enumerates all of the available keys in this storage system.
// return: Array of available keys
console.warn("dojox.storage.getKeys not implemented");
},
clear: function(/*string?*/ namespace){
// summary:
// Completely clears this storage system of all of it's values and
// keys. If 'namespace' is provided just clears the keys in that
// namespace.
console.warn("dojox.storage.clear not implemented");
},
remove: function(/*string*/ key, /*string?*/ namespace){
// summary: Removes the given key from this storage system.
console.warn("dojox.storage.remove not implemented");
},
getNamespaces: function(){ /*string[]*/
console.warn("dojox.storage.getNamespaces not implemented");
},
 
isPermanent: function(){ /*Boolean*/
// summary:
// Returns whether this storage provider's values are persisted
// when this platform is shutdown.
console.warn("dojox.storage.isPermanent not implemented");
},
 
getMaximumSize: function(){ /* mixed */
// summary: The maximum storage allowed by this provider
// returns:
// Returns the maximum storage size
// supported by this provider, in
// thousands of bytes (i.e., if it
// returns 60 then this means that 60K
// of storage is supported).
//
// If this provider can not determine
// it's maximum size, then
// dojox.storage.SIZE_NOT_AVAILABLE is
// returned; if there is no theoretical
// limit on the amount of storage
// this provider can return, then
// dojox.storage.SIZE_NO_LIMIT is
// returned
console.warn("dojox.storage.getMaximumSize not implemented");
},
putMultiple: function( /*array*/ keys,
/*array*/ values,
/*function*/ resultsHandler,
/*string?*/ namespace){
// summary:
// Puts multiple keys and values into this storage system.
// description:
// Example-
// var resultsHandler = function(status, key, message){
// alert("status="+status+", key="+key+", message="+message);
// };
// dojox.storage.put(["test"], ["hello world"], resultsHandler);
//
// Important note: if you are using Dojo Storage in conjunction with
// Dojo Offline, then you don't need to provide
// a resultsHandler; this is because for Dojo Offline we
// use Google Gears to persist data, which has unlimited data
// once the user has given permission. If you are using Dojo
// Storage apart from Dojo Offline, then under the covers hidden
// Flash might be used, which is both asychronous and which might
// get denied; in this case you must provide a resultsHandler.
// keys:
// An array of string keys to use when retrieving this value in the future,
// one per value to be stored
// values:
// An array of values to store; this can be any JavaScript type, though the
// performance of plain strings is considerably better
// resultsHandler:
// A callback function that will receive three arguments. The
// first argument is one of three values: dojox.storage.SUCCESS,
// dojox.storage.FAILED, or dojox.storage.PENDING; these values
// determine how the put request went. In some storage systems
// users can deny a storage request, resulting in a
// dojox.storage.FAILED, while in other storage systems a storage
// request must wait for user approval, resulting in a
// dojox.storage.PENDING status until the request is either
// approved or denied, resulting in another call back with
// dojox.storage.SUCCESS.
// The second argument in the call back is the key name that was being stored.
// The third argument in the call back is an optional message that
// details possible error messages that might have occurred during
// the storage process.
// namespace:
// Optional string namespace that this value will be placed into;
// if left off, the value will be placed into dojox.storage.DEFAULT_NAMESPACE
console.warn("dojox.storage.putMultiple not implemented");
// JAC: We could implement a 'default' puMultiple here by just doing each put individually
},
 
getMultiple: function(/*array*/ keys, /*string?*/ namespace){ /*Object*/
// summary:
// Gets the valuse corresponding to each of the given keys.
// Returns a null array element for each given key that is
// not in the storage system.
// keys:
// An array of string keys to get the value of.
// namespace:
// Optional string namespace that this value will be retrieved from;
// if left off, the value will be retrieved from dojox.storage.DEFAULT_NAMESPACE
// return: Returns any JavaScript object type; null if the key is not present
 
console.warn("dojox.storage.getMultiple not implemented");
// JAC: We could implement a 'default' getMultiple here by just doing each get individually
},
 
removeMultiple: function(/*array*/ keys, /*string?*/ namespace) {
// summary: Removes the given keys from this storage system.
 
// JAC: We could implement a 'default' removeMultiple here by just doing each remove individually
console.warn("dojox.storage.remove not implemented");
},
isValidKeyArray: function( keys) {
if(keys === null || typeof keys === "undefined" || ! keys instanceof Array){
return false;
}
// JAC: This could be optimized by running the key validity test directly over a joined string
for(var k=0;k<keys.length;k++){
if(!this.isValidKey(keys[k])){
return false;
}
}
return true;
},
 
hasSettingsUI: function(){ /*Boolean*/
// summary: Determines whether this provider has a settings UI.
return false;
},
 
showSettingsUI: function(){
// summary: If this provider has a settings UI, determined
// by calling hasSettingsUI(), it is shown.
console.warn("dojox.storage.showSettingsUI not implemented");
},
 
hideSettingsUI: function(){
// summary: If this provider has a settings UI, hides it.
console.warn("dojox.storage.hideSettingsUI not implemented");
},
isValidKey: function(/*string*/ keyName){ /*Boolean*/
// summary:
// Subclasses can call this to ensure that the key given is valid
// in a consistent way across different storage providers. We use
// the lowest common denominator for key values allowed: only
// letters, numbers, and underscores are allowed. No spaces.
if((keyName == null)||(typeof keyName == "undefined")){
return false;
}
return /^[0-9A-Za-z_]*$/.test(keyName);
},
getResourceList: function(){ /* Array[] */
// summary:
// Returns a list of URLs that this
// storage provider might depend on.
// description:
// This method returns a list of URLs that this
// storage provider depends on to do its work.
// This list is used by the Dojo Offline Toolkit
// to cache these resources to ensure the machinery
// used by this storage provider is available offline.
// What is returned is an array of URLs.
return [];
}
});
 
}
 
if(!dojo._hasResource["dojox.storage.manager"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.storage.manager"] = true;
dojo.provide("dojox.storage.manager");
//dojo.require("dojo.AdapterRegistry");
// FIXME: refactor this to use an AdapterRegistry
 
dojox.storage.manager = new function(){
// summary: A singleton class in charge of the dojox.storage system
// description:
// Initializes the storage systems and figures out the best available
// storage options on this platform.
// currentProvider: Object
// The storage provider that was automagically chosen to do storage
// on this platform, such as dojox.storage.FlashStorageProvider.
this.currentProvider = null;
// available: Boolean
// Whether storage of some kind is available.
this.available = false;
this._initialized = false;
this._providers = [];
this._onLoadListeners = [];
this.initialize = function(){
// summary:
// Initializes the storage system and autodetects the best storage
// provider we can provide on this platform
this.autodetect();
};
this.register = function(/*string*/ name, /*Object*/ instance){
// summary:
// Registers the existence of a new storage provider; used by
// subclasses to inform the manager of their existence. The
// storage manager will select storage providers based on
// their ordering, so the order in which you call this method
// matters.
// name:
// The full class name of this provider, such as
// "dojox.storage.FlashStorageProvider".
// instance:
// An instance of this provider, which we will use to call
// isAvailable() on.
this._providers[this._providers.length] = instance; //FIXME: push?
this._providers[name] = instance; // FIXME: this._providers is an array, not a hash
};
this.setProvider = function(storageClass){
// summary:
// Instructs the storageManager to use the given storage class for
// all storage requests.
// description:
// Example-
// dojox.storage.setProvider(
// dojox.storage.IEStorageProvider)
};
this.autodetect = function(){
// summary:
// Autodetects the best possible persistent storage provider
// available on this platform.
//console.debug("dojox.storage.manager.autodetect");
if(this._initialized){ // already finished
//console.debug("dojox.storage.manager already initialized; returning");
return;
}
 
// a flag to force the storage manager to use a particular
// storage provider type, such as
// djConfig = {forceStorageProvider: "dojox.storage.WhatWGStorageProvider"};
var forceProvider = djConfig["forceStorageProvider"]||false;
 
// go through each provider, seeing if it can be used
var providerToUse;
//FIXME: use dojo.some
for(var i = 0; i < this._providers.length; i++){
providerToUse = this._providers[i];
if(forceProvider == providerToUse.declaredClass){
// still call isAvailable for this provider, since this helps some
// providers internally figure out if they are available
// FIXME: This should be refactored since it is non-intuitive
// that isAvailable() would initialize some state
providerToUse.isAvailable();
break;
}else if(providerToUse.isAvailable()){
break;
}
}
if(!providerToUse){ // no provider available
this._initialized = true;
this.available = false;
this.currentProvider = null;
console.warn("No storage provider found for this platform");
this.loaded();
return;
}
// create this provider and mix in it's properties
// so that developers can do dojox.storage.put rather
// than dojox.storage.currentProvider.put, for example
this.currentProvider = providerToUse;
dojo.mixin(dojox.storage, this.currentProvider);
// have the provider initialize itself
dojox.storage.initialize();
this._initialized = true;
this.available = true;
};
this.isAvailable = function(){ /*Boolean*/
// summary: Returns whether any storage options are available.
return this.available;
};
this.addOnLoad = function(func){ /* void */
// summary:
// Adds an onload listener to know when Dojo Offline can be used.
// description:
// Adds a listener to know when Dojo Offline can be used. This
// ensures that the Dojo Offline framework is loaded and that the
// local dojox.storage system is ready to be used. This method is
// useful if you don't want to have a dependency on Dojo Events
// when using dojox.storage.
// func: Function
// A function to call when Dojo Offline is ready to go
this._onLoadListeners.push(func);
if(this.isInitialized()){
this._fireLoaded();
}
};
this.removeOnLoad = function(func){ /* void */
// summary: Removes the given onLoad listener
for(var i = 0; i < this._onLoadListeners.length; i++){
if(func == this._onLoadListeners[i]){
this._onLoadListeners = this._onLoadListeners.splice(i, 1);
break;
}
}
};
this.isInitialized = function(){ /*Boolean*/
// summary:
// Returns whether the storage system is initialized and ready to
// be used.
 
// FIXME: This should REALLY not be in here, but it fixes a tricky
// Flash timing bug
if(this.currentProvider != null
&& this.currentProvider.declaredClass == "dojox.storage.FlashStorageProvider"
&& dojox.flash.ready == false){
return false;
}else{
return this._initialized;
}
};
 
this.supportsProvider = function(/*string*/ storageClass){ /* Boolean */
// summary: Determines if this platform supports the given storage provider.
// description:
// Example-
// dojox.storage.manager.supportsProvider(
// "dojox.storage.InternetExplorerStorageProvider");
 
// construct this class dynamically
try{
// dynamically call the given providers class level isAvailable()
// method
var provider = eval("new " + storageClass + "()");
var results = provider.isAvailable();
if(!results){ return false; }
return results;
}catch(e){
return false;
}
};
 
this.getProvider = function(){ /* Object */
// summary: Gets the current provider
return this.currentProvider;
};
this.loaded = function(){
// summary:
// The storage provider should call this method when it is loaded
// and ready to be used. Clients who will use the provider will
// connect to this method to know when they can use the storage
// system. You can either use dojo.connect to connect to this
// function, or can use dojox.storage.manager.addOnLoad() to add
// a listener that does not depend on the dojo.event package.
// description:
// Example 1-
// if(dojox.storage.manager.isInitialized() == false){
// dojo.connect(dojox.storage.manager, "loaded", TestStorage, "initialize");
// }else{
// dojo.connect(dojo, "loaded", TestStorage, "initialize");
// }
// Example 2-
// dojox.storage.manager.addOnLoad(someFunction);
 
 
// FIXME: we should just provide a Deferred for this. That way you
// don't care when this happens or has happened. Deferreds are in Base
this._fireLoaded();
};
this._fireLoaded = function(){
//console.debug("dojox.storage.manager._fireLoaded");
dojo.forEach(this._onLoadListeners, function(i){
try{
i();
}catch(e){ console.debug(e); }
});
};
this.getResourceList = function(){
// summary:
// Returns a list of whatever resources are necessary for storage
// providers to work.
// description:
// This will return all files needed by all storage providers for
// this particular environment type. For example, if we are in the
// browser environment, then this will return the hidden SWF files
// needed by the FlashStorageProvider, even if we don't need them
// for the particular browser we are working within. This is meant
// to faciliate Dojo Offline, which must retrieve all resources we
// need offline into the offline cache -- we retrieve everything
// needed, in case another browser that requires different storage
// mechanisms hits the local offline cache. For example, if we
// were to sync against Dojo Offline on Firefox 2, then we would
// not grab the FlashStorageProvider resources needed for Safari.
var results = [];
dojo.forEach(dojox.storage.manager._providers, function(currentProvider){
results = results.concat(currentProvider.getResourceList());
});
return results;
}
};
 
}
 
if(!dojo._hasResource["dojox._sql._crypto"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox._sql._crypto"] = true;
// Taken from http://www.movable-type.co.uk/scripts/aes.html by
// Chris Veness (CLA signed); adapted for Dojo and Google Gears Worker Pool
// by Brad Neuberg, bkn3@columbia.edu
 
dojo.provide("dojox._sql._crypto");
 
dojo.mixin(dojox._sql._crypto,{
// _POOL_SIZE:
// Size of worker pool to create to help with crypto
_POOL_SIZE: 100,
encrypt: function(plaintext, password, callback){
// summary:
// Use Corrected Block TEA to encrypt plaintext using password
// (note plaintext & password must be strings not string objects).
// Results will be returned to the 'callback' asychronously.
this._initWorkerPool();
var msg ={plaintext: plaintext, password: password};
msg = dojo.toJson(msg);
msg = "encr:" + String(msg);
this._assignWork(msg, callback);
},
 
decrypt: function(ciphertext, password, callback){
// summary:
// Use Corrected Block TEA to decrypt ciphertext using password
// (note ciphertext & password must be strings not string objects).
// Results will be returned to the 'callback' asychronously.
this._initWorkerPool();
var msg ={ciphertext: ciphertext, password: password};
msg = dojo.toJson(msg);
msg = "decr:" + String(msg);
this._assignWork(msg, callback);
},
_initWorkerPool: function(){
// bugs in Google Gears prevents us from dynamically creating
// and destroying workers as we need them -- the worker
// pool functionality stops working after a number of crypto
// cycles (probably related to a memory leak in Google Gears).
// this is too bad, since it results in much simpler code.
// instead, we have to create a pool of workers and reuse them. we
// keep a stack of 'unemployed' Worker IDs that are currently not working.
// if a work request comes in, we pop off the 'unemployed' stack
// and put them to work, storing them in an 'employed' hashtable,
// keyed by their Worker ID with the value being the callback function
// that wants the result. when an employed worker is done, we get
// a message in our 'manager' which adds this worker back to the
// unemployed stack and routes the result to the callback that
// wanted it. if all the workers were employed in the past but
// more work needed to be done (i.e. it's a tight labor pool ;)
// then the work messages are pushed onto
// a 'handleMessage' queue as an object tuple{msg: msg, callback: callback}
if(!this._manager){
try{
this._manager = google.gears.factory.create("beta.workerpool", "1.0");
this._unemployed = [];
this._employed ={};
this._handleMessage = [];
var self = this;
this._manager.onmessage = function(msg, sender){
// get the callback necessary to serve this result
var callback = self._employed["_" + sender];
// make this worker unemployed
self._employed["_" + sender] = undefined;
self._unemployed.push("_" + sender);
// see if we need to assign new work
// that was queued up needing to be done
if(self._handleMessage.length){
var handleMe = self._handleMessage.shift();
self._assignWork(handleMe.msg, handleMe.callback);
}
// return results
callback(msg);
}
var workerInit = "function _workerInit(){"
+ "gearsWorkerPool.onmessage = "
+ String(this._workerHandler)
+ ";"
+ "}";
var code = workerInit + " _workerInit();";
// create our worker pool
for(var i = 0; i < this._POOL_SIZE; i++){
this._unemployed.push("_" + this._manager.createWorker(code));
}
}catch(exp){
throw exp.message||exp;
}
}
},
 
_assignWork: function(msg, callback){
// can we immediately assign this work?
if(!this._handleMessage.length && this._unemployed.length){
// get an unemployed worker
var workerID = this._unemployed.shift().substring(1); // remove _
// list this worker as employed
this._employed["_" + workerID] = callback;
// do the worke
this._manager.sendMessage(msg, workerID);
}else{
// we have to queue it up
this._handleMessage ={msg: msg, callback: callback};
}
},
 
_workerHandler: function(msg, sender){
/* Begin AES Implementation */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
// Sbox is pre-computed multiplicative inverse in GF(2^8) used in SubBytes and KeyExpansion [§5.1.1]
var Sbox = [0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16];
 
// Rcon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [§5.2]
var Rcon = [ [0x00, 0x00, 0x00, 0x00],
[0x01, 0x00, 0x00, 0x00],
[0x02, 0x00, 0x00, 0x00],
[0x04, 0x00, 0x00, 0x00],
[0x08, 0x00, 0x00, 0x00],
[0x10, 0x00, 0x00, 0x00],
[0x20, 0x00, 0x00, 0x00],
[0x40, 0x00, 0x00, 0x00],
[0x80, 0x00, 0x00, 0x00],
[0x1b, 0x00, 0x00, 0x00],
[0x36, 0x00, 0x00, 0x00] ];
 
/*
* AES Cipher function: encrypt 'input' with Rijndael algorithm
*
* takes byte-array 'input' (16 bytes)
* 2D byte-array key schedule 'w' (Nr+1 x Nb bytes)
*
* applies Nr rounds (10/12/14) using key schedule w for 'add round key' stage
*
* returns byte-array encrypted value (16 bytes)
*/
function Cipher(input, w) { // main Cipher function [§5.1]
var Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES)
var Nr = w.length/Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys
 
var state = [[],[],[],[]]; // initialise 4xNb byte-array 'state' with input [§3.4]
for (var i=0; i<4*Nb; i++) state[i%4][Math.floor(i/4)] = input[i];
 
state = AddRoundKey(state, w, 0, Nb);
 
for (var round=1; round<Nr; round++) {
state = SubBytes(state, Nb);
state = ShiftRows(state, Nb);
state = MixColumns(state, Nb);
state = AddRoundKey(state, w, round, Nb);
}
 
state = SubBytes(state, Nb);
state = ShiftRows(state, Nb);
state = AddRoundKey(state, w, Nr, Nb);
 
var output = new Array(4*Nb); // convert state to 1-d array before returning [§3.4]
for (var i=0; i<4*Nb; i++) output[i] = state[i%4][Math.floor(i/4)];
return output;
}
 
 
function SubBytes(s, Nb) { // apply SBox to state S [§5.1.1]
for (var r=0; r<4; r++) {
for (var c=0; c<Nb; c++) s[r][c] = Sbox[s[r][c]];
}
return s;
}
 
 
function ShiftRows(s, Nb) { // shift row r of state S left by r bytes [§5.1.2]
var t = new Array(4);
for (var r=1; r<4; r++) {
for (var c=0; c<4; c++) t[c] = s[r][(c+r)%Nb]; // shift into temp copy
for (var c=0; c<4; c++) s[r][c] = t[c]; // and copy back
} // note that this will work for Nb=4,5,6, but not 7,8 (always 4 for AES):
return s; // see fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.311.pdf
}
 
 
function MixColumns(s, Nb) { // combine bytes of each col of state S [§5.1.3]
for (var c=0; c<4; c++) {
var a = new Array(4); // 'a' is a copy of the current column from 's'
var b = new Array(4); // 'b' is a•{02} in GF(2^8)
for (var i=0; i<4; i++) {
a[i] = s[i][c];
b[i] = s[i][c]&0x80 ? s[i][c]<<1 ^ 0x011b : s[i][c]<<1;
}
// a[n] ^ b[n] is a•{03} in GF(2^8)
s[0][c] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3]; // 2*a0 + 3*a1 + a2 + a3
s[1][c] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3]; // a0 * 2*a1 + 3*a2 + a3
s[2][c] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3]; // a0 + a1 + 2*a2 + 3*a3
s[3][c] = a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3]; // 3*a0 + a1 + a2 + 2*a3
}
return s;
}
 
 
function AddRoundKey(state, w, rnd, Nb) { // xor Round Key into state S [§5.1.4]
for (var r=0; r<4; r++) {
for (var c=0; c<Nb; c++) state[r][c] ^= w[rnd*4+c][r];
}
return state;
}
 
 
function KeyExpansion(key) { // generate Key Schedule (byte-array Nr+1 x Nb) from Key [§5.2]
var Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES)
var Nk = key.length/4 // key length (in words): 4/6/8 for 128/192/256-bit keys
var Nr = Nk + 6; // no of rounds: 10/12/14 for 128/192/256-bit keys
 
var w = new Array(Nb*(Nr+1));
var temp = new Array(4);
 
for (var i=0; i<Nk; i++) {
var r = [key[4*i], key[4*i+1], key[4*i+2], key[4*i+3]];
w[i] = r;
}
 
for (var i=Nk; i<(Nb*(Nr+1)); i++) {
w[i] = new Array(4);
for (var t=0; t<4; t++) temp[t] = w[i-1][t];
if (i % Nk == 0) {
temp = SubWord(RotWord(temp));
for (var t=0; t<4; t++) temp[t] ^= Rcon[i/Nk][t];
} else if (Nk > 6 && i%Nk == 4) {
temp = SubWord(temp);
}
for (var t=0; t<4; t++) w[i][t] = w[i-Nk][t] ^ temp[t];
}
 
return w;
}
 
function SubWord(w) { // apply SBox to 4-byte word w
for (var i=0; i<4; i++) w[i] = Sbox[w[i]];
return w;
}
 
function RotWord(w) { // rotate 4-byte word w left by one byte
w[4] = w[0];
for (var i=0; i<4; i++) w[i] = w[i+1];
return w;
}
 
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
 
/*
* Use AES to encrypt 'plaintext' with 'password' using 'nBits' key, in 'Counter' mode of operation
* - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
* for each block
* - outputblock = cipher(counter, key)
* - cipherblock = plaintext xor outputblock
*/
function AESEncryptCtr(plaintext, password, nBits) {
if (!(nBits==128 || nBits==192 || nBits==256)) return ''; // standard allows 128/192/256 bit keys
// for this example script, generate the key by applying Cipher to 1st 16/24/32 chars of password;
// for real-world applications, a more secure approach would be to hash the password e.g. with SHA-1
var nBytes = nBits/8; // no bytes in key
var pwBytes = new Array(nBytes);
for (var i=0; i<nBytes; i++) pwBytes[i] = password.charCodeAt(i) & 0xff;
 
var key = Cipher(pwBytes, KeyExpansion(pwBytes));
 
key = key.concat(key.slice(0, nBytes-16)); // key is now 16/24/32 bytes long
 
// initialise counter block (NIST SP800-38A §B.2): millisecond time-stamp for nonce in 1st 8 bytes,
// block counter in 2nd 8 bytes
var blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
var counterBlock = new Array(blockSize); // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
var nonce = (new Date()).getTime(); // milliseconds since 1-Jan-1970
 
// encode nonce in two stages to cater for JavaScript 32-bit limit on bitwise ops
for (var i=0; i<4; i++) counterBlock[i] = (nonce >>> i*8) & 0xff;
for (var i=0; i<4; i++) counterBlock[i+4] = (nonce/0x100000000 >>> i*8) & 0xff;
 
// generate key schedule - an expansion of the key into distinct Key Rounds for each round
var keySchedule = KeyExpansion(key);
 
var blockCount = Math.ceil(plaintext.length/blockSize);
var ciphertext = new Array(blockCount); // ciphertext as array of strings
for (var b=0; b<blockCount; b++) {
// set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
// again done in two stages for 32-bit ops
for (var c=0; c<4; c++) counterBlock[15-c] = (b >>> c*8) & 0xff;
for (var c=0; c<4; c++) counterBlock[15-c-4] = (b/0x100000000 >>> c*8)
 
var cipherCntr = Cipher(counterBlock, keySchedule); // -- encrypt counter block --
// calculate length of final block:
var blockLength = b<blockCount-1 ? blockSize : (plaintext.length-1)%blockSize+1;
 
var ct = '';
for (var i=0; i<blockLength; i++) { // -- xor plaintext with ciphered counter byte-by-byte --
var plaintextByte = plaintext.charCodeAt(b*blockSize+i);
var cipherByte = plaintextByte ^ cipherCntr[i];
ct += String.fromCharCode(cipherByte);
}
// ct is now ciphertext for this block
 
ciphertext[b] = escCtrlChars(ct); // escape troublesome characters in ciphertext
}
 
// convert the nonce to a string to go on the front of the ciphertext
var ctrTxt = '';
for (var i=0; i<8; i++) ctrTxt += String.fromCharCode(counterBlock[i]);
ctrTxt = escCtrlChars(ctrTxt);
 
// use '-' to separate blocks, use Array.join to concatenate arrays of strings for efficiency
return ctrTxt + '-' + ciphertext.join('-');
}
 
 
/*
* Use AES to decrypt 'ciphertext' with 'password' using 'nBits' key, in Counter mode of operation
*
* for each block
* - outputblock = cipher(counter, key)
* - cipherblock = plaintext xor outputblock
*/
function AESDecryptCtr(ciphertext, password, nBits) {
if (!(nBits==128 || nBits==192 || nBits==256)) return ''; // standard allows 128/192/256 bit keys
 
var nBytes = nBits/8; // no bytes in key
var pwBytes = new Array(nBytes);
for (var i=0; i<nBytes; i++) pwBytes[i] = password.charCodeAt(i) & 0xff;
var pwKeySchedule = KeyExpansion(pwBytes);
var key = Cipher(pwBytes, pwKeySchedule);
key = key.concat(key.slice(0, nBytes-16)); // key is now 16/24/32 bytes long
 
var keySchedule = KeyExpansion(key);
 
ciphertext = ciphertext.split('-'); // split ciphertext into array of block-length strings
 
// recover nonce from 1st element of ciphertext
var blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
var counterBlock = new Array(blockSize);
var ctrTxt = unescCtrlChars(ciphertext[0]);
for (var i=0; i<8; i++) counterBlock[i] = ctrTxt.charCodeAt(i);
 
var plaintext = new Array(ciphertext.length-1);
 
for (var b=1; b<ciphertext.length; b++) {
// set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
for (var c=0; c<4; c++) counterBlock[15-c] = ((b-1) >>> c*8) & 0xff;
for (var c=0; c<4; c++) counterBlock[15-c-4] = ((b/0x100000000-1) >>> c*8) & 0xff;
 
var cipherCntr = Cipher(counterBlock, keySchedule); // encrypt counter block
 
ciphertext[b] = unescCtrlChars(ciphertext[b]);
 
var pt = '';
for (var i=0; i<ciphertext[b].length; i++) {
// -- xor plaintext with ciphered counter byte-by-byte --
var ciphertextByte = ciphertext[b].charCodeAt(i);
var plaintextByte = ciphertextByte ^ cipherCntr[i];
pt += String.fromCharCode(plaintextByte);
}
// pt is now plaintext for this block
 
plaintext[b-1] = pt; // b-1 'cos no initial nonce block in plaintext
}
 
return plaintext.join('');
}
 
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
 
function escCtrlChars(str) { // escape control chars which might cause problems handling ciphertext
return str.replace(/[\0\t\n\v\f\r\xa0!-]/g, function(c) { return '!' + c.charCodeAt(0) + '!'; });
} // \xa0 to cater for bug in Firefox; include '-' to leave it free for use as a block marker
 
function unescCtrlChars(str) { // unescape potentially problematic control characters
return str.replace(/!\d\d?\d?!/g, function(c) { return String.fromCharCode(c.slice(1,-1)); });
}
 
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
function encrypt(plaintext, password){
return AESEncryptCtr(plaintext, password, 256);
}
 
function decrypt(ciphertext, password){
return AESDecryptCtr(ciphertext, password, 256);
}
/* End AES Implementation */
var cmd = msg.substr(0,4);
var arg = msg.substr(5);
if(cmd == "encr"){
arg = eval("(" + arg + ")");
var plaintext = arg.plaintext;
var password = arg.password;
var results = encrypt(plaintext, password);
gearsWorkerPool.sendMessage(String(results), sender);
}else if(cmd == "decr"){
arg = eval("(" + arg + ")");
var ciphertext = arg.ciphertext;
var password = arg.password;
var results = decrypt(ciphertext, password);
gearsWorkerPool.sendMessage(String(results), sender);
}
}
});
 
}
 
if(!dojo._hasResource["dojox._sql.common"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox._sql.common"] = true;
dojo.provide("dojox._sql.common");
 
 
 
// summary:
// Executes a SQL expression.
// description:
// There are four ways to call this:
// 1) Straight SQL: dojox.sql("SELECT * FROM FOOBAR");
// 2) SQL with parameters: dojox.sql("INSERT INTO FOOBAR VALUES (?)", someParam)
// 3) Encrypting particular values:
// dojox.sql("INSERT INTO FOOBAR VALUES (ENCRYPT(?))", someParam, "somePassword", callback)
// 4) Decrypting particular values:
// dojox.sql("SELECT DECRYPT(SOMECOL1), DECRYPT(SOMECOL2) FROM
// FOOBAR WHERE SOMECOL3 = ?", someParam,
// "somePassword", callback)
//
// For encryption and decryption the last two values should be the the password for
// encryption/decryption, and the callback function that gets the result set.
//
// Note: We only support ENCRYPT(?) statements, and
// and DECRYPT(*) statements for now -- you can not have a literal string
// inside of these, such as ENCRYPT('foobar')
//
// Note: If you have multiple columns to encrypt and decrypt, you can use the following
// convenience form to not have to type ENCRYPT(?)/DECRYPT(*) many times:
//
// dojox.sql("INSERT INTO FOOBAR VALUES (ENCRYPT(?, ?, ?))",
// someParam1, someParam2, someParam3,
// "somePassword", callback)
//
// dojox.sql("SELECT DECRYPT(SOMECOL1, SOMECOL2) FROM
// FOOBAR WHERE SOMECOL3 = ?", someParam,
// "somePassword", callback)
dojox.sql = new Function("return dojox.sql._exec(arguments);");
 
dojo.mixin(dojox.sql, {
dbName: null,
// summary:
// If true, then we print out any SQL that is executed
// to the debug window
debug: (dojo.exists("dojox.sql.debug")?dojox.sql.debug:false),
 
open: function(dbName){
if(this._dbOpen && (!dbName || dbName == this.dbName)){
return;
}
if(!this.dbName){
this.dbName = "dot_store_"
+ window.location.href.replace(/[^0-9A-Za-z_]/g, "_");
//console.debug("Using Google Gears database " + this.dbName);
}
if(!dbName){
dbName = this.dbName;
}
try{
this._initDb();
this.db.open(dbName);
this._dbOpen = true;
}catch(exp){
throw exp.message||exp;
}
},
 
close: function(dbName){
// on Internet Explorer, Google Gears throws an exception
// "Object not a collection", when we try to close the
// database -- just don't close it on this platform
// since we are running into a Gears bug; the Gears team
// said it's ok to not close a database connection
if(dojo.isIE){ return; }
if(!this._dbOpen && (!dbName || dbName == this.dbName)){
return;
}
if(!dbName){
dbName = this.dbName;
}
try{
this.db.close(dbName);
this._dbOpen = false;
}catch(exp){
throw exp.message||exp;
}
},
_exec: function(params){
try{
// get the Gears Database object
this._initDb();
// see if we need to open the db; if programmer
// manually called dojox.sql.open() let them handle
// it; otherwise we open and close automatically on
// each SQL execution
if(!this._dbOpen){
this.open();
this._autoClose = true;
}
// determine our parameters
var sql = null;
var callback = null;
var password = null;
 
var args = dojo._toArray(params);
 
sql = args.splice(0, 1)[0];
 
// does this SQL statement use the ENCRYPT or DECRYPT
// keywords? if so, extract our callback and crypto
// password
if(this._needsEncrypt(sql) || this._needsDecrypt(sql)){
callback = args.splice(args.length - 1, 1)[0];
password = args.splice(args.length - 1, 1)[0];
}
 
// 'args' now just has the SQL parameters
 
// print out debug SQL output if the developer wants that
if(this.debug){
this._printDebugSQL(sql, args);
}
 
// handle SQL that needs encryption/decryption differently
// do we have an ENCRYPT SQL statement? if so, handle that first
if(this._needsEncrypt(sql)){
var crypto = new dojox.sql._SQLCrypto("encrypt", sql,
password, args,
callback);
return; // encrypted results will arrive asynchronously
}else if(this._needsDecrypt(sql)){ // otherwise we have a DECRYPT statement
var crypto = new dojox.sql._SQLCrypto("decrypt", sql,
password, args,
callback);
return; // decrypted results will arrive asynchronously
}
 
// execute the SQL and get the results
var rs = this.db.execute(sql, args);
// Gears ResultSet object's are ugly -- normalize
// these into something JavaScript programmers know
// how to work with, basically an array of
// JavaScript objects where each property name is
// simply the field name for a column of data
rs = this._normalizeResults(rs);
if(this._autoClose){
this.close();
}
return rs;
}catch(exp){
exp = exp.message||exp;
console.debug("SQL Exception: " + exp);
if(this._autoClose){
try{
this.close();
}catch(e){
console.debug("Error closing database: "
+ e.message||e);
}
}
throw exp;
}
},
 
_initDb: function(){
if(!this.db){
try{
this.db = google.gears.factory.create('beta.database', '1.0');
}catch(exp){
dojo.setObject("google.gears.denied", true);
dojox.off.onFrameworkEvent("coreOperationFailed");
throw "Google Gears must be allowed to run";
}
}
},
 
_printDebugSQL: function(sql, args){
var msg = "dojox.sql(\"" + sql + "\"";
for(var i = 0; i < args.length; i++){
if(typeof args[i] == "string"){
msg += ", \"" + args[i] + "\"";
}else{
msg += ", " + args[i];
}
}
msg += ")";
console.debug(msg);
},
 
_normalizeResults: function(rs){
var results = [];
if(!rs){ return []; }
while(rs.isValidRow()){
var row = {};
for(var i = 0; i < rs.fieldCount(); i++){
var fieldName = rs.fieldName(i);
var fieldValue = rs.field(i);
row[fieldName] = fieldValue;
}
results.push(row);
rs.next();
}
rs.close();
return results;
},
 
_needsEncrypt: function(sql){
return /encrypt\([^\)]*\)/i.test(sql);
},
 
_needsDecrypt: function(sql){
return /decrypt\([^\)]*\)/i.test(sql);
}
});
 
// summary:
// A private class encapsulating any cryptography that must be done
// on a SQL statement. We instantiate this class and have it hold
// it's state so that we can potentially have several encryption
// operations happening at the same time by different SQL statements.
dojo.declare("dojox.sql._SQLCrypto", null, {
constructor: function(action, sql, password, args, callback){
if(action == "encrypt"){
this._execEncryptSQL(sql, password, args, callback);
}else{
this._execDecryptSQL(sql, password, args, callback);
}
},
_execEncryptSQL: function(sql, password, args, callback){
// strip the ENCRYPT/DECRYPT keywords from the SQL
var strippedSQL = this._stripCryptoSQL(sql);
// determine what arguments need encryption
var encryptColumns = this._flagEncryptedArgs(sql, args);
// asynchronously encrypt each argument that needs it
var self = this;
this._encrypt(strippedSQL, password, args, encryptColumns, function(finalArgs){
// execute the SQL
var error = false;
var resultSet = [];
var exp = null;
try{
resultSet = dojox.sql.db.execute(strippedSQL, finalArgs);
}catch(execError){
error = true;
exp = execError.message||execError;
}
// was there an error during SQL execution?
if(exp != null){
if(dojox.sql._autoClose){
try{ dojox.sql.close(); }catch(e){}
}
callback(null, true, exp.toString());
return;
}
// normalize SQL results into a JavaScript object
// we can work with
resultSet = dojox.sql._normalizeResults(resultSet);
if(dojox.sql._autoClose){
dojox.sql.close();
}
// are any decryptions necessary on the result set?
if(dojox.sql._needsDecrypt(sql)){
// determine which of the result set columns needs decryption
var needsDecrypt = self._determineDecryptedColumns(sql);
 
// now decrypt columns asynchronously
// decrypt columns that need it
self._decrypt(resultSet, needsDecrypt, password, function(finalResultSet){
callback(finalResultSet, false, null);
});
}else{
callback(resultSet, false, null);
}
});
},
 
_execDecryptSQL: function(sql, password, args, callback){
// strip the ENCRYPT/DECRYPT keywords from the SQL
var strippedSQL = this._stripCryptoSQL(sql);
// determine which columns needs decryption; this either
// returns the value *, which means all result set columns will
// be decrypted, or it will return the column names that need
// decryption set on a hashtable so we can quickly test a given
// column name; the key is the column name that needs
// decryption and the value is 'true' (i.e. needsDecrypt["someColumn"]
// would return 'true' if it needs decryption, and would be 'undefined'
// or false otherwise)
var needsDecrypt = this._determineDecryptedColumns(sql);
// execute the SQL
var error = false;
var resultSet = [];
var exp = null;
try{
resultSet = dojox.sql.db.execute(strippedSQL, args);
}catch(execError){
error = true;
exp = execError.message||execError;
}
// was there an error during SQL execution?
if(exp != null){
if(dojox.sql._autoClose){
try{ dojox.sql.close(); }catch(e){}
}
callback(resultSet, true, exp.toString());
return;
}
// normalize SQL results into a JavaScript object
// we can work with
resultSet = dojox.sql._normalizeResults(resultSet);
if(dojox.sql._autoClose){
dojox.sql.close();
}
// decrypt columns that need it
this._decrypt(resultSet, needsDecrypt, password, function(finalResultSet){
callback(finalResultSet, false, null);
});
},
 
_encrypt: function(sql, password, args, encryptColumns, callback){
//console.debug("_encrypt, sql="+sql+", password="+password+", encryptColumns="+encryptColumns+", args="+args);
this._totalCrypto = 0;
this._finishedCrypto = 0;
this._finishedSpawningCrypto = false;
this._finalArgs = args;
for(var i = 0; i < args.length; i++){
if(encryptColumns[i]){
// we have an encrypt() keyword -- get just the value inside
// the encrypt() parantheses -- for now this must be a ?
var sqlParam = args[i];
var paramIndex = i;
// update the total number of encryptions we know must be done asynchronously
this._totalCrypto++;
// FIXME: This currently uses DES as a proof-of-concept since the
// DES code used is quite fast and was easy to work with. Modify dojox.sql
// to be able to specify a different encryption provider through a
// a SQL-like syntax, such as dojox.sql("SET ENCRYPTION BLOWFISH"),
// and modify the dojox.crypto.Blowfish code to be able to work using
// a Google Gears Worker Pool
// do the actual encryption now, asychronously on a Gears worker thread
dojox._sql._crypto.encrypt(sqlParam, password, dojo.hitch(this, function(results){
// set the new encrypted value
this._finalArgs[paramIndex] = results;
this._finishedCrypto++;
// are we done with all encryption?
if(this._finishedCrypto >= this._totalCrypto
&& this._finishedSpawningCrypto){
callback(this._finalArgs);
}
}));
}
}
this._finishedSpawningCrypto = true;
},
 
_decrypt: function(resultSet, needsDecrypt, password, callback){
//console.debug("decrypt, resultSet="+resultSet+", needsDecrypt="+needsDecrypt+", password="+password);
this._totalCrypto = 0;
this._finishedCrypto = 0;
this._finishedSpawningCrypto = false;
this._finalResultSet = resultSet;
for(var i = 0; i < resultSet.length; i++){
var row = resultSet[i];
// go through each of the column names in row,
// seeing if they need decryption
for(var columnName in row){
if(needsDecrypt == "*" || needsDecrypt[columnName]){
this._totalCrypto++;
var columnValue = row[columnName];
// forming a closure here can cause issues, with values not cleanly
// saved on Firefox/Mac OS X for some of the values above that
// are needed in the callback below; call a subroutine that will form
// a closure inside of itself instead
this._decryptSingleColumn(columnName, columnValue, password, i,
function(finalResultSet){
callback(finalResultSet);
});
}
}
}
this._finishedSpawningCrypto = true;
},
 
_stripCryptoSQL: function(sql){
// replace all DECRYPT(*) occurrences with a *
sql = sql.replace(/DECRYPT\(\*\)/ig, "*");
// match any ENCRYPT(?, ?, ?, etc) occurrences,
// then replace with just the question marks in the
// middle
var matches = sql.match(/ENCRYPT\([^\)]*\)/ig);
if(matches != null){
for(var i = 0; i < matches.length; i++){
var encryptStatement = matches[i];
var encryptValue = encryptStatement.match(/ENCRYPT\(([^\)]*)\)/i)[1];
sql = sql.replace(encryptStatement, encryptValue);
}
}
// match any DECRYPT(COL1, COL2, etc) occurrences,
// then replace with just the column names
// in the middle
matches = sql.match(/DECRYPT\([^\)]*\)/ig);
if(matches != null){
for(var i = 0; i < matches.length; i++){
var decryptStatement = matches[i];
var decryptValue = decryptStatement.match(/DECRYPT\(([^\)]*)\)/i)[1];
sql = sql.replace(decryptStatement, decryptValue);
}
}
return sql;
},
 
_flagEncryptedArgs: function(sql, args){
// capture literal strings that have question marks in them,
// and also capture question marks that stand alone
var tester = new RegExp(/([\"][^\"]*\?[^\"]*[\"])|([\'][^\']*\?[^\']*[\'])|(\?)/ig);
var matches;
var currentParam = 0;
var results = [];
while((matches = tester.exec(sql)) != null){
var currentMatch = RegExp.lastMatch+"";
 
// are we a literal string? then ignore it
if(/^[\"\']/.test(currentMatch)){
continue;
}
 
// do we have an encrypt keyword to our left?
var needsEncrypt = false;
if(/ENCRYPT\([^\)]*$/i.test(RegExp.leftContext)){
needsEncrypt = true;
}
 
// set the encrypted flag
results[currentParam] = needsEncrypt;
 
currentParam++;
}
return results;
},
 
_determineDecryptedColumns: function(sql){
var results = {};
 
if(/DECRYPT\(\*\)/i.test(sql)){
results = "*";
}else{
var tester = /DECRYPT\((?:\s*\w*\s*\,?)*\)/ig;
var matches;
while(matches = tester.exec(sql)){
var lastMatch = new String(RegExp.lastMatch);
var columnNames = lastMatch.replace(/DECRYPT\(/i, "");
columnNames = columnNames.replace(/\)/, "");
columnNames = columnNames.split(/\s*,\s*/);
dojo.forEach(columnNames, function(column){
if(/\s*\w* AS (\w*)/i.test(column)){
column = column.match(/\s*\w* AS (\w*)/i)[1];
}
results[column] = true;
});
}
}
 
return results;
},
 
_decryptSingleColumn: function(columnName, columnValue, password, currentRowIndex,
callback){
//console.debug("decryptSingleColumn, columnName="+columnName+", columnValue="+columnValue+", currentRowIndex="+currentRowIndex)
dojox._sql._crypto.decrypt(columnValue, password, dojo.hitch(this, function(results){
// set the new decrypted value
this._finalResultSet[currentRowIndex][columnName] = results;
this._finishedCrypto++;
// are we done with all encryption?
if(this._finishedCrypto >= this._totalCrypto
&& this._finishedSpawningCrypto){
//console.debug("done with all decrypts");
callback(this._finalResultSet);
}
}));
}
});
 
}
 
if(!dojo._hasResource["dojox.sql"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.sql"] = true;
 
dojo.provide("dojox.sql");
 
}
 
if(!dojo._hasResource["dojox.storage.GearsStorageProvider"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.storage.GearsStorageProvider"] = true;
dojo.provide("dojox.storage.GearsStorageProvider");
 
 
 
 
if(dojo.isGears){
(function(){
// make sure we don't define the gears provider if we're not gears
// enabled
dojo.declare("dojox.storage.GearsStorageProvider", dojox.storage.Provider, {
// summary:
// Storage provider that uses the features of Google Gears
// to store data (it is saved into the local SQL database
// provided by Gears, using dojox.sql)
// description:
//
//
// You can disable this storage provider with the following djConfig
// variable:
// var djConfig = { disableGearsStorage: true };
//
// Authors of this storage provider-
// Brad Neuberg, bkn3@columbia.edu
constructor: function(){
},
// instance methods and properties
TABLE_NAME: "__DOJO_STORAGE",
initialized: false,
_available: null,
initialize: function(){
//console.debug("dojox.storage.GearsStorageProvider.initialize");
if(djConfig["disableGearsStorage"] == true){
return;
}
// partition our storage data so that multiple apps
// on the same host won't collide
this.TABLE_NAME = "__DOJO_STORAGE";
// create the table that holds our data
try{
dojox.sql("CREATE TABLE IF NOT EXISTS " + this.TABLE_NAME + "( "
+ " namespace TEXT, "
+ " key TEXT, "
+ " value TEXT "
+ ")"
);
dojox.sql("CREATE UNIQUE INDEX IF NOT EXISTS namespace_key_index"
+ " ON " + this.TABLE_NAME
+ " (namespace, key)");
}catch(e){
console.debug("dojox.storage.GearsStorageProvider.initialize:", e);
this.initialized = false; // we were unable to initialize
dojox.storage.manager.loaded();
return;
}
// indicate that this storage provider is now loaded
this.initialized = true;
dojox.storage.manager.loaded();
},
isAvailable: function(){
// is Google Gears available and defined?
return this._available = dojo.isGears;
},
 
put: function(key, value, resultsHandler, namespace){
if(this.isValidKey(key) == false){
throw new Error("Invalid key given: " + key);
}
namespace = namespace||this.DEFAULT_NAMESPACE;
// serialize the value;
// handle strings differently so they have better performance
if(dojo.isString(value)){
value = "string:" + value;
}else{
value = dojo.toJson(value);
}
// try to store the value
try{
dojox.sql("DELETE FROM " + this.TABLE_NAME
+ " WHERE namespace = ? AND key = ?",
namespace, key);
dojox.sql("INSERT INTO " + this.TABLE_NAME
+ " VALUES (?, ?, ?)",
namespace, key, value);
}catch(e){
// indicate we failed
console.debug("dojox.storage.GearsStorageProvider.put:", e);
resultsHandler(this.FAILED, key, e.toString());
return;
}
if(resultsHandler){
resultsHandler(dojox.storage.SUCCESS, key, null);
}
},
 
get: function(key, namespace){
if(this.isValidKey(key) == false){
throw new Error("Invalid key given: " + key);
}
namespace = namespace||this.DEFAULT_NAMESPACE;
// try to find this key in the database
var results = dojox.sql("SELECT * FROM " + this.TABLE_NAME
+ " WHERE namespace = ? AND "
+ " key = ?",
namespace, key);
if(!results.length){
return null;
}else{
results = results[0].value;
}
// destringify the content back into a
// real JavaScript object;
// handle strings differently so they have better performance
if(dojo.isString(results) && (/^string:/.test(results))){
results = results.substring("string:".length);
}else{
results = dojo.fromJson(results);
}
return results;
},
getNamespaces: function(){
var results = [ dojox.storage.DEFAULT_NAMESPACE ];
var rs = dojox.sql("SELECT namespace FROM " + this.TABLE_NAME
+ " DESC GROUP BY namespace");
for(var i = 0; i < rs.length; i++){
if(rs[i].namespace != dojox.storage.DEFAULT_NAMESPACE){
results.push(rs[i].namespace);
}
}
return results;
},
 
getKeys: function(namespace){
namespace = namespace||this.DEFAULT_NAMESPACE;
if(this.isValidKey(namespace) == false){
throw new Error("Invalid namespace given: " + namespace);
}
var rs = dojox.sql("SELECT key FROM " + this.TABLE_NAME
+ " WHERE namespace = ?",
namespace);
var results = [];
for(var i = 0; i < rs.length; i++){
results.push(rs[i].key);
}
return results;
},
 
clear: function(namespace){
if(this.isValidKey(namespace) == false){
throw new Error("Invalid namespace given: " + namespace);
}
namespace = namespace||this.DEFAULT_NAMESPACE;
dojox.sql("DELETE FROM " + this.TABLE_NAME
+ " WHERE namespace = ?",
namespace);
},
remove: function(key, namespace){
namespace = namespace||this.DEFAULT_NAMESPACE;
dojox.sql("DELETE FROM " + this.TABLE_NAME
+ " WHERE namespace = ? AND"
+ " key = ?",
namespace,
key);
},
putMultiple: function(keys, values, resultsHandler, namespace) {
if(this.isValidKeyArray(keys) === false
|| ! values instanceof Array
|| keys.length != values.length){
throw new Error("Invalid arguments: keys = ["
+ keys + "], values = [" + values + "]");
}
if(namespace == null || typeof namespace == "undefined"){
namespace = dojox.storage.DEFAULT_NAMESPACE;
}
if(this.isValidKey(namespace) == false){
throw new Error("Invalid namespace given: " + namespace);
}
this._statusHandler = resultsHandler;
 
// try to store the value
try{
dojox.sql.open();
dojox.sql.db.execute("BEGIN TRANSACTION");
var _stmt = "REPLACE INTO " + this.TABLE_NAME + " VALUES (?, ?, ?)";
for(var i=0;i<keys.length;i++) {
// serialize the value;
// handle strings differently so they have better performance
var value = values[i];
if(dojo.isString(value)){
value = "string:" + value;
}else{
value = dojo.toJson(value);
}
dojox.sql.db.execute( _stmt,
[namespace, keys[i], value]);
}
dojox.sql.db.execute("COMMIT TRANSACTION");
dojox.sql.close();
}catch(e){
// indicate we failed
console.debug("dojox.storage.GearsStorageProvider.putMultiple:", e);
if(resultsHandler){
resultsHandler(this.FAILED, keys, e.toString());
}
return;
}
if(resultsHandler){
resultsHandler(dojox.storage.SUCCESS, key, null);
}
},
 
getMultiple: function(keys, namespace){
// TODO: Maybe use SELECT IN instead
 
if(this.isValidKeyArray(keys) === false){
throw new ("Invalid key array given: " + keys);
}
if(namespace == null || typeof namespace == "undefined"){
namespace = dojox.storage.DEFAULT_NAMESPACE;
}
if(this.isValidKey(namespace) == false){
throw new Error("Invalid namespace given: " + namespace);
}
var _stmt = "SELECT * FROM " + this.TABLE_NAME +
" WHERE namespace = ? AND " + " key = ?";
var results = [];
for(var i=0;i<keys.length;i++){
var result = dojox.sql( _stmt, namespace, keys[i]);
if( ! result.length){
results[i] = null;
}else{
result = result[0].value;
// destringify the content back into a
// real JavaScript object;
// handle strings differently so they have better performance
if(dojo.isString(result) && (/^string:/.test(result))){
results[i] = result.substring("string:".length);
}else{
results[i] = dojo.fromJson(result);
}
}
}
return results;
},
removeMultiple: function(keys, namespace){
namespace = namespace||this.DEFAULT_NAMESPACE;
dojox.sql.open();
dojox.sql.db.execute("BEGIN TRANSACTION");
var _stmt = "DELETE FROM " + this.TABLE_NAME + " WHERE namespace = ? AND key = ?";
 
for(var i=0;i<keys.length;i++){
dojox.sql.db.execute( _stmt,
[namespace, keys[i]]);
}
dojox.sql.db.execute("COMMIT TRANSACTION");
dojox.sql.close();
},
isPermanent: function(){ return true; },
 
getMaximumSize: function(){ return this.SIZE_NO_LIMIT; },
 
hasSettingsUI: function(){ return false; },
showSettingsUI: function(){
throw new Error(this.declaredClass
+ " does not support a storage settings user-interface");
},
hideSettingsUI: function(){
throw new Error(this.declaredClass
+ " does not support a storage settings user-interface");
}
});
 
// register the existence of our storage providers
dojox.storage.manager.register("dojox.storage.GearsStorageProvider",
new dojox.storage.GearsStorageProvider());
dojox.storage.manager.initialize();
})();
}
 
}
 
if(!dojo._hasResource["dojox.storage._common"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.storage._common"] = true;
dojo.provide("dojox.storage._common");
 
 
 
 
 
// FIXME: Find way to set isGears from offline.profile.js file; it didn't
// work for me
//dojo.requireIf(!dojo.isGears, "dojox.storage.FlashStorageProvider");
//dojo.requireIf(!dojo.isGears, "dojox.storage.WhatWGStorageProvider");
 
// now that we are loaded and registered tell the storage manager to
// initialize itself
dojox.storage.manager.initialize();
 
}
 
if(!dojo._hasResource["dojox.storage"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.storage"] = true;
dojo.provide("dojox.storage");
 
 
}
 
if(!dojo._hasResource["dojox.off.files"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.off.files"] = true;
dojo.provide("dojox.off.files");
 
// Author: Brad Neuberg, bkn3@columbia.edu, http://codinginparadise.org
 
// summary:
// Helps maintain resources that should be
// available offline, such as CSS files.
// description:
// dojox.off.files makes it easy to indicate
// what resources should be available offline,
// such as CSS files, JavaScript, HTML, etc.
dojox.off.files = {
// versionURL: String
// An optional file, that if present, records the version
// of our bundle of files to make available offline. If this
// file is present, and we are not currently debugging,
// then we only refresh our offline files if the version has
// changed.
versionURL: "version.js",
// listOfURLs: Array
// For advanced usage; most developers can ignore this.
// Our list of URLs that will be cached and made available
// offline.
listOfURLs: [],
// refreshing: boolean
// For advanced usage; most developers can ignore this.
// Whether we are currently in the middle
// of refreshing our list of offline files.
refreshing: false,
 
_cancelID: null,
_error: false,
_errorMessages: [],
_currentFileIndex: 0,
_store: null,
_doSlurp: false,
slurp: function(){
// summary:
// Autoscans the page to find all resources to
// cache. This includes scripts, images, CSS, and hyperlinks
// to pages that are in the same scheme/port/host as this
// page. We also scan the embedded CSS of any stylesheets
// to find @import statements and url()'s.
// You should call this method from the top-level, outside of
// any functions and before the page loads:
//
// <script>
//
//
//
//
//
// // configure how we should work offline
//
// // set our application name
// dojox.off.ui.appName = "Moxie";
//
// // automatically "slurp" the page and
// // capture the resources we need offline
// dojox.off.files.slurp();
//
// // tell Dojo Offline we are ready for it to initialize itself now
// // that we have finished configuring it for our application
// dojox.off.initialize();
// </script>
//
// Note that inline styles on elements are not handled (i.e.
// if you somehow have an inline style that uses a URL);
// object and embed tags are not scanned since their format
// differs based on type; and elements created by JavaScript
// after page load are not found. For these you must manually
// add them with a dojox.off.files.cache() method call.
// just schedule the slurp once the page is loaded and
// Dojo Offline is ready to slurp; dojox.off will call
// our _slurp() method before indicating it is finished
// loading
this._doSlurp = true;
},
cache: function(urlOrList){ /* void */
// summary:
// Caches a file or list of files to be available offline. This
// can either be a full URL, such as http://foobar.com/index.html,
// or a relative URL, such as ../index.html. This URL is not
// actually cached until dojox.off.sync.synchronize() is called.
// urlOrList: String or Array[]
// A URL of a file to cache or an Array of Strings of files to
// cache
//console.debug("dojox.off.files.cache, urlOrList="+urlOrList);
if(dojo.isString(urlOrList)){
var url = this._trimAnchor(urlOrList+"");
if(!this.isAvailable(url)){
this.listOfURLs.push(url);
}
}else if(urlOrList instanceof dojo._Url){
var url = this._trimAnchor(urlOrList.uri);
if(!this.isAvailable(url)){
this.listOfURLs.push(url);
}
}else{
dojo.forEach(urlOrList, function(url){
url = this._trimAnchor(url);
if(!this.isAvailable(url)){
this.listOfURLs.push(url);
}
}, this);
}
},
printURLs: function(){
// summary:
// A helper function that will dump and print out
// all of the URLs that are cached for offline
// availability. This can help with debugging if you
// are trying to make sure that all of your URLs are
// available offline
console.debug("The following URLs are cached for offline use:");
dojo.forEach(this.listOfURLs, function(i){
console.debug(i);
});
},
remove: function(url){ /* void */
// summary:
// Removes a URL from the list of files to cache.
// description:
// Removes a URL from the list of URLs to cache. Note that this
// does not actually remove the file from the offline cache;
// instead, it just prevents us from refreshing this file at a
// later time, so that it will naturally time out and be removed
// from the offline cache
// url: String
// The URL to remove
for(var i = 0; i < this.listOfURLs.length; i++){
if(this.listOfURLs[i] == url){
this.listOfURLs = this.listOfURLs.splice(i, 1);
break;
}
}
},
isAvailable: function(url){ /* boolean */
// summary:
// Determines whether the given resource is available offline.
// url: String
// The URL to check
for(var i = 0; i < this.listOfURLs.length; i++){
if(this.listOfURLs[i] == url){
return true;
}
}
return false;
},
refresh: function(callback){ /* void */
//console.debug("dojox.off.files.refresh");
// summary:
// For advanced usage; most developers can ignore this.
// Refreshes our list of offline resources,
// making them available offline.
// callback: Function
// A callback that receives two arguments: whether an error
// occurred, which is a boolean; and an array of error message strings
// with details on errors encountered. If no error occured then message is
// empty array with length 0.
try{
if(djConfig.isDebug){
this.printURLs();
}
this.refreshing = true;
if(this.versionURL){
this._getVersionInfo(function(oldVersion, newVersion, justDebugged){
//console.warn("getVersionInfo, oldVersion="+oldVersion+", newVersion="+newVersion
// + ", justDebugged="+justDebugged+", isDebug="+djConfig.isDebug);
if(djConfig.isDebug || !newVersion || justDebugged
|| !oldVersion || oldVersion != newVersion){
console.warn("Refreshing offline file list");
this._doRefresh(callback, newVersion);
}else{
console.warn("No need to refresh offline file list");
callback(false, []);
}
});
}else{
console.warn("Refreshing offline file list");
this._doRefresh(callback);
}
}catch(e){
this.refreshing = false;
// can't refresh files -- core operation --
// fail fast
dojox.off.coreOpFailed = true;
dojox.off.enabled = false;
dojox.off.onFrameworkEvent("coreOperationFailed");
}
},
abortRefresh: function(){
// summary:
// For advanced usage; most developers can ignore this.
// Aborts and cancels a refresh.
if(!this.refreshing){
return;
}
this._store.abortCapture(this._cancelID);
this.refreshing = false;
},
_slurp: function(){
if(!this._doSlurp){
return;
}
var handleUrl = dojo.hitch(this, function(url){
if(this._sameLocation(url)){
this.cache(url);
}
});
handleUrl(window.location.href);
dojo.query("script").forEach(function(i){
try{
handleUrl(i.getAttribute("src"));
}catch(exp){
//console.debug("dojox.off.files.slurp 'script' error: "
// + exp.message||exp);
}
});
dojo.query("link").forEach(function(i){
try{
if(!i.getAttribute("rel")
|| i.getAttribute("rel").toLowerCase() != "stylesheet"){
return;
}
handleUrl(i.getAttribute("href"));
}catch(exp){
//console.debug("dojox.off.files.slurp 'link' error: "
// + exp.message||exp);
}
});
dojo.query("img").forEach(function(i){
try{
handleUrl(i.getAttribute("src"));
}catch(exp){
//console.debug("dojox.off.files.slurp 'img' error: "
// + exp.message||exp);
}
});
dojo.query("a").forEach(function(i){
try{
handleUrl(i.getAttribute("href"));
}catch(exp){
//console.debug("dojox.off.files.slurp 'a' error: "
// + exp.message||exp);
}
});
// FIXME: handle 'object' and 'embed' tag
// parse our style sheets for inline URLs and imports
dojo.forEach(document.styleSheets, function(sheet){
try{
if(sheet.cssRules){ // Firefox
dojo.forEach(sheet.cssRules, function(rule){
var text = rule.cssText;
if(text){
var matches = text.match(/url\(\s*([^\) ]*)\s*\)/i);
if(!matches){
return;
}
for(var i = 1; i < matches.length; i++){
handleUrl(matches[i])
}
}
});
}else if(sheet.cssText){ // IE
var matches;
var text = sheet.cssText.toString();
// unfortunately, using RegExp.exec seems to be flakey
// for looping across multiple lines on IE using the
// global flag, so we have to simulate it
var lines = text.split(/\f|\r|\n/);
for(var i = 0; i < lines.length; i++){
matches = lines[i].match(/url\(\s*([^\) ]*)\s*\)/i);
if(matches && matches.length){
handleUrl(matches[1]);
}
}
}
}catch(exp){
//console.debug("dojox.off.files.slurp stylesheet parse error: "
// + exp.message||exp);
}
});
//this.printURLs();
},
_sameLocation: function(url){
if(!url){ return false; }
// filter out anchors
if(url.length && url.charAt(0) == "#"){
return false;
}
// FIXME: dojo._Url should be made public;
// it's functionality is very useful for
// parsing URLs correctly, which is hard to
// do right
url = new dojo._Url(url);
// totally relative -- ../../someFile.html
if(!url.scheme && !url.port && !url.host){
return true;
}
// scheme relative with port specified -- brad.com:8080
if(!url.scheme && url.host && url.port
&& window.location.hostname == url.host
&& window.location.port == url.port){
return true;
}
// scheme relative with no-port specified -- brad.com
if(!url.scheme && url.host && !url.port
&& window.location.hostname == url.host
&& window.location.port == 80){
return true;
}
// else we have everything
return window.location.protocol == (url.scheme + ":")
&& window.location.hostname == url.host
&& (window.location.port == url.port || !window.location.port && !url.port);
},
_trimAnchor: function(url){
return url.replace(/\#.*$/, "");
},
_doRefresh: function(callback, newVersion){
// get our local server
var localServer;
try{
localServer = google.gears.factory.create("beta.localserver", "1.0");
}catch(exp){
dojo.setObject("google.gears.denied", true);
dojox.off.onFrameworkEvent("coreOperationFailed");
throw "Google Gears must be allowed to run";
}
var storeName = "dot_store_"
+ window.location.href.replace(/[^0-9A-Za-z_]/g, "_");
// refresh everything by simply removing
// any older stores
localServer.removeStore(storeName);
// open/create the resource store
localServer.openStore(storeName);
var store = localServer.createStore(storeName);
this._store = store;
 
// add our list of files to capture
var self = this;
this._currentFileIndex = 0;
this._cancelID = store.capture(this.listOfURLs, function(url, success, captureId){
//console.debug("store.capture, url="+url+", success="+success);
if(!success && self.refreshing){
self._cancelID = null;
self.refreshing = false;
var errorMsgs = [];
errorMsgs.push("Unable to capture: " + url);
callback(true, errorMsgs);
return;
}else if(success){
self._currentFileIndex++;
}
if(success && self._currentFileIndex >= self.listOfURLs.length){
self._cancelID = null;
self.refreshing = false;
if(newVersion){
dojox.storage.put("oldVersion", newVersion, null,
dojox.off.STORAGE_NAMESPACE);
}
dojox.storage.put("justDebugged", djConfig.isDebug, null,
dojox.off.STORAGE_NAMESPACE);
callback(false, []);
}
});
},
_getVersionInfo: function(callback){
var justDebugged = dojox.storage.get("justDebugged",
dojox.off.STORAGE_NAMESPACE);
var oldVersion = dojox.storage.get("oldVersion",
dojox.off.STORAGE_NAMESPACE);
var newVersion = null;
callback = dojo.hitch(this, callback);
dojo.xhrGet({
url: this.versionURL + "?browserbust=" + new Date().getTime(),
timeout: 5 * 1000,
handleAs: "javascript",
error: function(err){
//console.warn("dojox.off.files._getVersionInfo, err=",err);
dojox.storage.remove("oldVersion", dojox.off.STORAGE_NAMESPACE);
dojox.storage.remove("justDebugged", dojox.off.STORAGE_NAMESPACE);
callback(oldVersion, newVersion, justDebugged);
},
load: function(data){
//console.warn("dojox.off.files._getVersionInfo, load=",data);
// some servers incorrectly return 404's
// as a real page
if(data){
newVersion = data;
}
callback(oldVersion, newVersion, justDebugged);
}
});
}
}
 
}
 
if(!dojo._hasResource["dojox.off.sync"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.off.sync"] = true;
dojo.provide("dojox.off.sync");
 
 
 
 
 
// Author: Brad Neuberg, bkn3@columbia.edu, http://codinginparadise.org
 
// summary:
// Exposes syncing functionality to offline applications
dojo.mixin(dojox.off.sync, {
// isSyncing: boolean
// Whether we are in the middle of a syncing session.
isSyncing: false,
// cancelled: boolean
// Whether we were cancelled during our last sync request or not. If
// we are cancelled, then successful will be false.
cancelled: false,
// successful: boolean
// Whether the last sync was successful or not. If false, an error
// occurred.
successful: true,
// details: String[]
// Details on the sync. If the sync was successful, this will carry
// any conflict or merging messages that might be available; if the
// sync was unsuccessful, this will have an error message. For both
// of these, this should be an array of Strings, where each string
// carries details on the sync.
// Example:
// dojox.off.sync.details = ["The document 'foobar' had conflicts - yours one",
// "The document 'hello world' was automatically merged"];
details: [],
// error: boolean
// Whether an error occurred during the syncing process.
error: false,
// actions: dojox.off.sync.ActionLog
// Our ActionLog that we store offline actions into for later
// replaying when we go online
actions: null,
// autoSync: boolean
// For advanced usage; most developers can ignore this.
// Whether we do automatically sync on page load or when we go online.
// If true we do, if false syncing must be manually initiated.
// Defaults to true.
autoSync: true,
// summary:
// An event handler that is called during the syncing process with
// the state of syncing. It is important that you connect to this
// method and respond to certain sync events, especially the
// "download" event.
// description:
// This event handler is called during the syncing process. You can
// do a dojo.connect to receive sync feedback:
//
// dojo.connect(dojox.off.sync, "onSync", someFunc);
//
// You will receive one argument, which is the type of the event
// and which can have the following values.
//
// The most common two types that you need to care about are "download"
// and "finished", especially if you are using the default
// Dojo Offline UI widget that does the hard work of informing
// the user through the UI about what is occuring during syncing.
//
// If you receive the "download" event, you should make a network call
// to retrieve and store your data somehow for offline access. The
// "finished" event indicates that syncing is done. An example:
//
// dojo.connect(dojox.off.sync, "onSync", function(type){
// if(type == "download"){
// // make a network call to download some data
// // for use offline
// dojo.xhrGet({
// url: "downloadData.php",
// handleAs: "javascript",
// error: function(err){
// dojox.off.sync.finishedDownloading(false, "Can't download data");
// },
// load: function(data){
// // store our data
// dojox.storage.put("myData", data);
//
// // indicate we are finished downloading
// dojox.off.sync.finishedDownloading(true);
// }
// });
// }else if(type == "finished"){
// // update UI somehow to indicate we are finished,
// // such as using the download data to change the
// // available data
// }
// })
//
// Here is the full list of event types if you want to do deep
// customization, such as updating your UI to display the progress
// of syncing (note that the default Dojo Offline UI widget does
// this for you if you choose to pull that in). Most of these
// are only appropriate for advanced usage and can be safely
// ignored:
//
// * "start"
// syncing has started
// * "refreshFiles"
// syncing will begin refreshing
// our offline file cache
// * "upload"
// syncing will begin uploading
// any local data changes we have on the client.
// This event is fired before we fire
// the dojox.off.sync.actions.onReplay event for
// each action to replay; use it to completely
// over-ride the replaying behavior and prevent
// it entirely, perhaps rolling your own sync
// protocol if needed.
// * "download"
// syncing will begin downloading any new data that is
// needed into persistent storage. Applications are required to
// implement this themselves, storing the required data into
// persistent local storage using Dojo Storage.
// * "finished"
// syncing is finished; this
// will be called whether an error ocurred or not; check
// dojox.off.sync.successful and dojox.off.sync.error for sync details
// * "cancel"
// Fired when canceling has been initiated; canceling will be
// attempted, followed by the sync event "finished".
onSync: function(/* String */ type){},
synchronize: function(){ /* void */
// summary: Starts synchronizing
 
//dojo.debug("synchronize");
if(this.isSyncing || dojox.off.goingOnline || (!dojox.off.isOnline)){
return;
}
this.isSyncing = true;
this.successful = false;
this.details = [];
this.cancelled = false;
this.start();
},
cancel: function(){ /* void */
// summary:
// Attempts to cancel this sync session
if(!this.isSyncing){ return; }
this.cancelled = true;
if(dojox.off.files.refreshing){
dojox.off.files.abortRefresh();
}
this.onSync("cancel");
},
finishedDownloading: function(successful /* boolean? */,
errorMessage /* String? */){
// summary:
// Applications call this method from their
// after getting a "download" event in
// dojox.off.sync.onSync to signal that
// they are finished downloading any data
// that should be available offline
// successful: boolean?
// Whether our downloading was successful or not.
// If not present, defaults to true.
// errorMessage: String?
// If unsuccessful, a message explaining why
if(typeof successful == "undefined"){
successful = true;
}
if(!successful){
this.successful = false;
this.details.push(errorMessage);
this.error = true;
}
this.finished();
},
start: function(){ /* void */
// summary:
// For advanced usage; most developers can ignore this.
// Called at the start of the syncing process. Advanced
// developers can over-ride this method to use their
// own sync mechanism to start syncing.
if(this.cancelled){
this.finished();
return;
}
this.onSync("start");
this.refreshFiles();
},
refreshFiles: function(){ /* void */
// summary:
// For advanced usage; most developers can ignore this.
// Called when we are going to refresh our list
// of offline files during syncing. Advanced developers
// can over-ride this method to do some advanced magic related to
// refreshing files.
//dojo.debug("refreshFiles");
if(this.cancelled){
this.finished();
return;
}
this.onSync("refreshFiles");
dojox.off.files.refresh(dojo.hitch(this, function(error, errorMessages){
if(error){
this.error = true;
this.successful = false;
for(var i = 0; i < errorMessages.length; i++){
this.details.push(errorMessages[i]);
}
// even if we get an error while syncing files,
// keep syncing so we can upload and download
// data
}
this.upload();
}));
},
upload: function(){ /* void */
// summary:
// For advanced usage; most developers can ignore this.
// Called when syncing wants to upload data. Advanced
// developers can over-ride this method to completely
// throw away the Action Log and replaying system
// and roll their own advanced sync mechanism if needed.
if(this.cancelled){
this.finished();
return;
}
this.onSync("upload");
// when we are done uploading start downloading
dojo.connect(this.actions, "onReplayFinished", this, this.download);
// replay the actions log
this.actions.replay();
},
download: function(){ /* void */
// summary:
// For advanced usage; most developers can ignore this.
// Called when syncing wants to download data. Advanced
// developers can over-ride this method to use their
// own sync mechanism.
if(this.cancelled){
this.finished();
return;
}
// apps should respond to the "download"
// event to download their data; when done
// they must call dojox.off.sync.finishedDownloading()
this.onSync("download");
},
finished: function(){ /* void */
// summary:
// For advanced usage; most developers can ignore this.
// Called when syncing is finished. Advanced
// developers can over-ride this method to clean
// up after finishing their own sync
// mechanism they might have rolled.
this.isSyncing = false;
this.successful = (!this.cancelled && !this.error);
this.onSync("finished");
},
_save: function(callback){
this.actions._save(function(){
callback();
});
},
_load: function(callback){
this.actions._load(function(){
callback();
});
}
});
 
 
// summary:
// A class that records actions taken by a user when they are offline,
// suitable for replaying when the network reappears.
// description:
// The basic idea behind this method is to record user actions that would
// normally have to contact a server into an action log when we are
// offline, so that later when we are online we can simply replay this log
// in the order user actions happened so that they can be executed against
// the server, causing synchronization to happen.
//
// When we replay, for each of the actions that were added, we call a
// method named onReplay that applications should connect to and
// which will be called over and over for each of our actions --
// applications should take the offline action
// information and use it to talk to a server to have this action
// actually happen online, 'syncing' themselves with the server.
//
// For example, if the action was "update" with the item that was updated, we
// might call some RESTian server API that exists for updating an item in
// our application. The server could either then do sophisticated merging
// and conflict resolution on the server side, for example, allowing you
// to pop up a custom merge UI, or could do automatic merging or nothing
// of the sort. When you are finished with this particular action, your
// application is then required to call continueReplay() on the actionLog object
// passed to onReplay() to continue replaying the action log, or haltReplay()
// with the reason for halting to completely stop the syncing/replaying
// process.
//
// For example, imagine that we have a web application that allows us to add
// contacts. If we are offline, and we update a contact, we would add an action;
// imagine that the user has to click an Update button after changing the values
// for a given contact:
//
// dojox.off.whenOffline(dojo.byId("updateButton"), "onclick", function(evt){
// // get the updated customer values
// var customer = getCustomerValues();
//
// // we are offline -- just record this action
// var action = {name: "update", customer: customer};
// dojox.off.sync.actions.add(action)
//
// // persist this customer data into local storage as well
// dojox.storage.put(customer.name, customer);
// })
//
// Then, when we go back online, the dojox.off.sync.actions.onReplay event
// will fire over and over, once for each action that was recorded while offline:
//
// dojo.connect(dojox.off.sync.actions, "onReplay", function(action, actionLog){
// // called once for each action we added while offline, in the order
// // they were added
// if(action.name == "update"){
// var customer = action.customer;
//
// // call some network service to update this customer
// dojo.xhrPost({
// url: "updateCustomer.php",
// content: {customer: dojo.toJson(customer)},
// error: function(err){
// actionLog.haltReplay(err);
// },
// load: function(data){
// actionLog.continueReplay();
// }
// })
// }
// })
//
// Note that the actions log is always automatically persisted locally while using it, so
// that if the user closes the browser or it crashes the actions will safely be stored
// for later replaying.
dojo.declare("dojox.off.sync.ActionLog", null, {
// entries: Array
// An array of our action entries, where each one is simply a custom
// object literal that were passed to add() when this action entry
// was added.
entries: [],
// reasonHalted: String
// If we halted, the reason why
reasonHalted: null,
// isReplaying: boolean
// If true, we are in the middle of replaying a command log; if false,
// then we are not
isReplaying: false,
// autoSave: boolean
// Whether we automatically save the action log after each call to
// add(); defaults to true. For applications that are rapidly adding
// many action log entries in a short period of time, it can be
// useful to set this to false and simply call save() yourself when
// you are ready to persist your command log -- otherwise performance
// could be slow as the default action is to attempt to persist the
// actions log constantly with calls to add().
autoSave: true,
add: function(action /* Object */){ /* void */
// summary:
// Adds an action to our action log
// description:
// This method will add an action to our
// action log, later to be replayed when we
// go from offline to online. 'action'
// will be available when this action is
// replayed and will be passed to onReplay.
//
// Example usage:
//
// dojox.off.sync.log.add({actionName: "create", itemType: "document",
// {title: "Message", content: "Hello World"}});
//
// The object literal is simply a custom object appropriate
// for our application -- it can be anything that preserves the state
// of a user action that will be executed when we go back online
// and replay this log. In the above example,
// "create" is the name of this action; "documents" is the
// type of item this command is operating on, such as documents, contacts,
// tasks, etc.; and the final argument is the document that was created.
if(this.isReplaying){
throw "Programming error: you can not call "
+ "dojox.off.sync.actions.add() while "
+ "we are replaying an action log";
}
this.entries.push(action);
// save our updated state into persistent
// storage
if(this.autoSave){
this._save();
}
},
onReplay: function(action /* Object */,
actionLog /* dojox.off.sync.ActionLog */){ /* void */
// summary:
// Called when we replay our log, for each of our action
// entries.
// action: Object
// A custom object literal representing an action for this
// application, such as
// {actionName: "create", item: {title: "message", content: "hello world"}}
// actionLog: dojox.off.sync.ActionLog
// A reference to the dojox.off.sync.actions log so that developers
// can easily call actionLog.continueReplay() or actionLog.haltReplay().
// description:
// This callback should be connected to by applications so that
// they can sync themselves when we go back online:
//
// dojo.connect(dojox.off.sync.actions, "onReplay", function(action, actionLog){
// // do something
// })
//
// When we replay our action log, this callback is called for each
// of our action entries in the order they were added. The
// 'action' entry that was passed to add() for this action will
// also be passed in to onReplay, so that applications can use this information
// to do their syncing, such as contacting a server web-service
// to create a new item, for example.
//
// Inside the method you connected to onReplay, you should either call
// actionLog.haltReplay(reason) if an error occurred and you would like to halt
// action replaying or actionLog.continueReplay() to have the action log
// continue replaying its log and proceed to the next action;
// the reason you must call these is the action you execute inside of
// onAction will probably be asynchronous, since it will be talking on
// the network, and you should call one of these two methods based on
// the result of your network call.
},
length: function(){ /* Number */
// summary:
// Returns the length of this
// action log
return this.entries.length;
},
haltReplay: function(reason /* String */){ /* void */
// summary: Halts replaying this command log.
// reason: String
// The reason we halted.
// description:
// This method is called as we are replaying an action log; it
// can be called from dojox.off.sync.actions.onReplay, for
// example, for an application to indicate an error occurred
// while replaying this action, halting further processing of
// the action log. Note that any action log entries that
// were processed before have their effects retained (i.e.
// they are not rolled back), while the action entry that was
// halted stays in our list of actions to later be replayed.
if(!this.isReplaying){
return;
}
if(reason){
this.reasonHalted = reason.toString();
}
// save the state of our action log, then
// tell anyone who is interested that we are
// done when we are finished saving
if(this.autoSave){
var self = this;
this._save(function(){
self.isReplaying = false;
self.onReplayFinished();
});
}else{
this.isReplaying = false;
this.onReplayFinished();
}
},
continueReplay: function(){ /* void */
// summary:
// Indicates that we should continue processing out list of
// actions.
// description:
// This method is called by applications that have overridden
// dojox.off.sync.actions.onReplay() to continue replaying our
// action log after the application has finished handling the
// current action.
if(!this.isReplaying){
return;
}
// shift off the old action we just ran
this.entries.shift();
// are we done?
if(!this.entries.length){
// save the state of our action log, then
// tell anyone who is interested that we are
// done when we are finished saving
if(this.autoSave){
var self = this;
this._save(function(){
self.isReplaying = false;
self.onReplayFinished();
});
return;
}else{
this.isReplaying = false;
this.onReplayFinished();
return;
}
}
// get the next action
var nextAction = this.entries[0];
this.onReplay(nextAction, this);
},
clear: function(){ /* void */
// summary:
// Completely clears this action log of its entries
if(this.isReplaying){
return;
}
this.entries = [];
// save our updated state into persistent
// storage
if(this.autoSave){
this._save();
}
},
replay: function(){ /* void */
// summary:
// For advanced usage; most developers can ignore this.
// Replays all of the commands that have been
// cached in this command log when we go back online;
// onCommand will be called for each command we have
if(this.isReplaying){
return;
}
this.reasonHalted = null;
if(!this.entries.length){
this.onReplayFinished();
return;
}
this.isReplaying = true;
var nextAction = this.entries[0];
this.onReplay(nextAction, this);
},
// onReplayFinished: Function
// For advanced usage; most developers can ignore this.
// Called when we are finished replaying our commands;
// called if we have successfully exhausted all of our
// commands, or if an error occurred during replaying.
// The default implementation simply continues the
// synchronization process. Connect to this to register
// for the event:
//
// dojo.connect(dojox.off.sync.actions, "onReplayFinished",
// someFunc)
onReplayFinished: function(){
},
 
toString: function(){
var results = "";
results += "[";
for(var i = 0; i < this.entries.length; i++){
results += "{";
for(var j in this.entries[i]){
results += j + ": \"" + this.entries[i][j] + "\"";
results += ", ";
}
results += "}, ";
}
results += "]";
return results;
},
_save: function(callback){
if(!callback){
callback = function(){};
}
try{
var self = this;
var resultsHandler = function(status, key, message){
//console.debug("resultsHandler, status="+status+", key="+key+", message="+message);
if(status == dojox.storage.FAILED){
dojox.off.onFrameworkEvent("save",
{status: dojox.storage.FAILED,
isCoreSave: true,
key: key,
value: message,
namespace: dojox.off.STORAGE_NAMESPACE});
callback();
}else if(status == dojox.storage.SUCCESS){
callback();
}
};
dojox.storage.put("actionlog", this.entries, resultsHandler,
dojox.off.STORAGE_NAMESPACE);
}catch(exp){
console.debug("dojox.off.sync._save: " + exp.message||exp);
dojox.off.onFrameworkEvent("save",
{status: dojox.storage.FAILED,
isCoreSave: true,
key: "actionlog",
value: this.entries,
namespace: dojox.off.STORAGE_NAMESPACE});
callback();
}
},
_load: function(callback){
var entries = dojox.storage.get("actionlog", dojox.off.STORAGE_NAMESPACE);
if(!entries){
entries = [];
}
this.entries = entries;
callback();
}
}
);
 
dojox.off.sync.actions = new dojox.off.sync.ActionLog();
 
}
 
if(!dojo._hasResource["dojox.off._common"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.off._common"] = true;
dojo.provide("dojox.off._common");
 
 
 
 
 
// Author: Brad Neuberg, bkn3@columbia.edu, http://codinginparadise.org
 
// summary:
// dojox.off is the main object for offline applications.
dojo.mixin(dojox.off, {
// isOnline: boolean
// true if we are online, false if not
isOnline: false,
// NET_CHECK: int
// For advanced usage; most developers can ignore this.
// Time in seconds on how often we should check the status of the
// network with an automatic background timer. The current default
// is 5 seconds.
NET_CHECK: 5,
// STORAGE_NAMESPACE: String
// For advanced usage; most developers can ignore this.
// The namespace we use to save core data into Dojo Storage.
STORAGE_NAMESPACE: "_dot",
// enabled: boolean
// For advanced usage; most developers can ignore this.
// Whether offline ability is enabled or not. Defaults to true.
enabled: true,
// availabilityURL: String
// For advanced usage; most developers can ignore this.
// The URL to check for site availability. We do a GET request on
// this URL to check for site availability. By default we check for a
// simple text file in src/off/network_check.txt that has one value
// it, the value '1'.
availabilityURL: dojo.moduleUrl("dojox", "off/network_check.txt"),
// goingOnline: boolean
// For advanced usage; most developers can ignore this.
// True if we are attempting to go online, false otherwise
goingOnline: false,
// coreOpFailed: boolean
// For advanced usage; most developers can ignore this.
// A flag set by the Dojo Offline framework that indicates that the
// user denied some operation that required the offline cache or an
// operation failed in some critical way that was unrecoverable. For
// example, if the offline cache is Google Gears and we try to get a
// Gears database, a popup window appears asking the user whether they
// will approve or deny this request. If the user denies the request,
// and we are doing some operation that is core to Dojo Offline, then
// we set this flag to 'true'. This flag causes a 'fail fast'
// condition, turning off offline ability.
coreOpFailed: false,
// doNetChecking: boolean
// For advanced usage; most developers can ignore this.
// Whether to have a timing interval in the background doing automatic
// network checks at regular intervals; the length of time between
// checks is controlled by dojox.off.NET_CHECK. Defaults to true.
doNetChecking: true,
// hasOfflineCache: boolean
// For advanced usage; most developers can ignore this.
// Determines if an offline cache is available or installed; an
// offline cache is a facility that can truely cache offline
// resources, such as JavaScript, HTML, etc. in such a way that they
// won't be removed from the cache inappropriately like a browser
// cache would. If this is false then an offline cache will be
// installed. Only Google Gears is currently supported as an offline
// cache. Future possible offline caches include Firefox 3.
hasOfflineCache: null,
// browserRestart: boolean
// For advanced usage; most developers can ignore this.
// If true, the browser must be restarted to register the existence of
// a new host added offline (from a call to addHostOffline); if false,
// then nothing is needed.
browserRestart: false,
_STORAGE_APP_NAME: window.location.href.replace(/[^0-9A-Za-z_]/g, "_"),
_initializeCalled: false,
_storageLoaded: false,
_pageLoaded: false,
onLoad: function(){
// summary:
// Called when Dojo Offline can be used.
// description:
// Do a dojo.connect to this to know when you can
// start using Dojo Offline:
// dojo.connect(dojox.off, "onLoad", myFunc);
},
onNetwork: function(type){
// summary:
// Called when our on- or offline- status changes.
// description:
// If we move online, then this method is called with the
// value "online". If we move offline, then this method is
// called with the value "offline". You can connect to this
// method to do add your own behavior:
//
// dojo.connect(dojox.off, "onNetwork", someFunc)
//
// Note that if you are using the default Dojo Offline UI
// widget that most of the on- and off-line notification
// and syncing is automatically handled and provided to the
// user.
// type: String
// Either "online" or "offline".
},
initialize: function(){ /* void */
// summary:
// Called when a Dojo Offline-enabled application is finished
// configuring Dojo Offline, and is ready for Dojo Offline to
// initialize itself.
// description:
// When an application has finished filling out the variables Dojo
// Offline needs to work, such as dojox.off.ui.appName, it must
// this method to tell Dojo Offline to initialize itself.
// Note:
// This method is needed for a rare edge case. In some conditions,
// especially if we are dealing with a compressed Dojo build, the
// entire Dojo Offline subsystem might initialize itself and be
// running even before the JavaScript for an application has had a
// chance to run and configure Dojo Offline, causing Dojo Offline
// to have incorrect initialization parameters for a given app,
// such as no value for dojox.off.ui.appName. This method is
// provided to prevent this scenario, to slightly 'slow down' Dojo
// Offline so it can be configured before running off and doing
// its thing.
 
//console.debug("dojox.off.initialize");
this._initializeCalled = true;
if(this._storageLoaded && this._pageLoaded){
this._onLoad();
}
},
goOffline: function(){ /* void */
// summary:
// For advanced usage; most developers can ignore this.
// Manually goes offline, away from the network.
if((dojox.off.sync.isSyncing)||(this.goingOnline)){ return; }
this.goingOnline = false;
this.isOnline = false;
},
goOnline: function(callback){ /* void */
// summary:
// For advanced usage; most developers can ignore this.
// Attempts to go online.
// description:
// Attempts to go online, making sure this web application's web
// site is available. 'callback' is called asychronously with the
// result of whether we were able to go online or not.
// callback: Function
// An optional callback function that will receive one argument:
// whether the site is available or not and is boolean. If this
// function is not present we call dojo.xoff.onOnline instead if
// we are able to go online.
//console.debug("goOnline");
if(dojox.off.sync.isSyncing || dojox.off.goingOnline){
return;
}
this.goingOnline = true;
this.isOnline = false;
// see if can reach our web application's web site
this._isSiteAvailable(callback);
},
onFrameworkEvent: function(type /* String */, saveData /* Object? */){
// summary:
// For advanced usage; most developers can ignore this.
// A standard event handler that can be attached to to find out
// about low-level framework events. Most developers will not need to
// attach to this method; it is meant for low-level information
// that can be useful for updating offline user-interfaces in
// exceptional circumstances. The default Dojo Offline UI
// widget takes care of most of these situations.
// type: String
// The type of the event:
//
// * "offlineCacheInstalled"
// An event that is fired when a user
// has installed an offline cache after the page has been loaded.
// If a user didn't have an offline cache when the page loaded, a
// UI of some kind might have prompted them to download one. This
// method is called if they have downloaded and installed an
// offline cache so a UI can reinitialize itself to begin using
// this offline cache.
// * "coreOperationFailed"
// Fired when a core operation during interaction with the
// offline cache is denied by the user. Some offline caches, such
// as Google Gears, prompts the user to approve or deny caching
// files, using the database, and more. If the user denies a
// request that is core to Dojo Offline's operation, we set
// dojox.off.coreOpFailed to true and call this method for
// listeners that would like to respond some how to Dojo Offline
// 'failing fast'.
// * "save"
// Called whenever the framework saves data into persistent
// storage. This could be useful for providing save feedback
// or providing appropriate error feedback if saving fails
// due to a user not allowing the save to occur
// saveData: Object?
// If the type was 'save', then a saveData object is provided with
// further save information. This object has the following properties:
//
// * status - dojox.storage.SUCCESS, dojox.storage.PENDING, dojox.storage.FAILED
// Whether the save succeeded, whether it is pending based on a UI
// dialog asking the user for permission, or whether it failed.
//
// * isCoreSave - boolean
// If true, then this save was for a core piece of data necessary
// for the functioning of Dojo Offline. If false, then it is a
// piece of normal data being saved for offline access. Dojo
// Offline will 'fail fast' if some core piece of data could not
// be saved, automatically setting dojox.off.coreOpFailed to
// 'true' and dojox.off.enabled to 'false'.
//
// * key - String
// The key that we are attempting to persist
//
// * value - Object
// The object we are trying to persist
//
// * namespace - String
// The Dojo Storage namespace we are saving this key/value pair
// into, such as "default", "Documents", "Contacts", etc.
// Optional.
if(type == "save"){
if(saveData.isCoreSave && (saveData.status == dojox.storage.FAILED)){
dojox.off.coreOpFailed = true;
dojox.off.enabled = false;
// FIXME: Stop the background network thread
dojox.off.onFrameworkEvent("coreOperationFailed");
}
}else if(type == "coreOperationFailed"){
dojox.off.coreOpFailed = true;
dojox.off.enabled = false;
// FIXME: Stop the background network thread
}
},
_checkOfflineCacheAvailable: function(callback){
// is a true, offline cache running on this machine?
this.hasOfflineCache = dojo.isGears;
callback();
},
_onLoad: function(){
//console.debug("dojox.off._onLoad");
// both local storage and the page are finished loading
// cache the Dojo JavaScript -- just use the default dojo.js
// name for the most common scenario
// FIXME: TEST: Make sure syncing doesn't break if dojo.js
// can't be found, or report an error to developer
dojox.off.files.cache(dojo.moduleUrl("dojo", "dojo.js"));
// pull in the files needed by Dojo
this._cacheDojoResources();
// FIXME: need to pull in the firebug lite files here!
// workaround or else we will get an error on page load
// from Dojo that it can't find 'console.debug' for optimized builds
// dojox.off.files.cache(djConfig.baseRelativePath + "src/debug.js");
// make sure that resources needed by all of our underlying
// Dojo Storage storage providers will be available
// offline
dojox.off.files.cache(dojox.storage.manager.getResourceList());
// slurp the page if the end-developer wants that
dojox.off.files._slurp();
// see if we have an offline cache; when done, move
// on to the rest of our startup tasks
this._checkOfflineCacheAvailable(dojo.hitch(this, "_onOfflineCacheChecked"));
},
_onOfflineCacheChecked: function(){
// this method is part of our _onLoad series of startup tasks
// if we have an offline cache, see if we have been added to the
// list of available offline web apps yet
if(this.hasOfflineCache && this.enabled){
// load framework data; when we are finished, continue
// initializing ourselves
this._load(dojo.hitch(this, "_finishStartingUp"));
}else if(this.hasOfflineCache && !this.enabled){
// we have an offline cache, but it is disabled for some reason
// perhaps due to the user denying a core operation
this._finishStartingUp();
}else{
this._keepCheckingUntilInstalled();
}
},
_keepCheckingUntilInstalled: function(){
// this method is part of our _onLoad series of startup tasks
// kick off a background interval that keeps
// checking to see if an offline cache has been
// installed since this page loaded
// FIXME: Gears: See if we are installed somehow after the
// page has been loaded
// now continue starting up
this._finishStartingUp();
},
_finishStartingUp: function(){
//console.debug("dojox.off._finishStartingUp");
// this method is part of our _onLoad series of startup tasks
if(!this.hasOfflineCache){
this.onLoad();
}else if(this.enabled){
// kick off a thread to check network status on
// a regular basis
this._startNetworkThread();
 
// try to go online
this.goOnline(dojo.hitch(this, function(){
//console.debug("Finished trying to go online");
// indicate we are ready to be used
dojox.off.onLoad();
}));
}else{ // we are disabled or a core operation failed
if(this.coreOpFailed){
this.onFrameworkEvent("coreOperationFailed");
}else{
this.onLoad();
}
}
},
_onPageLoad: function(){
//console.debug("dojox.off._onPageLoad");
this._pageLoaded = true;
if(this._storageLoaded && this._initializeCalled){
this._onLoad();
}
},
_onStorageLoad: function(){
//console.debug("dojox.off._onStorageLoad");
this._storageLoaded = true;
// were we able to initialize storage? if
// not, then this is a core operation, and
// let's indicate we will need to fail fast
if(!dojox.storage.manager.isAvailable()
&& dojox.storage.manager.isInitialized()){
this.coreOpFailed = true;
this.enabled = false;
}
if(this._pageLoaded && this._initializeCalled){
this._onLoad();
}
},
_isSiteAvailable: function(callback){
// summary:
// Determines if our web application's website is available.
// description:
// This method will asychronously determine if our web
// application's web site is available, which is a good proxy for
// network availability. The URL dojox.off.availabilityURL is
// used, which defaults to this site's domain name (ex:
// foobar.com). We check for dojox.off.AVAILABILITY_TIMEOUT (in
// seconds) and abort after that
// callback: Function
// An optional callback function that will receive one argument:
// whether the site is available or not and is boolean. If this
// function is not present we call dojox.off.onNetwork instead if we
// are able to go online.
dojo.xhrGet({
url: this._getAvailabilityURL(),
handleAs: "text",
timeout: this.NET_CHECK * 1000,
error: dojo.hitch(this, function(err){
//console.debug("dojox.off._isSiteAvailable.error: " + err);
this.goingOnline = false;
this.isOnline = false;
if(callback){ callback(false); }
}),
load: dojo.hitch(this, function(data){
//console.debug("dojox.off._isSiteAvailable.load, data="+data);
this.goingOnline = false;
this.isOnline = true;
if(callback){ callback(true);
}else{ this.onNetwork("online"); }
})
});
},
_startNetworkThread: function(){
//console.debug("startNetworkThread");
// kick off a thread that does periodic
// checks on the status of the network
if(!this.doNetChecking){
return;
}
window.setInterval(dojo.hitch(this, function(){
var d = dojo.xhrGet({
url: this._getAvailabilityURL(),
handleAs: "text",
timeout: this.NET_CHECK * 1000,
error: dojo.hitch(this,
function(err){
if(this.isOnline){
this.isOnline = false;
// FIXME: xhrGet() is not
// correctly calling abort
// on the XHR object when
// it times out; fix inside
// there instead of externally
// here
try{
if(typeof d.ioArgs.xhr.abort == "function"){
d.ioArgs.xhr.abort();
}
}catch(e){}
// if things fell in the middle of syncing,
// stop syncing
dojox.off.sync.isSyncing = false;
this.onNetwork("offline");
}
}
),
load: dojo.hitch(this,
function(data){
if(!this.isOnline){
this.isOnline = true;
this.onNetwork("online");
}
}
)
});
 
}), this.NET_CHECK * 1000);
},
_getAvailabilityURL: function(){
var url = this.availabilityURL.toString();
// bust the browser's cache to make sure we are really talking to
// the server
if(url.indexOf("?") == -1){
url += "?";
}else{
url += "&";
}
url += "browserbust=" + new Date().getTime();
return url;
},
_onOfflineCacheInstalled: function(){
this.onFrameworkEvent("offlineCacheInstalled");
},
_cacheDojoResources: function(){
// if we are a non-optimized build, then the core Dojo bootstrap
// system was loaded as separate JavaScript files;
// add these to our offline cache list. these are
// loaded before the dojo.require() system exists
// FIXME: create a better mechanism in the Dojo core to
// expose whether you are dealing with an optimized build;
// right now we just scan the SCRIPT tags attached to this
// page and see if there is one for _base/_loader/bootstrap.js
var isOptimizedBuild = true;
dojo.forEach(dojo.query("script"), function(i){
var src = i.getAttribute("src");
if(!src){ return; }
if(src.indexOf("_base/_loader/bootstrap.js") != -1){
isOptimizedBuild = false;
}
});
if(!isOptimizedBuild){
dojox.off.files.cache(dojo.moduleUrl("dojo", "_base.js").uri);
dojox.off.files.cache(dojo.moduleUrl("dojo", "_base/_loader/loader.js").uri);
dojox.off.files.cache(dojo.moduleUrl("dojo", "_base/_loader/bootstrap.js").uri);
// FIXME: pull in the host environment file in a more generic way
// for other host environments
dojox.off.files.cache(dojo.moduleUrl("dojo", "_base/_loader/hostenv_browser.js").uri);
}
// add anything that was brought in with a
// dojo.require() that resulted in a JavaScript
// URL being fetched
// FIXME: modify dojo/_base/_loader/loader.js to
// expose a public API to get this information
for(var i = 0; i < dojo._loadedUrls.length; i++){
dojox.off.files.cache(dojo._loadedUrls[i]);
}
// FIXME: add the standard Dojo CSS file
},
_save: function(){
// summary:
// Causes the Dojo Offline framework to save its configuration
// data into local storage.
},
_load: function(callback){
// summary:
// Causes the Dojo Offline framework to load its configuration
// data from local storage
dojox.off.sync._load(callback);
}
});
 
 
// wait until the storage system is finished loading
dojox.storage.manager.addOnLoad(dojo.hitch(dojox.off, "_onStorageLoad"));
 
// wait until the page is finished loading
dojo.addOnLoad(dojox.off, "_onPageLoad");
 
}
 
if(!dojo._hasResource["dojox.off"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.off"] = true;
dojo.provide("dojox.off");
 
 
}
 
if(!dojo._hasResource["dojox.off.ui"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.off.ui"] = true;
dojo.provide("dojox.off.ui");
 
 
 
 
 
// Author: Brad Neuberg, bkn3@columbia.edu, http://codinginparadise.org
 
// summary:
// dojox.off.ui provides a standard,
// default user-interface for a
// Dojo Offline Widget that can easily
// be dropped into applications that would
// like to work offline.
dojo.mixin(dojox.off.ui, {
// appName: String
// This application's name, such as "Foobar". Note that
// this is a string, not HTML, so embedded markup will
// not work, including entities. Only the following
// characters are allowed: numbers, letters, and spaces.
// You must set this property.
appName: "setme",
// autoEmbed: boolean
// For advanced usage; most developers can ignore this.
// Whether to automatically auto-embed the default Dojo Offline
// widget into this page; default is true.
autoEmbed: true,
// autoEmbedID: String
// For advanced usage; most developers can ignore this.
// The ID of the DOM element that will contain our
// Dojo Offline widget; defaults to the ID 'dot-widget'.
autoEmbedID: "dot-widget",
// runLink: String
// For advanced usage; most developers can ignore this.
// The URL that should be navigated to to run this
// application offline; this will be placed inside of a
// link that the user can drag to their desktop and double
// click. Note that this URL must exactly match the URL
// of the main page of our resource that is offline for
// it to be retrieved from the offline cache correctly.
// For example, if you have cached your main page as
// http://foobar.com/index.html, and you set this to
// http://www.foobar.com/index.html, the run link will
// not work. By default this value is automatically set to
// the URL of this page, so it does not need to be set
// manually unless you have unusual needs.
runLink: window.location.href,
// runLinkTitle: String
// For advanced usage; most developers can ignore this.
// The text that will be inside of the link that a user
// can drag to their desktop to run this application offline.
// By default this is automatically set to "Run " plus your
// application's name.
runLinkTitle: "Run Application",
// learnHowPath: String
// For advanced usage; most developers can ignore this.
// The path to a web page that has information on
// how to use this web app offline; defaults to
// src/off/ui-template/learnhow.html, relative to
// your Dojo installation. Make sure to set
// dojo.to.ui.customLearnHowPath to true if you want
// a custom Learn How page.
learnHowPath: dojo.moduleUrl("dojox", "off/resources/learnhow.html"),
// customLearnHowPath: boolean
// For advanced usage; most developers can ignore this.
// Whether the developer is using their own custom page
// for the Learn How instructional page; defaults to false.
// Use in conjunction with dojox.off.ui.learnHowPath.
customLearnHowPath: false,
htmlTemplatePath: dojo.moduleUrl("dojox", "off/resources/offline-widget.html").uri,
cssTemplatePath: dojo.moduleUrl("dojox", "off/resources/offline-widget.css").uri,
onlineImagePath: dojo.moduleUrl("dojox", "off/resources/greenball.png").uri,
offlineImagePath: dojo.moduleUrl("dojox", "off/resources/redball.png").uri,
rollerImagePath: dojo.moduleUrl("dojox", "off/resources/roller.gif").uri,
checkmarkImagePath: dojo.moduleUrl("dojox", "off/resources/checkmark.png").uri,
learnHowJSPath: dojo.moduleUrl("dojox", "off/resources/learnhow.js").uri,
_initialized: false,
onLoad: function(){
// summary:
// A function that should be connected to allow your
// application to know when Dojo Offline, the page, and
// the Offline Widget are all initialized and ready to be
// used:
//
// dojo.connect(dojox.off.ui, "onLoad", someFunc)
},
 
_initialize: function(){
//console.debug("dojox.off.ui._initialize");
// make sure our app name is correct
if(this._validateAppName(this.appName) == false){
alert("You must set dojox.off.ui.appName; it can only contain "
+ "letters, numbers, and spaces; right now it "
+ "is incorrectly set to '" + dojox.off.ui.appName + "'");
dojox.off.enabled = false;
return;
}
// set our run link text to its default
this.runLinkText = "Run " + this.appName;
// setup our event listeners for Dojo Offline events
// to update our UI
dojo.connect(dojox.off, "onNetwork", this, "_onNetwork");
dojo.connect(dojox.off.sync, "onSync", this, "_onSync");
// cache our default UI resources
dojox.off.files.cache([
this.htmlTemplatePath,
this.cssTemplatePath,
this.onlineImagePath,
this.offlineImagePath,
this.rollerImagePath,
this.checkmarkImagePath
]);
// embed the offline widget UI
if(this.autoEmbed){
this._doAutoEmbed();
}
},
_doAutoEmbed: function(){
// fetch our HTML for the offline widget
 
// dispatch the request
dojo.xhrGet({
url: this.htmlTemplatePath,
handleAs: "text",
error: function(err){
dojox.off.enabled = false;
err = err.message||err;
alert("Error loading the Dojo Offline Widget from "
+ this.htmlTemplatePath + ": " + err);
},
load: dojo.hitch(this, this._templateLoaded)
});
},
_templateLoaded: function(data){
//console.debug("dojox.off.ui._templateLoaded");
// inline our HTML
var container = dojo.byId(this.autoEmbedID);
if(container){ container.innerHTML = data; }
// fill out our image paths
this._initImages();
// update our network indicator status ball
this._updateNetIndicator();
// update our 'Learn How' text
this._initLearnHow();
this._initialized = true;
// check offline cache settings
if(!dojox.off.hasOfflineCache){
this._showNeedsOfflineCache();
return;
}
// check to see if we need a browser restart
// to be able to use this web app offline
if(dojox.off.hasOfflineCache && dojox.off.browserRestart){
this._needsBrowserRestart();
return;
}else{
var browserRestart = dojo.byId("dot-widget-browser-restart");
if(browserRestart){ browserRestart.style.display = "none"; }
}
// update our sync UI
this._updateSyncUI();
// register our event listeners for our main buttons
this._initMainEvtHandlers();
// if offline functionality is disabled, disable everything
this._setOfflineEnabled(dojox.off.enabled);
// update our UI based on the state of the network
this._onNetwork(dojox.off.isOnline ? "online" : "offline");
// try to go online
this._testNet();
},
_testNet: function(){
dojox.off.goOnline(dojo.hitch(this, function(isOnline){
//console.debug("testNet callback, isOnline="+isOnline);
// display our online/offline results
this._onNetwork(isOnline ? "online" : "offline");
// indicate that our default UI
// and Dojo Offline are now ready to
// be used
this.onLoad();
}));
},
_updateNetIndicator: function(){
var onlineImg = dojo.byId("dot-widget-network-indicator-online");
var offlineImg = dojo.byId("dot-widget-network-indicator-offline");
var titleText = dojo.byId("dot-widget-title-text");
if(onlineImg && offlineImg){
if(dojox.off.isOnline == true){
onlineImg.style.display = "inline";
offlineImg.style.display = "none";
}else{
onlineImg.style.display = "none";
offlineImg.style.display = "inline";
}
}
if(titleText){
if(dojox.off.isOnline){
titleText.innerHTML = "Online";
}else{
titleText.innerHTML = "Offline";
}
}
},
_initLearnHow: function(){
var learnHow = dojo.byId("dot-widget-learn-how-link");
if(!learnHow){ return; }
if(!this.customLearnHowPath){
// add parameters to URL so the Learn How page
// can customize itself and display itself
// correctly based on framework settings
var dojoPath = djConfig.baseRelativePath;
this.learnHowPath += "?appName=" + encodeURIComponent(this.appName)
+ "&hasOfflineCache=" + dojox.off.hasOfflineCache
+ "&runLink=" + encodeURIComponent(this.runLink)
+ "&runLinkText=" + encodeURIComponent(this.runLinkText)
+ "&baseRelativePath=" + encodeURIComponent(dojoPath);
// cache our Learn How JavaScript page and
// the HTML version with full query parameters
// so it is available offline without a cache miss
dojox.off.files.cache(this.learnHowJSPath);
dojox.off.files.cache(this.learnHowPath);
}
learnHow.setAttribute("href", this.learnHowPath);
var appName = dojo.byId("dot-widget-learn-how-app-name");
if(!appName){ return; }
appName.innerHTML = "";
appName.appendChild(document.createTextNode(this.appName));
},
_validateAppName: function(appName){
if(!appName){ return false; }
return (/^[a-z0-9 ]*$/i.test(appName));
},
_updateSyncUI: function(){
var roller = dojo.byId("dot-roller");
var checkmark = dojo.byId("dot-success-checkmark");
var syncMessages = dojo.byId("dot-sync-messages");
var details = dojo.byId("dot-sync-details");
var cancel = dojo.byId("dot-sync-cancel");
if(dojox.off.sync.isSyncing){
this._clearSyncMessage();
if(roller){ roller.style.display = "inline"; }
if(checkmark){ checkmark.style.display = "none"; }
if(syncMessages){
dojo.removeClass(syncMessages, "dot-sync-error");
}
if(details){ details.style.display = "none"; }
if(cancel){ cancel.style.display = "inline"; }
}else{
if(roller){ roller.style.display = "none"; }
if(cancel){ cancel.style.display = "none"; }
if(syncMessages){
dojo.removeClass(syncMessages, "dot-sync-error");
}
}
},
_setSyncMessage: function(message){
var syncMessage = dojo.byId("dot-sync-messages");
if(syncMessage){
// when used with Google Gears pre-release in Firefox/Mac OS X,
// the browser would crash when testing in Moxie
// if we set the message this way for some reason.
// Brad Neuberg, bkn3@columbia.edu
//syncMessage.innerHTML = message;
while(syncMessage.firstChild){
syncMessage.removeChild(syncMessage.firstChild);
}
syncMessage.appendChild(document.createTextNode(message));
}
},
_clearSyncMessage: function(){
this._setSyncMessage("");
},
_initImages: function(){
var onlineImg = dojo.byId("dot-widget-network-indicator-online");
if(onlineImg){
onlineImg.setAttribute("src", this.onlineImagePath);
}
var offlineImg = dojo.byId("dot-widget-network-indicator-offline");
if(offlineImg){
offlineImg.setAttribute("src", this.offlineImagePath);
}
var roller = dojo.byId("dot-roller");
if(roller){
roller.setAttribute("src", this.rollerImagePath);
}
var checkmark = dojo.byId("dot-success-checkmark");
if(checkmark){
checkmark.setAttribute("src", this.checkmarkImagePath);
}
},
_showDetails: function(evt){
// cancel the button's default behavior
evt.preventDefault();
evt.stopPropagation();
if(!dojox.off.sync.details.length){
return;
}
// determine our HTML message to display
var html = "";
html += "<html><head><title>Sync Details</title><head><body>";
html += "<h1>Sync Details</h1>\n";
html += "<ul>\n";
for(var i = 0; i < dojox.off.sync.details.length; i++){
html += "<li>";
html += dojox.off.sync.details[i];
html += "</li>";
}
html += "</ul>\n";
html += "<a href='javascript:window.close()' "
+ "style='text-align: right; padding-right: 2em;'>"
+ "Close Window"
+ "</a>\n";
html += "</body></html>";
// open a popup window with this message
var windowParams = "height=400,width=600,resizable=true,"
+ "scrollbars=true,toolbar=no,menubar=no,"
+ "location=no,directories=no,dependent=yes";
 
var popup = window.open("", "SyncDetails", windowParams);
if(!popup){ // aggressive popup blocker
alert("Please allow popup windows for this domain; can't display sync details window");
return;
}
popup.document.open();
popup.document.write(html);
popup.document.close();
// put the focus on the popup window
if(popup.focus){
popup.focus();
}
},
_cancel: function(evt){
// cancel the button's default behavior
evt.preventDefault();
evt.stopPropagation();
dojox.off.sync.cancel();
},
_needsBrowserRestart: function(){
var browserRestart = dojo.byId("dot-widget-browser-restart");
if(browserRestart){
dojo.addClass(browserRestart, "dot-needs-browser-restart");
}
var appName = dojo.byId("dot-widget-browser-restart-app-name");
if(appName){
appName.innerHTML = "";
appName.appendChild(document.createTextNode(this.appName));
}
var status = dojo.byId("dot-sync-status");
if(status){
status.style.display = "none";
}
},
_showNeedsOfflineCache: function(){
var widgetContainer = dojo.byId("dot-widget-container");
if(widgetContainer){
dojo.addClass(widgetContainer, "dot-needs-offline-cache");
}
},
_hideNeedsOfflineCache: function(){
var widgetContainer = dojo.byId("dot-widget-container");
if(widgetContainer){
dojo.removeClass(widgetContainer, "dot-needs-offline-cache");
}
},
_initMainEvtHandlers: function(){
var detailsButton = dojo.byId("dot-sync-details-button");
if(detailsButton){
dojo.connect(detailsButton, "onclick", this, this._showDetails);
}
var cancelButton = dojo.byId("dot-sync-cancel-button");
if(cancelButton){
dojo.connect(cancelButton, "onclick", this, this._cancel);
}
},
_setOfflineEnabled: function(enabled){
var elems = [];
elems.push(dojo.byId("dot-sync-status"));
for(var i = 0; i < elems.length; i++){
if(elems[i]){
elems[i].style.visibility =
(enabled ? "visible" : "hidden");
}
}
},
_syncFinished: function(){
this._updateSyncUI();
var checkmark = dojo.byId("dot-success-checkmark");
var details = dojo.byId("dot-sync-details");
if(dojox.off.sync.successful == true){
this._setSyncMessage("Sync Successful");
if(checkmark){ checkmark.style.display = "inline"; }
}else if(dojox.off.sync.cancelled == true){
this._setSyncMessage("Sync Cancelled");
if(checkmark){ checkmark.style.display = "none"; }
}else{
this._setSyncMessage("Sync Error");
var messages = dojo.byId("dot-sync-messages");
if(messages){
dojo.addClass(messages, "dot-sync-error");
}
if(checkmark){ checkmark.style.display = "none"; }
}
if(dojox.off.sync.details.length && details){
details.style.display = "inline";
}
},
_onFrameworkEvent: function(type, saveData){
if(type == "save"){
if(saveData.status == dojox.storage.FAILED && !saveData.isCoreSave){
alert("Please increase the amount of local storage available "
+ "to this application");
if(dojox.storage.hasSettingsUI()){
dojox.storage.showSettingsUI();
}
// FIXME: Be able to know if storage size has changed
// due to user configuration
}
}else if(type == "coreOperationFailed"){
console.log("Application does not have permission to use Dojo Offline");
if(!this._userInformed){
alert("This application will not work if Google Gears is not allowed to run");
this._userInformed = true;
}
}else if(type == "offlineCacheInstalled"){
// clear out the 'needs offline cache' info
this._hideNeedsOfflineCache();
// check to see if we need a browser restart
// to be able to use this web app offline
if(dojox.off.hasOfflineCache == true
&& dojox.off.browserRestart == true){
this._needsBrowserRestart();
return;
}else{
var browserRestart = dojo.byId("dot-widget-browser-restart");
if(browserRestart){
browserRestart.style.display = "none";
}
}
// update our sync UI
this._updateSyncUI();
// register our event listeners for our main buttons
this._initMainEvtHandlers();
// if offline is disabled, disable everything
this._setOfflineEnabled(dojox.off.enabled);
// try to go online
this._testNet();
}
},
_onSync: function(type){
//console.debug("ui, onSync="+type);
switch(type){
case "start":
this._updateSyncUI();
break;
case "refreshFiles":
this._setSyncMessage("Downloading UI...");
break;
case "upload":
this._setSyncMessage("Uploading new data...");
break;
case "download":
this._setSyncMessage("Downloading new data...");
break;
case "finished":
this._syncFinished();
break;
case "cancel":
this._setSyncMessage("Canceling Sync...");
break;
default:
dojo.warn("Programming error: "
+ "Unknown sync type in dojox.off.ui: " + type);
break;
}
},
_onNetwork: function(type){
// summary:
// Called when we go on- or off-line
// description:
// When we go online or offline, this method is called to update
// our UI. Default behavior is to update the Offline
// Widget UI and to attempt a synchronization.
// type: String
// "online" if we just moved online, and "offline" if we just
// moved offline.
if(!this._initialized){ return; }
// update UI
this._updateNetIndicator();
if(type == "offline"){
this._setSyncMessage("You are working offline");
// clear old details
var details = dojo.byId("dot-sync-details");
if(details){ details.style.display = "none"; }
// if we fell offline during a sync, hide
// the sync info
this._updateSyncUI();
}else{ // online
// synchronize, but pause for a few seconds
// so that the user can orient themselves
if(dojox.off.sync.autoSync){
window.setTimeout("dojox.off.sync.synchronize()", 1000);
}
}
}
});
 
// register ourselves for low-level framework events
dojo.connect(dojox.off, "onFrameworkEvent", dojox.off.ui, "_onFrameworkEvent");
 
// start our magic when the Dojo Offline framework is ready to go
dojo.connect(dojox.off, "onLoad", dojox.off.ui, dojox.off.ui._initialize);
 
}
 
if(!dojo._hasResource["dojox.off.offline"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.off.offline"] = true;
dojo.provide("dojox.off.offline");
 
 
 
 
 
 
 
}
 
/trunk/api/js/dojo1.0/dojox/off/_common.js
New file
0,0 → 1,559
if(!dojo._hasResource["dojox.off._common"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.off._common"] = true;
dojo.provide("dojox.off._common");
 
dojo.require("dojox.storage");
dojo.require("dojox.sql");
dojo.require("dojox.off.sync");
 
// Author: Brad Neuberg, bkn3@columbia.edu, http://codinginparadise.org
 
// summary:
// dojox.off is the main object for offline applications.
dojo.mixin(dojox.off, {
// isOnline: boolean
// true if we are online, false if not
isOnline: false,
// NET_CHECK: int
// For advanced usage; most developers can ignore this.
// Time in seconds on how often we should check the status of the
// network with an automatic background timer. The current default
// is 5 seconds.
NET_CHECK: 5,
// STORAGE_NAMESPACE: String
// For advanced usage; most developers can ignore this.
// The namespace we use to save core data into Dojo Storage.
STORAGE_NAMESPACE: "_dot",
// enabled: boolean
// For advanced usage; most developers can ignore this.
// Whether offline ability is enabled or not. Defaults to true.
enabled: true,
// availabilityURL: String
// For advanced usage; most developers can ignore this.
// The URL to check for site availability. We do a GET request on
// this URL to check for site availability. By default we check for a
// simple text file in src/off/network_check.txt that has one value
// it, the value '1'.
availabilityURL: dojo.moduleUrl("dojox", "off/network_check.txt"),
// goingOnline: boolean
// For advanced usage; most developers can ignore this.
// True if we are attempting to go online, false otherwise
goingOnline: false,
// coreOpFailed: boolean
// For advanced usage; most developers can ignore this.
// A flag set by the Dojo Offline framework that indicates that the
// user denied some operation that required the offline cache or an
// operation failed in some critical way that was unrecoverable. For
// example, if the offline cache is Google Gears and we try to get a
// Gears database, a popup window appears asking the user whether they
// will approve or deny this request. If the user denies the request,
// and we are doing some operation that is core to Dojo Offline, then
// we set this flag to 'true'. This flag causes a 'fail fast'
// condition, turning off offline ability.
coreOpFailed: false,
// doNetChecking: boolean
// For advanced usage; most developers can ignore this.
// Whether to have a timing interval in the background doing automatic
// network checks at regular intervals; the length of time between
// checks is controlled by dojox.off.NET_CHECK. Defaults to true.
doNetChecking: true,
// hasOfflineCache: boolean
// For advanced usage; most developers can ignore this.
// Determines if an offline cache is available or installed; an
// offline cache is a facility that can truely cache offline
// resources, such as JavaScript, HTML, etc. in such a way that they
// won't be removed from the cache inappropriately like a browser
// cache would. If this is false then an offline cache will be
// installed. Only Google Gears is currently supported as an offline
// cache. Future possible offline caches include Firefox 3.
hasOfflineCache: null,
// browserRestart: boolean
// For advanced usage; most developers can ignore this.
// If true, the browser must be restarted to register the existence of
// a new host added offline (from a call to addHostOffline); if false,
// then nothing is needed.
browserRestart: false,
_STORAGE_APP_NAME: window.location.href.replace(/[^0-9A-Za-z_]/g, "_"),
_initializeCalled: false,
_storageLoaded: false,
_pageLoaded: false,
onLoad: function(){
// summary:
// Called when Dojo Offline can be used.
// description:
// Do a dojo.connect to this to know when you can
// start using Dojo Offline:
// dojo.connect(dojox.off, "onLoad", myFunc);
},
onNetwork: function(type){
// summary:
// Called when our on- or offline- status changes.
// description:
// If we move online, then this method is called with the
// value "online". If we move offline, then this method is
// called with the value "offline". You can connect to this
// method to do add your own behavior:
//
// dojo.connect(dojox.off, "onNetwork", someFunc)
//
// Note that if you are using the default Dojo Offline UI
// widget that most of the on- and off-line notification
// and syncing is automatically handled and provided to the
// user.
// type: String
// Either "online" or "offline".
},
initialize: function(){ /* void */
// summary:
// Called when a Dojo Offline-enabled application is finished
// configuring Dojo Offline, and is ready for Dojo Offline to
// initialize itself.
// description:
// When an application has finished filling out the variables Dojo
// Offline needs to work, such as dojox.off.ui.appName, it must
// this method to tell Dojo Offline to initialize itself.
// Note:
// This method is needed for a rare edge case. In some conditions,
// especially if we are dealing with a compressed Dojo build, the
// entire Dojo Offline subsystem might initialize itself and be
// running even before the JavaScript for an application has had a
// chance to run and configure Dojo Offline, causing Dojo Offline
// to have incorrect initialization parameters for a given app,
// such as no value for dojox.off.ui.appName. This method is
// provided to prevent this scenario, to slightly 'slow down' Dojo
// Offline so it can be configured before running off and doing
// its thing.
 
//console.debug("dojox.off.initialize");
this._initializeCalled = true;
if(this._storageLoaded && this._pageLoaded){
this._onLoad();
}
},
goOffline: function(){ /* void */
// summary:
// For advanced usage; most developers can ignore this.
// Manually goes offline, away from the network.
if((dojox.off.sync.isSyncing)||(this.goingOnline)){ return; }
this.goingOnline = false;
this.isOnline = false;
},
goOnline: function(callback){ /* void */
// summary:
// For advanced usage; most developers can ignore this.
// Attempts to go online.
// description:
// Attempts to go online, making sure this web application's web
// site is available. 'callback' is called asychronously with the
// result of whether we were able to go online or not.
// callback: Function
// An optional callback function that will receive one argument:
// whether the site is available or not and is boolean. If this
// function is not present we call dojo.xoff.onOnline instead if
// we are able to go online.
//console.debug("goOnline");
if(dojox.off.sync.isSyncing || dojox.off.goingOnline){
return;
}
this.goingOnline = true;
this.isOnline = false;
// see if can reach our web application's web site
this._isSiteAvailable(callback);
},
onFrameworkEvent: function(type /* String */, saveData /* Object? */){
// summary:
// For advanced usage; most developers can ignore this.
// A standard event handler that can be attached to to find out
// about low-level framework events. Most developers will not need to
// attach to this method; it is meant for low-level information
// that can be useful for updating offline user-interfaces in
// exceptional circumstances. The default Dojo Offline UI
// widget takes care of most of these situations.
// type: String
// The type of the event:
//
// * "offlineCacheInstalled"
// An event that is fired when a user
// has installed an offline cache after the page has been loaded.
// If a user didn't have an offline cache when the page loaded, a
// UI of some kind might have prompted them to download one. This
// method is called if they have downloaded and installed an
// offline cache so a UI can reinitialize itself to begin using
// this offline cache.
// * "coreOperationFailed"
// Fired when a core operation during interaction with the
// offline cache is denied by the user. Some offline caches, such
// as Google Gears, prompts the user to approve or deny caching
// files, using the database, and more. If the user denies a
// request that is core to Dojo Offline's operation, we set
// dojox.off.coreOpFailed to true and call this method for
// listeners that would like to respond some how to Dojo Offline
// 'failing fast'.
// * "save"
// Called whenever the framework saves data into persistent
// storage. This could be useful for providing save feedback
// or providing appropriate error feedback if saving fails
// due to a user not allowing the save to occur
// saveData: Object?
// If the type was 'save', then a saveData object is provided with
// further save information. This object has the following properties:
//
// * status - dojox.storage.SUCCESS, dojox.storage.PENDING, dojox.storage.FAILED
// Whether the save succeeded, whether it is pending based on a UI
// dialog asking the user for permission, or whether it failed.
//
// * isCoreSave - boolean
// If true, then this save was for a core piece of data necessary
// for the functioning of Dojo Offline. If false, then it is a
// piece of normal data being saved for offline access. Dojo
// Offline will 'fail fast' if some core piece of data could not
// be saved, automatically setting dojox.off.coreOpFailed to
// 'true' and dojox.off.enabled to 'false'.
//
// * key - String
// The key that we are attempting to persist
//
// * value - Object
// The object we are trying to persist
//
// * namespace - String
// The Dojo Storage namespace we are saving this key/value pair
// into, such as "default", "Documents", "Contacts", etc.
// Optional.
if(type == "save"){
if(saveData.isCoreSave && (saveData.status == dojox.storage.FAILED)){
dojox.off.coreOpFailed = true;
dojox.off.enabled = false;
// FIXME: Stop the background network thread
dojox.off.onFrameworkEvent("coreOperationFailed");
}
}else if(type == "coreOperationFailed"){
dojox.off.coreOpFailed = true;
dojox.off.enabled = false;
// FIXME: Stop the background network thread
}
},
_checkOfflineCacheAvailable: function(callback){
// is a true, offline cache running on this machine?
this.hasOfflineCache = dojo.isGears;
callback();
},
_onLoad: function(){
//console.debug("dojox.off._onLoad");
// both local storage and the page are finished loading
// cache the Dojo JavaScript -- just use the default dojo.js
// name for the most common scenario
// FIXME: TEST: Make sure syncing doesn't break if dojo.js
// can't be found, or report an error to developer
dojox.off.files.cache(dojo.moduleUrl("dojo", "dojo.js"));
// pull in the files needed by Dojo
this._cacheDojoResources();
// FIXME: need to pull in the firebug lite files here!
// workaround or else we will get an error on page load
// from Dojo that it can't find 'console.debug' for optimized builds
// dojox.off.files.cache(djConfig.baseRelativePath + "src/debug.js");
// make sure that resources needed by all of our underlying
// Dojo Storage storage providers will be available
// offline
dojox.off.files.cache(dojox.storage.manager.getResourceList());
// slurp the page if the end-developer wants that
dojox.off.files._slurp();
// see if we have an offline cache; when done, move
// on to the rest of our startup tasks
this._checkOfflineCacheAvailable(dojo.hitch(this, "_onOfflineCacheChecked"));
},
_onOfflineCacheChecked: function(){
// this method is part of our _onLoad series of startup tasks
// if we have an offline cache, see if we have been added to the
// list of available offline web apps yet
if(this.hasOfflineCache && this.enabled){
// load framework data; when we are finished, continue
// initializing ourselves
this._load(dojo.hitch(this, "_finishStartingUp"));
}else if(this.hasOfflineCache && !this.enabled){
// we have an offline cache, but it is disabled for some reason
// perhaps due to the user denying a core operation
this._finishStartingUp();
}else{
this._keepCheckingUntilInstalled();
}
},
_keepCheckingUntilInstalled: function(){
// this method is part of our _onLoad series of startup tasks
// kick off a background interval that keeps
// checking to see if an offline cache has been
// installed since this page loaded
// FIXME: Gears: See if we are installed somehow after the
// page has been loaded
// now continue starting up
this._finishStartingUp();
},
_finishStartingUp: function(){
//console.debug("dojox.off._finishStartingUp");
// this method is part of our _onLoad series of startup tasks
if(!this.hasOfflineCache){
this.onLoad();
}else if(this.enabled){
// kick off a thread to check network status on
// a regular basis
this._startNetworkThread();
 
// try to go online
this.goOnline(dojo.hitch(this, function(){
//console.debug("Finished trying to go online");
// indicate we are ready to be used
dojox.off.onLoad();
}));
}else{ // we are disabled or a core operation failed
if(this.coreOpFailed){
this.onFrameworkEvent("coreOperationFailed");
}else{
this.onLoad();
}
}
},
_onPageLoad: function(){
//console.debug("dojox.off._onPageLoad");
this._pageLoaded = true;
if(this._storageLoaded && this._initializeCalled){
this._onLoad();
}
},
_onStorageLoad: function(){
//console.debug("dojox.off._onStorageLoad");
this._storageLoaded = true;
// were we able to initialize storage? if
// not, then this is a core operation, and
// let's indicate we will need to fail fast
if(!dojox.storage.manager.isAvailable()
&& dojox.storage.manager.isInitialized()){
this.coreOpFailed = true;
this.enabled = false;
}
if(this._pageLoaded && this._initializeCalled){
this._onLoad();
}
},
_isSiteAvailable: function(callback){
// summary:
// Determines if our web application's website is available.
// description:
// This method will asychronously determine if our web
// application's web site is available, which is a good proxy for
// network availability. The URL dojox.off.availabilityURL is
// used, which defaults to this site's domain name (ex:
// foobar.com). We check for dojox.off.AVAILABILITY_TIMEOUT (in
// seconds) and abort after that
// callback: Function
// An optional callback function that will receive one argument:
// whether the site is available or not and is boolean. If this
// function is not present we call dojox.off.onNetwork instead if we
// are able to go online.
dojo.xhrGet({
url: this._getAvailabilityURL(),
handleAs: "text",
timeout: this.NET_CHECK * 1000,
error: dojo.hitch(this, function(err){
//console.debug("dojox.off._isSiteAvailable.error: " + err);
this.goingOnline = false;
this.isOnline = false;
if(callback){ callback(false); }
}),
load: dojo.hitch(this, function(data){
//console.debug("dojox.off._isSiteAvailable.load, data="+data);
this.goingOnline = false;
this.isOnline = true;
if(callback){ callback(true);
}else{ this.onNetwork("online"); }
})
});
},
_startNetworkThread: function(){
//console.debug("startNetworkThread");
// kick off a thread that does periodic
// checks on the status of the network
if(!this.doNetChecking){
return;
}
window.setInterval(dojo.hitch(this, function(){
var d = dojo.xhrGet({
url: this._getAvailabilityURL(),
handleAs: "text",
timeout: this.NET_CHECK * 1000,
error: dojo.hitch(this,
function(err){
if(this.isOnline){
this.isOnline = false;
// FIXME: xhrGet() is not
// correctly calling abort
// on the XHR object when
// it times out; fix inside
// there instead of externally
// here
try{
if(typeof d.ioArgs.xhr.abort == "function"){
d.ioArgs.xhr.abort();
}
}catch(e){}
// if things fell in the middle of syncing,
// stop syncing
dojox.off.sync.isSyncing = false;
this.onNetwork("offline");
}
}
),
load: dojo.hitch(this,
function(data){
if(!this.isOnline){
this.isOnline = true;
this.onNetwork("online");
}
}
)
});
 
}), this.NET_CHECK * 1000);
},
_getAvailabilityURL: function(){
var url = this.availabilityURL.toString();
// bust the browser's cache to make sure we are really talking to
// the server
if(url.indexOf("?") == -1){
url += "?";
}else{
url += "&";
}
url += "browserbust=" + new Date().getTime();
return url;
},
_onOfflineCacheInstalled: function(){
this.onFrameworkEvent("offlineCacheInstalled");
},
_cacheDojoResources: function(){
// if we are a non-optimized build, then the core Dojo bootstrap
// system was loaded as separate JavaScript files;
// add these to our offline cache list. these are
// loaded before the dojo.require() system exists
// FIXME: create a better mechanism in the Dojo core to
// expose whether you are dealing with an optimized build;
// right now we just scan the SCRIPT tags attached to this
// page and see if there is one for _base/_loader/bootstrap.js
var isOptimizedBuild = true;
dojo.forEach(dojo.query("script"), function(i){
var src = i.getAttribute("src");
if(!src){ return; }
if(src.indexOf("_base/_loader/bootstrap.js") != -1){
isOptimizedBuild = false;
}
});
if(!isOptimizedBuild){
dojox.off.files.cache(dojo.moduleUrl("dojo", "_base.js").uri);
dojox.off.files.cache(dojo.moduleUrl("dojo", "_base/_loader/loader.js").uri);
dojox.off.files.cache(dojo.moduleUrl("dojo", "_base/_loader/bootstrap.js").uri);
// FIXME: pull in the host environment file in a more generic way
// for other host environments
dojox.off.files.cache(dojo.moduleUrl("dojo", "_base/_loader/hostenv_browser.js").uri);
}
// add anything that was brought in with a
// dojo.require() that resulted in a JavaScript
// URL being fetched
// FIXME: modify dojo/_base/_loader/loader.js to
// expose a public API to get this information
for(var i = 0; i < dojo._loadedUrls.length; i++){
dojox.off.files.cache(dojo._loadedUrls[i]);
}
// FIXME: add the standard Dojo CSS file
},
_save: function(){
// summary:
// Causes the Dojo Offline framework to save its configuration
// data into local storage.
},
_load: function(callback){
// summary:
// Causes the Dojo Offline framework to load its configuration
// data from local storage
dojox.off.sync._load(callback);
}
});
 
 
// wait until the storage system is finished loading
dojox.storage.manager.addOnLoad(dojo.hitch(dojox.off, "_onStorageLoad"));
 
// wait until the page is finished loading
dojo.addOnLoad(dojox.off, "_onPageLoad");
 
}
/trunk/api/js/dojo1.0/dojox/off/network_check.txt
New file
0,0 → 1,0
1
/trunk/api/js/dojo1.0/dojox/off/sync.js
New file
0,0 → 1,690
if(!dojo._hasResource["dojox.off.sync"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.off.sync"] = true;
dojo.provide("dojox.off.sync");
 
dojo.require("dojox.storage.GearsStorageProvider");
dojo.require("dojox.off._common");
dojo.require("dojox.off.files");
 
// Author: Brad Neuberg, bkn3@columbia.edu, http://codinginparadise.org
 
// summary:
// Exposes syncing functionality to offline applications
dojo.mixin(dojox.off.sync, {
// isSyncing: boolean
// Whether we are in the middle of a syncing session.
isSyncing: false,
// cancelled: boolean
// Whether we were cancelled during our last sync request or not. If
// we are cancelled, then successful will be false.
cancelled: false,
// successful: boolean
// Whether the last sync was successful or not. If false, an error
// occurred.
successful: true,
// details: String[]
// Details on the sync. If the sync was successful, this will carry
// any conflict or merging messages that might be available; if the
// sync was unsuccessful, this will have an error message. For both
// of these, this should be an array of Strings, where each string
// carries details on the sync.
// Example:
// dojox.off.sync.details = ["The document 'foobar' had conflicts - yours one",
// "The document 'hello world' was automatically merged"];
details: [],
// error: boolean
// Whether an error occurred during the syncing process.
error: false,
// actions: dojox.off.sync.ActionLog
// Our ActionLog that we store offline actions into for later
// replaying when we go online
actions: null,
// autoSync: boolean
// For advanced usage; most developers can ignore this.
// Whether we do automatically sync on page load or when we go online.
// If true we do, if false syncing must be manually initiated.
// Defaults to true.
autoSync: true,
// summary:
// An event handler that is called during the syncing process with
// the state of syncing. It is important that you connect to this
// method and respond to certain sync events, especially the
// "download" event.
// description:
// This event handler is called during the syncing process. You can
// do a dojo.connect to receive sync feedback:
//
// dojo.connect(dojox.off.sync, "onSync", someFunc);
//
// You will receive one argument, which is the type of the event
// and which can have the following values.
//
// The most common two types that you need to care about are "download"
// and "finished", especially if you are using the default
// Dojo Offline UI widget that does the hard work of informing
// the user through the UI about what is occuring during syncing.
//
// If you receive the "download" event, you should make a network call
// to retrieve and store your data somehow for offline access. The
// "finished" event indicates that syncing is done. An example:
//
// dojo.connect(dojox.off.sync, "onSync", function(type){
// if(type == "download"){
// // make a network call to download some data
// // for use offline
// dojo.xhrGet({
// url: "downloadData.php",
// handleAs: "javascript",
// error: function(err){
// dojox.off.sync.finishedDownloading(false, "Can't download data");
// },
// load: function(data){
// // store our data
// dojox.storage.put("myData", data);
//
// // indicate we are finished downloading
// dojox.off.sync.finishedDownloading(true);
// }
// });
// }else if(type == "finished"){
// // update UI somehow to indicate we are finished,
// // such as using the download data to change the
// // available data
// }
// })
//
// Here is the full list of event types if you want to do deep
// customization, such as updating your UI to display the progress
// of syncing (note that the default Dojo Offline UI widget does
// this for you if you choose to pull that in). Most of these
// are only appropriate for advanced usage and can be safely
// ignored:
//
// * "start"
// syncing has started
// * "refreshFiles"
// syncing will begin refreshing
// our offline file cache
// * "upload"
// syncing will begin uploading
// any local data changes we have on the client.
// This event is fired before we fire
// the dojox.off.sync.actions.onReplay event for
// each action to replay; use it to completely
// over-ride the replaying behavior and prevent
// it entirely, perhaps rolling your own sync
// protocol if needed.
// * "download"
// syncing will begin downloading any new data that is
// needed into persistent storage. Applications are required to
// implement this themselves, storing the required data into
// persistent local storage using Dojo Storage.
// * "finished"
// syncing is finished; this
// will be called whether an error ocurred or not; check
// dojox.off.sync.successful and dojox.off.sync.error for sync details
// * "cancel"
// Fired when canceling has been initiated; canceling will be
// attempted, followed by the sync event "finished".
onSync: function(/* String */ type){},
synchronize: function(){ /* void */
// summary: Starts synchronizing
 
//dojo.debug("synchronize");
if(this.isSyncing || dojox.off.goingOnline || (!dojox.off.isOnline)){
return;
}
this.isSyncing = true;
this.successful = false;
this.details = [];
this.cancelled = false;
this.start();
},
cancel: function(){ /* void */
// summary:
// Attempts to cancel this sync session
if(!this.isSyncing){ return; }
this.cancelled = true;
if(dojox.off.files.refreshing){
dojox.off.files.abortRefresh();
}
this.onSync("cancel");
},
finishedDownloading: function(successful /* boolean? */,
errorMessage /* String? */){
// summary:
// Applications call this method from their
// after getting a "download" event in
// dojox.off.sync.onSync to signal that
// they are finished downloading any data
// that should be available offline
// successful: boolean?
// Whether our downloading was successful or not.
// If not present, defaults to true.
// errorMessage: String?
// If unsuccessful, a message explaining why
if(typeof successful == "undefined"){
successful = true;
}
if(!successful){
this.successful = false;
this.details.push(errorMessage);
this.error = true;
}
this.finished();
},
start: function(){ /* void */
// summary:
// For advanced usage; most developers can ignore this.
// Called at the start of the syncing process. Advanced
// developers can over-ride this method to use their
// own sync mechanism to start syncing.
if(this.cancelled){
this.finished();
return;
}
this.onSync("start");
this.refreshFiles();
},
refreshFiles: function(){ /* void */
// summary:
// For advanced usage; most developers can ignore this.
// Called when we are going to refresh our list
// of offline files during syncing. Advanced developers
// can over-ride this method to do some advanced magic related to
// refreshing files.
//dojo.debug("refreshFiles");
if(this.cancelled){
this.finished();
return;
}
this.onSync("refreshFiles");
dojox.off.files.refresh(dojo.hitch(this, function(error, errorMessages){
if(error){
this.error = true;
this.successful = false;
for(var i = 0; i < errorMessages.length; i++){
this.details.push(errorMessages[i]);
}
// even if we get an error while syncing files,
// keep syncing so we can upload and download
// data
}
this.upload();
}));
},
upload: function(){ /* void */
// summary:
// For advanced usage; most developers can ignore this.
// Called when syncing wants to upload data. Advanced
// developers can over-ride this method to completely
// throw away the Action Log and replaying system
// and roll their own advanced sync mechanism if needed.
if(this.cancelled){
this.finished();
return;
}
this.onSync("upload");
// when we are done uploading start downloading
dojo.connect(this.actions, "onReplayFinished", this, this.download);
// replay the actions log
this.actions.replay();
},
download: function(){ /* void */
// summary:
// For advanced usage; most developers can ignore this.
// Called when syncing wants to download data. Advanced
// developers can over-ride this method to use their
// own sync mechanism.
if(this.cancelled){
this.finished();
return;
}
// apps should respond to the "download"
// event to download their data; when done
// they must call dojox.off.sync.finishedDownloading()
this.onSync("download");
},
finished: function(){ /* void */
// summary:
// For advanced usage; most developers can ignore this.
// Called when syncing is finished. Advanced
// developers can over-ride this method to clean
// up after finishing their own sync
// mechanism they might have rolled.
this.isSyncing = false;
this.successful = (!this.cancelled && !this.error);
this.onSync("finished");
},
_save: function(callback){
this.actions._save(function(){
callback();
});
},
_load: function(callback){
this.actions._load(function(){
callback();
});
}
});
 
 
// summary:
// A class that records actions taken by a user when they are offline,
// suitable for replaying when the network reappears.
// description:
// The basic idea behind this method is to record user actions that would
// normally have to contact a server into an action log when we are
// offline, so that later when we are online we can simply replay this log
// in the order user actions happened so that they can be executed against
// the server, causing synchronization to happen.
//
// When we replay, for each of the actions that were added, we call a
// method named onReplay that applications should connect to and
// which will be called over and over for each of our actions --
// applications should take the offline action
// information and use it to talk to a server to have this action
// actually happen online, 'syncing' themselves with the server.
//
// For example, if the action was "update" with the item that was updated, we
// might call some RESTian server API that exists for updating an item in
// our application. The server could either then do sophisticated merging
// and conflict resolution on the server side, for example, allowing you
// to pop up a custom merge UI, or could do automatic merging or nothing
// of the sort. When you are finished with this particular action, your
// application is then required to call continueReplay() on the actionLog object
// passed to onReplay() to continue replaying the action log, or haltReplay()
// with the reason for halting to completely stop the syncing/replaying
// process.
//
// For example, imagine that we have a web application that allows us to add
// contacts. If we are offline, and we update a contact, we would add an action;
// imagine that the user has to click an Update button after changing the values
// for a given contact:
//
// dojox.off.whenOffline(dojo.byId("updateButton"), "onclick", function(evt){
// // get the updated customer values
// var customer = getCustomerValues();
//
// // we are offline -- just record this action
// var action = {name: "update", customer: customer};
// dojox.off.sync.actions.add(action)
//
// // persist this customer data into local storage as well
// dojox.storage.put(customer.name, customer);
// })
//
// Then, when we go back online, the dojox.off.sync.actions.onReplay event
// will fire over and over, once for each action that was recorded while offline:
//
// dojo.connect(dojox.off.sync.actions, "onReplay", function(action, actionLog){
// // called once for each action we added while offline, in the order
// // they were added
// if(action.name == "update"){
// var customer = action.customer;
//
// // call some network service to update this customer
// dojo.xhrPost({
// url: "updateCustomer.php",
// content: {customer: dojo.toJson(customer)},
// error: function(err){
// actionLog.haltReplay(err);
// },
// load: function(data){
// actionLog.continueReplay();
// }
// })
// }
// })
//
// Note that the actions log is always automatically persisted locally while using it, so
// that if the user closes the browser or it crashes the actions will safely be stored
// for later replaying.
dojo.declare("dojox.off.sync.ActionLog", null, {
// entries: Array
// An array of our action entries, where each one is simply a custom
// object literal that were passed to add() when this action entry
// was added.
entries: [],
// reasonHalted: String
// If we halted, the reason why
reasonHalted: null,
// isReplaying: boolean
// If true, we are in the middle of replaying a command log; if false,
// then we are not
isReplaying: false,
// autoSave: boolean
// Whether we automatically save the action log after each call to
// add(); defaults to true. For applications that are rapidly adding
// many action log entries in a short period of time, it can be
// useful to set this to false and simply call save() yourself when
// you are ready to persist your command log -- otherwise performance
// could be slow as the default action is to attempt to persist the
// actions log constantly with calls to add().
autoSave: true,
add: function(action /* Object */){ /* void */
// summary:
// Adds an action to our action log
// description:
// This method will add an action to our
// action log, later to be replayed when we
// go from offline to online. 'action'
// will be available when this action is
// replayed and will be passed to onReplay.
//
// Example usage:
//
// dojox.off.sync.log.add({actionName: "create", itemType: "document",
// {title: "Message", content: "Hello World"}});
//
// The object literal is simply a custom object appropriate
// for our application -- it can be anything that preserves the state
// of a user action that will be executed when we go back online
// and replay this log. In the above example,
// "create" is the name of this action; "documents" is the
// type of item this command is operating on, such as documents, contacts,
// tasks, etc.; and the final argument is the document that was created.
if(this.isReplaying){
throw "Programming error: you can not call "
+ "dojox.off.sync.actions.add() while "
+ "we are replaying an action log";
}
this.entries.push(action);
// save our updated state into persistent
// storage
if(this.autoSave){
this._save();
}
},
onReplay: function(action /* Object */,
actionLog /* dojox.off.sync.ActionLog */){ /* void */
// summary:
// Called when we replay our log, for each of our action
// entries.
// action: Object
// A custom object literal representing an action for this
// application, such as
// {actionName: "create", item: {title: "message", content: "hello world"}}
// actionLog: dojox.off.sync.ActionLog
// A reference to the dojox.off.sync.actions log so that developers
// can easily call actionLog.continueReplay() or actionLog.haltReplay().
// description:
// This callback should be connected to by applications so that
// they can sync themselves when we go back online:
//
// dojo.connect(dojox.off.sync.actions, "onReplay", function(action, actionLog){
// // do something
// })
//
// When we replay our action log, this callback is called for each
// of our action entries in the order they were added. The
// 'action' entry that was passed to add() for this action will
// also be passed in to onReplay, so that applications can use this information
// to do their syncing, such as contacting a server web-service
// to create a new item, for example.
//
// Inside the method you connected to onReplay, you should either call
// actionLog.haltReplay(reason) if an error occurred and you would like to halt
// action replaying or actionLog.continueReplay() to have the action log
// continue replaying its log and proceed to the next action;
// the reason you must call these is the action you execute inside of
// onAction will probably be asynchronous, since it will be talking on
// the network, and you should call one of these two methods based on
// the result of your network call.
},
length: function(){ /* Number */
// summary:
// Returns the length of this
// action log
return this.entries.length;
},
haltReplay: function(reason /* String */){ /* void */
// summary: Halts replaying this command log.
// reason: String
// The reason we halted.
// description:
// This method is called as we are replaying an action log; it
// can be called from dojox.off.sync.actions.onReplay, for
// example, for an application to indicate an error occurred
// while replaying this action, halting further processing of
// the action log. Note that any action log entries that
// were processed before have their effects retained (i.e.
// they are not rolled back), while the action entry that was
// halted stays in our list of actions to later be replayed.
if(!this.isReplaying){
return;
}
if(reason){
this.reasonHalted = reason.toString();
}
// save the state of our action log, then
// tell anyone who is interested that we are
// done when we are finished saving
if(this.autoSave){
var self = this;
this._save(function(){
self.isReplaying = false;
self.onReplayFinished();
});
}else{
this.isReplaying = false;
this.onReplayFinished();
}
},
continueReplay: function(){ /* void */
// summary:
// Indicates that we should continue processing out list of
// actions.
// description:
// This method is called by applications that have overridden
// dojox.off.sync.actions.onReplay() to continue replaying our
// action log after the application has finished handling the
// current action.
if(!this.isReplaying){
return;
}
// shift off the old action we just ran
this.entries.shift();
// are we done?
if(!this.entries.length){
// save the state of our action log, then
// tell anyone who is interested that we are
// done when we are finished saving
if(this.autoSave){
var self = this;
this._save(function(){
self.isReplaying = false;
self.onReplayFinished();
});
return;
}else{
this.isReplaying = false;
this.onReplayFinished();
return;
}
}
// get the next action
var nextAction = this.entries[0];
this.onReplay(nextAction, this);
},
clear: function(){ /* void */
// summary:
// Completely clears this action log of its entries
if(this.isReplaying){
return;
}
this.entries = [];
// save our updated state into persistent
// storage
if(this.autoSave){
this._save();
}
},
replay: function(){ /* void */
// summary:
// For advanced usage; most developers can ignore this.
// Replays all of the commands that have been
// cached in this command log when we go back online;
// onCommand will be called for each command we have
if(this.isReplaying){
return;
}
this.reasonHalted = null;
if(!this.entries.length){
this.onReplayFinished();
return;
}
this.isReplaying = true;
var nextAction = this.entries[0];
this.onReplay(nextAction, this);
},
// onReplayFinished: Function
// For advanced usage; most developers can ignore this.
// Called when we are finished replaying our commands;
// called if we have successfully exhausted all of our
// commands, or if an error occurred during replaying.
// The default implementation simply continues the
// synchronization process. Connect to this to register
// for the event:
//
// dojo.connect(dojox.off.sync.actions, "onReplayFinished",
// someFunc)
onReplayFinished: function(){
},
 
toString: function(){
var results = "";
results += "[";
for(var i = 0; i < this.entries.length; i++){
results += "{";
for(var j in this.entries[i]){
results += j + ": \"" + this.entries[i][j] + "\"";
results += ", ";
}
results += "}, ";
}
results += "]";
return results;
},
_save: function(callback){
if(!callback){
callback = function(){};
}
try{
var self = this;
var resultsHandler = function(status, key, message){
//console.debug("resultsHandler, status="+status+", key="+key+", message="+message);
if(status == dojox.storage.FAILED){
dojox.off.onFrameworkEvent("save",
{status: dojox.storage.FAILED,
isCoreSave: true,
key: key,
value: message,
namespace: dojox.off.STORAGE_NAMESPACE});
callback();
}else if(status == dojox.storage.SUCCESS){
callback();
}
};
dojox.storage.put("actionlog", this.entries, resultsHandler,
dojox.off.STORAGE_NAMESPACE);
}catch(exp){
console.debug("dojox.off.sync._save: " + exp.message||exp);
dojox.off.onFrameworkEvent("save",
{status: dojox.storage.FAILED,
isCoreSave: true,
key: "actionlog",
value: this.entries,
namespace: dojox.off.STORAGE_NAMESPACE});
callback();
}
},
_load: function(callback){
var entries = dojox.storage.get("actionlog", dojox.off.STORAGE_NAMESPACE);
if(!entries){
entries = [];
}
this.entries = entries;
callback();
}
}
);
 
dojox.off.sync.actions = new dojox.off.sync.ActionLog();
 
}
/trunk/api/js/dojo1.0/dojox/off/docs/bookmarklets.html
New file
0,0 → 1,10
<html>
<body>
<h1>Browser Bookmarklets</h1>
 
<p>Drag the following bookmarklets to your links toolbar and press to clear the Google Gears cache:</p>
 
<p>Firefox: <a title="Clear Gears Cache" href="javascript:(function(){new GearsFactory().create('beta.localserver', '1.0').removeStore('dot_store_'+window.location.href.replace(/[^0-9A-Za-z_]/g, '_'));dojox.storage.remove('oldVersion', '_dot');}())">Clear Gears Cache</a></p>
<p>Internet Explorer: <a title="Clear Gears Cache" href="javascript:(function(){new ActiveXObject('Gears.Factory').create('beta.localserver', '1.0').removeStore('dot_store_'+window.location.href.replace(/[^0-9A-Za-z_]/g, '_'));dojox.storage.remove('oldVersion', '_dot');}())">Clear Gears Cache</a></p>
</body>
</html>
/trunk/api/js/dojo1.0/dojox/off/ui.js
New file
0,0 → 1,618
if(!dojo._hasResource["dojox.off.ui"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.off.ui"] = true;
dojo.provide("dojox.off.ui");
 
dojo.require("dojox.storage.Provider");
dojo.require("dojox.storage.manager");
dojo.require("dojox.storage.GearsStorageProvider");
 
// Author: Brad Neuberg, bkn3@columbia.edu, http://codinginparadise.org
 
// summary:
// dojox.off.ui provides a standard,
// default user-interface for a
// Dojo Offline Widget that can easily
// be dropped into applications that would
// like to work offline.
dojo.mixin(dojox.off.ui, {
// appName: String
// This application's name, such as "Foobar". Note that
// this is a string, not HTML, so embedded markup will
// not work, including entities. Only the following
// characters are allowed: numbers, letters, and spaces.
// You must set this property.
appName: "setme",
// autoEmbed: boolean
// For advanced usage; most developers can ignore this.
// Whether to automatically auto-embed the default Dojo Offline
// widget into this page; default is true.
autoEmbed: true,
// autoEmbedID: String
// For advanced usage; most developers can ignore this.
// The ID of the DOM element that will contain our
// Dojo Offline widget; defaults to the ID 'dot-widget'.
autoEmbedID: "dot-widget",
// runLink: String
// For advanced usage; most developers can ignore this.
// The URL that should be navigated to to run this
// application offline; this will be placed inside of a
// link that the user can drag to their desktop and double
// click. Note that this URL must exactly match the URL
// of the main page of our resource that is offline for
// it to be retrieved from the offline cache correctly.
// For example, if you have cached your main page as
// http://foobar.com/index.html, and you set this to
// http://www.foobar.com/index.html, the run link will
// not work. By default this value is automatically set to
// the URL of this page, so it does not need to be set
// manually unless you have unusual needs.
runLink: window.location.href,
// runLinkTitle: String
// For advanced usage; most developers can ignore this.
// The text that will be inside of the link that a user
// can drag to their desktop to run this application offline.
// By default this is automatically set to "Run " plus your
// application's name.
runLinkTitle: "Run Application",
// learnHowPath: String
// For advanced usage; most developers can ignore this.
// The path to a web page that has information on
// how to use this web app offline; defaults to
// src/off/ui-template/learnhow.html, relative to
// your Dojo installation. Make sure to set
// dojo.to.ui.customLearnHowPath to true if you want
// a custom Learn How page.
learnHowPath: dojo.moduleUrl("dojox", "off/resources/learnhow.html"),
// customLearnHowPath: boolean
// For advanced usage; most developers can ignore this.
// Whether the developer is using their own custom page
// for the Learn How instructional page; defaults to false.
// Use in conjunction with dojox.off.ui.learnHowPath.
customLearnHowPath: false,
htmlTemplatePath: dojo.moduleUrl("dojox", "off/resources/offline-widget.html").uri,
cssTemplatePath: dojo.moduleUrl("dojox", "off/resources/offline-widget.css").uri,
onlineImagePath: dojo.moduleUrl("dojox", "off/resources/greenball.png").uri,
offlineImagePath: dojo.moduleUrl("dojox", "off/resources/redball.png").uri,
rollerImagePath: dojo.moduleUrl("dojox", "off/resources/roller.gif").uri,
checkmarkImagePath: dojo.moduleUrl("dojox", "off/resources/checkmark.png").uri,
learnHowJSPath: dojo.moduleUrl("dojox", "off/resources/learnhow.js").uri,
_initialized: false,
onLoad: function(){
// summary:
// A function that should be connected to allow your
// application to know when Dojo Offline, the page, and
// the Offline Widget are all initialized and ready to be
// used:
//
// dojo.connect(dojox.off.ui, "onLoad", someFunc)
},
 
_initialize: function(){
//console.debug("dojox.off.ui._initialize");
// make sure our app name is correct
if(this._validateAppName(this.appName) == false){
alert("You must set dojox.off.ui.appName; it can only contain "
+ "letters, numbers, and spaces; right now it "
+ "is incorrectly set to '" + dojox.off.ui.appName + "'");
dojox.off.enabled = false;
return;
}
// set our run link text to its default
this.runLinkText = "Run " + this.appName;
// setup our event listeners for Dojo Offline events
// to update our UI
dojo.connect(dojox.off, "onNetwork", this, "_onNetwork");
dojo.connect(dojox.off.sync, "onSync", this, "_onSync");
// cache our default UI resources
dojox.off.files.cache([
this.htmlTemplatePath,
this.cssTemplatePath,
this.onlineImagePath,
this.offlineImagePath,
this.rollerImagePath,
this.checkmarkImagePath
]);
// embed the offline widget UI
if(this.autoEmbed){
this._doAutoEmbed();
}
},
_doAutoEmbed: function(){
// fetch our HTML for the offline widget
 
// dispatch the request
dojo.xhrGet({
url: this.htmlTemplatePath,
handleAs: "text",
error: function(err){
dojox.off.enabled = false;
err = err.message||err;
alert("Error loading the Dojo Offline Widget from "
+ this.htmlTemplatePath + ": " + err);
},
load: dojo.hitch(this, this._templateLoaded)
});
},
_templateLoaded: function(data){
//console.debug("dojox.off.ui._templateLoaded");
// inline our HTML
var container = dojo.byId(this.autoEmbedID);
if(container){ container.innerHTML = data; }
// fill out our image paths
this._initImages();
// update our network indicator status ball
this._updateNetIndicator();
// update our 'Learn How' text
this._initLearnHow();
this._initialized = true;
// check offline cache settings
if(!dojox.off.hasOfflineCache){
this._showNeedsOfflineCache();
return;
}
// check to see if we need a browser restart
// to be able to use this web app offline
if(dojox.off.hasOfflineCache && dojox.off.browserRestart){
this._needsBrowserRestart();
return;
}else{
var browserRestart = dojo.byId("dot-widget-browser-restart");
if(browserRestart){ browserRestart.style.display = "none"; }
}
// update our sync UI
this._updateSyncUI();
// register our event listeners for our main buttons
this._initMainEvtHandlers();
// if offline functionality is disabled, disable everything
this._setOfflineEnabled(dojox.off.enabled);
// update our UI based on the state of the network
this._onNetwork(dojox.off.isOnline ? "online" : "offline");
// try to go online
this._testNet();
},
_testNet: function(){
dojox.off.goOnline(dojo.hitch(this, function(isOnline){
//console.debug("testNet callback, isOnline="+isOnline);
// display our online/offline results
this._onNetwork(isOnline ? "online" : "offline");
// indicate that our default UI
// and Dojo Offline are now ready to
// be used
this.onLoad();
}));
},
_updateNetIndicator: function(){
var onlineImg = dojo.byId("dot-widget-network-indicator-online");
var offlineImg = dojo.byId("dot-widget-network-indicator-offline");
var titleText = dojo.byId("dot-widget-title-text");
if(onlineImg && offlineImg){
if(dojox.off.isOnline == true){
onlineImg.style.display = "inline";
offlineImg.style.display = "none";
}else{
onlineImg.style.display = "none";
offlineImg.style.display = "inline";
}
}
if(titleText){
if(dojox.off.isOnline){
titleText.innerHTML = "Online";
}else{
titleText.innerHTML = "Offline";
}
}
},
_initLearnHow: function(){
var learnHow = dojo.byId("dot-widget-learn-how-link");
if(!learnHow){ return; }
if(!this.customLearnHowPath){
// add parameters to URL so the Learn How page
// can customize itself and display itself
// correctly based on framework settings
var dojoPath = djConfig.baseRelativePath;
this.learnHowPath += "?appName=" + encodeURIComponent(this.appName)
+ "&hasOfflineCache=" + dojox.off.hasOfflineCache
+ "&runLink=" + encodeURIComponent(this.runLink)
+ "&runLinkText=" + encodeURIComponent(this.runLinkText)
+ "&baseRelativePath=" + encodeURIComponent(dojoPath);
// cache our Learn How JavaScript page and
// the HTML version with full query parameters
// so it is available offline without a cache miss
dojox.off.files.cache(this.learnHowJSPath);
dojox.off.files.cache(this.learnHowPath);
}
learnHow.setAttribute("href", this.learnHowPath);
var appName = dojo.byId("dot-widget-learn-how-app-name");
if(!appName){ return; }
appName.innerHTML = "";
appName.appendChild(document.createTextNode(this.appName));
},
_validateAppName: function(appName){
if(!appName){ return false; }
return (/^[a-z0-9 ]*$/i.test(appName));
},
_updateSyncUI: function(){
var roller = dojo.byId("dot-roller");
var checkmark = dojo.byId("dot-success-checkmark");
var syncMessages = dojo.byId("dot-sync-messages");
var details = dojo.byId("dot-sync-details");
var cancel = dojo.byId("dot-sync-cancel");
if(dojox.off.sync.isSyncing){
this._clearSyncMessage();
if(roller){ roller.style.display = "inline"; }
if(checkmark){ checkmark.style.display = "none"; }
if(syncMessages){
dojo.removeClass(syncMessages, "dot-sync-error");
}
if(details){ details.style.display = "none"; }
if(cancel){ cancel.style.display = "inline"; }
}else{
if(roller){ roller.style.display = "none"; }
if(cancel){ cancel.style.display = "none"; }
if(syncMessages){
dojo.removeClass(syncMessages, "dot-sync-error");
}
}
},
_setSyncMessage: function(message){
var syncMessage = dojo.byId("dot-sync-messages");
if(syncMessage){
// when used with Google Gears pre-release in Firefox/Mac OS X,
// the browser would crash when testing in Moxie
// if we set the message this way for some reason.
// Brad Neuberg, bkn3@columbia.edu
//syncMessage.innerHTML = message;
while(syncMessage.firstChild){
syncMessage.removeChild(syncMessage.firstChild);
}
syncMessage.appendChild(document.createTextNode(message));
}
},
_clearSyncMessage: function(){
this._setSyncMessage("");
},
_initImages: function(){
var onlineImg = dojo.byId("dot-widget-network-indicator-online");
if(onlineImg){
onlineImg.setAttribute("src", this.onlineImagePath);
}
var offlineImg = dojo.byId("dot-widget-network-indicator-offline");
if(offlineImg){
offlineImg.setAttribute("src", this.offlineImagePath);
}
var roller = dojo.byId("dot-roller");
if(roller){
roller.setAttribute("src", this.rollerImagePath);
}
var checkmark = dojo.byId("dot-success-checkmark");
if(checkmark){
checkmark.setAttribute("src", this.checkmarkImagePath);
}
},
_showDetails: function(evt){
// cancel the button's default behavior
evt.preventDefault();
evt.stopPropagation();
if(!dojox.off.sync.details.length){
return;
}
// determine our HTML message to display
var html = "";
html += "<html><head><title>Sync Details</title><head><body>";
html += "<h1>Sync Details</h1>\n";
html += "<ul>\n";
for(var i = 0; i < dojox.off.sync.details.length; i++){
html += "<li>";
html += dojox.off.sync.details[i];
html += "</li>";
}
html += "</ul>\n";
html += "<a href='javascript:window.close()' "
+ "style='text-align: right; padding-right: 2em;'>"
+ "Close Window"
+ "</a>\n";
html += "</body></html>";
// open a popup window with this message
var windowParams = "height=400,width=600,resizable=true,"
+ "scrollbars=true,toolbar=no,menubar=no,"
+ "location=no,directories=no,dependent=yes";
 
var popup = window.open("", "SyncDetails", windowParams);
if(!popup){ // aggressive popup blocker
alert("Please allow popup windows for this domain; can't display sync details window");
return;
}
popup.document.open();
popup.document.write(html);
popup.document.close();
// put the focus on the popup window
if(popup.focus){
popup.focus();
}
},
_cancel: function(evt){
// cancel the button's default behavior
evt.preventDefault();
evt.stopPropagation();
dojox.off.sync.cancel();
},
_needsBrowserRestart: function(){
var browserRestart = dojo.byId("dot-widget-browser-restart");
if(browserRestart){
dojo.addClass(browserRestart, "dot-needs-browser-restart");
}
var appName = dojo.byId("dot-widget-browser-restart-app-name");
if(appName){
appName.innerHTML = "";
appName.appendChild(document.createTextNode(this.appName));
}
var status = dojo.byId("dot-sync-status");
if(status){
status.style.display = "none";
}
},
_showNeedsOfflineCache: function(){
var widgetContainer = dojo.byId("dot-widget-container");
if(widgetContainer){
dojo.addClass(widgetContainer, "dot-needs-offline-cache");
}
},
_hideNeedsOfflineCache: function(){
var widgetContainer = dojo.byId("dot-widget-container");
if(widgetContainer){
dojo.removeClass(widgetContainer, "dot-needs-offline-cache");
}
},
_initMainEvtHandlers: function(){
var detailsButton = dojo.byId("dot-sync-details-button");
if(detailsButton){
dojo.connect(detailsButton, "onclick", this, this._showDetails);
}
var cancelButton = dojo.byId("dot-sync-cancel-button");
if(cancelButton){
dojo.connect(cancelButton, "onclick", this, this._cancel);
}
},
_setOfflineEnabled: function(enabled){
var elems = [];
elems.push(dojo.byId("dot-sync-status"));
for(var i = 0; i < elems.length; i++){
if(elems[i]){
elems[i].style.visibility =
(enabled ? "visible" : "hidden");
}
}
},
_syncFinished: function(){
this._updateSyncUI();
var checkmark = dojo.byId("dot-success-checkmark");
var details = dojo.byId("dot-sync-details");
if(dojox.off.sync.successful == true){
this._setSyncMessage("Sync Successful");
if(checkmark){ checkmark.style.display = "inline"; }
}else if(dojox.off.sync.cancelled == true){
this._setSyncMessage("Sync Cancelled");
if(checkmark){ checkmark.style.display = "none"; }
}else{
this._setSyncMessage("Sync Error");
var messages = dojo.byId("dot-sync-messages");
if(messages){
dojo.addClass(messages, "dot-sync-error");
}
if(checkmark){ checkmark.style.display = "none"; }
}
if(dojox.off.sync.details.length && details){
details.style.display = "inline";
}
},
_onFrameworkEvent: function(type, saveData){
if(type == "save"){
if(saveData.status == dojox.storage.FAILED && !saveData.isCoreSave){
alert("Please increase the amount of local storage available "
+ "to this application");
if(dojox.storage.hasSettingsUI()){
dojox.storage.showSettingsUI();
}
// FIXME: Be able to know if storage size has changed
// due to user configuration
}
}else if(type == "coreOperationFailed"){
console.log("Application does not have permission to use Dojo Offline");
if(!this._userInformed){
alert("This application will not work if Google Gears is not allowed to run");
this._userInformed = true;
}
}else if(type == "offlineCacheInstalled"){
// clear out the 'needs offline cache' info
this._hideNeedsOfflineCache();
// check to see if we need a browser restart
// to be able to use this web app offline
if(dojox.off.hasOfflineCache == true
&& dojox.off.browserRestart == true){
this._needsBrowserRestart();
return;
}else{
var browserRestart = dojo.byId("dot-widget-browser-restart");
if(browserRestart){
browserRestart.style.display = "none";
}
}
// update our sync UI
this._updateSyncUI();
// register our event listeners for our main buttons
this._initMainEvtHandlers();
// if offline is disabled, disable everything
this._setOfflineEnabled(dojox.off.enabled);
// try to go online
this._testNet();
}
},
_onSync: function(type){
//console.debug("ui, onSync="+type);
switch(type){
case "start":
this._updateSyncUI();
break;
case "refreshFiles":
this._setSyncMessage("Downloading UI...");
break;
case "upload":
this._setSyncMessage("Uploading new data...");
break;
case "download":
this._setSyncMessage("Downloading new data...");
break;
case "finished":
this._syncFinished();
break;
case "cancel":
this._setSyncMessage("Canceling Sync...");
break;
default:
dojo.warn("Programming error: "
+ "Unknown sync type in dojox.off.ui: " + type);
break;
}
},
_onNetwork: function(type){
// summary:
// Called when we go on- or off-line
// description:
// When we go online or offline, this method is called to update
// our UI. Default behavior is to update the Offline
// Widget UI and to attempt a synchronization.
// type: String
// "online" if we just moved online, and "offline" if we just
// moved offline.
if(!this._initialized){ return; }
// update UI
this._updateNetIndicator();
if(type == "offline"){
this._setSyncMessage("You are working offline");
// clear old details
var details = dojo.byId("dot-sync-details");
if(details){ details.style.display = "none"; }
// if we fell offline during a sync, hide
// the sync info
this._updateSyncUI();
}else{ // online
// synchronize, but pause for a few seconds
// so that the user can orient themselves
if(dojox.off.sync.autoSync){
window.setTimeout("dojox.off.sync.synchronize()", 1000);
}
}
}
});
 
// register ourselves for low-level framework events
dojo.connect(dojox.off, "onFrameworkEvent", dojox.off.ui, "_onFrameworkEvent");
 
// start our magic when the Dojo Offline framework is ready to go
dojo.connect(dojox.off, "onLoad", dojox.off.ui, dojox.off.ui._initialize);
 
}
/trunk/api/js/dojo1.0/dojox/off/resources/offline-widget.html
New file
0,0 → 1,40
<!--
Note: The elements in this UI can be broken apart
and spread around your page, as long as you keep the
IDs intact. Elements can also be dropped without
Dojo Offline's default UI breaking.
-->
 
<div id="dot-widget-container" style="visibility: hidden;">
<div id="dot-widget-title-bar">
<span id="dot-widget-network-indicator">
<img id="dot-widget-network-indicator-online" />
<img id="dot-widget-network-indicator-offline" />
</span>
<span id="dot-widget-title-text"></span>
</div>
<div id="dot-widget-contents">
<div id="dot-widget-browser-restart">
Please restart your browser to
use <span id="dot-widget-browser-restart-app-name"></span> Offline
</div>
<div id="dot-sync-status">
<img id="dot-roller" />
<img id="dot-success-checkmark" />
<span id="dot-sync-messages"></span>
<span id="dot-sync-details">
(<a id="dot-sync-details-button" href="#">details</a>)
</span>
<span id="dot-sync-cancel">
(<a id="dot-sync-cancel-button" href="#">cancel</a>)
</span>
</div>
<div id="dot-widget-learn-how">
<a id="dot-widget-learn-how-link" target="_blank" href="#">Learn How</a>
to use <span id="dot-widget-learn-how-app-name"></span>&nbsp;Offline!
</div>
</div>
</div>
/trunk/api/js/dojo1.0/dojox/off/resources/redball.png
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/api/js/dojo1.0/dojox/off/resources/redball.png
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/api/js/dojo1.0/dojox/off/resources/learnhow.js
New file
0,0 → 1,43
window.onload = function(){
// get the app name from our URL
var href = window.location.href;
var matches = href.match(/appName=([a-z0-9 \%]*)/i);
var appName = "Application";
if(matches && matches.length > 0){
appName = decodeURIComponent(matches[1]);
}
// set it in our UI
var appNameSpan = document.getElementById("dot-learn-how-app-name");
appNameSpan.innerHTML = "";
appNameSpan.appendChild(document.createTextNode(appName));
// if we need an offline cache, and we already have one installed,
// update the UI
matches = href.match(/hasOfflineCache=(true|false)/);
var hasOfflineCache = false;
if(matches && matches.length > 0){
hasOfflineCache = matches[1];
// convert to boolean
hasOfflineCache = (hasOfflineCache == "true") ? true : false;
}
if(hasOfflineCache == true){
// delete the download and install steps
var downloadStep = document.getElementById("dot-download-step");
var installStep = document.getElementById("dot-install-step");
downloadStep.parentNode.removeChild(downloadStep);
installStep.parentNode.removeChild(installStep);
}
// get our run link info and update the UI
matches = href.match(/runLink=([^\&]*)\&runLinkText=([^\&]*)/);
if(matches && matches.length > 0){
var runLink = decodeURIComponent(matches[1]);
var runLinkElem = document.getElementById("dot-learn-how-run-link");
runLinkElem.setAttribute("href", runLink);
var runLinkText = decodeURIComponent(matches[2]);
runLinkElem.innerHTML = "";
runLinkElem.appendChild(document.createTextNode(runLinkText));
}
}
/trunk/api/js/dojo1.0/dojox/off/resources/offline-widget.css
New file
0,0 → 1,112
/** Offline Widget Styles */
 
#dot-widget-container{
/**
Keep these as EMs so widget reflows fluidly based on
user-font size settings
*/
width: 13em;
height: auto;
border: 2px solid #CDDDE9; /* light tundra blue */
position: relative;
visibility: visible !important;
}
 
#dot-widget-title-bar{
background-color: #CDDDE9; /* light tundra blue */
padding-top: 0.2em;
padding-bottom: 0.2em;
}
 
#dot-widget-network-indicator{
height: 8px;
width: 8px;
padding-left: 0.3em;
}
 
#dot-widget-title-text{
vertical-align: middle;
font-weight: bold;
font-size: 14pt;
padding-left: 2px;
}
 
#dot-widget-contents{
padding: 8px 5px 8px 5px;
}
 
#dot-widget-learn-how{
font-size: 11pt;
}
 
#dot-sync-cancel,
#dot-sync-status{
font-size: 11pt;
}
 
#dot-success-checkmark{
display: none;
}
 
#dot-roller{
display: none;
padding-right: 4px;
}
 
.dot-sync-error{
color: red;
}
 
#dot-sync-details{
display: none;
padding-left: 0.2em;
}
 
#dot-sync-status{
height: 2em;
margin-top: 0.8em;
margin-bottom: 0.8em;
}
 
.dot-needs-offline-cache #dot-widget-learn-how,
.dot-needs-browser-restart{
text-align: center;
line-height: 1.2;
font-size: 16pt !important;
}
 
.dot-needs-offline-cache #dot-sync-status,
.dot-needs-offline-cache #dot-widget-browser-restart{
display: none;
}
 
.dot-needs-browser-restart{
font-size: 14pt !important;
padding-bottom: 1em;
padding-top: 1em;
}
 
/** Learn How Page Styles */
#dot-learn-how-body{
padding: 3em;
background-color: #CDDDE9; /* light tundra blue */
}
 
#dot-learn-how-contents{
border: 1px solid black;
background-color: white;
padding: 0.4em 0.6em 0.4em 0.6em;
font-size: 16pt;
}
 
#dot-learn-how-contents h1{
font-size: 24pt;
}
 
#dot-learn-how-contents h2{
font-size: 18pt;
}
 
#dot-learn-how-contents li{
padding-bottom: 0.6em;
}
/trunk/api/js/dojo1.0/dojox/off/resources/checkmark.png
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/api/js/dojo1.0/dojox/off/resources/checkmark.png
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/api/js/dojo1.0/dojox/off/resources/greenball.png
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/api/js/dojo1.0/dojox/off/resources/greenball.png
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/api/js/dojo1.0/dojox/off/resources/learnhow.html
New file
0,0 → 1,43
<html>
<head>
<link rel="stylesheet" type="text/css" href="offline-widget.css"></link>
<script type="text/javascript" src="learnhow.js"></script>
</head>
<body id="dot-learn-how-body">
<div id="dot-learn-how-contents">
<h1><b>Want to use <span id="dot-learn-how-app-name">Application</span> offline?</b></h1>
<p id="dot-toolkit-info">It's simple with Dojo Offline! Dojo Offline is a free open source utility that makes it easy
for this web application to work, even if you're offline. Now you can
access your data even when away from the network!</p>
<p>Dojo Offline is an open source project brought to you by
<a href="http://dojotoolkit.org">Dojo</a>, <a href="http://sitepen.com">SitePen</a>,
and <a href="http://codinginparadise.org">Brad Neuberg</a>. It incorporates
technologies created by <a href="http://google.com">Google</a>.</p>
<h2>To get started:</h2>
<ol>
<li id="dot-download-step">
<a target="_new" href="http://gears.google.com">Download Gears</a>, a small, open source utility created by Google that allows this web site
to work offline. This tool is safe and secure for your machine, and only takes
a few seconds to download.
</li>
<li id="dot-install-step">
Once downloaded, run the installer. Restart your web browser when finished installing.
</li>
<li id="dot-drag-link-step">
To access this website even when offline, drag the following link to your
desktop or your browser's link toolbar above: <a id="dot-learn-how-run-link" href="#">Run Application</a>.
</li>
<li id="dot-run-link-step">
Double-click the link on your desktop to start this web application, even
if offline.
</li>
</ol>
</div>
</body>
</html>
/trunk/api/js/dojo1.0/dojox/off/resources/roller.gif
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/api/js/dojo1.0/dojox/off/resources/roller.gif
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property