dojo.provide("dojox.off._common"); dojo.require("dojo.gears"); 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.gears.available; 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");