New file |
0,0 → 1,4276 |
/* |
Copyright (c) 2004-2007, 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){ /*Boolean*/ |
// summary: Determines whether the storage has the given key. |
return (this.get(key) != null); |
}, |
|
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 || typeof keys === "undefined" || ! keys instanceof Array){ |
return false; |
} |
|
// JAC: This could be optimized by running the key validity test directly over a joined string |
for(var k=0;k<keys.length;k++){ |
if(!this.isValidKey(keys[k])){ |
return false; |
} |
} |
return true; |
}, |
|
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)||(typeof 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. |
|
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; |
|
this._initialized = false; |
|
this._providers = []; |
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. |
this._providers[this._providers.length] = instance; //FIXME: push? |
this._providers[name] = instance; // FIXME: this._providers is an array, not a hash |
}; |
|
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 |
//console.debug("dojox.storage.manager already initialized; returning"); |
return; |
} |
|
// a flag to force the storage manager to use a particular |
// storage provider type, such as |
// djConfig = {forceStorageProvider: "dojox.storage.WhatWGStorageProvider"}; |
var forceProvider = djConfig["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 == 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(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 |
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, "_"); |
//console.debug("Using Google Gears database " + this.dbName); |
} |
|
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(djConfig["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()); |
|
dojox.storage.manager.initialize(); |
})(); |
} |
|
} |
|
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"); |
|
|
|
|
|
// FIXME: Find way to set isGears from offline.profile.js file; it didn't |
// work for me |
//dojo.requireIf(!dojo.isGears, "dojox.storage.FlashStorageProvider"); |
//dojo.requireIf(!dojo.isGears, "dojox.storage.WhatWGStorageProvider"); |
|
// 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(djConfig.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="+djConfig.isDebug); |
if(djConfig.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, "_"); |
|
// 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", djConfig.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(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"); |
|
} |
|
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 = djConfig.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){ |
window.setTimeout("dojox.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"); |
|
|
|
|
|
|
|
} |
|