summaryrefslogtreecommitdiffstatshomepage
path: root/includes/js/dojox/off
diff options
context:
space:
mode:
Diffstat (limited to 'includes/js/dojox/off')
-rw-r--r--includes/js/dojox/off/README28
-rw-r--r--includes/js/dojox/off/_common.js559
-rw-r--r--includes/js/dojox/off/docs/bookmarklets.html10
-rw-r--r--includes/js/dojox/off/files.js454
-rw-r--r--includes/js/dojox/off/network_check.txt1
-rw-r--r--includes/js/dojox/off/offline.js20
-rw-r--r--includes/js/dojox/off/offline.js.uncompressed.js5910
-rw-r--r--includes/js/dojox/off/resources/checkmark.pngbin0 -> 27329 bytes
-rw-r--r--includes/js/dojox/off/resources/greenball.pngbin0 -> 27414 bytes
-rw-r--r--includes/js/dojox/off/resources/learnhow.html43
-rw-r--r--includes/js/dojox/off/resources/learnhow.js43
-rw-r--r--includes/js/dojox/off/resources/offline-widget.css88
-rw-r--r--includes/js/dojox/off/resources/offline-widget.css.commented.css112
-rw-r--r--includes/js/dojox/off/resources/offline-widget.html40
-rw-r--r--includes/js/dojox/off/resources/redball.pngbin0 -> 27409 bytes
-rw-r--r--includes/js/dojox/off/resources/roller.gifbin0 -> 7462 bytes
-rw-r--r--includes/js/dojox/off/sync.js690
-rw-r--r--includes/js/dojox/off/ui.js622
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,"&amp;$1;");data=data.replace(/</g,"&lt;");data=data.replace(/>/g,"&gt;");data=data.replace("\\","&custom_backslash;");data=data.replace(/\0/g,"\\0");data=data.replace(/\"/g,"&quot;");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, "&amp;$1;");
+
+ // entity encode XML-ish characters, or Flash's broken XML serializer
+ // breaks
+ data = data.replace(/</g, "&lt;");
+ data = data.replace(/>/g, "&gt;");
+
+ // 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, "&quot;");
+
+ 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
new file mode 100644
index 0000000..a0ffbb1
--- /dev/null
+++ b/includes/js/dojox/off/resources/checkmark.png
Binary files differ
diff --git a/includes/js/dojox/off/resources/greenball.png b/includes/js/dojox/off/resources/greenball.png
new file mode 100644
index 0000000..520b6a6
--- /dev/null
+++ b/includes/js/dojox/off/resources/greenball.png
Binary files differ
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>&nbsp;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
new file mode 100644
index 0000000..cc224c3
--- /dev/null
+++ b/includes/js/dojox/off/resources/redball.png
Binary files differ
diff --git a/includes/js/dojox/off/resources/roller.gif b/includes/js/dojox/off/resources/roller.gif
new file mode 100644
index 0000000..24a3a24
--- /dev/null
+++ b/includes/js/dojox/off/resources/roller.gif
Binary files differ
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);
+
+}