Subversion Repositories Applications.papyrus

Rev

Blame | Last modification | View Log | RSS feed

if(!dojo._hasResource["dojox.off._common"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.off._common"] = true;
dojo.provide("dojox.off._common");

dojo.require("dojox.storage");
dojo.require("dojox.sql");
dojo.require("dojox.off.sync");

// Author: Brad Neuberg, bkn3@columbia.edu, http://codinginparadise.org

// summary:
//              dojox.off is the main object for offline applications.
dojo.mixin(dojox.off, {
        // isOnline: boolean
        //      true if we are online, false if not
        isOnline: false,
        
        // NET_CHECK: int
        //              For advanced usage; most developers can ignore this.
        //              Time in seconds on how often we should check the status of the
        //              network with an automatic background timer. The current default
        //              is 5 seconds.
        NET_CHECK: 5,
        
        // STORAGE_NAMESPACE: String
        //              For advanced usage; most developers can ignore this.
        //              The namespace we use to save core data into Dojo Storage.
        STORAGE_NAMESPACE: "_dot",
        
        // enabled: boolean
        //              For advanced usage; most developers can ignore this.
        //              Whether offline ability is enabled or not. Defaults to true.
        enabled: true,
        
        // availabilityURL: String
        //              For advanced usage; most developers can ignore this.
        //              The URL to check for site availability.  We do a GET request on
        //              this URL to check for site availability.  By default we check for a
        //              simple text file in src/off/network_check.txt that has one value
        //              it, the value '1'.
        availabilityURL: dojo.moduleUrl("dojox", "off/network_check.txt"),
        
        // goingOnline: boolean
        //              For advanced usage; most developers can ignore this.
        //              True if we are attempting to go online, false otherwise
        goingOnline: false,
        
        // coreOpFailed: boolean
        //              For advanced usage; most developers can ignore this.
        //              A flag set by the Dojo Offline framework that indicates that the
        //              user denied some operation that required the offline cache or an
        //              operation failed in some critical way that was unrecoverable. For
        //              example, if the offline cache is Google Gears and we try to get a
        //              Gears database, a popup window appears asking the user whether they
        //              will approve or deny this request. If the user denies the request,
        //              and we are doing some operation that is core to Dojo Offline, then
        //              we set this flag to 'true'.  This flag causes a 'fail fast'
        //              condition, turning off offline ability.
        coreOpFailed: false,
        
        // doNetChecking: boolean
        //              For advanced usage; most developers can ignore this.
        //              Whether to have a timing interval in the background doing automatic
        //              network checks at regular intervals; the length of time between
        //              checks is controlled by dojox.off.NET_CHECK. Defaults to true.
        doNetChecking: true,
        
        // hasOfflineCache: boolean
        //              For advanced usage; most developers can ignore this.
        //      Determines if an offline cache is available or installed; an
        //      offline cache is a facility that can truely cache offline
        //      resources, such as JavaScript, HTML, etc. in such a way that they
        //      won't be removed from the cache inappropriately like a browser
        //      cache would. If this is false then an offline cache will be
        //      installed. Only Google Gears is currently supported as an offline
        //      cache. Future possible offline caches include Firefox 3.
        hasOfflineCache: null,
        
        // browserRestart: boolean
        //              For advanced usage; most developers can ignore this.
        //              If true, the browser must be restarted to register the existence of
        //              a new host added offline (from a call to addHostOffline); if false,
        //              then nothing is needed.
        browserRestart: false,
        
        _STORAGE_APP_NAME: window.location.href.replace(/[^0-9A-Za-z_]/g, "_"),
        
        _initializeCalled: false,
        _storageLoaded: false,
        _pageLoaded: false,
        
        onLoad: function(){
                // summary:
                //      Called when Dojo Offline can be used.
                // description:
                //      Do a dojo.connect to this to know when you can
                //      start using Dojo Offline:
                //              dojo.connect(dojox.off, "onLoad", myFunc);
        },
        
        onNetwork: function(type){
                // summary:
                //      Called when our on- or offline- status changes.
                // description:
                //      If we move online, then this method is called with the
                //      value "online". If we move offline, then this method is
                //      called with the value "offline". You can connect to this
                //      method to do add your own behavior:
                //
                //              dojo.connect(dojox.off, "onNetwork", someFunc)
                //
                //      Note that if you are using the default Dojo Offline UI
                //      widget that most of the on- and off-line notification
                //      and syncing is automatically handled and provided to the
                //      user.
                // type: String
                //      Either "online" or "offline".
        },
        
        initialize: function(){ /* void */
                // summary:
                //              Called when a Dojo Offline-enabled application is finished
                //              configuring Dojo Offline, and is ready for Dojo Offline to
                //              initialize itself.
                // description:
                //              When an application has finished filling out the variables Dojo
                //              Offline needs to work, such as dojox.off.ui.appName, it must
                //              this method to tell Dojo Offline to initialize itself.
                
                //              Note:
                //              This method is needed for a rare edge case. In some conditions,
                //              especially if we are dealing with a compressed Dojo build, the
                //              entire Dojo Offline subsystem might initialize itself and be
                //              running even before the JavaScript for an application has had a
                //              chance to run and configure Dojo Offline, causing Dojo Offline
                //              to have incorrect initialization parameters for a given app,
                //              such as no value for dojox.off.ui.appName. This method is
                //              provided to prevent this scenario, to slightly 'slow down' Dojo
                //              Offline so it can be configured before running off and doing
                //              its thing.      

                //console.debug("dojox.off.initialize");
                this._initializeCalled = true;
                
                if(this._storageLoaded && this._pageLoaded){
                        this._onLoad();
                }
        },
        
        goOffline: function(){ /* void */
                // summary:
                //              For advanced usage; most developers can ignore this.
                //              Manually goes offline, away from the network.
                if((dojox.off.sync.isSyncing)||(this.goingOnline)){ return; }
                
                this.goingOnline = false;
                this.isOnline = false;
        },
        
        goOnline: function(callback){ /* void */
                // summary: 
                //              For advanced usage; most developers can ignore this.
                //              Attempts to go online.
                // description:
                //              Attempts to go online, making sure this web application's web
                //              site is available. 'callback' is called asychronously with the
                //              result of whether we were able to go online or not.
                // callback: Function
                //              An optional callback function that will receive one argument:
                //              whether the site is available or not and is boolean. If this
                //              function is not present we call dojo.xoff.onOnline instead if
                //              we are able to go online.
                
                //console.debug("goOnline");
                
                if(dojox.off.sync.isSyncing || dojox.off.goingOnline){
                        return;
                }
                
                this.goingOnline = true;
                this.isOnline = false;
                
                // see if can reach our web application's web site
                this._isSiteAvailable(callback);
        },
        
        onFrameworkEvent: function(type /* String */, saveData /* Object? */){
                //      summary:
                //              For advanced usage; most developers can ignore this.
                //              A standard event handler that can be attached to to find out
                //              about low-level framework events. Most developers will not need to
                //              attach to this method; it is meant for low-level information
                //              that can be useful for updating offline user-interfaces in
                //              exceptional circumstances. The default Dojo Offline UI
                //              widget takes care of most of these situations.
                //      type: String
                //              The type of the event:
                //
                //              * "offlineCacheInstalled"
                //                      An event that is fired when a user
                //                      has installed an offline cache after the page has been loaded.
                //                      If a user didn't have an offline cache when the page loaded, a
                //                      UI of some kind might have prompted them to download one. This
                //                      method is called if they have downloaded and installed an
                //                      offline cache so a UI can reinitialize itself to begin using
                //                      this offline cache.
                //              * "coreOperationFailed"
                //                      Fired when a core operation during interaction with the
                //                      offline cache is denied by the user. Some offline caches, such
                //                      as Google Gears, prompts the user to approve or deny caching
                //                      files, using the database, and more. If the user denies a
                //                      request that is core to Dojo Offline's operation, we set
                //                      dojox.off.coreOpFailed to true and call this method for
                //                      listeners that would like to respond some how to Dojo Offline
                //                      'failing fast'.
                //              * "save"
                //                      Called whenever the framework saves data into persistent
                //                      storage. This could be useful for providing save feedback
                //                      or providing appropriate error feedback if saving fails 
                //                      due to a user not allowing the save to occur
                //      saveData: Object?
                //              If the type was 'save', then a saveData object is provided with
                //              further save information. This object has the following properties:     
                //
                //              * status - dojox.storage.SUCCESS, dojox.storage.PENDING, dojox.storage.FAILED
                //              Whether the save succeeded, whether it is pending based on a UI
                //              dialog asking the user for permission, or whether it failed.    
                //
                //              * isCoreSave - boolean
                //              If true, then this save was for a core piece of data necessary
                //              for the functioning of Dojo Offline. If false, then it is a
                //              piece of normal data being saved for offline access. Dojo
                //              Offline will 'fail fast' if some core piece of data could not
                //              be saved, automatically setting dojox.off.coreOpFailed to
                //              'true' and dojox.off.enabled to 'false'.
                //
                //              * key - String
                //              The key that we are attempting to persist
                //
                //              * value - Object
                //              The object we are trying to persist
                //
                //              * namespace - String
                //              The Dojo Storage namespace we are saving this key/value pair
                //              into, such as "default", "Documents", "Contacts", etc.
                //              Optional.
                if(type == "save"){
                        if(saveData.isCoreSave && (saveData.status == dojox.storage.FAILED)){
                                dojox.off.coreOpFailed = true;
                                dojox.off.enabled = false;
                        
                                // FIXME: Stop the background network thread
                                dojox.off.onFrameworkEvent("coreOperationFailed");
                        }
                }else if(type == "coreOperationFailed"){
                        dojox.off.coreOpFailed = true;
                        dojox.off.enabled = false;
                        // FIXME: Stop the background network thread
                }
        },
        
        _checkOfflineCacheAvailable: function(callback){
                // is a true, offline cache running on this machine?
                this.hasOfflineCache = dojo.isGears;
                
                callback();
        },
        
        _onLoad: function(){
                //console.debug("dojox.off._onLoad");
                
                // both local storage and the page are finished loading
                
                // cache the Dojo JavaScript -- just use the default dojo.js
                // name for the most common scenario
                // FIXME: TEST: Make sure syncing doesn't break if dojo.js
                // can't be found, or report an error to developer
                dojox.off.files.cache(dojo.moduleUrl("dojo", "dojo.js"));
                
                // pull in the files needed by Dojo
                this._cacheDojoResources();
                
                // FIXME: need to pull in the firebug lite files here!
                // workaround or else we will get an error on page load
                // from Dojo that it can't find 'console.debug' for optimized builds
                // dojox.off.files.cache(djConfig.baseRelativePath + "src/debug.js");
                
                // make sure that resources needed by all of our underlying
                // Dojo Storage storage providers will be available
                // offline
                dojox.off.files.cache(dojox.storage.manager.getResourceList());
                
                // slurp the page if the end-developer wants that
                dojox.off.files._slurp();
                
                // see if we have an offline cache; when done, move
                // on to the rest of our startup tasks
                this._checkOfflineCacheAvailable(dojo.hitch(this, "_onOfflineCacheChecked"));
        },
        
        _onOfflineCacheChecked: function(){
                // this method is part of our _onLoad series of startup tasks
                
                // if we have an offline cache, see if we have been added to the 
                // list of available offline web apps yet
                if(this.hasOfflineCache && this.enabled){
                        // load framework data; when we are finished, continue
                        // initializing ourselves
                        this._load(dojo.hitch(this, "_finishStartingUp"));
                }else if(this.hasOfflineCache && !this.enabled){
                        // we have an offline cache, but it is disabled for some reason
                        // perhaps due to the user denying a core operation
                        this._finishStartingUp();
                }else{
                        this._keepCheckingUntilInstalled();
                }
        },
        
        _keepCheckingUntilInstalled: function(){
                // this method is part of our _onLoad series of startup tasks
                
                // kick off a background interval that keeps
                // checking to see if an offline cache has been
                // installed since this page loaded
                        
                // FIXME: Gears: See if we are installed somehow after the
                // page has been loaded
                
                // now continue starting up
                this._finishStartingUp();
        },
        
        _finishStartingUp: function(){
                //console.debug("dojox.off._finishStartingUp");
                
                // this method is part of our _onLoad series of startup tasks
                
                if(!this.hasOfflineCache){
                        this.onLoad();
                }else if(this.enabled){
                        // kick off a thread to check network status on
                        // a regular basis
                        this._startNetworkThread();

                        // try to go online
                        this.goOnline(dojo.hitch(this, function(){
                                //console.debug("Finished trying to go online");
                                // indicate we are ready to be used
                                dojox.off.onLoad();
                        }));
                }else{ // we are disabled or a core operation failed
                        if(this.coreOpFailed){
                                this.onFrameworkEvent("coreOperationFailed");
                        }else{
                                this.onLoad();
                        }
                }
        },
        
        _onPageLoad: function(){
                //console.debug("dojox.off._onPageLoad");
                this._pageLoaded = true;
                
                if(this._storageLoaded && this._initializeCalled){
                        this._onLoad();
                }
        },
        
        _onStorageLoad: function(){
                //console.debug("dojox.off._onStorageLoad");
                this._storageLoaded = true;
                
                // were we able to initialize storage? if
                // not, then this is a core operation, and
                // let's indicate we will need to fail fast
                if(!dojox.storage.manager.isAvailable()
                        && dojox.storage.manager.isInitialized()){
                        this.coreOpFailed = true;
                        this.enabled = false;
                }
                
                if(this._pageLoaded && this._initializeCalled){
                        this._onLoad();         
                }
        },
        
        _isSiteAvailable: function(callback){
                // summary:
                //              Determines if our web application's website is available.
                // description:
                //              This method will asychronously determine if our web
                //              application's web site is available, which is a good proxy for
                //              network availability. The URL dojox.off.availabilityURL is
                //              used, which defaults to this site's domain name (ex:
                //              foobar.com). We check for dojox.off.AVAILABILITY_TIMEOUT (in
                //              seconds) and abort after that
                // callback: Function
                //              An optional callback function that will receive one argument:
                //              whether the site is available or not and is boolean. If this
                //              function is not present we call dojox.off.onNetwork instead if we
                //              are able to go online.
                dojo.xhrGet({
                        url:            this._getAvailabilityURL(),
                        handleAs:       "text",
                        timeout:        this.NET_CHECK * 1000, 
                        error:          dojo.hitch(this, function(err){
                                //console.debug("dojox.off._isSiteAvailable.error: " + err);
                                this.goingOnline = false;
                                this.isOnline = false;
                                if(callback){ callback(false); }
                        }),
                        load:           dojo.hitch(this, function(data){
                                //console.debug("dojox.off._isSiteAvailable.load, data="+data);
                                this.goingOnline = false;
                                this.isOnline = true;
                                
                                if(callback){ callback(true);
                                }else{ this.onNetwork("online"); }
                        })
                });
        },
        
        _startNetworkThread: function(){
                //console.debug("startNetworkThread");
                
                // kick off a thread that does periodic
                // checks on the status of the network
                if(!this.doNetChecking){
                        return;
                }
                
                window.setInterval(dojo.hitch(this, function(){ 
                        var d = dojo.xhrGet({
                                url:            this._getAvailabilityURL(),
                                handleAs:       "text",
                                timeout:        this.NET_CHECK * 1000,
                                error:          dojo.hitch(this, 
                                                                function(err){
                                                                        if(this.isOnline){
                                                                                this.isOnline = false;
                                                                                
                                                                                // FIXME: xhrGet() is not
                                                                                // correctly calling abort
                                                                                // on the XHR object when
                                                                                // it times out; fix inside
                                                                                // there instead of externally
                                                                                // here
                                                                                try{
                                                                                        if(typeof d.ioArgs.xhr.abort == "function"){
                                                                                                d.ioArgs.xhr.abort();
                                                                                        }
                                                                                }catch(e){}
                                        
                                                                                // if things fell in the middle of syncing, 
                                                                                // stop syncing
                                                                                dojox.off.sync.isSyncing = false;
                                        
                                                                                this.onNetwork("offline");
                                                                        }
                                                                }
                                                        ),
                                load:           dojo.hitch(this, 
                                                                function(data){
                                                                        if(!this.isOnline){
                                                                                this.isOnline = true;
                                                                                this.onNetwork("online");
                                                                        }
                                                                }
                                                        )
                        });

                }), this.NET_CHECK * 1000);
        },
        
        _getAvailabilityURL: function(){
                var url = this.availabilityURL.toString();
                
                // bust the browser's cache to make sure we are really talking to
                // the server
                if(url.indexOf("?") == -1){
                        url += "?";
                }else{
                        url += "&";
                }
                url += "browserbust=" + new Date().getTime();
                
                return url;
        },
        
        _onOfflineCacheInstalled: function(){
                this.onFrameworkEvent("offlineCacheInstalled");
        },
        
        _cacheDojoResources: function(){
                // if we are a non-optimized build, then the core Dojo bootstrap
                // system was loaded as separate JavaScript files;
                // add these to our offline cache list. these are
                // loaded before the dojo.require() system exists
                
                // FIXME: create a better mechanism in the Dojo core to
                // expose whether you are dealing with an optimized build;
                // right now we just scan the SCRIPT tags attached to this
                // page and see if there is one for _base/_loader/bootstrap.js
                var isOptimizedBuild = true;
                dojo.forEach(dojo.query("script"), function(i){
                        var src = i.getAttribute("src");
                        if(!src){ return; }
                        
                        if(src.indexOf("_base/_loader/bootstrap.js") != -1){
                                isOptimizedBuild = false;
                        }
                });
                
                if(!isOptimizedBuild){
                        dojox.off.files.cache(dojo.moduleUrl("dojo", "_base.js").uri);
                        dojox.off.files.cache(dojo.moduleUrl("dojo", "_base/_loader/loader.js").uri);
                        dojox.off.files.cache(dojo.moduleUrl("dojo", "_base/_loader/bootstrap.js").uri);
                        
                        // FIXME: pull in the host environment file in a more generic way
                        // for other host environments
                        dojox.off.files.cache(dojo.moduleUrl("dojo", "_base/_loader/hostenv_browser.js").uri);
                }
                
                // add anything that was brought in with a 
                // dojo.require() that resulted in a JavaScript
                // URL being fetched
                
                // FIXME: modify dojo/_base/_loader/loader.js to
                // expose a public API to get this information
        
                for(var i = 0; i < dojo._loadedUrls.length; i++){
                        dojox.off.files.cache(dojo._loadedUrls[i]);
                }
                
                // FIXME: add the standard Dojo CSS file
        },
        
        _save: function(){
                // summary:
                //              Causes the Dojo Offline framework to save its configuration
                //              data into local storage.        
        },
        
        _load: function(callback){
                // summary:
                //              Causes the Dojo Offline framework to load its configuration
                //              data from local storage
                dojox.off.sync._load(callback);
        }
});


// wait until the storage system is finished loading
dojox.storage.manager.addOnLoad(dojo.hitch(dojox.off, "_onStorageLoad"));

// wait until the page is finished loading
dojo.addOnLoad(dojox.off, "_onPageLoad");

}