diff options
Diffstat (limited to 'includes/js/dojox/off')
18 files changed, 8620 insertions, 0 deletions
diff --git a/includes/js/dojox/off/README b/includes/js/dojox/off/README new file mode 100644 index 0000000..64a1f4b --- /dev/null +++ b/includes/js/dojox/off/README @@ -0,0 +1,28 @@ +-------------------------------------------------------------------------------
+Dojo Offline
+-------------------------------------------------------------------------------
+Release date: May 2007 (Release date used as version)
+-------------------------------------------------------------------------------
+Project state:
+experimental
+-------------------------------------------------------------------------------
+Credits
+ Brad Neuberg
+ SitePen
+-------------------------------------------------------------------------------
+Project description
+Toolkit to help build offline web applications; uses Google Gears under the covers.
+-------------------------------------------------------------------------------
+Dependencies:
+Dojo Storage, Dojo Crypto, Dojo Core, Google Gears
+-------------------------------------------------------------------------------
+Documentation
+
+See http://docs.google.com/View?docid=dhkhksk4_8gdp9gr for documentation and a t
+utorial on using Dojo Offline.
+
+-------------------------------------------------------------------------------
+Installation instructions
+
+See full documentation at URL given right above.
+-------------------------------------------------------------------------------
diff --git a/includes/js/dojox/off/_common.js b/includes/js/dojox/off/_common.js new file mode 100644 index 0000000..005cd31 --- /dev/null +++ b/includes/js/dojox/off/_common.js @@ -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(dojo.config.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"); + +} diff --git a/includes/js/dojox/off/docs/bookmarklets.html b/includes/js/dojox/off/docs/bookmarklets.html new file mode 100644 index 0000000..c5ece2e --- /dev/null +++ b/includes/js/dojox/off/docs/bookmarklets.html @@ -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> diff --git a/includes/js/dojox/off/files.js b/includes/js/dojox/off/files.js new file mode 100644 index 0000000..6c19ea0 --- /dev/null +++ b/includes/js/dojox/off/files.js @@ -0,0 +1,454 @@ +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(dojo.config.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="+dojo.config.isDebug); + if(dojo.config.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, "_"); + + // clip at 64 characters, the max length of a resource store name + if(storeName.length >= 64){ + storeName = storeName.substring(0, 63); + } + + // 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", dojo.config.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); + } + }); + } +} + +} diff --git a/includes/js/dojox/off/network_check.txt b/includes/js/dojox/off/network_check.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/includes/js/dojox/off/network_check.txt @@ -0,0 +1 @@ +1 diff --git a/includes/js/dojox/off/offline.js b/includes/js/dojox/off/offline.js new file mode 100644 index 0000000..997206e --- /dev/null +++ b/includes/js/dojox/off/offline.js @@ -0,0 +1,20 @@ +/* + Copyright (c) 2004-2008, 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,_8);},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||_15===undefined||!dojo.isArray(_15)){return false;}return !dojo.some(_15,function(key){return !this.isValidKey(key);});},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||_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.providers=[];this._initialized=false;this._onLoadListeners=[];this.initialize=function(){this.autodetect();};this.register=function(_18,_19){this.providers.push(_19);this.providers[_18]=_19;};this.setProvider=function(_1a){};this.autodetect=function(){if(this._initialized){return;}var _1b=dojo.config["forceStorageProvider"]||false;var _1c;for(var i=0;i<this.providers.length;i++){_1c=this.providers[i];if(_1b&&_1b==_1c.declaredClass){_1c.isAvailable();break;}else{if(!_1b&&_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(this.dbName.length>63){this.dbName=this.dbName.substring(0,63);}}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(dojo.config["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());})();}}if(!dojo._hasResource["dojox.storage.WhatWGStorageProvider"]){dojo._hasResource["dojox.storage.WhatWGStorageProvider"]=true;dojo.provide("dojox.storage.WhatWGStorageProvider");dojo.declare("dojox.storage.WhatWGStorageProvider",[dojox.storage.Provider],{initialized:false,_domain:null,_available:null,_statusHandler:null,_allNamespaces:null,_storageEventListener:null,initialize:function(){if(dojo.config["disableWhatWGStorage"]==true){return;}this._domain=(location.hostname=="localhost")?"localhost.localdomain":location.hostname;this.initialized=true;dojox.storage.manager.loaded();},isAvailable:function(){try{var _11f=globalStorage[((location.hostname=="localhost")?"localhost.localdomain":location.hostname)];}catch(e){this._available=false;return this._available;}this._available=true;return this._available;},put:function(key,_121,_122,_123){if(this.isValidKey(key)==false){throw new Error("Invalid key given: "+key);}_123=_123||this.DEFAULT_NAMESPACE;key=this.getFullKey(key,_123);this._statusHandler=_122;if(dojo.isString(_121)){_121="string:"+_121;}else{_121=dojo.toJson(_121);}var _124=dojo.hitch(this,function(evt){window.removeEventListener("storage",_124,false);if(_122){_122.call(null,this.SUCCESS,key);}});window.addEventListener("storage",_124,false);try{var _126=globalStorage[this._domain];_126.setItem(key,_121);}catch(e){this._statusHandler.call(null,this.FAILED,key,e.toString());}},get:function(key,_128){if(this.isValidKey(key)==false){throw new Error("Invalid key given: "+key);}_128=_128||this.DEFAULT_NAMESPACE;key=this.getFullKey(key,_128);var _129=globalStorage[this._domain];var _12a=_129.getItem(key);if(_12a==null||_12a==""){return null;}_12a=_12a.value;if(dojo.isString(_12a)&&(/^string:/.test(_12a))){_12a=_12a.substring("string:".length);}else{_12a=dojo.fromJson(_12a);}return _12a;},getNamespaces:function(){var _12b=[this.DEFAULT_NAMESPACE];var _12c={};var _12d=globalStorage[this._domain];var _12e=/^__([^_]*)_/;for(var i=0;i<_12d.length;i++){var _130=_12d.key(i);if(_12e.test(_130)==true){var _131=_130.match(_12e)[1];if(typeof _12c[_131]=="undefined"){_12c[_131]=true;_12b.push(_131);}}}return _12b;},getKeys:function(_132){_132=_132||this.DEFAULT_NAMESPACE;if(this.isValidKey(_132)==false){throw new Error("Invalid namespace given: "+_132);}var _133;if(_132==this.DEFAULT_NAMESPACE){_133=new RegExp("^([^_]{2}.*)$");}else{_133=new RegExp("^__"+_132+"_(.*)$");}var _134=globalStorage[this._domain];var _135=[];for(var i=0;i<_134.length;i++){var _137=_134.key(i);if(_133.test(_137)==true){_137=_137.match(_133)[1];_135.push(_137);}}return _135;},clear:function(_138){_138=_138||this.DEFAULT_NAMESPACE;if(this.isValidKey(_138)==false){throw new Error("Invalid namespace given: "+_138);}var _139;if(_138==this.DEFAULT_NAMESPACE){_139=new RegExp("^[^_]{2}");}else{_139=new RegExp("^__"+_138+"_");}var _13a=globalStorage[this._domain];var keys=[];for(var i=0;i<_13a.length;i++){if(_139.test(_13a.key(i))==true){keys[keys.length]=_13a.key(i);}}dojo.forEach(keys,dojo.hitch(_13a,"removeItem"));},remove:function(key,_13e){key=this.getFullKey(key,_13e);var _13f=globalStorage[this._domain];_13f.removeItem(key);},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");},getFullKey:function(key,_141){_141=_141||this.DEFAULT_NAMESPACE;if(this.isValidKey(_141)==false){throw new Error("Invalid namespace given: "+_141);}if(_141==this.DEFAULT_NAMESPACE){return key;}else{return "__"+_141+"_"+key;}}});dojox.storage.manager.register("dojox.storage.WhatWGStorageProvider",new dojox.storage.WhatWGStorageProvider());}if(!dojo._hasResource["dijit._base.place"]){dojo._hasResource["dijit._base.place"]=true;dojo.provide("dijit._base.place");dijit.getViewport=function(){var _142=dojo.global;var _143=dojo.doc;var w=0,h=0;var de=_143.documentElement;var dew=de.clientWidth,deh=de.clientHeight;if(dojo.isMozilla){var minw,minh,maxw,maxh;var dbw=_143.body.clientWidth;if(dbw>dew){minw=dew;maxw=dbw;}else{maxw=dew;minw=dbw;}var dbh=_143.body.clientHeight;if(dbh>deh){minh=deh;maxh=dbh;}else{maxh=deh;minh=dbh;}w=(maxw>_142.innerWidth)?minw:maxw;h=(maxh>_142.innerHeight)?minh:maxh;}else{if(!dojo.isOpera&&_142.innerWidth){w=_142.innerWidth;h=_142.innerHeight;}else{if(dojo.isIE&&de&&deh){w=dew;h=deh;}else{if(dojo.body().clientWidth){w=dojo.body().clientWidth;h=dojo.body().clientHeight;}}}}var _14f=dojo._docScroll();return {w:w,h:h,l:_14f.x,t:_14f.y};};dijit.placeOnScreen=function(node,pos,_152,_153){var _154=dojo.map(_152,function(_155){return {corner:_155,pos:pos};});return dijit._place(node,_154);};dijit._place=function(node,_157,_158){var view=dijit.getViewport();if(!node.parentNode||String(node.parentNode.tagName).toLowerCase()!="body"){dojo.body().appendChild(node);}var best=null;dojo.some(_157,function(_15b){var _15c=_15b.corner;var pos=_15b.pos;if(_158){_158(node,_15b.aroundCorner,_15c);}var _15e=node.style;var _15f=_15e.display;var _160=_15e.visibility;_15e.visibility="hidden";_15e.display="";var mb=dojo.marginBox(node);_15e.display=_15f;_15e.visibility=_160;var _162=(_15c.charAt(1)=="L"?pos.x:Math.max(view.l,pos.x-mb.w)),_163=(_15c.charAt(0)=="T"?pos.y:Math.max(view.t,pos.y-mb.h)),endX=(_15c.charAt(1)=="L"?Math.min(view.l+view.w,_162+mb.w):pos.x),endY=(_15c.charAt(0)=="T"?Math.min(view.t+view.h,_163+mb.h):pos.y),_166=endX-_162,_167=endY-_163,_168=(mb.w-_166)+(mb.h-_167);if(best==null||_168<best.overflow){best={corner:_15c,aroundCorner:_15b.aroundCorner,x:_162,y:_163,w:_166,h:_167,overflow:_168};}return !_168;});node.style.left=best.x+"px";node.style.top=best.y+"px";if(best.overflow&&_158){_158(node,best.aroundCorner,best.corner);}return best;};dijit.placeOnScreenAroundElement=function(node,_16a,_16b,_16c){_16a=dojo.byId(_16a);var _16d=_16a.style.display;_16a.style.display="";var _16e=_16a.offsetWidth;var _16f=_16a.offsetHeight;var _170=dojo.coords(_16a,true);_16a.style.display=_16d;var _171=[];for(var _172 in _16b){_171.push({aroundCorner:_172,corner:_16b[_172],pos:{x:_170.x+(_172.charAt(1)=="L"?0:_16e),y:_170.y+(_172.charAt(0)=="T"?0:_16f)}});}return dijit._place(node,_171,_16c);};}if(!dojo._hasResource["dojox.flash._base"]){dojo._hasResource["dojox.flash._base"]=true;dojo.provide("dojox.flash._base");dojox.flash=function(){};dojox.flash={ready:false,url:null,_visible:true,_loadedListeners:new Array(),_installingListeners:new Array(),setSwf:function(url,_174){this.url=url;if(typeof _174!="undefined"){this._visible=_174;}this._initialize();},addLoadedListener:function(_175){this._loadedListeners.push(_175);},addInstallingListener:function(_176){this._installingListeners.push(_176);},loaded:function(){dojox.flash.ready=true;if(dojox.flash._loadedListeners.length>0){for(var i=0;i<dojox.flash._loadedListeners.length;i++){dojox.flash._loadedListeners[i].call(null);}}},installing:function(){if(dojox.flash._installingListeners.length>0){for(var i=0;i<dojox.flash._installingListeners.length;i++){dojox.flash._installingListeners[i].call(null);}}},_initialize:function(){var _179=new dojox.flash.Install();dojox.flash.installer=_179;if(_179.needed()==true){_179.install();}else{dojox.flash.obj=new dojox.flash.Embed(this._visible);dojox.flash.obj.write();dojox.flash.comm=new dojox.flash.Communicator();}}};dojox.flash.Info=function(){if(dojo.isIE){document.write(["<script language=\"VBScript\" type=\"text/vbscript\">","Function VBGetSwfVer(i)"," on error resume next"," Dim swControl, swVersion"," swVersion = 0"," set swControl = CreateObject(\"ShockwaveFlash.ShockwaveFlash.\" + CStr(i))"," if (IsObject(swControl)) then"," swVersion = swControl.GetVariable(\"$version\")"," end if"," VBGetSwfVer = swVersion","End Function","</script>"].join("\r\n"));}this._detectVersion();};dojox.flash.Info.prototype={version:-1,versionMajor:-1,versionMinor:-1,versionRevision:-1,capable:false,installing:false,isVersionOrAbove:function(_17a,_17b,_17c){_17c=parseFloat("."+_17c);if(this.versionMajor>=_17a&&this.versionMinor>=_17b&&this.versionRevision>=_17c){return true;}else{return false;}},_detectVersion:function(){var _17d;for(var _17e=25;_17e>0;_17e--){if(dojo.isIE){_17d=VBGetSwfVer(_17e);}else{_17d=this._JSFlashInfo(_17e);}if(_17d==-1){this.capable=false;return;}else{if(_17d!=0){var _17f;if(dojo.isIE){var _180=_17d.split(" ");var _181=_180[1];_17f=_181.split(",");}else{_17f=_17d.split(".");}this.versionMajor=_17f[0];this.versionMinor=_17f[1];this.versionRevision=_17f[2];var _182=this.versionMajor+"."+this.versionRevision;this.version=parseFloat(_182);this.capable=true;break;}}}},_JSFlashInfo:function(_183){if(navigator.plugins!=null&&navigator.plugins.length>0){if(navigator.plugins["Shockwave Flash 2.0"]||navigator.plugins["Shockwave Flash"]){var _184=navigator.plugins["Shockwave Flash 2.0"]?" 2.0":"";var _185=navigator.plugins["Shockwave Flash"+_184].description;var _186=_185.split(" ");var _187=_186[2].split(".");var _188=_187[0];var _189=_187[1];if(_186[3]!=""){var _18a=_186[3].split("r");}else{var _18a=_186[4].split("r");}var _18b=_18a[1]>0?_18a[1]:0;var _18c=_188+"."+_189+"."+_18b;return _18c;}}return -1;}};dojox.flash.Embed=function(_18d){this._visible=_18d;};dojox.flash.Embed.prototype={width:215,height:138,id:"flashObject",_visible:true,protocol:function(){switch(window.location.protocol){case "https:":return "https";break;default:return "http";break;}},write:function(_18e){var _18f="";_18f+=("width: "+this.width+"px; ");_18f+=("height: "+this.height+"px; ");if(!this._visible){_18f+="position: absolute; z-index: 10000; top: -1000px; left: -1000px; ";}var _190;var _191=dojox.flash.url;var _192=_191;var _193=_191;var _194=dojo.baseUrl;if(_18e){var _195=escape(window.location);document.title=document.title.slice(0,47)+" - Flash Player Installation";var _196=escape(document.title);_192+="?MMredirectURL="+_195+"&MMplayerType=ActiveX"+"&MMdoctitle="+_196+"&baseUrl="+escape(_194);_193+="?MMredirectURL="+_195+"&MMplayerType=PlugIn"+"&baseUrl="+escape(_194);}else{_192+="?cachebust="+new Date().getTime();}if(_193.indexOf("?")==-1){_193+="?baseUrl="+escape(_194);}else{_193+="&baseUrl="+escape(_194);}_190="<object classid=\"clsid:d27cdb6e-ae6d-11cf-96b8-444553540000\" "+"codebase=\""+this.protocol()+"://fpdownload.macromedia.com/pub/shockwave/cabs/flash/"+"swflash.cab#version=8,0,0,0\"\n "+"width=\""+this.width+"\"\n "+"height=\""+this.height+"\"\n "+"id=\""+this.id+"\"\n "+"name=\""+this.id+"\"\n "+"align=\"middle\">\n "+"<param name=\"allowScriptAccess\" value=\"sameDomain\"></param>\n "+"<param name=\"movie\" value=\""+_192+"\"></param>\n "+"<param name=\"quality\" value=\"high\"></param>\n "+"<param name=\"bgcolor\" value=\"#ffffff\"></param>\n "+"<embed src=\""+_193+"\" "+"quality=\"high\" "+"bgcolor=\"#ffffff\" "+"width=\""+this.width+"\" "+"height=\""+this.height+"\" "+"id=\""+this.id+"Embed"+"\" "+"name=\""+this.id+"\" "+"swLiveConnect=\"true\" "+"align=\"middle\" "+"allowScriptAccess=\"sameDomain\" "+"type=\"application/x-shockwave-flash\" "+"pluginspage=\""+this.protocol()+"://www.macromedia.com/go/getflashplayer\" "+"></embed>\n"+"</object>\n";dojo.connect(dojo,"loaded",dojo.hitch(this,function(){var div=document.createElement("div");div.setAttribute("id",this.id+"Container");div.setAttribute("style",_18f);div.innerHTML=_190;var body=document.getElementsByTagName("body");if(!body||!body.length){throw new Error("No body tag for this page");}body=body[0];body.appendChild(div);}));},get:function(){if(dojo.isIE||dojo.isSafari){return document.getElementById(this.id);}else{return document[this.id+"Embed"];}},setVisible:function(_199){var _19a=dojo.byId(this.id+"Container");if(_199==true){_19a.style.position="absolute";_19a.style.visibility="visible";}else{_19a.style.position="absolute";_19a.style.x="-1000px";_19a.style.y="-1000px";_19a.style.visibility="hidden";}},center:function(){var _19b=this.width;var _19c=this.height;var _19d=dijit.getViewport();var x=_19d.l+(_19d.w-_19b)/2;var y=_19d.t+(_19d.h-_19c)/2;var _1a0=dojo.byId(this.id+"Container");_1a0.style.top=y+"px";_1a0.style.left=x+"px";}};dojox.flash.Communicator=function(){};dojox.flash.Communicator.prototype={_addExternalInterfaceCallback:function(_1a1){var _1a2=dojo.hitch(this,function(){var _1a3=new Array(arguments.length);for(var i=0;i<arguments.length;i++){_1a3[i]=this._encodeData(arguments[i]);}var _1a5=this._execFlash(_1a1,_1a3);_1a5=this._decodeData(_1a5);return _1a5;});this[_1a1]=_1a2;},_encodeData:function(data){if(!data||typeof data!="string"){return data;}var _1a7=/\&([^;]*)\;/g;data=data.replace(_1a7,"&$1;");data=data.replace(/</g,"<");data=data.replace(/>/g,">");data=data.replace("\\","&custom_backslash;");data=data.replace(/\0/g,"\\0");data=data.replace(/\"/g,""");return data;},_decodeData:function(data){if(data&&data.length&&typeof data!="string"){data=data[0];}if(!data||typeof data!="string"){return data;}data=data.replace(/\&custom_lt\;/g,"<");data=data.replace(/\&custom_gt\;/g,">");data=data.replace(/\&custom_backslash\;/g,"\\");data=data.replace(/\\0/g," diff --git a/includes/js/dojox/off/offline.js.uncompressed.js b/includes/js/dojox/off/offline.js.uncompressed.js new file mode 100644 index 0000000..aa2866d --- /dev/null +++ b/includes/js/dojox/off/offline.js.uncompressed.js @@ -0,0 +1,5910 @@ +/* + Copyright (c) 2004-2008, 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){ + // summary: Determines whether the storage has the given key. + return !!this.get(key, namespace); // Boolean + }, + + 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 || keys === undefined || !dojo.isArray(keys)){ + return false; + } + + // JAC: This could be optimized by running the key validity test + // directly over a joined string + return !dojo.some(keys, function(key){ + return !this.isValidKey(key); + }); // Boolean + }, + + 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 || 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. + // Note that Dojo Offline uses Gears as its native + // storage provider, and does not support using other + // kinds of storage providers while offline anymore. + + 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; + + // providers: Array + // Array of all the static provider instances, useful if you want to + // loop through and see what providers have been registered. + this.providers = []; + + this._initialized = false; + + 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. + + // keep list of providers as a list so that we can know what order + // storage providers are preferred; also, store the providers hashed + // by name in case someone wants to get a provider that uses + // a particular storage backend + this.providers.push(instance); + this.providers[name] = instance; + }; + + 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 + return; + } + + // a flag to force the storage manager to use a particular + // storage provider type, such as + // djConfig = {forceStorageProvider: "dojox.storage.WhatWGStorageProvider"}; + var forceProvider = dojo.config["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 && 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(!forceProvider && 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. + // Confirm that this is still needed with the newly refactored Dojo + // Flash. Used to be for Internet Explorer. -- Brad Neuberg + 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, "_"); + // database names in Gears are limited to 64 characters long + if(this.dbName.length > 63){ + this.dbName = this.dbName.substring(0, 63); + } + } + + 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(dojo.config["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()); + })(); +} + +} + +if(!dojo._hasResource["dojox.storage.WhatWGStorageProvider"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojox.storage.WhatWGStorageProvider"] = true; +dojo.provide("dojox.storage.WhatWGStorageProvider"); + + + +dojo.declare("dojox.storage.WhatWGStorageProvider", [ dojox.storage.Provider ], { + // summary: + // Storage provider that uses WHAT Working Group features in Firefox 2 + // to achieve permanent storage. + // description: + // The WHAT WG storage API is documented at + // http://www.whatwg.org/specs/web-apps/current-work/#scs-client-side + // + // You can disable this storage provider with the following djConfig + // variable: + // var djConfig = { disableWhatWGStorage: true }; + // + // Authors of this storage provider- + // JB Boisseau, jb.boisseau@eutech-ssii.com + // Brad Neuberg, bkn3@columbia.edu + + initialized: false, + + _domain: null, + _available: null, + _statusHandler: null, + _allNamespaces: null, + _storageEventListener: null, + + initialize: function(){ + if(dojo.config["disableWhatWGStorage"] == true){ + return; + } + + // get current domain + // see: https://bugzilla.mozilla.org/show_bug.cgi?id=357323 + this._domain = (location.hostname == "localhost") ? "localhost.localdomain" : location.hostname; + // console.debug(this._domain); + + // indicate that this storage provider is now loaded + this.initialized = true; + dojox.storage.manager.loaded(); + }, + + isAvailable: function(){ + try{ + // see: https://bugzilla.mozilla.org/show_bug.cgi?id=357323 + var myStorage = globalStorage[((location.hostname == "localhost") ? "localhost.localdomain" : location.hostname)]; + }catch(e){ + this._available = false; + return this._available; + } + + this._available = true; + return this._available; + }, + + put: function(key, value, resultsHandler, namespace){ + if(this.isValidKey(key) == false){ + throw new Error("Invalid key given: " + key); + } + namespace = namespace||this.DEFAULT_NAMESPACE; + + // get our full key name, which is namespace + key + key = this.getFullKey(key, namespace); + + this._statusHandler = resultsHandler; + + // serialize the value; + // handle strings differently so they have better performance + if(dojo.isString(value)){ + value = "string:" + value; + }else{ + value = dojo.toJson(value); + } + + // register for successful storage events. + var storageListener = dojo.hitch(this, function(evt){ + // remove any old storage event listener we might have added + // to the window on old put() requests; Firefox has a bug + // where it can occassionaly go into infinite loops calling + // our storage event listener over and over -- this is a + // workaround + // FIXME: Simplify this into a test case and submit it + // to Firefox + window.removeEventListener("storage", storageListener, false); + + // indicate we succeeded + if(resultsHandler){ + resultsHandler.call(null, this.SUCCESS, key); + } + }); + + window.addEventListener("storage", storageListener, false); + + // try to store the value + try{ + var myStorage = globalStorage[this._domain]; + myStorage.setItem(key, value); + }catch(e){ + // indicate we failed + this._statusHandler.call(null, this.FAILED, key, e.toString()); + } + }, + + get: function(key, namespace){ + if(this.isValidKey(key) == false){ + throw new Error("Invalid key given: " + key); + } + namespace = namespace||this.DEFAULT_NAMESPACE; + + // get our full key name, which is namespace + key + key = this.getFullKey(key, namespace); + + // sometimes, even if a key doesn't exist, Firefox + // will return a blank string instead of a null -- + // this _might_ be due to having underscores in the + // keyname, but I am not sure. + + // FIXME: Simplify this bug into a testcase and + // submit it to Firefox + var myStorage = globalStorage[this._domain]; + var results = myStorage.getItem(key); + + if(results == null || results == ""){ + return null; + } + + results = results.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 = [ this.DEFAULT_NAMESPACE ]; + + // simply enumerate through our array and save any string + // that starts with __ + var found = {}; + var myStorage = globalStorage[this._domain]; + var tester = /^__([^_]*)_/; + for(var i = 0; i < myStorage.length; i++){ + var currentKey = myStorage.key(i); + if(tester.test(currentKey) == true){ + var currentNS = currentKey.match(tester)[1]; + // have we seen this namespace before? + if(typeof found[currentNS] == "undefined"){ + found[currentNS] = true; + results.push(currentNS); + } + } + } + + return results; + }, + + getKeys: function(namespace){ + namespace = namespace||this.DEFAULT_NAMESPACE; + + if(this.isValidKey(namespace) == false){ + throw new Error("Invalid namespace given: " + namespace); + } + + // create a regular expression to test the beginning + // of our key names to see if they match our namespace; + // if it is the default namespace then test for the presence + // of no namespace for compatibility with older versions + // of dojox.storage + var namespaceTester; + if(namespace == this.DEFAULT_NAMESPACE){ + namespaceTester = new RegExp("^([^_]{2}.*)$"); + }else{ + namespaceTester = new RegExp("^__" + namespace + "_(.*)$"); + } + + var myStorage = globalStorage[this._domain]; + var keysArray = []; + for(var i = 0; i < myStorage.length; i++){ + var currentKey = myStorage.key(i); + if(namespaceTester.test(currentKey) == true){ + // strip off the namespace portion + currentKey = currentKey.match(namespaceTester)[1]; + keysArray.push(currentKey); + } + } + + return keysArray; + }, + + clear: function(namespace){ + namespace = namespace||this.DEFAULT_NAMESPACE; + + if(this.isValidKey(namespace) == false){ + throw new Error("Invalid namespace given: " + namespace); + } + + // create a regular expression to test the beginning + // of our key names to see if they match our namespace; + // if it is the default namespace then test for the presence + // of no namespace for compatibility with older versions + // of dojox.storage + var namespaceTester; + if(namespace == this.DEFAULT_NAMESPACE){ + namespaceTester = new RegExp("^[^_]{2}"); + }else{ + namespaceTester = new RegExp("^__" + namespace + "_"); + } + + var myStorage = globalStorage[this._domain]; + var keys = []; + for(var i = 0; i < myStorage.length; i++){ + if(namespaceTester.test(myStorage.key(i)) == true){ + keys[keys.length] = myStorage.key(i); + } + } + + dojo.forEach(keys, dojo.hitch(myStorage, "removeItem")); + }, + + remove: function(key, namespace){ + // get our full key name, which is namespace + key + key = this.getFullKey(key, namespace); + + var myStorage = globalStorage[this._domain]; + myStorage.removeItem(key); + }, + + 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"); + }, + + getFullKey: function(key, namespace){ + namespace = namespace||this.DEFAULT_NAMESPACE; + + if(this.isValidKey(namespace) == false){ + throw new Error("Invalid namespace given: " + namespace); + } + + // don't append a namespace string for the default namespace, + // for compatibility with older versions of dojox.storage + if(namespace == this.DEFAULT_NAMESPACE){ + return key; + }else{ + return "__" + namespace + "_" + key; + } + } +}); + +dojox.storage.manager.register("dojox.storage.WhatWGStorageProvider", + new dojox.storage.WhatWGStorageProvider()); + +} + +if(!dojo._hasResource["dijit._base.place"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit._base.place"] = true; +dojo.provide("dijit._base.place"); + +// ported from dojo.html.util + +dijit.getViewport = function(){ + // summary + // Returns the dimensions and scroll position of the viewable area of a browser window + + var _window = dojo.global; + var _document = dojo.doc; + + // get viewport size + var w = 0, h = 0; + var de = _document.documentElement; + var dew = de.clientWidth, deh = de.clientHeight; + if(dojo.isMozilla){ + // mozilla + // _window.innerHeight includes the height taken by the scroll bar + // clientHeight is ideal but has DTD issues: + // #4539: FF reverses the roles of body.clientHeight/Width and documentElement.clientHeight/Width based on the DTD! + // check DTD to see whether body or documentElement returns the viewport dimensions using this algorithm: + var minw, minh, maxw, maxh; + var dbw = _document.body.clientWidth; + if(dbw > dew){ + minw = dew; + maxw = dbw; + }else{ + maxw = dew; + minw = dbw; + } + var dbh = _document.body.clientHeight; + if(dbh > deh){ + minh = deh; + maxh = dbh; + }else{ + maxh = deh; + minh = dbh; + } + w = (maxw > _window.innerWidth) ? minw : maxw; + h = (maxh > _window.innerHeight) ? minh : maxh; + }else if(!dojo.isOpera && _window.innerWidth){ + //in opera9, dojo.body().clientWidth should be used, instead + //of window.innerWidth/document.documentElement.clientWidth + //so we have to check whether it is opera + w = _window.innerWidth; + h = _window.innerHeight; + }else if(dojo.isIE && de && deh){ + w = dew; + h = deh; + }else if(dojo.body().clientWidth){ + // IE5, Opera + w = dojo.body().clientWidth; + h = dojo.body().clientHeight; + } + + // get scroll position + var scroll = dojo._docScroll(); + + return { w: w, h: h, l: scroll.x, t: scroll.y }; // object +}; + +dijit.placeOnScreen = function( + /* DomNode */ node, + /* Object */ pos, + /* Object */ corners, + /* boolean? */ tryOnly){ + // summary: + // Keeps 'node' in the visible area of the screen while trying to + // place closest to pos.x, pos.y. The input coordinates are + // expected to be the desired document position. + // + // Set which corner(s) you want to bind to, such as + // + // placeOnScreen(node, {x: 10, y: 20}, ["TR", "BL"]) + // + // The desired x/y will be treated as the topleft(TL)/topright(TR) or + // BottomLeft(BL)/BottomRight(BR) corner of the node. Each corner is tested + // and if a perfect match is found, it will be used. Otherwise, it goes through + // all of the specified corners, and choose the most appropriate one. + // + // NOTE: node is assumed to be absolutely or relatively positioned. + + var choices = dojo.map(corners, function(corner){ return { corner: corner, pos: pos }; }); + + return dijit._place(node, choices); +} + +dijit._place = function(/*DomNode*/ node, /* Array */ choices, /* Function */ layoutNode){ + // summary: + // Given a list of spots to put node, put it at the first spot where it fits, + // of if it doesn't fit anywhere then the place with the least overflow + // choices: Array + // Array of elements like: {corner: 'TL', pos: {x: 10, y: 20} } + // Above example says to put the top-left corner of the node at (10,20) + // layoutNode: Function(node, aroundNodeCorner, nodeCorner) + // for things like tooltip, they are displayed differently (and have different dimensions) + // based on their orientation relative to the parent. This adjusts the popup based on orientation. + + // get {x: 10, y: 10, w: 100, h:100} type obj representing position of + // viewport over document + var view = dijit.getViewport(); + + // This won't work if the node is inside a <div style="position: relative">, + // so reattach it to dojo.doc.body. (Otherwise, the positioning will be wrong + // and also it might get cutoff) + if(!node.parentNode || String(node.parentNode.tagName).toLowerCase() != "body"){ + dojo.body().appendChild(node); + } + + var best = null; + dojo.some(choices, function(choice){ + var corner = choice.corner; + var pos = choice.pos; + + // configure node to be displayed in given position relative to button + // (need to do this in order to get an accurate size for the node, because + // a tooltips size changes based on position, due to triangle) + if(layoutNode){ + layoutNode(node, choice.aroundCorner, corner); + } + + // get node's size + var style = node.style; + var oldDisplay = style.display; + var oldVis = style.visibility; + style.visibility = "hidden"; + style.display = ""; + var mb = dojo.marginBox(node); + style.display = oldDisplay; + style.visibility = oldVis; + + // coordinates and size of node with specified corner placed at pos, + // and clipped by viewport + var startX = (corner.charAt(1) == 'L' ? pos.x : Math.max(view.l, pos.x - mb.w)), + startY = (corner.charAt(0) == 'T' ? pos.y : Math.max(view.t, pos.y - mb.h)), + endX = (corner.charAt(1) == 'L' ? Math.min(view.l + view.w, startX + mb.w) : pos.x), + endY = (corner.charAt(0) == 'T' ? Math.min(view.t + view.h, startY + mb.h) : pos.y), + width = endX - startX, + height = endY - startY, + overflow = (mb.w - width) + (mb.h - height); + + if(best == null || overflow < best.overflow){ + best = { + corner: corner, + aroundCorner: choice.aroundCorner, + x: startX, + y: startY, + w: width, + h: height, + overflow: overflow + }; + } + return !overflow; + }); + + node.style.left = best.x + "px"; + node.style.top = best.y + "px"; + if(best.overflow && layoutNode){ + layoutNode(node, best.aroundCorner, best.corner); + } + return best; +} + +dijit.placeOnScreenAroundElement = function( + /* DomNode */ node, + /* DomNode */ aroundNode, + /* Object */ aroundCorners, + /* Function */ layoutNode){ + + // summary + // Like placeOnScreen, except it accepts aroundNode instead of x,y + // and attempts to place node around it. Uses margin box dimensions. + // + // aroundCorners + // specify Which corner of aroundNode should be + // used to place the node => which corner(s) of node to use (see the + // corners parameter in dijit.placeOnScreen) + // e.g. {'TL': 'BL', 'BL': 'TL'} + // + // layoutNode: Function(node, aroundNodeCorner, nodeCorner) + // for things like tooltip, they are displayed differently (and have different dimensions) + // based on their orientation relative to the parent. This adjusts the popup based on orientation. + + + // get coordinates of aroundNode + aroundNode = dojo.byId(aroundNode); + var oldDisplay = aroundNode.style.display; + aroundNode.style.display=""; + // #3172: use the slightly tighter border box instead of marginBox + var aroundNodeW = aroundNode.offsetWidth; //mb.w; + var aroundNodeH = aroundNode.offsetHeight; //mb.h; + var aroundNodePos = dojo.coords(aroundNode, true); + aroundNode.style.display=oldDisplay; + + // Generate list of possible positions for node + var choices = []; + for(var nodeCorner in aroundCorners){ + choices.push( { + aroundCorner: nodeCorner, + corner: aroundCorners[nodeCorner], + pos: { + x: aroundNodePos.x + (nodeCorner.charAt(1) == 'L' ? 0 : aroundNodeW), + y: aroundNodePos.y + (nodeCorner.charAt(0) == 'T' ? 0 : aroundNodeH) + } + }); + } + + return dijit._place(node, choices, layoutNode); +} + +} + +if(!dojo._hasResource["dojox.flash._base"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojox.flash._base"] = true; +dojo.provide("dojox.flash._base"); + +// for dijit.getViewport(), needed by dojox.flash.Embed.center() + + +dojox.flash = function(){ + // summary: + // The goal of dojox.flash is to make it easy to extend Flash's capabilities + // into an Ajax/DHTML environment. + // + // dojox.flash provides an easy object for interacting with the Flash plugin. + // This object provides methods to determine the current version of the Flash + // plugin (dojox.flash.info); write out the necessary markup to + // dynamically insert a Flash object into the page (dojox.flash.Embed; and + // do dynamic installation and upgrading of the current Flash plugin in + // use (dojox.flash.Install). If you want to call methods on the Flash object + // embedded into the page it is your responsibility to use Flash's ExternalInterface + // API and get a reference to the Flash object yourself. + // + // To use dojox.flash, you must first wait until Flash is finished loading + // and initializing before you attempt communication or interaction. + // To know when Flash is finished use dojo.connect: + // + // dojo.connect(dojox.flash, "loaded", myInstance, "myCallback"); + // + // Then, while the page is still loading provide the file name: + // + // dojox.flash.setSwf(dojo.moduleUrl("dojox", "_storage/storage.swf")); + // + // If no SWF files are specified, then Flash is not initialized. + // + // Your Flash must use Flash's ExternalInterface to expose Flash methods and + // to call JavaScript. + // + // setSwf can take an optional 'visible' attribute to control whether + // the Flash object is visible or not on the page; the default is visible: + // + // dojox.flash.setSwf(dojo.moduleUrl("dojox", "_storage/storage.swf"), + // false); + // + // Once finished, you can query Flash version information: + // + // dojox.flash.info.version + // + // Or can communicate with Flash methods that were exposed: + // + // var f = dojox.flash.get(); + // var results = f.sayHello("Some Message"); + // + // Your Flash files should use DojoExternalInterface.as to register methods; + // this file wraps Flash's normal ExternalInterface but correct various + // serialization bugs that ExternalInterface has. + // + // Note that dojox.flash is not meant to be a generic Flash embedding + // mechanism; it is as generic as necessary to make Dojo Storage's + // Flash Storage Provider as clean and modular as possible. If you want + // a generic Flash embed mechanism see SWFObject + // (http://blog.deconcept.com/swfobject/). + // + // Notes: + // Note that dojox.flash can currently only work with one Flash object + // on the page; it does not yet support multiple Flash objects on + // the same page. + // + // Your code can detect whether the Flash player is installing or having + // its version revved in two ways. First, if dojox.flash detects that + // Flash installation needs to occur, it sets dojox.flash.info.installing + // to true. Second, you can detect if installation is necessary with the + // following callback: + // + // dojo.connect(dojox.flash, "installing", myInstance, "myCallback"); + // + // You can use this callback to delay further actions that might need Flash; + // when installation is finished the full page will be refreshed and the + // user will be placed back on your page with Flash installed. + // + // ------------------- + // Todo/Known Issues + // ------------------- + // * On Internet Explorer, after doing a basic install, the page is + // not refreshed or does not detect that Flash is now available. The way + // to fix this is to create a custom small Flash file that is pointed to + // during installation; when it is finished loading, it does a callback + // that says that Flash installation is complete on IE, and we can proceed + // to initialize the dojox.flash subsystem. + // * Things aren't super tested for sending complex objects to Flash + // methods, since Dojo Storage only needs strings + // + // Author- Brad Neuberg, http://codinginparadise.org +} + +dojox.flash = { + ready: false, + url: null, + + _visible: true, + _loadedListeners: new Array(), + _installingListeners: new Array(), + + setSwf: function(/* String */ url, /* boolean? */ visible){ + // summary: Sets the SWF files and versions we are using. + // url: String + // The URL to this Flash file. + // visible: boolean? + // Whether the Flash file is visible or not. If it is not visible we hide it off the + // screen. This defaults to true (i.e. the Flash file is visible). + this.url = url; + + if(typeof visible != "undefined"){ + this._visible = visible; + } + + // initialize ourselves + this._initialize(); + }, + + addLoadedListener: function(/* Function */ listener){ + // summary: + // Adds a listener to know when Flash is finished loading. + // Useful if you don't want a dependency on dojo.event. + // listener: Function + // A function that will be called when Flash is done loading. + + this._loadedListeners.push(listener); + }, + + addInstallingListener: function(/* Function */ listener){ + // summary: + // Adds a listener to know if Flash is being installed. + // Useful if you don't want a dependency on dojo.event. + // listener: Function + // A function that will be called if Flash is being + // installed + + this._installingListeners.push(listener); + }, + + loaded: function(){ + // summary: Called back when the Flash subsystem is finished loading. + // description: + // A callback when the Flash subsystem is finished loading and can be + // worked with. To be notified when Flash is finished loading, add a + // loaded listener: + // + // dojox.flash.addLoadedListener(loadedListener); + + dojox.flash.ready = true; + if(dojox.flash._loadedListeners.length > 0){ + for(var i = 0;i < dojox.flash._loadedListeners.length; i++){ + dojox.flash._loadedListeners[i].call(null); + } + } + }, + + installing: function(){ + // summary: Called if Flash is being installed. + // description: + // A callback to know if Flash is currently being installed or + // having its version revved. To be notified if Flash is installing, connect + // your callback to this method using the following: + // + // dojo.event.connect(dojox.flash, "installing", myInstance, "myCallback"); + + if(dojox.flash._installingListeners.length > 0){ + for(var i = 0; i < dojox.flash._installingListeners.length; i++){ + dojox.flash._installingListeners[i].call(null); + } + } + }, + + // Initializes dojox.flash. + _initialize: function(){ + //console.debug("dojox.flash._initialize"); + // see if we need to rev or install Flash on this platform + var installer = new dojox.flash.Install(); + dojox.flash.installer = installer; + + if(installer.needed() == true){ + installer.install(); + }else{ + // write the flash object into the page + dojox.flash.obj = new dojox.flash.Embed(this._visible); + dojox.flash.obj.write(); + + // setup the communicator + dojox.flash.comm = new dojox.flash.Communicator(); + } + } +}; + + +dojox.flash.Info = function(){ + // summary: A class that helps us determine whether Flash is available. + // description: + // A class that helps us determine whether Flash is available, + // it's major and minor versions, and what Flash version features should + // be used for Flash/JavaScript communication. Parts of this code + // are adapted from the automatic Flash plugin detection code autogenerated + // by the Macromedia Flash 8 authoring environment. + // + // An instance of this class can be accessed on dojox.flash.info after + // the page is finished loading. + // + // This constructor must be called before the page is finished loading. + + // Visual basic helper required to detect Flash Player ActiveX control + // version information on Internet Explorer + if(dojo.isIE){ + document.write([ + '<script language="VBScript" type="text/vbscript"\>', + 'Function VBGetSwfVer(i)', + ' on error resume next', + ' Dim swControl, swVersion', + ' swVersion = 0', + ' set swControl = CreateObject("ShockwaveFlash.ShockwaveFlash." + CStr(i))', + ' if (IsObject(swControl)) then', + ' swVersion = swControl.GetVariable("$version")', + ' end if', + ' VBGetSwfVer = swVersion', + 'End Function', + '</script\>'].join("\r\n")); + } + + this._detectVersion(); +} + +dojox.flash.Info.prototype = { + // version: String + // The full version string, such as "8r22". + version: -1, + + // versionMajor, versionMinor, versionRevision: String + // The major, minor, and revisions of the plugin. For example, if the + // plugin is 8r22, then the major version is 8, the minor version is 0, + // and the revision is 22. + versionMajor: -1, + versionMinor: -1, + versionRevision: -1, + + // capable: Boolean + // Whether this platform has Flash already installed. + capable: false, + + // installing: Boolean + // Set if we are in the middle of a Flash installation session. + installing: false, + + isVersionOrAbove: function( + /* int */ reqMajorVer, + /* int */ reqMinorVer, + /* int */ reqVer){ /* Boolean */ + // summary: + // Asserts that this environment has the given major, minor, and revision + // numbers for the Flash player. + // description: + // Asserts that this environment has the given major, minor, and revision + // numbers for the Flash player. + // + // Example- To test for Flash Player 7r14: + // + // dojox.flash.info.isVersionOrAbove(7, 0, 14) + // returns: + // Returns true if the player is equal + // or above the given version, false otherwise. + + // make the revision a decimal (i.e. transform revision 14 into + // 0.14 + reqVer = parseFloat("." + reqVer); + + if(this.versionMajor >= reqMajorVer && this.versionMinor >= reqMinorVer + && this.versionRevision >= reqVer){ + return true; + }else{ + return false; + } + }, + + _detectVersion: function(){ + var versionStr; + + // loop backwards through the versions until we find the newest version + for(var testVersion = 25; testVersion > 0; testVersion--){ + if(dojo.isIE){ + versionStr = VBGetSwfVer(testVersion); + }else{ + versionStr = this._JSFlashInfo(testVersion); + } + + if(versionStr == -1 ){ + this.capable = false; + return; + }else if(versionStr != 0){ + var versionArray; + if(dojo.isIE){ + var tempArray = versionStr.split(" "); + var tempString = tempArray[1]; + versionArray = tempString.split(","); + }else{ + versionArray = versionStr.split("."); + } + + this.versionMajor = versionArray[0]; + this.versionMinor = versionArray[1]; + this.versionRevision = versionArray[2]; + + // 7.0r24 == 7.24 + var versionString = this.versionMajor + "." + this.versionRevision; + this.version = parseFloat(versionString); + + this.capable = true; + + break; + } + } + }, + + // JavaScript helper required to detect Flash Player PlugIn version + // information. Internet Explorer uses a corresponding Visual Basic + // version to interact with the Flash ActiveX control. + _JSFlashInfo: function(testVersion){ + // NS/Opera version >= 3 check for Flash plugin in plugin array + if(navigator.plugins != null && navigator.plugins.length > 0){ + if(navigator.plugins["Shockwave Flash 2.0"] || + navigator.plugins["Shockwave Flash"]){ + var swVer2 = navigator.plugins["Shockwave Flash 2.0"] ? " 2.0" : ""; + var flashDescription = navigator.plugins["Shockwave Flash" + swVer2].description; + var descArray = flashDescription.split(" "); + var tempArrayMajor = descArray[2].split("."); + var versionMajor = tempArrayMajor[0]; + var versionMinor = tempArrayMajor[1]; + if(descArray[3] != ""){ + var tempArrayMinor = descArray[3].split("r"); + }else{ + var tempArrayMinor = descArray[4].split("r"); + } + var versionRevision = tempArrayMinor[1] > 0 ? tempArrayMinor[1] : 0; + var version = versionMajor + "." + versionMinor + "." + + versionRevision; + + return version; + } + } + + return -1; + } +}; + +dojox.flash.Embed = function(visible){ + // summary: A class that is used to write out the Flash object into the page. + // description: + // Writes out the necessary tags to embed a Flash file into the page. Note that + // these tags are written out as the page is loaded using document.write, so + // you must call this class before the page has finished loading. + + this._visible = visible; +} + +dojox.flash.Embed.prototype = { + // width: int + // The width of this Flash applet. The default is the minimal width + // necessary to show the Flash settings dialog. Current value is + // 215 pixels. + width: 215, + + // height: int + // The height of this Flash applet. The default is the minimal height + // necessary to show the Flash settings dialog. Current value is + // 138 pixels. + height: 138, + + // id: String + // The id of the Flash object. Current value is 'flashObject'. + id: "flashObject", + + // Controls whether this is a visible Flash applet or not. + _visible: true, + + protocol: function(){ + switch(window.location.protocol){ + case "https:": + return "https"; + break; + default: + return "http"; + break; + } + }, + + write: function(/* Boolean? */ doExpressInstall){ + // summary: Writes the Flash into the page. + // description: + // This must be called before the page + // is finished loading. + // doExpressInstall: Boolean + // Whether to write out Express Install + // information. Optional value; defaults to false. + + // determine our container div's styling + var containerStyle = ""; + containerStyle += ("width: " + this.width + "px; "); + containerStyle += ("height: " + this.height + "px; "); + if(!this._visible){ + containerStyle += "position: absolute; z-index: 10000; top: -1000px; left: -1000px; "; + } + + // figure out the SWF file to get and how to write out the correct HTML + // for this Flash version + var objectHTML; + var swfloc = dojox.flash.url; + var swflocObject = swfloc; + var swflocEmbed = swfloc; + var dojoUrl = dojo.baseUrl; + if(doExpressInstall){ + // the location to redirect to after installing + var redirectURL = escape(window.location); + document.title = document.title.slice(0, 47) + " - Flash Player Installation"; + var docTitle = escape(document.title); + swflocObject += "?MMredirectURL=" + redirectURL + + "&MMplayerType=ActiveX" + + "&MMdoctitle=" + docTitle + + "&baseUrl=" + escape(dojoUrl); + swflocEmbed += "?MMredirectURL=" + redirectURL + + "&MMplayerType=PlugIn" + + "&baseUrl=" + escape(dojoUrl); + }else{ + // IE/Flash has an evil bug that shows up some time: if we load the + // Flash and it isn't in the cache, ExternalInterface works fine -- + // however, the second time when its loaded from the cache a timing + // bug can keep ExternalInterface from working. The trick below + // simply invalidates the Flash object in the cache all the time to + // keep it loading fresh. -- Brad Neuberg + swflocObject += "?cachebust=" + new Date().getTime(); + } + + if(swflocEmbed.indexOf("?") == -1){ + swflocEmbed += '?baseUrl='+escape(dojoUrl); + }else{ + swflocEmbed += '&baseUrl='+escape(dojoUrl); + } + + objectHTML = + '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" ' + + 'codebase="' + + this.protocol() + + '://fpdownload.macromedia.com/pub/shockwave/cabs/flash/' + + 'swflash.cab#version=8,0,0,0"\n ' + + 'width="' + this.width + '"\n ' + + 'height="' + this.height + '"\n ' + + 'id="' + this.id + '"\n ' + + 'name="' + this.id + '"\n ' + + 'align="middle">\n ' + + '<param name="allowScriptAccess" value="sameDomain"></param>\n ' + + '<param name="movie" value="' + swflocObject + '"></param>\n ' + + '<param name="quality" value="high"></param>\n ' + + '<param name="bgcolor" value="#ffffff"></param>\n ' + + '<embed src="' + swflocEmbed + '" ' + + 'quality="high" ' + + 'bgcolor="#ffffff" ' + + 'width="' + this.width + '" ' + + 'height="' + this.height + '" ' + + 'id="' + this.id + 'Embed' + '" ' + + 'name="' + this.id + '" ' + + 'swLiveConnect="true" ' + + 'align="middle" ' + + 'allowScriptAccess="sameDomain" ' + + 'type="application/x-shockwave-flash" ' + + 'pluginspage="' + + this.protocol() + +'://www.macromedia.com/go/getflashplayer" ' + + '></embed>\n' + + '</object>\n'; + + // using same mechanism on all browsers now to write out + // Flash object into page + + // document.write no longer works correctly + // due to Eolas patent workaround in IE; + // nothing happens (i.e. object doesn't + // go into page if we use it) + dojo.connect(dojo, "loaded", dojo.hitch(this, function(){ + var div = document.createElement("div"); + div.setAttribute("id", this.id + "Container"); + div.setAttribute("style", containerStyle); + div.innerHTML = objectHTML; + + var body = document.getElementsByTagName("body"); + if(!body || !body.length){ + throw new Error("No body tag for this page"); + } + body = body[0]; + body.appendChild(div); + })); + }, + + get: function(){ /* Object */ + // summary: Gets the Flash object DOM node. + if(dojo.isIE || dojo.isSafari){ + return document.getElementById(this.id); + }else{ + // different IDs on OBJECT and EMBED tags or + // else Firefox will return wrong one and + // communication won't work; + // also, document.getElementById() returns a + // plugin but ExternalInterface calls don't + // work on it so we have to use + // document[id] instead + return document[this.id + "Embed"]; + } + }, + + setVisible: function(/* Boolean */ visible){ + //console.debug("setVisible, visible="+visible); + + // summary: Sets the visibility of this Flash object. + var container = dojo.byId(this.id + "Container"); + if(visible == true){ + container.style.position = "absolute"; // IE -- Brad Neuberg + container.style.visibility = "visible"; + }else{ + container.style.position = "absolute"; + container.style.x = "-1000px"; + container.style.y = "-1000px"; + container.style.visibility = "hidden"; + } + }, + + center: function(){ + // summary: Centers the flash applet on the page. + + var elementWidth = this.width; + var elementHeight = this.height; + + var viewport = dijit.getViewport(); + + // compute the centered position + var x = viewport.l + (viewport.w - elementWidth) / 2; + var y = viewport.t + (viewport.h - elementHeight) / 2; + + // set the centered position + var container = dojo.byId(this.id + "Container"); + container.style.top = y + "px"; + container.style.left = x + "px"; + } +}; + + +dojox.flash.Communicator = function(){ + // summary: + // A class that is used to communicate between Flash and JavaScript. + // description: + // This class helps mediate Flash and JavaScript communication. Internally + // it uses Flash 8's ExternalInterface API, but adds functionality to fix + // various encoding bugs that ExternalInterface has. +} + +dojox.flash.Communicator.prototype = { + // Registers the existence of a Flash method that we can call with + // JavaScript, using Flash 8's ExternalInterface. + _addExternalInterfaceCallback: function(methodName){ + var wrapperCall = dojo.hitch(this, function(){ + // some browsers don't like us changing values in the 'arguments' array, so + // make a fresh copy of it + var methodArgs = new Array(arguments.length); + for(var i = 0; i < arguments.length; i++){ + methodArgs[i] = this._encodeData(arguments[i]); + } + + var results = this._execFlash(methodName, methodArgs); + results = this._decodeData(results); + + return results; + }); + + this[methodName] = wrapperCall; + }, + + // Encodes our data to get around ExternalInterface bugs that are still + // present even in Flash 9. + _encodeData: function(data){ + if(!data || typeof data != "string"){ + return data; + } + + // double encode all entity values, or they will be mis-decoded + // by Flash when returned + var entityRE = /\&([^;]*)\;/g; + data = data.replace(entityRE, "&$1;"); + + // entity encode XML-ish characters, or Flash's broken XML serializer + // breaks + data = data.replace(/</g, "<"); + data = data.replace(/>/g, ">"); + + // transforming \ into \\ doesn't work; just use a custom encoding + data = data.replace("\\", "&custom_backslash;"); + + data = data.replace(/\0/g, "\\0"); // null character + data = data.replace(/\"/g, """); + + return data; + }, + + // Decodes our data to get around ExternalInterface bugs that are still + // present even in Flash 9. + _decodeData: function(data){ + // wierdly enough, Flash sometimes returns the result as an + // 'object' that is actually an array, rather than as a String; + // detect this by looking for a length property; for IE + // we also make sure that we aren't dealing with a typeof string + // since string objects have length property there + if(data && data.length && typeof data != "string"){ + data = data[0]; + } + + if(!data || typeof data != "string"){ + return data; + } + + // certain XMLish characters break Flash's wire serialization for + // ExternalInterface; these are encoded on the + // DojoExternalInterface side into a custom encoding, rather than + // the standard entity encoding, because otherwise we won't be able to + // differentiate between our own encoding and any entity characters + // that are being used in the string itself + data = data.replace(/\&custom_lt\;/g, "<"); + data = data.replace(/\&custom_gt\;/g, ">"); + data = data.replace(/\&custom_backslash\;/g, '\\'); + + // needed for IE; \0 is the NULL character + data = data.replace(/\\0/g, "\0"); + + return data; + }, + + // Executes a Flash method; called from the JavaScript wrapper proxy we + // create on dojox.flash.comm. + _execFlash: function(methodName, methodArgs){ + var plugin = dojox.flash.obj.get(); + methodArgs = (methodArgs) ? methodArgs : []; + + // encode arguments that are strings + for(var i = 0; i < methodArgs; i++){ + if(typeof methodArgs[i] == "string"){ + methodArgs[i] = this._encodeData(methodArgs[i]); + } + } + + // we use this gnarly hack below instead of + // plugin[methodName] for two reasons: + // 1) plugin[methodName] has no call() method, which + // means we can't pass in multiple arguments dynamically + // to a Flash method -- we can only have one + // 2) On IE plugin[methodName] returns undefined -- + // plugin[methodName] used to work on IE when we + // used document.write but doesn't now that + // we use dynamic DOM insertion of the Flash object + // -- Brad Neuberg + var flashExec = function(){ + return eval(plugin.CallFunction( + "<invoke name=\"" + methodName + + "\" returntype=\"javascript\">" + + __flash__argumentsToXML(methodArgs, 0) + + "</invoke>")); + }; + var results = flashExec.call(methodArgs); + + if(typeof results == "string"){ + results = this._decodeData(results); + } + + return results; + } +} + +// FIXME: dojo.declare()-ify this + +// TODO: I did not test the Install code when I refactored Dojo Flash from 0.4 to +// 1.0, so am not sure if it works. If Flash is not present I now prefer +// that Gears is installed instead of Flash because GearsStorageProvider is +// much easier to work with than Flash's hacky ExternalInteface. +// -- Brad Neuberg +dojox.flash.Install = function(){ + // summary: Helps install Flash plugin if needed. + // description: + // Figures out the best way to automatically install the Flash plugin + // for this browser and platform. Also determines if installation or + // revving of the current plugin is needed on this platform. +} + +dojox.flash.Install.prototype = { + needed: function(){ /* Boolean */ + // summary: + // Determines if installation or revving of the current plugin is + // needed. + + // do we even have flash? + if(dojox.flash.info.capable == false){ + return true; + } + + // Must have ExternalInterface which came in Flash 8 + if(!dojox.flash.info.isVersionOrAbove(8, 0, 0)){ + return true; + } + + // otherwise we don't need installation + return false; + }, + + install: function(){ + // summary: Performs installation or revving of the Flash plugin. + + // indicate that we are installing + dojox.flash.info.installing = true; + dojox.flash.installing(); + + if(dojox.flash.info.capable == false){ // we have no Flash at all + // write out a simple Flash object to force the browser to prompt + // the user to install things + var installObj = new dojox.flash.Embed(false); + installObj.write(); // write out HTML for Flash + }else if(dojox.flash.info.isVersionOrAbove(6, 0, 65)){ // Express Install + var installObj = new dojox.flash.Embed(false); + installObj.write(true); // write out HTML for Flash 8 version+ + installObj.setVisible(true); + installObj.center(); + }else{ // older Flash install than version 6r65 + alert("This content requires a more recent version of the Macromedia " + +" Flash Player."); + window.location.href = + dojox.flash.Embed.protocol() + + "://www.macromedia.com/go/getflashplayer"; + } + }, + + // Called when the Express Install is either finished, failed, or was + // rejected by the user. + _onInstallStatus: function(msg){ + if (msg == "Download.Complete"){ + // Installation is complete. + dojox.flash._initialize(); + }else if(msg == "Download.Cancelled"){ + alert("This content requires a more recent version of the Macromedia " + +" Flash Player."); + window.location.href = dojox.flash.Embed.protocol() + + "://www.macromedia.com/go/getflashplayer"; + }else if (msg == "Download.Failed"){ + // The end user failed to download the installer due to a network failure + alert("There was an error downloading the Flash Player update. " + + "Please try again later, or visit macromedia.com to download " + + "the latest version of the Flash plugin."); + } + } +} + +// find out if Flash is installed +dojox.flash.info = new dojox.flash.Info(); + +// vim:ts=4:noet:tw=0: + +} + +if(!dojo._hasResource["dojox.flash"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojox.flash"] = true; +dojo.provide("dojox.flash"); + + +} + +if(!dojo._hasResource["dojox.storage.FlashStorageProvider"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojox.storage.FlashStorageProvider"] = true; +dojo.provide("dojox.storage.FlashStorageProvider"); + + + + + +// summary: +// Storage provider that uses features in Flash to achieve permanent +// storage +// description: +// Authors of this storage provider- +// Brad Neuberg, bkn3@columbia.edu +dojo.declare("dojox.storage.FlashStorageProvider", dojox.storage.Provider, { + initialized: false, + + _available: null, + _statusHandler: null, + _flashReady: false, + _pageReady: false, + + initialize: function(){ + //console.debug("FlashStorageProvider.initialize"); + if(dojo.config["disableFlashStorage"] == true){ + return; + } + + // initialize our Flash + dojox.flash.addLoadedListener(dojo.hitch(this, function(){ + //console.debug("flashReady"); + // indicate our Flash subsystem is now loaded + this._flashReady = true; + if(this._flashReady && this._pageReady){ + this._loaded(); + } + })); + var swfLoc = dojo.moduleUrl("dojox", "storage/Storage.swf").toString(); + dojox.flash.setSwf(swfLoc, false); + + // wait till page is finished loading + dojo.connect(dojo, "loaded", this, function(){ + //console.debug("pageReady"); + this._pageReady = true; + if(this._flashReady && this._pageReady){ + this._loaded(); + } + }); + }, + + // Set a new value for the flush delay timer. + // Possible values: + // 0 : Perform the flush synchronously after each "put" request + // > 0 : Wait until 'newDelay' ms have passed without any "put" request to flush + // -1 : Do not automatically flush + setFlushDelay: function(newDelay){ + if(newDelay === null || typeof newDelay === "undefined" || isNaN(newDelay)){ + throw new Error("Invalid argunment: " + newDelay); + } + + dojox.flash.comm.setFlushDelay(String(newDelay)); + }, + + getFlushDelay: function(){ + return Number(dojox.flash.comm.getFlushDelay()); + }, + + flush: function(namespace){ + //FIXME: is this test necessary? Just use !namespace + if(namespace == null || typeof namespace == "undefined"){ + namespace = dojox.storage.DEFAULT_NAMESPACE; + } + dojox.flash.comm.flush(namespace); + }, + + isAvailable: function(){ + return (this._available = !dojo.config["disableFlashStorage"]); + }, + + put: function(key, value, resultsHandler, namespace){ + if(!this.isValidKey(key)){ + throw new Error("Invalid key given: " + key); + } + + if(!namespace){ + namespace = dojox.storage.DEFAULT_NAMESPACE; + } + + if(!this.isValidKey(namespace)){ + throw new Error("Invalid namespace given: " + namespace); + } + + this._statusHandler = resultsHandler; + + // serialize the value; + // handle strings differently so they have better performance + if(dojo.isString(value)){ + value = "string:" + value; + }else{ + value = dojo.toJson(value); + } + + dojox.flash.comm.put(key, value, namespace); + }, + + putMultiple: function(keys, values, resultsHandler, namespace){ + if(!this.isValidKeyArray(keys) || ! values instanceof Array + || keys.length != values.length){ + throw new Error("Invalid arguments: keys = [" + keys + "], values = [" + values + "]"); + } + + if(!namespace){ + namespace = dojox.storage.DEFAULT_NAMESPACE; + } + + if(!this.isValidKey(namespace)){ + throw new Error("Invalid namespace given: " + namespace); + } + + this._statusHandler = resultsHandler; + + // Convert the arguments on strings we can pass along to Flash + var metaKey = keys.join(","); + var lengths = []; + for(var i=0;i<values.length;i++){ + if(dojo.isString(values[i])){ + values[i] = "string:" + values[i]; + }else{ + values[i] = dojo.toJson(values[i]); + } + lengths[i] = values[i].length; + } + var metaValue = values.join(""); + var metaLengths = lengths.join(","); + + dojox.flash.comm.putMultiple(metaKey, metaValue, metaLengths, this.namespace); + }, + + get: function(key, namespace){ + if(!this.isValidKey(key)){ + throw new Error("Invalid key given: " + key); + } + + if(!namespace){ + namespace = dojox.storage.DEFAULT_NAMESPACE; + } + + if(!this.isValidKey(namespace)){ + throw new Error("Invalid namespace given: " + namespace); + } + + var results = dojox.flash.comm.get(key, namespace); + + if(results == ""){ + return null; + } + + return this._destringify(results); + }, + + getMultiple: function(/*array*/ keys, /*string?*/ namespace){ /*Object*/ + if(!this.isValidKeyArray(keys)){ + throw new ("Invalid key array given: " + keys); + } + + if(!namespace){ + namespace = dojox.storage.DEFAULT_NAMESPACE; + } + + if(!this.isValidKey(namespace)){ + throw new Error("Invalid namespace given: " + namespace); + } + + var metaKey = keys.join(","); + var metaResults = dojox.flash.comm.getMultiple(metaKey, this.namespace); + var results = eval("(" + metaResults + ")"); + + // destringify each entry back into a real JS object + //FIXME: use dojo.map + for(var i = 0; i < results.length; i++){ + results[i] = (results[i] == "") ? null : this._destringify(results[i]); + } + + return results; + }, + + _destringify: function(results){ + // 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; + }, + + getKeys: function(namespace){ + if(!namespace){ + namespace = dojox.storage.DEFAULT_NAMESPACE; + } + + if(!this.isValidKey(namespace)){ + throw new Error("Invalid namespace given: " + namespace); + } + + var results = dojox.flash.comm.getKeys(namespace); + + // Flash incorrectly returns an empty string as "null" + if(results == null || results == "null"){ + results = ""; + } + + results = results.split(","); + results.sort(); + + return results; + }, + + getNamespaces: function(){ + var results = dojox.flash.comm.getNamespaces(); + + // Flash incorrectly returns an empty string as "null" + if(results == null || results == "null"){ + results = dojox.storage.DEFAULT_NAMESPACE; + } + + results = results.split(","); + results.sort(); + + return results; + }, + + clear: function(namespace){ + if(!namespace){ + namespace = dojox.storage.DEFAULT_NAMESPACE; + } + + if(!this.isValidKey(namespace)){ + throw new Error("Invalid namespace given: " + namespace); + } + + dojox.flash.comm.clear(namespace); + }, + + remove: function(key, namespace){ + if(!namespace){ + namespace = dojox.storage.DEFAULT_NAMESPACE; + } + + if(!this.isValidKey(namespace)){ + throw new Error("Invalid namespace given: " + namespace); + } + + dojox.flash.comm.remove(key, namespace); + }, + + removeMultiple: function(/*array*/ keys, /*string?*/ namespace){ /*Object*/ + if(!this.isValidKeyArray(keys)){ + dojo.raise("Invalid key array given: " + keys); + } + if(!namespace){ + namespace = dojox.storage.DEFAULT_NAMESPACE; + } + + if(!this.isValidKey(namespace)){ + throw new Error("Invalid namespace given: " + namespace); + } + + var metaKey = keys.join(","); + dojox.flash.comm.removeMultiple(metaKey, this.namespace); + }, + + isPermanent: function(){ + return true; + }, + + getMaximumSize: function(){ + return dojox.storage.SIZE_NO_LIMIT; + }, + + hasSettingsUI: function(){ + return true; + }, + + showSettingsUI: function(){ + dojox.flash.comm.showSettings(); + dojox.flash.obj.setVisible(true); + dojox.flash.obj.center(); + }, + + hideSettingsUI: function(){ + // hide the dialog + dojox.flash.obj.setVisible(false); + + // call anyone who wants to know the dialog is + // now hidden + if(dojo.isFunction(dojox.storage.onHideSettingsUI)){ + dojox.storage.onHideSettingsUI.call(null); + } + }, + + getResourceList: function(){ /* Array[] */ + // Dojo Offline no longer uses the FlashStorageProvider for offline + // storage; Gears is now required + return []; + }, + + /** Called when Flash and the page are finished loading. */ + _loaded: function(){ + // get available namespaces + this._allNamespaces = this.getNamespaces(); + + this.initialized = true; + + // indicate that this storage provider is now loaded + dojox.storage.manager.loaded(); + }, + + // Called if the storage system needs to tell us about the status + // of a put() request. + _onStatus: function(statusResult, key, namespace){ + //console.debug("onStatus, statusResult="+statusResult+", key="+key); + var ds = dojox.storage; + var dfo = dojox.flash.obj; + + if(statusResult == ds.PENDING){ + dfo.center(); + dfo.setVisible(true); + }else{ + dfo.setVisible(false); + } + + if(ds._statusHandler){ + ds._statusHandler.call(null, statusResult, key, namespace); + } + } + } +); + +dojox.storage.manager.register("dojox.storage.FlashStorageProvider", + new dojox.storage.FlashStorageProvider()); + +} + +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"); + + + +/* + Note: if you are doing Dojo Offline builds you _must_ + have offlineProfile=true when you run the build script: + ./build.sh action=release profile=offline offlineProfile=true +*/ + + + + +// 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(dojo.config.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="+dojo.config.isDebug); + if(dojo.config.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, "_"); + + // clip at 64 characters, the max length of a resource store name + if(storeName.length >= 64){ + storeName = storeName.substring(0, 63); + } + + // 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", dojo.config.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(dojo.config.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 = dojo.config.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){ + if(dojo.isAIR){ + window.setTimeout(function(){dojox.off.sync.synchronize();}, 1000); + }else{ + window.setTimeout(dojox._scopeName + ".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"); + + + + + + + +} + diff --git a/includes/js/dojox/off/resources/checkmark.png b/includes/js/dojox/off/resources/checkmark.png Binary files differnew file mode 100644 index 0000000..a0ffbb1 --- /dev/null +++ b/includes/js/dojox/off/resources/checkmark.png diff --git a/includes/js/dojox/off/resources/greenball.png b/includes/js/dojox/off/resources/greenball.png Binary files differnew file mode 100644 index 0000000..520b6a6 --- /dev/null +++ b/includes/js/dojox/off/resources/greenball.png diff --git a/includes/js/dojox/off/resources/learnhow.html b/includes/js/dojox/off/resources/learnhow.html new file mode 100644 index 0000000..2833fcc --- /dev/null +++ b/includes/js/dojox/off/resources/learnhow.html @@ -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>
\ No newline at end of file diff --git a/includes/js/dojox/off/resources/learnhow.js b/includes/js/dojox/off/resources/learnhow.js new file mode 100644 index 0000000..82d5506 --- /dev/null +++ b/includes/js/dojox/off/resources/learnhow.js @@ -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)); + } +} diff --git a/includes/js/dojox/off/resources/offline-widget.css b/includes/js/dojox/off/resources/offline-widget.css new file mode 100644 index 0000000..3d095e9 --- /dev/null +++ b/includes/js/dojox/off/resources/offline-widget.css @@ -0,0 +1,88 @@ + +#dot-widget-container{ + + width: 13em; + height: auto; + border: 2px solid #CDDDE9; + position: relative; + visibility: visible !important; +} +#dot-widget-title-bar{ + background-color: #CDDDE9; + 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; +} +#dot-learn-how-body{ + padding: 3em; + background-color: #CDDDE9; +} +#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; +} diff --git a/includes/js/dojox/off/resources/offline-widget.css.commented.css b/includes/js/dojox/off/resources/offline-widget.css.commented.css new file mode 100644 index 0000000..374a43b --- /dev/null +++ b/includes/js/dojox/off/resources/offline-widget.css.commented.css @@ -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; +} diff --git a/includes/js/dojox/off/resources/offline-widget.html b/includes/js/dojox/off/resources/offline-widget.html new file mode 100644 index 0000000..5791644 --- /dev/null +++ b/includes/js/dojox/off/resources/offline-widget.html @@ -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> Offline! + </div> + </div> +</div>
\ No newline at end of file diff --git a/includes/js/dojox/off/resources/redball.png b/includes/js/dojox/off/resources/redball.png Binary files differnew file mode 100644 index 0000000..cc224c3 --- /dev/null +++ b/includes/js/dojox/off/resources/redball.png diff --git a/includes/js/dojox/off/resources/roller.gif b/includes/js/dojox/off/resources/roller.gif Binary files differnew file mode 100644 index 0000000..24a3a24 --- /dev/null +++ b/includes/js/dojox/off/resources/roller.gif diff --git a/includes/js/dojox/off/sync.js b/includes/js/dojox/off/sync.js new file mode 100644 index 0000000..9927fbe --- /dev/null +++ b/includes/js/dojox/off/sync.js @@ -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(); + +} diff --git a/includes/js/dojox/off/ui.js b/includes/js/dojox/off/ui.js new file mode 100644 index 0000000..40f33c4 --- /dev/null +++ b/includes/js/dojox/off/ui.js @@ -0,0 +1,622 @@ +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 = dojo.config.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){ + if(dojo.isAIR){ + window.setTimeout(function(){dojox.off.sync.synchronize();}, 1000); + }else{ + window.setTimeout(dojox._scopeName + ".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); + +} |