Blame | Last modification | View Log | RSS feed
/*Copyright (c) 2004-2007, The Dojo FoundationAll Rights Reserved.Licensed under the Academic Free License version 2.1 or above OR themodified 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 fordevelopment. To get an editable version, please visit:http://dojotoolkit.orgfor 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_NAMESPACEconsole.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 presentconsole.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 keysconsole.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// returnedconsole.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_NAMESPACEconsole.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 presentconsole.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 individuallyconsole.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 stringfor(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 AdapterRegistrydojox.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 platformthis.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 usedvar providerToUse;//FIXME: use dojo.somefor(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 stateproviderToUse.isAvailable();break;}else if(providerToUse.isAvailable()){break;}}if(!providerToUse){ // no provider availablethis._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 examplethis.currentProvider = providerToUse;dojo.mixin(dojox.storage, this.currentProvider);// have the provider initialize itselfdojox.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 gothis._onLoadListeners.push(func);if(this.isInitialized()){this._fireLoaded();}};this.removeOnLoad = function(func){ /* void */// summary: Removes the given onLoad listenerfor(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 bugif(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 dynamicallytry{// dynamically call the given providers class level isAvailable()// methodvar 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 providerreturn 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 Basethis._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.edudojo.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 resultvar callback = self._employed["_" + sender];// make this worker unemployedself._employed["_" + sender] = undefined;self._unemployed.push("_" + sender);// see if we need to assign new work// that was queued up needing to be doneif(self._handleMessage.length){var handleMe = self._handleMessage.shift();self._assignWork(handleMe.msg, handleMe.callback);}// return resultscallback(msg);}var workerInit = "function _workerInit(){"+ "gearsWorkerPool.onmessage = "+ String(this._workerHandler)+ ";"+ "}";var code = workerInit + " _workerInit();";// create our worker poolfor(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 workervar workerID = this._unemployed.shift().substring(1); // remove _// list this worker as employedthis._employed["_" + workerID] = callback;// do the workethis._manager.sendMessage(msg, workerID);}else{// we have to queue it upthis._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 keysvar 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 copyfor (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 + a3s[1][c] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3]; // a0 * 2*a1 + 3*a2 + a3s[2][c] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3]; // a0 + a1 + 2*a2 + 3*a3s[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 keysvar Nr = Nk + 6; // no of rounds: 10/12/14 for 128/192/256-bit keysvar 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 wfor (var i=0; i<4; i++) w[i] = Sbox[w[i]];return w;}function RotWord(w) { // rotate 4-byte word w left by one bytew[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-1var nBytes = nBits/8; // no bytes in keyvar 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 bytesvar blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AESvar counterBlock = new Array(blockSize); // block size fixed at 16 bytes / 128 bits (Nb=4) for AESvar nonce = (new Date()).getTime(); // milliseconds since 1-Jan-1970// encode nonce in two stages to cater for JavaScript 32-bit limit on bitwise opsfor (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 roundvar keySchedule = KeyExpansion(key);var blockCount = Math.ceil(plaintext.length/blockSize);var ciphertext = new Array(blockCount); // ciphertext as array of stringsfor (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 opsfor (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 blockciphertext[b] = escCtrlChars(ct); // escape troublesome characters in ciphertext}// convert the nonce to a string to go on the front of the ciphertextvar 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 efficiencyreturn 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 keysvar nBytes = nBits/8; // no bytes in keyvar 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 longvar keySchedule = KeyExpansion(key);ciphertext = ciphertext.split('-'); // split ciphertext into array of block-length strings// recover nonce from 1st element of ciphertextvar blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AESvar 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 blockciphertext[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 blockplaintext[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 ciphertextreturn 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 markerfunction unescCtrlChars(str) { // unescape potentially problematic control charactersreturn 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 windowdebug: (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 connectionif(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 objectthis._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 executionif(!this._dbOpen){this.open();this._autoClose = true;}// determine our parametersvar 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// passwordif(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 thatif(this.debug){this._printDebugSQL(sql, args);}// handle SQL that needs encryption/decryption differently// do we have an ENCRYPT SQL statement? if so, handle that firstif(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 statementvar crypto = new dojox.sql._SQLCrypto("decrypt", sql,password, args,callback);return; // decrypted results will arrive asynchronously}// execute the SQL and get the resultsvar 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 datars = 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 SQLvar strippedSQL = this._stripCryptoSQL(sql);// determine what arguments need encryptionvar encryptColumns = this._flagEncryptedArgs(sql, args);// asynchronously encrypt each argument that needs itvar self = this;this._encrypt(strippedSQL, password, args, encryptColumns, function(finalArgs){// execute the SQLvar 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 withresultSet = 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 decryptionvar needsDecrypt = self._determineDecryptedColumns(sql);// now decrypt columns asynchronously// decrypt columns that need itself._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 SQLvar 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 SQLvar 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 withresultSet = dojox.sql._normalizeResults(resultSet);if(dojox.sql._autoClose){dojox.sql.close();}// decrypt columns that need itthis._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 asynchronouslythis._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 threaddojox._sql._crypto.encrypt(sqlParam, password, dojo.hitch(this, function(results){// set the new encrypted valuethis._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 decryptionfor(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 insteadthis._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// middlevar 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 middlematches = 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 alonevar 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 itif(/^[\"\']/.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 flagresults[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 valuethis._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// enableddojo.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.educonstructor: function(){},// instance methods and propertiesTABLE_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 collidethis.TABLE_NAME = "__DOJO_STORAGE";// create the table that holds our datatry{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 initializedojox.storage.manager.loaded();return;}// indicate that this storage provider is now loadedthis.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 performanceif(dojo.isString(value)){value = "string:" + value;}else{value = dojo.toJson(value);}// try to store the valuetry{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 failedconsole.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 databasevar 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 performanceif(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 valuetry{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 performancevar 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 failedconsole.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 insteadif(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 performanceif(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 providersdojox.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 itselfdojox.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// loadingthis._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 offlineconsole.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 removefor(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 checkfor(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 fastdojox.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 importsdojo.forEach(document.styleSheets, function(sheet){try{if(sheet.cssRules){ // Firefoxdojo.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){ // IEvar 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 itvar 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 anchorsif(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 righturl = new dojo._Url(url);// totally relative -- ../../someFile.htmlif(!url.scheme && !url.port && !url.host){return true;}// scheme relative with port specified -- brad.com:8080if(!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.comif(!url.scheme && url.host && !url.port&& window.location.hostname == url.host&& window.location.port == 80){return true;}// else we have everythingreturn 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 servervar 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 storeslocalServer.removeStore(storeName);// open/create the resource storelocalServer.openStore(storeName);var store = localServer.createStore(storeName);this._store = store;// add our list of files to capturevar 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 pageif(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 applicationsdojo.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 onlineactions: 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 sessionif(!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 whyif(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 downloadingdojo.connect(this.actions, "onReplayFinished", this, this.download);// replay the actions logthis.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 whyreasonHalted: null,// isReplaying: boolean// If true, we are in the middle of replaying a command log; if false,// then we are notisReplaying: 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// storageif(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 logreturn 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 savingif(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 ranthis.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 savingif(this.autoSave){var self = this;this._save(function(){self.isReplaying = false;self.onReplayFinished();});return;}else{this.isReplaying = false;this.onReplayFinished();return;}}// get the next actionvar nextAction = this.entries[0];this.onReplay(nextAction, this);},clear: function(){ /* void */// summary:// Completely clears this action log of its entriesif(this.isReplaying){return;}this.entries = [];// save our updated state into persistent// storageif(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 haveif(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 notisOnline: 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 otherwisegoingOnline: 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 sitethis._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 threaddojox.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 developerdojox.off.files.cache(dojo.moduleUrl("dojo", "dojo.js"));// pull in the files needed by Dojothis._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// offlinedojox.off.files.cache(dojox.storage.manager.getResourceList());// slurp the page if the end-developer wants thatdojox.off.files._slurp();// see if we have an offline cache; when done, move// on to the rest of our startup tasksthis._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 yetif(this.hasOfflineCache && this.enabled){// load framework data; when we are finished, continue// initializing ourselvesthis._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 operationthis._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 upthis._finishStartingUp();},_finishStartingUp: function(){//console.debug("dojox.off._finishStartingUp");// this method is part of our _onLoad series of startup tasksif(!this.hasOfflineCache){this.onLoad();}else if(this.enabled){// kick off a thread to check network status on// a regular basisthis._startNetworkThread();// try to go onlinethis.goOnline(dojo.hitch(this, function(){//console.debug("Finished trying to go online");// indicate we are ready to be useddojox.off.onLoad();}));}else{ // we are disabled or a core operation failedif(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 fastif(!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 networkif(!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// heretry{if(typeof d.ioArgs.xhr.abort == "function"){d.ioArgs.xhr.abort();}}catch(e){}// if things fell in the middle of syncing,// stop syncingdojox.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 serverif(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.jsvar 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 environmentsdojox.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 informationfor(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 storagedojox.off.sync._load(callback);}});// wait until the storage system is finished loadingdojox.storage.manager.addOnLoad(dojo.hitch(dojox.off, "_onStorageLoad"));// wait until the page is finished loadingdojo.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 correctif(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 defaultthis.runLinkText = "Run " + this.appName;// setup our event listeners for Dojo Offline events// to update our UIdojo.connect(dojox.off, "onNetwork", this, "_onNetwork");dojo.connect(dojox.off.sync, "onSync", this, "_onSync");// cache our default UI resourcesdojox.off.files.cache([this.htmlTemplatePath,this.cssTemplatePath,this.onlineImagePath,this.offlineImagePath,this.rollerImagePath,this.checkmarkImagePath]);// embed the offline widget UIif(this.autoEmbed){this._doAutoEmbed();}},_doAutoEmbed: function(){// fetch our HTML for the offline widget// dispatch the requestdojo.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 HTMLvar container = dojo.byId(this.autoEmbedID);if(container){ container.innerHTML = data; }// fill out our image pathsthis._initImages();// update our network indicator status ballthis._updateNetIndicator();// update our 'Learn How' textthis._initLearnHow();this._initialized = true;// check offline cache settingsif(!dojox.off.hasOfflineCache){this._showNeedsOfflineCache();return;}// check to see if we need a browser restart// to be able to use this web app offlineif(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 UIthis._updateSyncUI();// register our event listeners for our main buttonsthis._initMainEvtHandlers();// if offline functionality is disabled, disable everythingthis._setOfflineEnabled(dojox.off.enabled);// update our UI based on the state of the networkthis._onNetwork(dojox.off.isOnline ? "online" : "offline");// try to go onlinethis._testNet();},_testNet: function(){dojox.off.goOnline(dojo.hitch(this, function(isOnline){//console.debug("testNet callback, isOnline="+isOnline);// display our online/offline resultsthis._onNetwork(isOnline ? "online" : "offline");// indicate that our default UI// and Dojo Offline are now ready to// be usedthis.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 settingsvar 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 missdojox.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 behaviorevt.preventDefault();evt.stopPropagation();if(!dojox.off.sync.details.length){return;}// determine our HTML message to displayvar 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 messagevar 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 blockeralert("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 windowif(popup.focus){popup.focus();}},_cancel: function(evt){// cancel the button's default behaviorevt.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' infothis._hideNeedsOfflineCache();// check to see if we need a browser restart// to be able to use this web app offlineif(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 UIthis._updateSyncUI();// register our event listeners for our main buttonsthis._initMainEvtHandlers();// if offline is disabled, disable everythingthis._setOfflineEnabled(dojox.off.enabled);// try to go onlinethis._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 UIthis._updateNetIndicator();if(type == "offline"){this._setSyncMessage("You are working offline");// clear old detailsvar details = dojo.byId("dot-sync-details");if(details){ details.style.display = "none"; }// if we fell offline during a sync, hide// the sync infothis._updateSyncUI();}else{ // online// synchronize, but pause for a few seconds// so that the user can orient themselvesif(dojox.off.sync.autoSync){window.setTimeout("dojox.off.sync.synchronize()", 1000);}}}});// register ourselves for low-level framework eventsdojo.connect(dojox.off, "onFrameworkEvent", dojox.off.ui, "_onFrameworkEvent");// start our magic when the Dojo Offline framework is ready to godojo.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");}