]> git.pond.sub.org Git - eow/blobdiff - static/dojo-release-1.1.1/dojox/off/offline.js.uncompressed.js
add Dojo 1.1.1
[eow] / static / dojo-release-1.1.1 / dojox / off / offline.js.uncompressed.js
diff --git a/static/dojo-release-1.1.1/dojox/off/offline.js.uncompressed.js b/static/dojo-release-1.1.1/dojox/off/offline.js.uncompressed.js
new file mode 100644 (file)
index 0000000..aa2866d
--- /dev/null
@@ -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");
+
+
+
+
+
+
+
+}
+