Subversion Repositories Applications.papyrus

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2150 mathias 1
/*
2
	Copyright (c) 2004-2007, The Dojo Foundation
3
	All Rights Reserved.
4
 
5
	Licensed under the Academic Free License version 2.1 or above OR the
6
	modified BSD license. For more information on Dojo licensing, see:
7
 
8
		http://dojotoolkit.org/book/dojo-book-0-9/introduction/licensing
9
*/
10
 
11
/*
12
	This is a compiled version of Dojo, built for deployment and not for
13
	development. To get an editable version, please visit:
14
 
15
		http://dojotoolkit.org
16
 
17
	for documentation and information on getting the source.
18
*/
19
 
20
if(!dojo._hasResource["dojox.storage.Provider"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
21
dojo._hasResource["dojox.storage.Provider"] = true;
22
dojo.provide("dojox.storage.Provider");
23
 
24
dojo.declare("dojox.storage.Provider", null, {
25
	// summary: A singleton for working with dojox.storage.
26
	// description:
27
	//		dojox.storage exposes the current available storage provider on this
28
	//		platform. It gives you methods such as dojox.storage.put(),
29
	//		dojox.storage.get(), etc.
30
	//
31
	//		For more details on dojox.storage, see the primary documentation
32
	//		page at
33
	//			http://manual.dojotoolkit.org/storage.html
34
	//
35
	//		Note for storage provider developers who are creating subclasses-
36
	//		This is the base class for all storage providers Specific kinds of
37
	//		Storage Providers should subclass this and implement these methods.
38
	//		You should avoid initialization in storage provider subclass's
39
	//		constructor; instead, perform initialization in your initialize()
40
	//		method.
41
	constructor: function(){
42
	},
43
 
44
	// SUCCESS: String
45
	//	Flag that indicates a put() call to a
46
	//	storage provider was succesful.
47
	SUCCESS: "success",
48
 
49
	// FAILED: String
50
	//	Flag that indicates a put() call to
51
	//	a storage provider failed.
52
	FAILED: "failed",
53
 
54
	// PENDING: String
55
	//	Flag that indicates a put() call to a
56
	//	storage provider is pending user approval.
57
	PENDING: "pending",
58
 
59
	// SIZE_NOT_AVAILABLE: String
60
	//	Returned by getMaximumSize() if this storage provider can not determine
61
	//	the maximum amount of data it can support.
62
	SIZE_NOT_AVAILABLE: "Size not available",
63
 
64
	// SIZE_NO_LIMIT: String
65
	//	Returned by getMaximumSize() if this storage provider has no theoretical
66
	//	limit on the amount of data it can store.
67
	SIZE_NO_LIMIT: "No size limit",
68
 
69
	// DEFAULT_NAMESPACE: String
70
	//	The namespace for all storage operations. This is useful if several
71
	//	applications want access to the storage system from the same domain but
72
	//	want different storage silos.
73
	DEFAULT_NAMESPACE: "default",
74
 
75
	// onHideSettingsUI: Function
76
	//	If a function is assigned to this property, then when the settings
77
	//	provider's UI is closed this function is called. Useful, for example,
78
	//	if the user has just cleared out all storage for this provider using
79
	//	the settings UI, and you want to update your UI.
80
	onHideSettingsUI: null,
81
 
82
	initialize: function(){
83
		// summary:
84
		//		Allows this storage provider to initialize itself. This is
85
		//		called after the page has finished loading, so you can not do
86
		//		document.writes(). Storage Provider subclasses should initialize
87
		//		themselves inside of here rather than in their function
88
		//		constructor.
89
		console.warn("dojox.storage.initialize not implemented");
90
	},
91
 
92
	isAvailable: function(){ /*Boolean*/
93
		// summary:
94
		//		Returns whether this storage provider is available on this
95
		//		platform.
96
		console.warn("dojox.storage.isAvailable not implemented");
97
	},
98
 
99
	put: function(	/*string*/ key,
100
					/*object*/ value,
101
					/*function*/ resultsHandler,
102
					/*string?*/ namespace){
103
		// summary:
104
		//		Puts a key and value into this storage system.
105
		// description:
106
		//		Example-
107
		//			var resultsHandler = function(status, key, message){
108
		//			  alert("status="+status+", key="+key+", message="+message);
109
		//			};
110
		//			dojox.storage.put("test", "hello world", resultsHandler);
111
		//
112
		//		Important note: if you are using Dojo Storage in conjunction with
113
		//		Dojo Offline, then you don't need to provide
114
		//		a resultsHandler; this is because for Dojo Offline we
115
		//		use Google Gears to persist data, which has unlimited data
116
		//		once the user has given permission. If you are using Dojo
117
		//		Storage apart from Dojo Offline, then under the covers hidden
118
		//		Flash might be used, which is both asychronous and which might
119
		//		get denied; in this case you must provide a resultsHandler.
120
		// key:
121
		//		A string key to use when retrieving this value in the future.
122
		// value:
123
		//		A value to store; this can be any JavaScript type.
124
		// resultsHandler:
125
		//		A callback function that will receive three arguments. The
126
		//		first argument is one of three values: dojox.storage.SUCCESS,
127
		//		dojox.storage.FAILED, or dojox.storage.PENDING; these values
128
		//		determine how the put request went. In some storage systems
129
		//		users can deny a storage request, resulting in a
130
		//		dojox.storage.FAILED, while in other storage systems a storage
131
		//		request must wait for user approval, resulting in a
132
		//		dojox.storage.PENDING status until the request is either
133
		//		approved or denied, resulting in another call back with
134
		//		dojox.storage.SUCCESS.
135
		//		The second argument in the call back is the key name that was being stored.
136
		//		The third argument in the call back is an optional message that
137
		//		details possible error messages that might have occurred during
138
		//		the storage process.
139
		//	namespace:
140
		//		Optional string namespace that this value will be placed into;
141
		//		if left off, the value will be placed into dojox.storage.DEFAULT_NAMESPACE
142
 
143
		console.warn("dojox.storage.put not implemented");
144
	},
145
 
146
	get: function(/*string*/ key, /*string?*/ namespace){ /*Object*/
147
		// summary:
148
		//		Gets the value with the given key. Returns null if this key is
149
		//		not in the storage system.
150
		// key:
151
		//		A string key to get the value of.
152
		//	namespace:
153
		//		Optional string namespace that this value will be retrieved from;
154
		//		if left off, the value will be retrieved from dojox.storage.DEFAULT_NAMESPACE
155
		// return: Returns any JavaScript object type; null if the key is not present
156
		console.warn("dojox.storage.get not implemented");
157
	},
158
 
159
	hasKey: function(/*string*/ key, /*string?*/ namespace){ /*Boolean*/
160
		// summary: Determines whether the storage has the given key.
161
		return (this.get(key) != null);
162
	},
163
 
164
	getKeys: function(/*string?*/ namespace){ /*Array*/
165
		// summary: Enumerates all of the available keys in this storage system.
166
		// return: Array of available keys
167
		console.warn("dojox.storage.getKeys not implemented");
168
	},
169
 
170
	clear: function(/*string?*/ namespace){
171
		// summary:
172
		//		Completely clears this storage system of all of it's values and
173
		//		keys. If 'namespace' is provided just clears the keys in that
174
		//		namespace.
175
		console.warn("dojox.storage.clear not implemented");
176
	},
177
 
178
	remove: function(/*string*/ key, /*string?*/ namespace){
179
		// summary: Removes the given key from this storage system.
180
		console.warn("dojox.storage.remove not implemented");
181
	},
182
 
183
	getNamespaces: function(){ /*string[]*/
184
		console.warn("dojox.storage.getNamespaces not implemented");
185
	},
186
 
187
	isPermanent: function(){ /*Boolean*/
188
		// summary:
189
		//		Returns whether this storage provider's values are persisted
190
		//		when this platform is shutdown.
191
		console.warn("dojox.storage.isPermanent not implemented");
192
	},
193
 
194
	getMaximumSize: function(){ /* mixed */
195
		// summary: The maximum storage allowed by this provider
196
		// returns:
197
		//	Returns the maximum storage size
198
		//	supported by this provider, in
199
		//	thousands of bytes (i.e., if it
200
		//	returns 60 then this means that 60K
201
		//	of storage is supported).
202
		//
203
		//	If this provider can not determine
204
		//	it's maximum size, then
205
		//	dojox.storage.SIZE_NOT_AVAILABLE is
206
		//	returned; if there is no theoretical
207
		//	limit on the amount of storage
208
		//	this provider can return, then
209
		//	dojox.storage.SIZE_NO_LIMIT is
210
		//	returned
211
		console.warn("dojox.storage.getMaximumSize not implemented");
212
	},
213
 
214
	putMultiple: function(	/*array*/ keys,
215
							/*array*/ values,
216
							/*function*/ resultsHandler,
217
							/*string?*/ namespace){
218
		// summary:
219
		//		Puts multiple keys and values into this storage system.
220
		// description:
221
		//		Example-
222
		//			var resultsHandler = function(status, key, message){
223
		//			  alert("status="+status+", key="+key+", message="+message);
224
		//			};
225
		//			dojox.storage.put(["test"], ["hello world"], resultsHandler);
226
		//
227
		//		Important note: if you are using Dojo Storage in conjunction with
228
		//		Dojo Offline, then you don't need to provide
229
		//		a resultsHandler; this is because for Dojo Offline we
230
		//		use Google Gears to persist data, which has unlimited data
231
		//		once the user has given permission. If you are using Dojo
232
		//		Storage apart from Dojo Offline, then under the covers hidden
233
		//		Flash might be used, which is both asychronous and which might
234
		//		get denied; in this case you must provide a resultsHandler.
235
		// keys:
236
		//		An array of string keys to use when retrieving this value in the future,
237
		//		one per value to be stored
238
		// values:
239
		//		An array of values to store; this can be any JavaScript type, though the
240
		//		performance of plain strings is considerably better
241
		// resultsHandler:
242
		//		A callback function that will receive three arguments. The
243
		//		first argument is one of three values: dojox.storage.SUCCESS,
244
		//		dojox.storage.FAILED, or dojox.storage.PENDING; these values
245
		//		determine how the put request went. In some storage systems
246
		//		users can deny a storage request, resulting in a
247
		//		dojox.storage.FAILED, while in other storage systems a storage
248
		//		request must wait for user approval, resulting in a
249
		//		dojox.storage.PENDING status until the request is either
250
		//		approved or denied, resulting in another call back with
251
		//		dojox.storage.SUCCESS.
252
		//		The second argument in the call back is the key name that was being stored.
253
		//		The third argument in the call back is an optional message that
254
		//		details possible error messages that might have occurred during
255
		//		the storage process.
256
		//	namespace:
257
		//		Optional string namespace that this value will be placed into;
258
		//		if left off, the value will be placed into dojox.storage.DEFAULT_NAMESPACE
259
 
260
		console.warn("dojox.storage.putMultiple not implemented");
261
		//	JAC: We could implement a 'default' puMultiple here by just doing each put individually
262
	},
263
 
264
	getMultiple: function(/*array*/ keys, /*string?*/ namespace){ /*Object*/
265
		// summary:
266
		//		Gets the valuse corresponding to each of the given keys.
267
		//		Returns a null array element for each given key that is
268
		//		not in the storage system.
269
		// keys:
270
		//		An array of string keys to get the value of.
271
		//	namespace:
272
		//		Optional string namespace that this value will be retrieved from;
273
		//		if left off, the value will be retrieved from dojox.storage.DEFAULT_NAMESPACE
274
		// return: Returns any JavaScript object type; null if the key is not present
275
 
276
		console.warn("dojox.storage.getMultiple not implemented");
277
		//	JAC: We could implement a 'default' getMultiple here by just doing each get individually
278
	},
279
 
280
	removeMultiple: function(/*array*/ keys, /*string?*/ namespace) {
281
		// summary: Removes the given keys from this storage system.
282
 
283
		//	JAC: We could implement a 'default' removeMultiple here by just doing each remove individually
284
		console.warn("dojox.storage.remove not implemented");
285
	},
286
 
287
	isValidKeyArray: function( keys) {
288
		if(keys === null || typeof keys === "undefined" || ! keys instanceof Array){
289
			return false;
290
		}
291
 
292
		//	JAC: This could be optimized by running the key validity test directly over a joined string
293
		for(var k=0;k<keys.length;k++){
294
			if(!this.isValidKey(keys[k])){
295
				return false;
296
			}
297
		}
298
		return true;
299
	},
300
 
301
	hasSettingsUI: function(){ /*Boolean*/
302
		// summary: Determines whether this provider has a settings UI.
303
		return false;
304
	},
305
 
306
	showSettingsUI: function(){
307
		// summary: If this provider has a settings UI, determined
308
		// by calling hasSettingsUI(), it is shown.
309
		console.warn("dojox.storage.showSettingsUI not implemented");
310
	},
311
 
312
	hideSettingsUI: function(){
313
		// summary: If this provider has a settings UI, hides it.
314
		console.warn("dojox.storage.hideSettingsUI not implemented");
315
	},
316
 
317
	isValidKey: function(/*string*/ keyName){ /*Boolean*/
318
		// summary:
319
		//		Subclasses can call this to ensure that the key given is valid
320
		//		in a consistent way across different storage providers. We use
321
		//		the lowest common denominator for key values allowed: only
322
		//		letters, numbers, and underscores are allowed. No spaces.
323
		if((keyName == null)||(typeof keyName == "undefined")){
324
			return false;
325
		}
326
 
327
		return /^[0-9A-Za-z_]*$/.test(keyName);
328
	},
329
 
330
	getResourceList: function(){ /* Array[] */
331
		// summary:
332
		//	Returns a list of URLs that this
333
		//	storage provider might depend on.
334
		// description:
335
		//	This method returns a list of URLs that this
336
		//	storage provider depends on to do its work.
337
		//	This list is used by the Dojo Offline Toolkit
338
		//	to cache these resources to ensure the machinery
339
		//	used by this storage provider is available offline.
340
		//	What is returned is an array of URLs.
341
 
342
		return [];
343
	}
344
});
345
 
346
}
347
 
348
if(!dojo._hasResource["dojox.storage.manager"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
349
dojo._hasResource["dojox.storage.manager"] = true;
350
dojo.provide("dojox.storage.manager");
351
//dojo.require("dojo.AdapterRegistry");
352
// FIXME: refactor this to use an AdapterRegistry
353
 
354
dojox.storage.manager = new function(){
355
	// summary: A singleton class in charge of the dojox.storage system
356
	// description:
357
	//		Initializes the storage systems and figures out the best available
358
	//		storage options on this platform.
359
 
360
	// currentProvider: Object
361
	//	The storage provider that was automagically chosen to do storage
362
	//	on this platform, such as dojox.storage.FlashStorageProvider.
363
	this.currentProvider = null;
364
 
365
	// available: Boolean
366
	//	Whether storage of some kind is available.
367
	this.available = false;
368
 
369
	this._initialized = false;
370
 
371
	this._providers = [];
372
	this._onLoadListeners = [];
373
 
374
	this.initialize = function(){
375
		// summary:
376
		//		Initializes the storage system and autodetects the best storage
377
		//		provider we can provide on this platform
378
		this.autodetect();
379
	};
380
 
381
	this.register = function(/*string*/ name, /*Object*/ instance){
382
		// summary:
383
		//		Registers the existence of a new storage provider; used by
384
		//		subclasses to inform the manager of their existence. The
385
		//		storage manager will select storage providers based on
386
		//		their ordering, so the order in which you call this method
387
		//		matters.
388
		// name:
389
		//		The full class name of this provider, such as
390
		//		"dojox.storage.FlashStorageProvider".
391
		// instance:
392
		//		An instance of this provider, which we will use to call
393
		//		isAvailable() on.
394
		this._providers[this._providers.length] = instance; //FIXME: push?
395
		this._providers[name] = instance; // FIXME: this._providers is an array, not a hash
396
	};
397
 
398
	this.setProvider = function(storageClass){
399
		// summary:
400
		//		Instructs the storageManager to use the given storage class for
401
		//		all storage requests.
402
		// description:
403
		//		Example-
404
		//			dojox.storage.setProvider(
405
		//				dojox.storage.IEStorageProvider)
406
 
407
	};
408
 
409
	this.autodetect = function(){
410
		// summary:
411
		//		Autodetects the best possible persistent storage provider
412
		//		available on this platform.
413
 
414
		//console.debug("dojox.storage.manager.autodetect");
415
 
416
		if(this._initialized){ // already finished
417
			//console.debug("dojox.storage.manager already initialized; returning");
418
			return;
419
		}
420
 
421
		// a flag to force the storage manager to use a particular
422
		// storage provider type, such as
423
		// djConfig = {forceStorageProvider: "dojox.storage.WhatWGStorageProvider"};
424
		var forceProvider = djConfig["forceStorageProvider"]||false;
425
 
426
		// go through each provider, seeing if it can be used
427
		var providerToUse;
428
		//FIXME: use dojo.some
429
		for(var i = 0; i < this._providers.length; i++){
430
			providerToUse = this._providers[i];
431
			if(forceProvider == providerToUse.declaredClass){
432
				// still call isAvailable for this provider, since this helps some
433
				// providers internally figure out if they are available
434
				// FIXME: This should be refactored since it is non-intuitive
435
				// that isAvailable() would initialize some state
436
				providerToUse.isAvailable();
437
				break;
438
			}else if(providerToUse.isAvailable()){
439
				break;
440
			}
441
		}
442
 
443
		if(!providerToUse){ // no provider available
444
			this._initialized = true;
445
			this.available = false;
446
			this.currentProvider = null;
447
			console.warn("No storage provider found for this platform");
448
			this.loaded();
449
			return;
450
		}
451
 
452
		// create this provider and mix in it's properties
453
		// so that developers can do dojox.storage.put rather
454
		// than dojox.storage.currentProvider.put, for example
455
		this.currentProvider = providerToUse;
456
		dojo.mixin(dojox.storage, this.currentProvider);
457
 
458
		// have the provider initialize itself
459
		dojox.storage.initialize();
460
 
461
		this._initialized = true;
462
		this.available = true;
463
	};
464
 
465
	this.isAvailable = function(){ /*Boolean*/
466
		// summary: Returns whether any storage options are available.
467
		return this.available;
468
	};
469
 
470
	this.addOnLoad = function(func){ /* void */
471
		// summary:
472
		//		Adds an onload listener to know when Dojo Offline can be used.
473
		// description:
474
		//		Adds a listener to know when Dojo Offline can be used. This
475
		//		ensures that the Dojo Offline framework is loaded and that the
476
		//		local dojox.storage system is ready to be used. This method is
477
		//		useful if you don't want to have a dependency on Dojo Events
478
		//		when using dojox.storage.
479
		// func: Function
480
		//		A function to call when Dojo Offline is ready to go
481
		this._onLoadListeners.push(func);
482
 
483
		if(this.isInitialized()){
484
			this._fireLoaded();
485
		}
486
	};
487
 
488
	this.removeOnLoad = function(func){ /* void */
489
		// summary: Removes the given onLoad listener
490
		for(var i = 0; i < this._onLoadListeners.length; i++){
491
			if(func == this._onLoadListeners[i]){
492
				this._onLoadListeners = this._onLoadListeners.splice(i, 1);
493
				break;
494
			}
495
		}
496
	};
497
 
498
	this.isInitialized = function(){ /*Boolean*/
499
	 	// summary:
500
		//		Returns whether the storage system is initialized and ready to
501
		//		be used.
502
 
503
		// FIXME: This should REALLY not be in here, but it fixes a tricky
504
		// Flash timing bug
505
		if(this.currentProvider != null
506
			&& this.currentProvider.declaredClass == "dojox.storage.FlashStorageProvider"
507
			&& dojox.flash.ready == false){
508
			return false;
509
		}else{
510
			return this._initialized;
511
		}
512
	};
513
 
514
	this.supportsProvider = function(/*string*/ storageClass){ /* Boolean */
515
		// summary: Determines if this platform supports the given storage provider.
516
		// description:
517
		//		Example-
518
		//			dojox.storage.manager.supportsProvider(
519
		//				"dojox.storage.InternetExplorerStorageProvider");
520
 
521
		// construct this class dynamically
522
		try{
523
			// dynamically call the given providers class level isAvailable()
524
			// method
525
			var provider = eval("new " + storageClass + "()");
526
			var results = provider.isAvailable();
527
			if(!results){ return false; }
528
			return results;
529
		}catch(e){
530
			return false;
531
		}
532
	};
533
 
534
	this.getProvider = function(){ /* Object */
535
		// summary: Gets the current provider
536
		return this.currentProvider;
537
	};
538
 
539
	this.loaded = function(){
540
		// summary:
541
		//		The storage provider should call this method when it is loaded
542
		//		and ready to be used. Clients who will use the provider will
543
		//		connect to this method to know when they can use the storage
544
		//		system. You can either use dojo.connect to connect to this
545
		//		function, or can use dojox.storage.manager.addOnLoad() to add
546
		//		a listener that does not depend on the dojo.event package.
547
		// description:
548
		//		Example 1-
549
		//			if(dojox.storage.manager.isInitialized() == false){
550
		//				dojo.connect(dojox.storage.manager, "loaded", TestStorage, "initialize");
551
		//			}else{
552
		//				dojo.connect(dojo, "loaded", TestStorage, "initialize");
553
		//			}
554
		//		Example 2-
555
		//			dojox.storage.manager.addOnLoad(someFunction);
556
 
557
 
558
		// FIXME: we should just provide a Deferred for this. That way you
559
		// don't care when this happens or has happened. Deferreds are in Base
560
		this._fireLoaded();
561
	};
562
 
563
	this._fireLoaded = function(){
564
		//console.debug("dojox.storage.manager._fireLoaded");
565
 
566
		dojo.forEach(this._onLoadListeners, function(i){
567
			try{
568
				i();
569
			}catch(e){ console.debug(e); }
570
		});
571
	};
572
 
573
	this.getResourceList = function(){
574
		// summary:
575
		//		Returns a list of whatever resources are necessary for storage
576
		//		providers to work.
577
		// description:
578
		//		This will return all files needed by all storage providers for
579
		//		this particular environment type. For example, if we are in the
580
		//		browser environment, then this will return the hidden SWF files
581
		//		needed by the FlashStorageProvider, even if we don't need them
582
		//		for the particular browser we are working within. This is meant
583
		//		to faciliate Dojo Offline, which must retrieve all resources we
584
		//		need offline into the offline cache -- we retrieve everything
585
		//		needed, in case another browser that requires different storage
586
		//		mechanisms hits the local offline cache. For example, if we
587
		//		were to sync against Dojo Offline on Firefox 2, then we would
588
		//		not grab the FlashStorageProvider resources needed for Safari.
589
		var results = [];
590
		dojo.forEach(dojox.storage.manager._providers, function(currentProvider){
591
			results = results.concat(currentProvider.getResourceList());
592
		});
593
 
594
		return results;
595
	}
596
};
597
 
598
}
599
 
600
if(!dojo._hasResource["dojox._sql._crypto"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
601
dojo._hasResource["dojox._sql._crypto"] = true;
602
// Taken from http://www.movable-type.co.uk/scripts/aes.html by
603
// Chris Veness (CLA signed); adapted for Dojo and Google Gears Worker Pool
604
// by Brad Neuberg, bkn3@columbia.edu
605
 
606
dojo.provide("dojox._sql._crypto");
607
 
608
dojo.mixin(dojox._sql._crypto,{
609
	// _POOL_SIZE:
610
	//	Size of worker pool to create to help with crypto
611
	_POOL_SIZE: 100,
612
 
613
	encrypt: function(plaintext, password, callback){
614
		// summary:
615
		//	Use Corrected Block TEA to encrypt plaintext using password
616
		//	(note plaintext & password must be strings not string objects).
617
		//	Results will be returned to the 'callback' asychronously.
618
		this._initWorkerPool();
619
 
620
		var msg ={plaintext: plaintext, password: password};
621
		msg = dojo.toJson(msg);
622
		msg = "encr:" + String(msg);
623
 
624
		this._assignWork(msg, callback);
625
	},
626
 
627
	decrypt: function(ciphertext, password, callback){
628
		// summary:
629
		//	Use Corrected Block TEA to decrypt ciphertext using password
630
		//	(note ciphertext & password must be strings not string objects).
631
		//	Results will be returned to the 'callback' asychronously.
632
		this._initWorkerPool();
633
 
634
		var msg ={ciphertext: ciphertext, password: password};
635
		msg = dojo.toJson(msg);
636
		msg = "decr:" + String(msg);
637
 
638
		this._assignWork(msg, callback);
639
	},
640
 
641
	_initWorkerPool: function(){
642
		// bugs in Google Gears prevents us from dynamically creating
643
		// and destroying workers as we need them -- the worker
644
		// pool functionality stops working after a number of crypto
645
		// cycles (probably related to a memory leak in Google Gears).
646
		// this is too bad, since it results in much simpler code.
647
 
648
		// instead, we have to create a pool of workers and reuse them. we
649
		// keep a stack of 'unemployed' Worker IDs that are currently not working.
650
		// if a work request comes in, we pop off the 'unemployed' stack
651
		// and put them to work, storing them in an 'employed' hashtable,
652
		// keyed by their Worker ID with the value being the callback function
653
		// that wants the result. when an employed worker is done, we get
654
		// a message in our 'manager' which adds this worker back to the
655
		// unemployed stack and routes the result to the callback that
656
		// wanted it. if all the workers were employed in the past but
657
		// more work needed to be done (i.e. it's a tight labor pool ;)
658
		// then the work messages are pushed onto
659
		// a 'handleMessage' queue as an object tuple{msg: msg, callback: callback}
660
 
661
		if(!this._manager){
662
			try{
663
				this._manager = google.gears.factory.create("beta.workerpool", "1.0");
664
				this._unemployed = [];
665
				this._employed ={};
666
				this._handleMessage = [];
667
 
668
				var self = this;
669
				this._manager.onmessage = function(msg, sender){
670
					// get the callback necessary to serve this result
671
					var callback = self._employed["_" + sender];
672
 
673
					// make this worker unemployed
674
					self._employed["_" + sender] = undefined;
675
					self._unemployed.push("_" + sender);
676
 
677
					// see if we need to assign new work
678
					// that was queued up needing to be done
679
					if(self._handleMessage.length){
680
						var handleMe = self._handleMessage.shift();
681
						self._assignWork(handleMe.msg, handleMe.callback);
682
					}
683
 
684
					// return results
685
					callback(msg);
686
				}
687
 
688
				var workerInit = "function _workerInit(){"
689
									+ "gearsWorkerPool.onmessage = "
690
										+ String(this._workerHandler)
691
									+ ";"
692
								+ "}";
693
 
694
				var code = workerInit + " _workerInit();";
695
 
696
				// create our worker pool
697
				for(var i = 0; i < this._POOL_SIZE; i++){
698
					this._unemployed.push("_" + this._manager.createWorker(code));
699
				}
700
			}catch(exp){
701
				throw exp.message||exp;
702
			}
703
		}
704
	},
705
 
706
	_assignWork: function(msg, callback){
707
		// can we immediately assign this work?
708
		if(!this._handleMessage.length && this._unemployed.length){
709
			// get an unemployed worker
710
			var workerID = this._unemployed.shift().substring(1); // remove _
711
 
712
			// list this worker as employed
713
			this._employed["_" + workerID] = callback;
714
 
715
			// do the worke
716
			this._manager.sendMessage(msg, workerID);
717
		}else{
718
			// we have to queue it up
719
			this._handleMessage ={msg: msg, callback: callback};
720
		}
721
	},
722
 
723
	_workerHandler: function(msg, sender){
724
 
725
		/* Begin AES Implementation */
726
 
727
		/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
728
 
729
		// Sbox is pre-computed multiplicative inverse in GF(2^8) used in SubBytes and KeyExpansion [§5.1.1]
730
		var Sbox =	[0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
731
					 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
732
					 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
733
					 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
734
					 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
735
					 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
736
					 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
737
					 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
738
					 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
739
					 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
740
					 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
741
					 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
742
					 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
743
					 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
744
					 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
745
					 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16];
746
 
747
		// Rcon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [§5.2]
748
		var Rcon = [ [0x00, 0x00, 0x00, 0x00],
749
					 [0x01, 0x00, 0x00, 0x00],
750
					 [0x02, 0x00, 0x00, 0x00],
751
					 [0x04, 0x00, 0x00, 0x00],
752
					 [0x08, 0x00, 0x00, 0x00],
753
					 [0x10, 0x00, 0x00, 0x00],
754
					 [0x20, 0x00, 0x00, 0x00],
755
					 [0x40, 0x00, 0x00, 0x00],
756
					 [0x80, 0x00, 0x00, 0x00],
757
					 [0x1b, 0x00, 0x00, 0x00],
758
					 [0x36, 0x00, 0x00, 0x00] ];
759
 
760
		/*
761
		 * AES Cipher function: encrypt 'input' with Rijndael algorithm
762
		 *
763
		 *	 takes	 byte-array 'input' (16 bytes)
764
		 *			 2D byte-array key schedule 'w' (Nr+1 x Nb bytes)
765
		 *
766
		 *	 applies Nr rounds (10/12/14) using key schedule w for 'add round key' stage
767
		 *
768
		 *	 returns byte-array encrypted value (16 bytes)
769
		 */
770
		function Cipher(input, w) {	   // main Cipher function [§5.1]
771
		  var Nb = 4;				// block size (in words): no of columns in state (fixed at 4 for AES)
772
		  var Nr = w.length/Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys
773
 
774
		  var state = [[],[],[],[]];  // initialise 4xNb byte-array 'state' with input [§3.4]
775
		  for (var i=0; i<4*Nb; i++) state[i%4][Math.floor(i/4)] = input[i];
776
 
777
		  state = AddRoundKey(state, w, 0, Nb);
778
 
779
		  for (var round=1; round<Nr; round++) {
780
			state = SubBytes(state, Nb);
781
			state = ShiftRows(state, Nb);
782
			state = MixColumns(state, Nb);
783
			state = AddRoundKey(state, w, round, Nb);
784
		  }
785
 
786
		  state = SubBytes(state, Nb);
787
		  state = ShiftRows(state, Nb);
788
		  state = AddRoundKey(state, w, Nr, Nb);
789
 
790
		  var output = new Array(4*Nb);	 // convert state to 1-d array before returning [§3.4]
791
		  for (var i=0; i<4*Nb; i++) output[i] = state[i%4][Math.floor(i/4)];
792
		  return output;
793
		}
794
 
795
 
796
		function SubBytes(s, Nb) {	  // apply SBox to state S [§5.1.1]
797
		  for (var r=0; r<4; r++) {
798
			for (var c=0; c<Nb; c++) s[r][c] = Sbox[s[r][c]];
799
		  }
800
		  return s;
801
		}
802
 
803
 
804
		function ShiftRows(s, Nb) {	   // shift row r of state S left by r bytes [§5.1.2]
805
		  var t = new Array(4);
806
		  for (var r=1; r<4; r++) {
807
			for (var c=0; c<4; c++) t[c] = s[r][(c+r)%Nb];	// shift into temp copy
808
			for (var c=0; c<4; c++) s[r][c] = t[c];			// and copy back
809
		  }			 // note that this will work for Nb=4,5,6, but not 7,8 (always 4 for AES):
810
		  return s;	 // see fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.311.pdf
811
		}
812
 
813
 
814
		function MixColumns(s, Nb) {   // combine bytes of each col of state S [§5.1.3]
815
		  for (var c=0; c<4; c++) {
816
			var a = new Array(4);  // 'a' is a copy of the current column from 's'
817
			var b = new Array(4);  // 'b' is a•{02} in GF(2^8)
818
			for (var i=0; i<4; i++) {
819
			  a[i] = s[i][c];
820
			  b[i] = s[i][c]&0x80 ? s[i][c]<<1 ^ 0x011b : s[i][c]<<1;
821
			}
822
			// a[n] ^ b[n] is a•{03} in GF(2^8)
823
			s[0][c] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3]; // 2*a0 + 3*a1 + a2 + a3
824
			s[1][c] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3]; // a0 * 2*a1 + 3*a2 + a3
825
			s[2][c] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3]; // a0 + a1 + 2*a2 + 3*a3
826
			s[3][c] = a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3]; // 3*a0 + a1 + a2 + 2*a3
827
		  }
828
		  return s;
829
		}
830
 
831
 
832
		function AddRoundKey(state, w, rnd, Nb) {  // xor Round Key into state S [§5.1.4]
833
		  for (var r=0; r<4; r++) {
834
			for (var c=0; c<Nb; c++) state[r][c] ^= w[rnd*4+c][r];
835
		  }
836
		  return state;
837
		}
838
 
839
 
840
		function KeyExpansion(key) {  // generate Key Schedule (byte-array Nr+1 x Nb) from Key [§5.2]
841
		  var Nb = 4;			 // block size (in words): no of columns in state (fixed at 4 for AES)
842
		  var Nk = key.length/4	 // key length (in words): 4/6/8 for 128/192/256-bit keys
843
		  var Nr = Nk + 6;		 // no of rounds: 10/12/14 for 128/192/256-bit keys
844
 
845
		  var w = new Array(Nb*(Nr+1));
846
		  var temp = new Array(4);
847
 
848
		  for (var i=0; i<Nk; i++) {
849
			var r = [key[4*i], key[4*i+1], key[4*i+2], key[4*i+3]];
850
			w[i] = r;
851
		  }
852
 
853
		  for (var i=Nk; i<(Nb*(Nr+1)); i++) {
854
			w[i] = new Array(4);
855
			for (var t=0; t<4; t++) temp[t] = w[i-1][t];
856
			if (i % Nk == 0) {
857
			  temp = SubWord(RotWord(temp));
858
			  for (var t=0; t<4; t++) temp[t] ^= Rcon[i/Nk][t];
859
			} else if (Nk > 6 && i%Nk == 4) {
860
			  temp = SubWord(temp);
861
			}
862
			for (var t=0; t<4; t++) w[i][t] = w[i-Nk][t] ^ temp[t];
863
		  }
864
 
865
		  return w;
866
		}
867
 
868
		function SubWord(w) {	 // apply SBox to 4-byte word w
869
		  for (var i=0; i<4; i++) w[i] = Sbox[w[i]];
870
		  return w;
871
		}
872
 
873
		function RotWord(w) {	 // rotate 4-byte word w left by one byte
874
		  w[4] = w[0];
875
		  for (var i=0; i<4; i++) w[i] = w[i+1];
876
		  return w;
877
		}
878
 
879
		/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
880
 
881
		/*
882
		 * Use AES to encrypt 'plaintext' with 'password' using 'nBits' key, in 'Counter' mode of operation
883
		 *							 - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
884
		 *	 for each block
885
		 *	 - outputblock = cipher(counter, key)
886
		 *	 - cipherblock = plaintext xor outputblock
887
		 */
888
		function AESEncryptCtr(plaintext, password, nBits) {
889
		  if (!(nBits==128 || nBits==192 || nBits==256)) return '';	 // standard allows 128/192/256 bit keys
890
 
891
		  // for this example script, generate the key by applying Cipher to 1st 16/24/32 chars of password;
892
		  // for real-world applications, a more secure approach would be to hash the password e.g. with SHA-1
893
		  var nBytes = nBits/8;	 // no bytes in key
894
		  var pwBytes = new Array(nBytes);
895
		  for (var i=0; i<nBytes; i++) pwBytes[i] = password.charCodeAt(i) & 0xff;
896
 
897
		  var key = Cipher(pwBytes, KeyExpansion(pwBytes));
898
 
899
		  key = key.concat(key.slice(0, nBytes-16));  // key is now 16/24/32 bytes long
900
 
901
		  // initialise counter block (NIST SP800-38A §B.2): millisecond time-stamp for nonce in 1st 8 bytes,
902
		  // block counter in 2nd 8 bytes
903
		  var blockSize = 16;  // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
904
		  var counterBlock = new Array(blockSize);	// block size fixed at 16 bytes / 128 bits (Nb=4) for AES
905
		  var nonce = (new Date()).getTime();  // milliseconds since 1-Jan-1970
906
 
907
		  // encode nonce in two stages to cater for JavaScript 32-bit limit on bitwise ops
908
		  for (var i=0; i<4; i++) counterBlock[i] = (nonce >>> i*8) & 0xff;
909
		  for (var i=0; i<4; i++) counterBlock[i+4] = (nonce/0x100000000 >>> i*8) & 0xff;
910
 
911
		  // generate key schedule - an expansion of the key into distinct Key Rounds for each round
912
		  var keySchedule = KeyExpansion(key);
913
 
914
		  var blockCount = Math.ceil(plaintext.length/blockSize);
915
		  var ciphertext = new Array(blockCount);  // ciphertext as array of strings
916
 
917
		  for (var b=0; b<blockCount; b++) {
918
			// set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
919
			// again done in two stages for 32-bit ops
920
			for (var c=0; c<4; c++) counterBlock[15-c] = (b >>> c*8) & 0xff;
921
			for (var c=0; c<4; c++) counterBlock[15-c-4] = (b/0x100000000 >>> c*8)
922
 
923
			var cipherCntr = Cipher(counterBlock, keySchedule);	 // -- encrypt counter block --
924
 
925
			// calculate length of final block:
926
			var blockLength = b<blockCount-1 ? blockSize : (plaintext.length-1)%blockSize+1;
927
 
928
			var ct = '';
929
			for (var i=0; i<blockLength; i++) {	 // -- xor plaintext with ciphered counter byte-by-byte --
930
			  var plaintextByte = plaintext.charCodeAt(b*blockSize+i);
931
			  var cipherByte = plaintextByte ^ cipherCntr[i];
932
			  ct += String.fromCharCode(cipherByte);
933
			}
934
			// ct is now ciphertext for this block
935
 
936
			ciphertext[b] = escCtrlChars(ct);  // escape troublesome characters in ciphertext
937
		  }
938
 
939
		  // convert the nonce to a string to go on the front of the ciphertext
940
		  var ctrTxt = '';
941
		  for (var i=0; i<8; i++) ctrTxt += String.fromCharCode(counterBlock[i]);
942
		  ctrTxt = escCtrlChars(ctrTxt);
943
 
944
		  // use '-' to separate blocks, use Array.join to concatenate arrays of strings for efficiency
945
		  return ctrTxt + '-' + ciphertext.join('-');
946
		}
947
 
948
 
949
		/*
950
		 * Use AES to decrypt 'ciphertext' with 'password' using 'nBits' key, in Counter mode of operation
951
		 *
952
		 *	 for each block
953
		 *	 - outputblock = cipher(counter, key)
954
		 *	 - cipherblock = plaintext xor outputblock
955
		 */
956
		function AESDecryptCtr(ciphertext, password, nBits) {
957
		  if (!(nBits==128 || nBits==192 || nBits==256)) return '';	 // standard allows 128/192/256 bit keys
958
 
959
		  var nBytes = nBits/8;	 // no bytes in key
960
		  var pwBytes = new Array(nBytes);
961
		  for (var i=0; i<nBytes; i++) pwBytes[i] = password.charCodeAt(i) & 0xff;
962
		  var pwKeySchedule = KeyExpansion(pwBytes);
963
		  var key = Cipher(pwBytes, pwKeySchedule);
964
		  key = key.concat(key.slice(0, nBytes-16));  // key is now 16/24/32 bytes long
965
 
966
		  var keySchedule = KeyExpansion(key);
967
 
968
		  ciphertext = ciphertext.split('-');  // split ciphertext into array of block-length strings
969
 
970
		  // recover nonce from 1st element of ciphertext
971
		  var blockSize = 16;  // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
972
		  var counterBlock = new Array(blockSize);
973
		  var ctrTxt = unescCtrlChars(ciphertext[0]);
974
		  for (var i=0; i<8; i++) counterBlock[i] = ctrTxt.charCodeAt(i);
975
 
976
		  var plaintext = new Array(ciphertext.length-1);
977
 
978
		  for (var b=1; b<ciphertext.length; b++) {
979
			// set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
980
			for (var c=0; c<4; c++) counterBlock[15-c] = ((b-1) >>> c*8) & 0xff;
981
			for (var c=0; c<4; c++) counterBlock[15-c-4] = ((b/0x100000000-1) >>> c*8) & 0xff;
982
 
983
			var cipherCntr = Cipher(counterBlock, keySchedule);	 // encrypt counter block
984
 
985
			ciphertext[b] = unescCtrlChars(ciphertext[b]);
986
 
987
			var pt = '';
988
			for (var i=0; i<ciphertext[b].length; i++) {
989
			  // -- xor plaintext with ciphered counter byte-by-byte --
990
			  var ciphertextByte = ciphertext[b].charCodeAt(i);
991
			  var plaintextByte = ciphertextByte ^ cipherCntr[i];
992
			  pt += String.fromCharCode(plaintextByte);
993
			}
994
			// pt is now plaintext for this block
995
 
996
			plaintext[b-1] = pt;  // b-1 'cos no initial nonce block in plaintext
997
		  }
998
 
999
		  return plaintext.join('');
1000
		}
1001
 
1002
		/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
1003
 
1004
		function escCtrlChars(str) {  // escape control chars which might cause problems handling ciphertext
1005
		  return str.replace(/[\0\t\n\v\f\r\xa0!-]/g, function(c) { return '!' + c.charCodeAt(0) + '!'; });
1006
		}  // \xa0 to cater for bug in Firefox; include '-' to leave it free for use as a block marker
1007
 
1008
		function unescCtrlChars(str) {	// unescape potentially problematic control characters
1009
		  return str.replace(/!\d\d?\d?!/g, function(c) { return String.fromCharCode(c.slice(1,-1)); });
1010
		}
1011
 
1012
		/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
1013
 
1014
		function encrypt(plaintext, password){
1015
			return AESEncryptCtr(plaintext, password, 256);
1016
		}
1017
 
1018
		function decrypt(ciphertext, password){
1019
			return AESDecryptCtr(ciphertext, password, 256);
1020
		}
1021
 
1022
		/* End AES Implementation */
1023
 
1024
		var cmd = msg.substr(0,4);
1025
		var arg = msg.substr(5);
1026
		if(cmd == "encr"){
1027
			arg = eval("(" + arg + ")");
1028
			var plaintext = arg.plaintext;
1029
			var password = arg.password;
1030
			var results = encrypt(plaintext, password);
1031
			gearsWorkerPool.sendMessage(String(results), sender);
1032
		}else if(cmd == "decr"){
1033
			arg = eval("(" + arg + ")");
1034
			var ciphertext = arg.ciphertext;
1035
			var password = arg.password;
1036
			var results = decrypt(ciphertext, password);
1037
			gearsWorkerPool.sendMessage(String(results), sender);
1038
		}
1039
	}
1040
});
1041
 
1042
}
1043
 
1044
if(!dojo._hasResource["dojox._sql.common"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
1045
dojo._hasResource["dojox._sql.common"] = true;
1046
dojo.provide("dojox._sql.common");
1047
 
1048
 
1049
 
1050
// summary:
1051
//	Executes a SQL expression.
1052
// description:
1053
// 	There are four ways to call this:
1054
// 	1) Straight SQL: dojox.sql("SELECT * FROM FOOBAR");
1055
// 	2) SQL with parameters: dojox.sql("INSERT INTO FOOBAR VALUES (?)", someParam)
1056
// 	3) Encrypting particular values:
1057
//			dojox.sql("INSERT INTO FOOBAR VALUES (ENCRYPT(?))", someParam, "somePassword", callback)
1058
// 	4) Decrypting particular values:
1059
//			dojox.sql("SELECT DECRYPT(SOMECOL1), DECRYPT(SOMECOL2) FROM
1060
//					FOOBAR WHERE SOMECOL3 = ?", someParam,
1061
//					"somePassword", callback)
1062
//
1063
// 	For encryption and decryption the last two values should be the the password for
1064
// 	encryption/decryption, and the callback function that gets the result set.
1065
//
1066
// 	Note: We only support ENCRYPT(?) statements, and
1067
// 	and DECRYPT(*) statements for now -- you can not have a literal string
1068
// 	inside of these, such as ENCRYPT('foobar')
1069
//
1070
// 	Note: If you have multiple columns to encrypt and decrypt, you can use the following
1071
// 	convenience form to not have to type ENCRYPT(?)/DECRYPT(*) many times:
1072
//
1073
// 	dojox.sql("INSERT INTO FOOBAR VALUES (ENCRYPT(?, ?, ?))",
1074
//					someParam1, someParam2, someParam3,
1075
//					"somePassword", callback)
1076
//
1077
// 	dojox.sql("SELECT DECRYPT(SOMECOL1, SOMECOL2) FROM
1078
//					FOOBAR WHERE SOMECOL3 = ?", someParam,
1079
//					"somePassword", callback)
1080
dojox.sql = new Function("return dojox.sql._exec(arguments);");
1081
 
1082
dojo.mixin(dojox.sql, {
1083
	dbName: null,
1084
 
1085
	// summary:
1086
	//	If true, then we print out any SQL that is executed
1087
	//	to the debug window
1088
	debug: (dojo.exists("dojox.sql.debug")?dojox.sql.debug:false),
1089
 
1090
	open: function(dbName){
1091
		if(this._dbOpen && (!dbName || dbName == this.dbName)){
1092
			return;
1093
		}
1094
 
1095
		if(!this.dbName){
1096
			this.dbName = "dot_store_"
1097
				+ window.location.href.replace(/[^0-9A-Za-z_]/g, "_");
1098
			//console.debug("Using Google Gears database " + this.dbName);
1099
		}
1100
 
1101
		if(!dbName){
1102
			dbName = this.dbName;
1103
		}
1104
 
1105
		try{
1106
			this._initDb();
1107
			this.db.open(dbName);
1108
			this._dbOpen = true;
1109
		}catch(exp){
1110
			throw exp.message||exp;
1111
		}
1112
	},
1113
 
1114
	close: function(dbName){
1115
		// on Internet Explorer, Google Gears throws an exception
1116
		// "Object not a collection", when we try to close the
1117
		// database -- just don't close it on this platform
1118
		// since we are running into a Gears bug; the Gears team
1119
		// said it's ok to not close a database connection
1120
		if(dojo.isIE){ return; }
1121
 
1122
		if(!this._dbOpen && (!dbName || dbName == this.dbName)){
1123
			return;
1124
		}
1125
 
1126
		if(!dbName){
1127
			dbName = this.dbName;
1128
		}
1129
 
1130
		try{
1131
			this.db.close(dbName);
1132
			this._dbOpen = false;
1133
		}catch(exp){
1134
			throw exp.message||exp;
1135
		}
1136
	},
1137
 
1138
	_exec: function(params){
1139
		try{
1140
			// get the Gears Database object
1141
			this._initDb();
1142
 
1143
			// see if we need to open the db; if programmer
1144
			// manually called dojox.sql.open() let them handle
1145
			// it; otherwise we open and close automatically on
1146
			// each SQL execution
1147
			if(!this._dbOpen){
1148
				this.open();
1149
				this._autoClose = true;
1150
			}
1151
 
1152
			// determine our parameters
1153
			var sql = null;
1154
			var callback = null;
1155
			var password = null;
1156
 
1157
			var args = dojo._toArray(params);
1158
 
1159
			sql = args.splice(0, 1)[0];
1160
 
1161
			// does this SQL statement use the ENCRYPT or DECRYPT
1162
			// keywords? if so, extract our callback and crypto
1163
			// password
1164
			if(this._needsEncrypt(sql) || this._needsDecrypt(sql)){
1165
				callback = args.splice(args.length - 1, 1)[0];
1166
				password = args.splice(args.length - 1, 1)[0];
1167
			}
1168
 
1169
			// 'args' now just has the SQL parameters
1170
 
1171
			// print out debug SQL output if the developer wants that
1172
			if(this.debug){
1173
				this._printDebugSQL(sql, args);
1174
			}
1175
 
1176
			// handle SQL that needs encryption/decryption differently
1177
			// do we have an ENCRYPT SQL statement? if so, handle that first
1178
			if(this._needsEncrypt(sql)){
1179
				var crypto = new dojox.sql._SQLCrypto("encrypt", sql,
1180
													password, args,
1181
													callback);
1182
				return; // encrypted results will arrive asynchronously
1183
			}else if(this._needsDecrypt(sql)){ // otherwise we have a DECRYPT statement
1184
				var crypto = new dojox.sql._SQLCrypto("decrypt", sql,
1185
													password, args,
1186
													callback);
1187
				return; // decrypted results will arrive asynchronously
1188
			}
1189
 
1190
			// execute the SQL and get the results
1191
			var rs = this.db.execute(sql, args);
1192
 
1193
			// Gears ResultSet object's are ugly -- normalize
1194
			// these into something JavaScript programmers know
1195
			// how to work with, basically an array of
1196
			// JavaScript objects where each property name is
1197
			// simply the field name for a column of data
1198
			rs = this._normalizeResults(rs);
1199
 
1200
			if(this._autoClose){
1201
				this.close();
1202
			}
1203
 
1204
			return rs;
1205
		}catch(exp){
1206
			exp = exp.message||exp;
1207
 
1208
			console.debug("SQL Exception: " + exp);
1209
 
1210
			if(this._autoClose){
1211
				try{
1212
					this.close();
1213
				}catch(e){
1214
					console.debug("Error closing database: "
1215
									+ e.message||e);
1216
				}
1217
			}
1218
 
1219
			throw exp;
1220
		}
1221
	},
1222
 
1223
	_initDb: function(){
1224
		if(!this.db){
1225
			try{
1226
				this.db = google.gears.factory.create('beta.database', '1.0');
1227
			}catch(exp){
1228
				dojo.setObject("google.gears.denied", true);
1229
				dojox.off.onFrameworkEvent("coreOperationFailed");
1230
				throw "Google Gears must be allowed to run";
1231
			}
1232
		}
1233
	},
1234
 
1235
	_printDebugSQL: function(sql, args){
1236
		var msg = "dojox.sql(\"" + sql + "\"";
1237
		for(var i = 0; i < args.length; i++){
1238
			if(typeof args[i] == "string"){
1239
				msg += ", \"" + args[i] + "\"";
1240
			}else{
1241
				msg += ", " + args[i];
1242
			}
1243
		}
1244
		msg += ")";
1245
 
1246
		console.debug(msg);
1247
	},
1248
 
1249
	_normalizeResults: function(rs){
1250
		var results = [];
1251
		if(!rs){ return []; }
1252
 
1253
		while(rs.isValidRow()){
1254
			var row = {};
1255
 
1256
			for(var i = 0; i < rs.fieldCount(); i++){
1257
				var fieldName = rs.fieldName(i);
1258
				var fieldValue = rs.field(i);
1259
				row[fieldName] = fieldValue;
1260
			}
1261
 
1262
			results.push(row);
1263
 
1264
			rs.next();
1265
		}
1266
 
1267
		rs.close();
1268
 
1269
		return results;
1270
	},
1271
 
1272
	_needsEncrypt: function(sql){
1273
		return /encrypt\([^\)]*\)/i.test(sql);
1274
	},
1275
 
1276
	_needsDecrypt: function(sql){
1277
		return /decrypt\([^\)]*\)/i.test(sql);
1278
	}
1279
});
1280
 
1281
// summary:
1282
//	A private class encapsulating any cryptography that must be done
1283
// 	on a SQL statement. We instantiate this class and have it hold
1284
//	it's state so that we can potentially have several encryption
1285
//	operations happening at the same time by different SQL statements.
1286
dojo.declare("dojox.sql._SQLCrypto", null, {
1287
	constructor: function(action, sql, password, args, callback){
1288
		if(action == "encrypt"){
1289
			this._execEncryptSQL(sql, password, args, callback);
1290
		}else{
1291
			this._execDecryptSQL(sql, password, args, callback);
1292
		}
1293
	},
1294
 
1295
	_execEncryptSQL: function(sql, password, args, callback){
1296
		// strip the ENCRYPT/DECRYPT keywords from the SQL
1297
		var strippedSQL = this._stripCryptoSQL(sql);
1298
 
1299
		// determine what arguments need encryption
1300
		var encryptColumns = this._flagEncryptedArgs(sql, args);
1301
 
1302
		// asynchronously encrypt each argument that needs it
1303
		var self = this;
1304
		this._encrypt(strippedSQL, password, args, encryptColumns, function(finalArgs){
1305
			// execute the SQL
1306
			var error = false;
1307
			var resultSet = [];
1308
			var exp = null;
1309
			try{
1310
				resultSet = dojox.sql.db.execute(strippedSQL, finalArgs);
1311
			}catch(execError){
1312
				error = true;
1313
				exp = execError.message||execError;
1314
			}
1315
 
1316
			// was there an error during SQL execution?
1317
			if(exp != null){
1318
				if(dojox.sql._autoClose){
1319
					try{ dojox.sql.close(); }catch(e){}
1320
				}
1321
 
1322
				callback(null, true, exp.toString());
1323
				return;
1324
			}
1325
 
1326
			// normalize SQL results into a JavaScript object
1327
			// we can work with
1328
			resultSet = dojox.sql._normalizeResults(resultSet);
1329
 
1330
			if(dojox.sql._autoClose){
1331
				dojox.sql.close();
1332
			}
1333
 
1334
			// are any decryptions necessary on the result set?
1335
			if(dojox.sql._needsDecrypt(sql)){
1336
				// determine which of the result set columns needs decryption
1337
	 			var needsDecrypt = self._determineDecryptedColumns(sql);
1338
 
1339
				// now decrypt columns asynchronously
1340
				// decrypt columns that need it
1341
				self._decrypt(resultSet, needsDecrypt, password, function(finalResultSet){
1342
					callback(finalResultSet, false, null);
1343
				});
1344
			}else{
1345
				callback(resultSet, false, null);
1346
			}
1347
		});
1348
	},
1349
 
1350
	_execDecryptSQL: function(sql, password, args, callback){
1351
		// strip the ENCRYPT/DECRYPT keywords from the SQL
1352
		var strippedSQL = this._stripCryptoSQL(sql);
1353
 
1354
		// determine which columns needs decryption; this either
1355
		// returns the value *, which means all result set columns will
1356
		// be decrypted, or it will return the column names that need
1357
		// decryption set on a hashtable so we can quickly test a given
1358
		// column name; the key is the column name that needs
1359
		// decryption and the value is 'true' (i.e. needsDecrypt["someColumn"]
1360
		// would return 'true' if it needs decryption, and would be 'undefined'
1361
		// or false otherwise)
1362
		var needsDecrypt = this._determineDecryptedColumns(sql);
1363
 
1364
		// execute the SQL
1365
		var error = false;
1366
		var resultSet = [];
1367
		var exp = null;
1368
		try{
1369
			resultSet = dojox.sql.db.execute(strippedSQL, args);
1370
		}catch(execError){
1371
			error = true;
1372
			exp = execError.message||execError;
1373
		}
1374
 
1375
		// was there an error during SQL execution?
1376
		if(exp != null){
1377
			if(dojox.sql._autoClose){
1378
				try{ dojox.sql.close(); }catch(e){}
1379
			}
1380
 
1381
			callback(resultSet, true, exp.toString());
1382
			return;
1383
		}
1384
 
1385
		// normalize SQL results into a JavaScript object
1386
		// we can work with
1387
		resultSet = dojox.sql._normalizeResults(resultSet);
1388
 
1389
		if(dojox.sql._autoClose){
1390
			dojox.sql.close();
1391
		}
1392
 
1393
		// decrypt columns that need it
1394
		this._decrypt(resultSet, needsDecrypt, password, function(finalResultSet){
1395
			callback(finalResultSet, false, null);
1396
		});
1397
	},
1398
 
1399
	_encrypt: function(sql, password, args, encryptColumns, callback){
1400
		//console.debug("_encrypt, sql="+sql+", password="+password+", encryptColumns="+encryptColumns+", args="+args);
1401
 
1402
		this._totalCrypto = 0;
1403
		this._finishedCrypto = 0;
1404
		this._finishedSpawningCrypto = false;
1405
		this._finalArgs = args;
1406
 
1407
		for(var i = 0; i < args.length; i++){
1408
			if(encryptColumns[i]){
1409
				// we have an encrypt() keyword -- get just the value inside
1410
				// the encrypt() parantheses -- for now this must be a ?
1411
				var sqlParam = args[i];
1412
				var paramIndex = i;
1413
 
1414
				// update the total number of encryptions we know must be done asynchronously
1415
				this._totalCrypto++;
1416
 
1417
				// FIXME: This currently uses DES as a proof-of-concept since the
1418
				// DES code used is quite fast and was easy to work with. Modify dojox.sql
1419
				// to be able to specify a different encryption provider through a
1420
				// a SQL-like syntax, such as dojox.sql("SET ENCRYPTION BLOWFISH"),
1421
				// and modify the dojox.crypto.Blowfish code to be able to work using
1422
				// a Google Gears Worker Pool
1423
 
1424
				// do the actual encryption now, asychronously on a Gears worker thread
1425
				dojox._sql._crypto.encrypt(sqlParam, password, dojo.hitch(this, function(results){
1426
					// set the new encrypted value
1427
					this._finalArgs[paramIndex] = results;
1428
					this._finishedCrypto++;
1429
					// are we done with all encryption?
1430
					if(this._finishedCrypto >= this._totalCrypto
1431
						&& this._finishedSpawningCrypto){
1432
						callback(this._finalArgs);
1433
					}
1434
				}));
1435
			}
1436
		}
1437
 
1438
		this._finishedSpawningCrypto = true;
1439
	},
1440
 
1441
	_decrypt: function(resultSet, needsDecrypt, password, callback){
1442
		//console.debug("decrypt, resultSet="+resultSet+", needsDecrypt="+needsDecrypt+", password="+password);
1443
 
1444
		this._totalCrypto = 0;
1445
		this._finishedCrypto = 0;
1446
		this._finishedSpawningCrypto = false;
1447
		this._finalResultSet = resultSet;
1448
 
1449
		for(var i = 0; i < resultSet.length; i++){
1450
			var row = resultSet[i];
1451
 
1452
			// go through each of the column names in row,
1453
			// seeing if they need decryption
1454
			for(var columnName in row){
1455
				if(needsDecrypt == "*" || needsDecrypt[columnName]){
1456
					this._totalCrypto++;
1457
					var columnValue = row[columnName];
1458
 
1459
					// forming a closure here can cause issues, with values not cleanly
1460
					// saved on Firefox/Mac OS X for some of the values above that
1461
					// are needed in the callback below; call a subroutine that will form
1462
					// a closure inside of itself instead
1463
					this._decryptSingleColumn(columnName, columnValue, password, i,
1464
												function(finalResultSet){
1465
						callback(finalResultSet);
1466
					});
1467
				}
1468
			}
1469
		}
1470
 
1471
		this._finishedSpawningCrypto = true;
1472
	},
1473
 
1474
	_stripCryptoSQL: function(sql){
1475
		// replace all DECRYPT(*) occurrences with a *
1476
		sql = sql.replace(/DECRYPT\(\*\)/ig, "*");
1477
 
1478
		// match any ENCRYPT(?, ?, ?, etc) occurrences,
1479
		// then replace with just the question marks in the
1480
		// middle
1481
		var matches = sql.match(/ENCRYPT\([^\)]*\)/ig);
1482
		if(matches != null){
1483
			for(var i = 0; i < matches.length; i++){
1484
				var encryptStatement = matches[i];
1485
				var encryptValue = encryptStatement.match(/ENCRYPT\(([^\)]*)\)/i)[1];
1486
				sql = sql.replace(encryptStatement, encryptValue);
1487
			}
1488
		}
1489
 
1490
		// match any DECRYPT(COL1, COL2, etc) occurrences,
1491
		// then replace with just the column names
1492
		// in the middle
1493
		matches = sql.match(/DECRYPT\([^\)]*\)/ig);
1494
		if(matches != null){
1495
			for(var i = 0; i < matches.length; i++){
1496
				var decryptStatement = matches[i];
1497
				var decryptValue = decryptStatement.match(/DECRYPT\(([^\)]*)\)/i)[1];
1498
				sql = sql.replace(decryptStatement, decryptValue);
1499
			}
1500
		}
1501
 
1502
		return sql;
1503
	},
1504
 
1505
	_flagEncryptedArgs: function(sql, args){
1506
		// capture literal strings that have question marks in them,
1507
		// and also capture question marks that stand alone
1508
		var tester = new RegExp(/([\"][^\"]*\?[^\"]*[\"])|([\'][^\']*\?[^\']*[\'])|(\?)/ig);
1509
		var matches;
1510
		var currentParam = 0;
1511
		var results = [];
1512
		while((matches = tester.exec(sql)) != null){
1513
			var currentMatch = RegExp.lastMatch+"";
1514
 
1515
			// are we a literal string? then ignore it
1516
			if(/^[\"\']/.test(currentMatch)){
1517
				continue;
1518
			}
1519
 
1520
			// do we have an encrypt keyword to our left?
1521
			var needsEncrypt = false;
1522
			if(/ENCRYPT\([^\)]*$/i.test(RegExp.leftContext)){
1523
				needsEncrypt = true;
1524
			}
1525
 
1526
			// set the encrypted flag
1527
			results[currentParam] = needsEncrypt;
1528
 
1529
			currentParam++;
1530
		}
1531
 
1532
		return results;
1533
	},
1534
 
1535
	_determineDecryptedColumns: function(sql){
1536
		var results = {};
1537
 
1538
		if(/DECRYPT\(\*\)/i.test(sql)){
1539
			results = "*";
1540
		}else{
1541
			var tester = /DECRYPT\((?:\s*\w*\s*\,?)*\)/ig;
1542
			var matches;
1543
			while(matches = tester.exec(sql)){
1544
				var lastMatch = new String(RegExp.lastMatch);
1545
				var columnNames = lastMatch.replace(/DECRYPT\(/i, "");
1546
				columnNames = columnNames.replace(/\)/, "");
1547
				columnNames = columnNames.split(/\s*,\s*/);
1548
				dojo.forEach(columnNames, function(column){
1549
					if(/\s*\w* AS (\w*)/i.test(column)){
1550
						column = column.match(/\s*\w* AS (\w*)/i)[1];
1551
					}
1552
					results[column] = true;
1553
				});
1554
			}
1555
		}
1556
 
1557
		return results;
1558
	},
1559
 
1560
	_decryptSingleColumn: function(columnName, columnValue, password, currentRowIndex,
1561
											callback){
1562
		//console.debug("decryptSingleColumn, columnName="+columnName+", columnValue="+columnValue+", currentRowIndex="+currentRowIndex)
1563
		dojox._sql._crypto.decrypt(columnValue, password, dojo.hitch(this, function(results){
1564
			// set the new decrypted value
1565
			this._finalResultSet[currentRowIndex][columnName] = results;
1566
			this._finishedCrypto++;
1567
 
1568
			// are we done with all encryption?
1569
			if(this._finishedCrypto >= this._totalCrypto
1570
				&& this._finishedSpawningCrypto){
1571
				//console.debug("done with all decrypts");
1572
				callback(this._finalResultSet);
1573
			}
1574
		}));
1575
	}
1576
});
1577
 
1578
}
1579
 
1580
if(!dojo._hasResource["dojox.sql"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
1581
dojo._hasResource["dojox.sql"] = true;
1582
 
1583
dojo.provide("dojox.sql");
1584
 
1585
}
1586
 
1587
if(!dojo._hasResource["dojox.storage.GearsStorageProvider"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
1588
dojo._hasResource["dojox.storage.GearsStorageProvider"] = true;
1589
dojo.provide("dojox.storage.GearsStorageProvider");
1590
 
1591
 
1592
 
1593
 
1594
if(dojo.isGears){
1595
 
1596
	(function(){
1597
		// make sure we don't define the gears provider if we're not gears
1598
		// enabled
1599
 
1600
		dojo.declare("dojox.storage.GearsStorageProvider", dojox.storage.Provider, {
1601
			// summary:
1602
			//		Storage provider that uses the features of Google Gears
1603
			//		to store data (it is saved into the local SQL database
1604
			//		provided by Gears, using dojox.sql)
1605
			// description:
1606
			//
1607
			//
1608
			//		You can disable this storage provider with the following djConfig
1609
			//		variable:
1610
			//		var djConfig = { disableGearsStorage: true };
1611
			//
1612
			//		Authors of this storage provider-
1613
			//			Brad Neuberg, bkn3@columbia.edu
1614
			constructor: function(){
1615
			},
1616
			// instance methods and properties
1617
			TABLE_NAME: "__DOJO_STORAGE",
1618
			initialized: false,
1619
 
1620
			_available: null,
1621
 
1622
			initialize: function(){
1623
				//console.debug("dojox.storage.GearsStorageProvider.initialize");
1624
				if(djConfig["disableGearsStorage"] == true){
1625
					return;
1626
				}
1627
 
1628
				// partition our storage data so that multiple apps
1629
				// on the same host won't collide
1630
				this.TABLE_NAME = "__DOJO_STORAGE";
1631
 
1632
				// create the table that holds our data
1633
				try{
1634
					dojox.sql("CREATE TABLE IF NOT EXISTS " + this.TABLE_NAME + "( "
1635
								+ " namespace TEXT, "
1636
								+ " key TEXT, "
1637
								+ " value TEXT "
1638
								+ ")"
1639
							);
1640
					dojox.sql("CREATE UNIQUE INDEX IF NOT EXISTS namespace_key_index"
1641
								+ " ON " + this.TABLE_NAME
1642
								+ " (namespace, key)");
1643
				}catch(e){
1644
					console.debug("dojox.storage.GearsStorageProvider.initialize:", e);
1645
 
1646
					this.initialized = false; // we were unable to initialize
1647
					dojox.storage.manager.loaded();
1648
					return;
1649
				}
1650
 
1651
				// indicate that this storage provider is now loaded
1652
				this.initialized = true;
1653
				dojox.storage.manager.loaded();
1654
			},
1655
 
1656
			isAvailable: function(){
1657
				// is Google Gears available and defined?
1658
				return this._available = dojo.isGears;
1659
			},
1660
 
1661
			put: function(key, value, resultsHandler, namespace){
1662
				if(this.isValidKey(key) == false){
1663
					throw new Error("Invalid key given: " + key);
1664
				}
1665
				namespace = namespace||this.DEFAULT_NAMESPACE;
1666
 
1667
				// serialize the value;
1668
				// handle strings differently so they have better performance
1669
				if(dojo.isString(value)){
1670
					value = "string:" + value;
1671
				}else{
1672
					value = dojo.toJson(value);
1673
				}
1674
 
1675
				// try to store the value
1676
				try{
1677
					dojox.sql("DELETE FROM " + this.TABLE_NAME
1678
								+ " WHERE namespace = ? AND key = ?",
1679
								namespace, key);
1680
					dojox.sql("INSERT INTO " + this.TABLE_NAME
1681
								+ " VALUES (?, ?, ?)",
1682
								namespace, key, value);
1683
				}catch(e){
1684
					// indicate we failed
1685
					console.debug("dojox.storage.GearsStorageProvider.put:", e);
1686
					resultsHandler(this.FAILED, key, e.toString());
1687
					return;
1688
				}
1689
 
1690
				if(resultsHandler){
1691
					resultsHandler(dojox.storage.SUCCESS, key, null);
1692
				}
1693
			},
1694
 
1695
			get: function(key, namespace){
1696
				if(this.isValidKey(key) == false){
1697
					throw new Error("Invalid key given: " + key);
1698
				}
1699
				namespace = namespace||this.DEFAULT_NAMESPACE;
1700
 
1701
				// try to find this key in the database
1702
				var results = dojox.sql("SELECT * FROM " + this.TABLE_NAME
1703
											+ " WHERE namespace = ? AND "
1704
											+ " key = ?",
1705
											namespace, key);
1706
				if(!results.length){
1707
					return null;
1708
				}else{
1709
					results = results[0].value;
1710
				}
1711
 
1712
				// destringify the content back into a
1713
				// real JavaScript object;
1714
				// handle strings differently so they have better performance
1715
				if(dojo.isString(results) && (/^string:/.test(results))){
1716
					results = results.substring("string:".length);
1717
				}else{
1718
					results = dojo.fromJson(results);
1719
				}
1720
 
1721
				return results;
1722
			},
1723
 
1724
			getNamespaces: function(){
1725
				var results = [ dojox.storage.DEFAULT_NAMESPACE ];
1726
 
1727
				var rs = dojox.sql("SELECT namespace FROM " + this.TABLE_NAME
1728
									+ " DESC GROUP BY namespace");
1729
				for(var i = 0; i < rs.length; i++){
1730
					if(rs[i].namespace != dojox.storage.DEFAULT_NAMESPACE){
1731
						results.push(rs[i].namespace);
1732
					}
1733
				}
1734
 
1735
				return results;
1736
			},
1737
 
1738
			getKeys: function(namespace){
1739
				namespace = namespace||this.DEFAULT_NAMESPACE;
1740
				if(this.isValidKey(namespace) == false){
1741
					throw new Error("Invalid namespace given: " + namespace);
1742
				}
1743
 
1744
				var rs = dojox.sql("SELECT key FROM " + this.TABLE_NAME
1745
									+ " WHERE namespace = ?",
1746
									namespace);
1747
 
1748
				var results = [];
1749
				for(var i = 0; i < rs.length; i++){
1750
					results.push(rs[i].key);
1751
				}
1752
 
1753
				return results;
1754
			},
1755
 
1756
			clear: function(namespace){
1757
				if(this.isValidKey(namespace) == false){
1758
					throw new Error("Invalid namespace given: " + namespace);
1759
				}
1760
				namespace = namespace||this.DEFAULT_NAMESPACE;
1761
 
1762
				dojox.sql("DELETE FROM " + this.TABLE_NAME
1763
							+ " WHERE namespace = ?",
1764
							namespace);
1765
			},
1766
 
1767
			remove: function(key, namespace){
1768
				namespace = namespace||this.DEFAULT_NAMESPACE;
1769
 
1770
				dojox.sql("DELETE FROM " + this.TABLE_NAME
1771
							+ " WHERE namespace = ? AND"
1772
							+ " key = ?",
1773
							namespace,
1774
							key);
1775
			},
1776
 
1777
			putMultiple: function(keys, values, resultsHandler, namespace) {
1778
 				if(this.isValidKeyArray(keys) === false
1779
						|| ! values instanceof Array
1780
						|| keys.length != values.length){
1781
					throw new Error("Invalid arguments: keys = ["
1782
									+ keys + "], values = [" + values + "]");
1783
				}
1784
 
1785
				if(namespace == null || typeof namespace == "undefined"){
1786
					namespace = dojox.storage.DEFAULT_NAMESPACE;
1787
				}
1788
 
1789
				if(this.isValidKey(namespace) == false){
1790
					throw new Error("Invalid namespace given: " + namespace);
1791
				}
1792
 
1793
				this._statusHandler = resultsHandler;
1794
 
1795
				// try to store the value
1796
				try{
1797
					dojox.sql.open();
1798
					dojox.sql.db.execute("BEGIN TRANSACTION");
1799
					var _stmt = "REPLACE INTO " + this.TABLE_NAME + " VALUES (?, ?, ?)";
1800
					for(var i=0;i<keys.length;i++) {
1801
						// serialize the value;
1802
						// handle strings differently so they have better performance
1803
						var value = values[i];
1804
						if(dojo.isString(value)){
1805
							value = "string:" + value;
1806
						}else{
1807
							value = dojo.toJson(value);
1808
						}
1809
 
1810
						dojox.sql.db.execute( _stmt,
1811
							[namespace, keys[i], value]);
1812
					}
1813
					dojox.sql.db.execute("COMMIT TRANSACTION");
1814
					dojox.sql.close();
1815
				}catch(e){
1816
					// indicate we failed
1817
					console.debug("dojox.storage.GearsStorageProvider.putMultiple:", e);
1818
					if(resultsHandler){
1819
						resultsHandler(this.FAILED, keys, e.toString());
1820
					}
1821
					return;
1822
				}
1823
 
1824
				if(resultsHandler){
1825
					resultsHandler(dojox.storage.SUCCESS, key, null);
1826
				}
1827
			},
1828
 
1829
			getMultiple: function(keys, namespace){
1830
				//	TODO: Maybe use SELECT IN instead
1831
 
1832
				if(this.isValidKeyArray(keys) === false){
1833
					throw new ("Invalid key array given: " + keys);
1834
				}
1835
 
1836
				if(namespace == null || typeof namespace == "undefined"){
1837
					namespace = dojox.storage.DEFAULT_NAMESPACE;
1838
				}
1839
 
1840
				if(this.isValidKey(namespace) == false){
1841
					throw new Error("Invalid namespace given: " + namespace);
1842
				}
1843
 
1844
				var _stmt = "SELECT * FROM " + this.TABLE_NAME	+
1845
					" WHERE namespace = ? AND "	+ " key = ?";
1846
 
1847
				var results = [];
1848
				for(var i=0;i<keys.length;i++){
1849
					var result = dojox.sql( _stmt, namespace, keys[i]);
1850
 
1851
					if( ! result.length){
1852
						results[i] = null;
1853
					}else{
1854
						result = result[0].value;
1855
 
1856
						// destringify the content back into a
1857
						// real JavaScript object;
1858
						// handle strings differently so they have better performance
1859
						if(dojo.isString(result) && (/^string:/.test(result))){
1860
							results[i] = result.substring("string:".length);
1861
						}else{
1862
							results[i] = dojo.fromJson(result);
1863
						}
1864
					}
1865
				}
1866
 
1867
				return results;
1868
			},
1869
 
1870
			removeMultiple: function(keys, namespace){
1871
				namespace = namespace||this.DEFAULT_NAMESPACE;
1872
 
1873
				dojox.sql.open();
1874
				dojox.sql.db.execute("BEGIN TRANSACTION");
1875
				var _stmt = "DELETE FROM " + this.TABLE_NAME + " WHERE namespace = ? AND key = ?";
1876
 
1877
				for(var i=0;i<keys.length;i++){
1878
					dojox.sql.db.execute( _stmt,
1879
						[namespace, keys[i]]);
1880
				}
1881
				dojox.sql.db.execute("COMMIT TRANSACTION");
1882
				dojox.sql.close();
1883
			},
1884
 
1885
			isPermanent: function(){ return true; },
1886
 
1887
			getMaximumSize: function(){ return this.SIZE_NO_LIMIT; },
1888
 
1889
			hasSettingsUI: function(){ return false; },
1890
 
1891
			showSettingsUI: function(){
1892
				throw new Error(this.declaredClass
1893
									+ " does not support a storage settings user-interface");
1894
			},
1895
 
1896
			hideSettingsUI: function(){
1897
				throw new Error(this.declaredClass
1898
									+ " does not support a storage settings user-interface");
1899
			}
1900
		});
1901
 
1902
		// register the existence of our storage providers
1903
		dojox.storage.manager.register("dojox.storage.GearsStorageProvider",
1904
										new dojox.storage.GearsStorageProvider());
1905
 
1906
		dojox.storage.manager.initialize();
1907
	})();
1908
}
1909
 
1910
}
1911
 
1912
if(!dojo._hasResource["dojox.storage._common"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
1913
dojo._hasResource["dojox.storage._common"] = true;
1914
dojo.provide("dojox.storage._common");
1915
 
1916
 
1917
 
1918
 
1919
 
1920
// FIXME: Find way to set isGears from offline.profile.js file; it didn't
1921
// work for me
1922
//dojo.requireIf(!dojo.isGears, "dojox.storage.FlashStorageProvider");
1923
//dojo.requireIf(!dojo.isGears, "dojox.storage.WhatWGStorageProvider");
1924
 
1925
// now that we are loaded and registered tell the storage manager to
1926
// initialize itself
1927
dojox.storage.manager.initialize();
1928
 
1929
}
1930
 
1931
if(!dojo._hasResource["dojox.storage"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
1932
dojo._hasResource["dojox.storage"] = true;
1933
dojo.provide("dojox.storage");
1934
 
1935
 
1936
}
1937
 
1938
if(!dojo._hasResource["dojox.off.files"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
1939
dojo._hasResource["dojox.off.files"] = true;
1940
dojo.provide("dojox.off.files");
1941
 
1942
// Author: Brad Neuberg, bkn3@columbia.edu, http://codinginparadise.org
1943
 
1944
// summary:
1945
//	Helps maintain resources that should be
1946
//	available offline, such as CSS files.
1947
// description:
1948
//	dojox.off.files makes it easy to indicate
1949
//	what resources should be available offline,
1950
//	such as CSS files, JavaScript, HTML, etc.
1951
dojox.off.files = {
1952
	// versionURL: String
1953
	//	An optional file, that if present, records the version
1954
	//	of our bundle of files to make available offline. If this
1955
	//	file is present, and we are not currently debugging,
1956
	//	then we only refresh our offline files if the version has
1957
	//	changed.
1958
	versionURL: "version.js",
1959
 
1960
	// listOfURLs: Array
1961
	//	For advanced usage; most developers can ignore this.
1962
	//	Our list of URLs that will be cached and made available
1963
	//	offline.
1964
	listOfURLs: [],
1965
 
1966
	// refreshing: boolean
1967
	//	For advanced usage; most developers can ignore this.
1968
	//	Whether we are currently in the middle
1969
	//	of refreshing our list of offline files.
1970
	refreshing: false,
1971
 
1972
	_cancelID: null,
1973
 
1974
	_error: false,
1975
	_errorMessages: [],
1976
	_currentFileIndex: 0,
1977
	_store: null,
1978
	_doSlurp: false,
1979
 
1980
	slurp: function(){
1981
		// summary:
1982
		//	Autoscans the page to find all resources to
1983
		//	cache. This includes scripts, images, CSS, and hyperlinks
1984
		//	to pages that are in the same scheme/port/host as this
1985
		//	page. We also scan the embedded CSS of any stylesheets
1986
		//	to find @import statements and url()'s.
1987
		//  You should call this method from the top-level, outside of
1988
		//	any functions and before the page loads:
1989
		//
1990
		//	<script>
1991
		//
1992
		//
1993
		//
1994
		//
1995
		//
1996
		//		// configure how we should work offline
1997
		//
1998
		//		// set our application name
1999
		//		dojox.off.ui.appName = "Moxie";
2000
		//
2001
		//		// automatically "slurp" the page and
2002
		//		// capture the resources we need offline
2003
		//		dojox.off.files.slurp();
2004
		//
2005
		// 		// tell Dojo Offline we are ready for it to initialize itself now
2006
		//		// that we have finished configuring it for our application
2007
		//		dojox.off.initialize();
2008
		//	</script>
2009
		//
2010
		//	Note that inline styles on elements are not handled (i.e.
2011
		//	if you somehow have an inline style that uses a URL);
2012
		//	object and embed tags are not scanned since their format
2013
		//	differs based on type; and elements created by JavaScript
2014
		//	after page load are not found. For these you must manually
2015
		//	add them with a dojox.off.files.cache() method call.
2016
 
2017
		// just schedule the slurp once the page is loaded and
2018
		// Dojo Offline is ready to slurp; dojox.off will call
2019
		// our _slurp() method before indicating it is finished
2020
		// loading
2021
		this._doSlurp = true;
2022
	},
2023
 
2024
	cache: function(urlOrList){ /* void */
2025
		// summary:
2026
		//		Caches a file or list of files to be available offline. This
2027
		//		can either be a full URL, such as http://foobar.com/index.html,
2028
		//		or a relative URL, such as ../index.html. This URL is not
2029
		//		actually cached until dojox.off.sync.synchronize() is called.
2030
		// urlOrList: String or Array[]
2031
		//		A URL of a file to cache or an Array of Strings of files to
2032
		//		cache
2033
 
2034
		//console.debug("dojox.off.files.cache, urlOrList="+urlOrList);
2035
 
2036
		if(dojo.isString(urlOrList)){
2037
			var url = this._trimAnchor(urlOrList+"");
2038
			if(!this.isAvailable(url)){
2039
				this.listOfURLs.push(url);
2040
			}
2041
		}else if(urlOrList instanceof dojo._Url){
2042
			var url = this._trimAnchor(urlOrList.uri);
2043
			if(!this.isAvailable(url)){
2044
				this.listOfURLs.push(url);
2045
			}
2046
		}else{
2047
			dojo.forEach(urlOrList, function(url){
2048
				url = this._trimAnchor(url);
2049
				if(!this.isAvailable(url)){
2050
					this.listOfURLs.push(url);
2051
				}
2052
			}, this);
2053
		}
2054
	},
2055
 
2056
	printURLs: function(){
2057
		// summary:
2058
		//	A helper function that will dump and print out
2059
		//	all of the URLs that are cached for offline
2060
		//	availability. This can help with debugging if you
2061
		//	are trying to make sure that all of your URLs are
2062
		//	available offline
2063
		console.debug("The following URLs are cached for offline use:");
2064
		dojo.forEach(this.listOfURLs, function(i){
2065
			console.debug(i);
2066
		});
2067
	},
2068
 
2069
	remove: function(url){ /* void */
2070
		// summary:
2071
		//		Removes a URL from the list of files to cache.
2072
		// description:
2073
		//		Removes a URL from the list of URLs to cache. Note that this
2074
		//		does not actually remove the file from the offline cache;
2075
		//		instead, it just prevents us from refreshing this file at a
2076
		//		later time, so that it will naturally time out and be removed
2077
		//		from the offline cache
2078
		// url: String
2079
		//		The URL to remove
2080
		for(var i = 0; i < this.listOfURLs.length; i++){
2081
			if(this.listOfURLs[i] == url){
2082
				this.listOfURLs = this.listOfURLs.splice(i, 1);
2083
				break;
2084
			}
2085
		}
2086
	},
2087
 
2088
	isAvailable: function(url){ /* boolean */
2089
		// summary:
2090
		//		Determines whether the given resource is available offline.
2091
		// url: String
2092
		//	The URL to check
2093
		for(var i = 0; i < this.listOfURLs.length; i++){
2094
			if(this.listOfURLs[i] == url){
2095
				return true;
2096
			}
2097
		}
2098
 
2099
		return false;
2100
	},
2101
 
2102
	refresh: function(callback){ /* void */
2103
		//console.debug("dojox.off.files.refresh");
2104
		// summary:
2105
		//	For advanced usage; most developers can ignore this.
2106
		//	Refreshes our list of offline resources,
2107
		//	making them available offline.
2108
		// callback: Function
2109
		//	A callback that receives two arguments: whether an error
2110
		//	occurred, which is a boolean; and an array of error message strings
2111
		//	with details on errors encountered. If no error occured then message is
2112
		//	empty array with length 0.
2113
		try{
2114
			if(djConfig.isDebug){
2115
				this.printURLs();
2116
			}
2117
 
2118
			this.refreshing = true;
2119
 
2120
			if(this.versionURL){
2121
				this._getVersionInfo(function(oldVersion, newVersion, justDebugged){
2122
					//console.warn("getVersionInfo, oldVersion="+oldVersion+", newVersion="+newVersion
2123
					//				+ ", justDebugged="+justDebugged+", isDebug="+djConfig.isDebug);
2124
					if(djConfig.isDebug || !newVersion || justDebugged
2125
							|| !oldVersion || oldVersion != newVersion){
2126
						console.warn("Refreshing offline file list");
2127
						this._doRefresh(callback, newVersion);
2128
					}else{
2129
						console.warn("No need to refresh offline file list");
2130
						callback(false, []);
2131
					}
2132
				});
2133
			}else{
2134
				console.warn("Refreshing offline file list");
2135
				this._doRefresh(callback);
2136
			}
2137
		}catch(e){
2138
			this.refreshing = false;
2139
 
2140
			// can't refresh files -- core operation --
2141
			// fail fast
2142
			dojox.off.coreOpFailed = true;
2143
			dojox.off.enabled = false;
2144
			dojox.off.onFrameworkEvent("coreOperationFailed");
2145
		}
2146
	},
2147
 
2148
	abortRefresh: function(){
2149
		// summary:
2150
		//	For advanced usage; most developers can ignore this.
2151
		//	Aborts and cancels a refresh.
2152
		if(!this.refreshing){
2153
			return;
2154
		}
2155
 
2156
		this._store.abortCapture(this._cancelID);
2157
		this.refreshing = false;
2158
	},
2159
 
2160
	_slurp: function(){
2161
		if(!this._doSlurp){
2162
			return;
2163
		}
2164
 
2165
		var handleUrl = dojo.hitch(this, function(url){
2166
			if(this._sameLocation(url)){
2167
				this.cache(url);
2168
			}
2169
		});
2170
 
2171
		handleUrl(window.location.href);
2172
 
2173
		dojo.query("script").forEach(function(i){
2174
			try{
2175
				handleUrl(i.getAttribute("src"));
2176
			}catch(exp){
2177
				//console.debug("dojox.off.files.slurp 'script' error: "
2178
				//				+ exp.message||exp);
2179
			}
2180
		});
2181
 
2182
		dojo.query("link").forEach(function(i){
2183
			try{
2184
				if(!i.getAttribute("rel")
2185
					|| i.getAttribute("rel").toLowerCase() != "stylesheet"){
2186
					return;
2187
				}
2188
 
2189
				handleUrl(i.getAttribute("href"));
2190
			}catch(exp){
2191
				//console.debug("dojox.off.files.slurp 'link' error: "
2192
				//				+ exp.message||exp);
2193
			}
2194
		});
2195
 
2196
		dojo.query("img").forEach(function(i){
2197
			try{
2198
				handleUrl(i.getAttribute("src"));
2199
			}catch(exp){
2200
				//console.debug("dojox.off.files.slurp 'img' error: "
2201
				//				+ exp.message||exp);
2202
			}
2203
		});
2204
 
2205
		dojo.query("a").forEach(function(i){
2206
			try{
2207
				handleUrl(i.getAttribute("href"));
2208
			}catch(exp){
2209
				//console.debug("dojox.off.files.slurp 'a' error: "
2210
				//				+ exp.message||exp);
2211
			}
2212
		});
2213
 
2214
		// FIXME: handle 'object' and 'embed' tag
2215
 
2216
		// parse our style sheets for inline URLs and imports
2217
		dojo.forEach(document.styleSheets, function(sheet){
2218
			try{
2219
				if(sheet.cssRules){ // Firefox
2220
					dojo.forEach(sheet.cssRules, function(rule){
2221
						var text = rule.cssText;
2222
						if(text){
2223
							var matches = text.match(/url\(\s*([^\) ]*)\s*\)/i);
2224
							if(!matches){
2225
								return;
2226
							}
2227
 
2228
							for(var i = 1; i < matches.length; i++){
2229
								handleUrl(matches[i])
2230
							}
2231
						}
2232
					});
2233
				}else if(sheet.cssText){ // IE
2234
					var matches;
2235
					var text = sheet.cssText.toString();
2236
					// unfortunately, using RegExp.exec seems to be flakey
2237
					// for looping across multiple lines on IE using the
2238
					// global flag, so we have to simulate it
2239
					var lines = text.split(/\f|\r|\n/);
2240
					for(var i = 0; i < lines.length; i++){
2241
						matches = lines[i].match(/url\(\s*([^\) ]*)\s*\)/i);
2242
						if(matches && matches.length){
2243
							handleUrl(matches[1]);
2244
						}
2245
					}
2246
				}
2247
			}catch(exp){
2248
				//console.debug("dojox.off.files.slurp stylesheet parse error: "
2249
				//				+ exp.message||exp);
2250
			}
2251
		});
2252
 
2253
		//this.printURLs();
2254
	},
2255
 
2256
	_sameLocation: function(url){
2257
		if(!url){ return false; }
2258
 
2259
		// filter out anchors
2260
		if(url.length && url.charAt(0) == "#"){
2261
			return false;
2262
		}
2263
 
2264
		// FIXME: dojo._Url should be made public;
2265
		// it's functionality is very useful for
2266
		// parsing URLs correctly, which is hard to
2267
		// do right
2268
		url = new dojo._Url(url);
2269
 
2270
		// totally relative -- ../../someFile.html
2271
		if(!url.scheme && !url.port && !url.host){
2272
			return true;
2273
		}
2274
 
2275
		// scheme relative with port specified -- brad.com:8080
2276
		if(!url.scheme && url.host && url.port
2277
				&& window.location.hostname == url.host
2278
				&& window.location.port == url.port){
2279
			return true;
2280
		}
2281
 
2282
		// scheme relative with no-port specified -- brad.com
2283
		if(!url.scheme && url.host && !url.port
2284
			&& window.location.hostname == url.host
2285
			&& window.location.port == 80){
2286
			return true;
2287
		}
2288
 
2289
		// else we have everything
2290
		return  window.location.protocol == (url.scheme + ":")
2291
				&& window.location.hostname == url.host
2292
				&& (window.location.port == url.port || !window.location.port && !url.port);
2293
	},
2294
 
2295
	_trimAnchor: function(url){
2296
		return url.replace(/\#.*$/, "");
2297
	},
2298
 
2299
	_doRefresh: function(callback, newVersion){
2300
		// get our local server
2301
		var localServer;
2302
		try{
2303
			localServer = google.gears.factory.create("beta.localserver", "1.0");
2304
		}catch(exp){
2305
			dojo.setObject("google.gears.denied", true);
2306
			dojox.off.onFrameworkEvent("coreOperationFailed");
2307
			throw "Google Gears must be allowed to run";
2308
		}
2309
 
2310
		var storeName = "dot_store_"
2311
							+ window.location.href.replace(/[^0-9A-Za-z_]/g, "_");
2312
 
2313
		// refresh everything by simply removing
2314
		// any older stores
2315
		localServer.removeStore(storeName);
2316
 
2317
		// open/create the resource store
2318
		localServer.openStore(storeName);
2319
		var store = localServer.createStore(storeName);
2320
		this._store = store;
2321
 
2322
		// add our list of files to capture
2323
		var self = this;
2324
		this._currentFileIndex = 0;
2325
		this._cancelID = store.capture(this.listOfURLs, function(url, success, captureId){
2326
			//console.debug("store.capture, url="+url+", success="+success);
2327
			if(!success && self.refreshing){
2328
				self._cancelID = null;
2329
				self.refreshing = false;
2330
				var errorMsgs = [];
2331
				errorMsgs.push("Unable to capture: " + url);
2332
				callback(true, errorMsgs);
2333
				return;
2334
			}else if(success){
2335
				self._currentFileIndex++;
2336
			}
2337
 
2338
			if(success && self._currentFileIndex >= self.listOfURLs.length){
2339
				self._cancelID = null;
2340
				self.refreshing = false;
2341
				if(newVersion){
2342
					dojox.storage.put("oldVersion", newVersion, null,
2343
									dojox.off.STORAGE_NAMESPACE);
2344
				}
2345
				dojox.storage.put("justDebugged", djConfig.isDebug, null,
2346
									dojox.off.STORAGE_NAMESPACE);
2347
				callback(false, []);
2348
			}
2349
		});
2350
	},
2351
 
2352
	_getVersionInfo: function(callback){
2353
		var justDebugged = dojox.storage.get("justDebugged",
2354
									dojox.off.STORAGE_NAMESPACE);
2355
		var oldVersion = dojox.storage.get("oldVersion",
2356
									dojox.off.STORAGE_NAMESPACE);
2357
		var newVersion = null;
2358
 
2359
		callback = dojo.hitch(this, callback);
2360
 
2361
		dojo.xhrGet({
2362
				url: this.versionURL + "?browserbust=" + new Date().getTime(),
2363
				timeout: 5 * 1000,
2364
				handleAs: "javascript",
2365
				error: function(err){
2366
					//console.warn("dojox.off.files._getVersionInfo, err=",err);
2367
					dojox.storage.remove("oldVersion", dojox.off.STORAGE_NAMESPACE);
2368
					dojox.storage.remove("justDebugged", dojox.off.STORAGE_NAMESPACE);
2369
					callback(oldVersion, newVersion, justDebugged);
2370
				},
2371
				load: function(data){
2372
					//console.warn("dojox.off.files._getVersionInfo, load=",data);
2373
 
2374
					// some servers incorrectly return 404's
2375
					// as a real page
2376
					if(data){
2377
						newVersion = data;
2378
					}
2379
 
2380
					callback(oldVersion, newVersion, justDebugged);
2381
				}
2382
		});
2383
	}
2384
}
2385
 
2386
}
2387
 
2388
if(!dojo._hasResource["dojox.off.sync"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2389
dojo._hasResource["dojox.off.sync"] = true;
2390
dojo.provide("dojox.off.sync");
2391
 
2392
 
2393
 
2394
 
2395
 
2396
// Author: Brad Neuberg, bkn3@columbia.edu, http://codinginparadise.org
2397
 
2398
// summary:
2399
//		Exposes syncing functionality to offline applications
2400
dojo.mixin(dojox.off.sync, {
2401
	// isSyncing: boolean
2402
	//		Whether we are in the middle of a syncing session.
2403
	isSyncing: false,
2404
 
2405
	// cancelled: boolean
2406
	//		Whether we were cancelled during our last sync request or not. If
2407
	//		we are cancelled, then successful will be false.
2408
	cancelled: false,
2409
 
2410
	// successful: boolean
2411
	//		Whether the last sync was successful or not.  If false, an error
2412
	//		occurred.
2413
	successful: true,
2414
 
2415
	// details: String[]
2416
	//		Details on the sync. If the sync was successful, this will carry
2417
	//		any conflict or merging messages that might be available; if the
2418
	//		sync was unsuccessful, this will have an error message.  For both
2419
	//		of these, this should be an array of Strings, where each string
2420
	//		carries details on the sync.
2421
	//	Example:
2422
	//		dojox.off.sync.details = ["The document 'foobar' had conflicts - yours one",
2423
	//						"The document 'hello world' was automatically merged"];
2424
	details: [],
2425
 
2426
	// error: boolean
2427
	//		Whether an error occurred during the syncing process.
2428
	error: false,
2429
 
2430
	// actions: dojox.off.sync.ActionLog
2431
	//		Our ActionLog that we store offline actions into for later
2432
	//		replaying when we go online
2433
	actions: null,
2434
 
2435
	// autoSync: boolean
2436
	//		For advanced usage; most developers can ignore this.
2437
	//		Whether we do automatically sync on page load or when we go online.
2438
	//		If true we do, if false syncing must be manually initiated.
2439
	//		Defaults to true.
2440
	autoSync: true,
2441
 
2442
	// summary:
2443
	//	An event handler that is called during the syncing process with
2444
	//	the state of syncing. It is important that you connect to this
2445
	//	method and respond to certain sync events, especially the
2446
	//	"download" event.
2447
	// description:
2448
	//	This event handler is called during the syncing process. You can
2449
	//	do a dojo.connect to receive sync feedback:
2450
	//
2451
	//		dojo.connect(dojox.off.sync, "onSync", someFunc);
2452
	//
2453
	//	You will receive one argument, which is the type of the event
2454
	//	and which can have the following values.
2455
	//
2456
	//	The most common two types that you need to care about are "download"
2457
	//	and "finished", especially if you are using the default
2458
	//	Dojo Offline UI widget that does the hard work of informing
2459
	//	the user through the UI about what is occuring during syncing.
2460
	//
2461
	//	If you receive the "download" event, you should make a network call
2462
	//	to retrieve and store your data somehow for offline access. The
2463
	//	"finished" event indicates that syncing is done. An example:
2464
	//
2465
	//		dojo.connect(dojox.off.sync, "onSync", function(type){
2466
	//			if(type == "download"){
2467
	//				// make a network call to download some data
2468
	//				// for use offline
2469
	//				dojo.xhrGet({
2470
	//					url: 		"downloadData.php",
2471
	//					handleAs:	"javascript",
2472
	//					error:		function(err){
2473
	//						dojox.off.sync.finishedDownloading(false, "Can't download data");
2474
	//					},
2475
	//					load:		function(data){
2476
	//						// store our data
2477
	//						dojox.storage.put("myData", data);
2478
	//
2479
	//						// indicate we are finished downloading
2480
	//						dojox.off.sync.finishedDownloading(true);
2481
	//					}
2482
	//				});
2483
	//			}else if(type == "finished"){
2484
	//				// update UI somehow to indicate we are finished,
2485
	//				// such as using the download data to change the
2486
	//				// available data
2487
	//			}
2488
	//		})
2489
	//
2490
	//	Here is the full list of event types if you want to do deep
2491
	//	customization, such as updating your UI to display the progress
2492
	//	of syncing (note that the default Dojo Offline UI widget does
2493
	//	this for you if you choose to pull that in). Most of these
2494
	//	are only appropriate for advanced usage and can be safely
2495
	//	ignored:
2496
	//
2497
	//		* "start"
2498
	//				syncing has started
2499
	//		* "refreshFiles"
2500
	//				syncing will begin refreshing
2501
	//				our offline file cache
2502
	//		* "upload"
2503
	//				syncing will begin uploading
2504
	//				any local data changes we have on the client.
2505
	//				This event is fired before we fire
2506
	//				the dojox.off.sync.actions.onReplay event for
2507
	//				each action to replay; use it to completely
2508
	//				over-ride the replaying behavior and prevent
2509
	//				it entirely, perhaps rolling your own sync
2510
	//				protocol if needed.
2511
	//		* "download"
2512
	//				syncing will begin downloading any new data that is
2513
	//				needed into persistent storage. Applications are required to
2514
	//				implement this themselves, storing the required data into
2515
	//				persistent local storage using Dojo Storage.
2516
	//		* "finished"
2517
	//				syncing is finished; this
2518
	//				will be called whether an error ocurred or not; check
2519
	//				dojox.off.sync.successful and dojox.off.sync.error for sync details
2520
	//		* "cancel"
2521
	//				Fired when canceling has been initiated; canceling will be
2522
	//				attempted, followed by the sync event "finished".
2523
	onSync: function(/* String */ type){},
2524
 
2525
	synchronize: function(){ /* void */
2526
		// summary: Starts synchronizing
2527
 
2528
		//dojo.debug("synchronize");
2529
		if(this.isSyncing || dojox.off.goingOnline || (!dojox.off.isOnline)){
2530
			return;
2531
		}
2532
 
2533
		this.isSyncing = true;
2534
		this.successful = false;
2535
		this.details = [];
2536
		this.cancelled = false;
2537
 
2538
		this.start();
2539
	},
2540
 
2541
	cancel: function(){ /* void */
2542
		// summary:
2543
		//	Attempts to cancel this sync session
2544
 
2545
		if(!this.isSyncing){ return; }
2546
 
2547
		this.cancelled = true;
2548
		if(dojox.off.files.refreshing){
2549
			dojox.off.files.abortRefresh();
2550
		}
2551
 
2552
		this.onSync("cancel");
2553
	},
2554
 
2555
	finishedDownloading: function(successful /* boolean? */,
2556
									errorMessage /* String? */){
2557
		// summary:
2558
		//		Applications call this method from their
2559
		//		after getting a "download" event in
2560
		//		dojox.off.sync.onSync to signal that
2561
		//		they are finished downloading any data
2562
		//		that should be available offline
2563
		// successful: boolean?
2564
		//		Whether our downloading was successful or not.
2565
		//		If not present, defaults to true.
2566
		// errorMessage: String?
2567
		//		If unsuccessful, a message explaining why
2568
		if(typeof successful == "undefined"){
2569
			successful = true;
2570
		}
2571
 
2572
		if(!successful){
2573
			this.successful = false;
2574
			this.details.push(errorMessage);
2575
			this.error = true;
2576
		}
2577
 
2578
		this.finished();
2579
	},
2580
 
2581
	start: function(){ /* void */
2582
		// summary:
2583
		//	For advanced usage; most developers can ignore this.
2584
		//	Called at the start of the syncing process. Advanced
2585
		//	developers can over-ride this method to use their
2586
		//	own sync mechanism to start syncing.
2587
 
2588
		if(this.cancelled){
2589
			this.finished();
2590
			return;
2591
		}
2592
		this.onSync("start");
2593
		this.refreshFiles();
2594
	},
2595
 
2596
	refreshFiles: function(){ /* void */
2597
		// summary:
2598
		//	For advanced usage; most developers can ignore this.
2599
		//	Called when we are going to refresh our list
2600
		//	of offline files during syncing. Advanced developers
2601
		//	can over-ride this method to do some advanced magic related to
2602
		//	refreshing files.
2603
 
2604
		//dojo.debug("refreshFiles");
2605
		if(this.cancelled){
2606
			this.finished();
2607
			return;
2608
		}
2609
 
2610
		this.onSync("refreshFiles");
2611
 
2612
		dojox.off.files.refresh(dojo.hitch(this, function(error, errorMessages){
2613
			if(error){
2614
				this.error = true;
2615
				this.successful = false;
2616
				for(var i = 0; i < errorMessages.length; i++){
2617
					this.details.push(errorMessages[i]);
2618
				}
2619
 
2620
				// even if we get an error while syncing files,
2621
				// keep syncing so we can upload and download
2622
				// data
2623
			}
2624
 
2625
			this.upload();
2626
		}));
2627
	},
2628
 
2629
	upload: function(){ /* void */
2630
		// summary:
2631
		//	For advanced usage; most developers can ignore this.
2632
		//	Called when syncing wants to upload data. Advanced
2633
		//	developers can over-ride this method to completely
2634
		//	throw away the Action Log and replaying system
2635
		//	and roll their own advanced sync mechanism if needed.
2636
 
2637
		if(this.cancelled){
2638
			this.finished();
2639
			return;
2640
		}
2641
 
2642
		this.onSync("upload");
2643
 
2644
		// when we are done uploading start downloading
2645
		dojo.connect(this.actions, "onReplayFinished", this, this.download);
2646
 
2647
		// replay the actions log
2648
		this.actions.replay();
2649
	},
2650
 
2651
	download: function(){ /* void */
2652
		// summary:
2653
		//	For advanced usage; most developers can ignore this.
2654
		//	Called when syncing wants to download data. Advanced
2655
		//	developers can over-ride this method to use their
2656
		//	own sync mechanism.
2657
 
2658
		if(this.cancelled){
2659
			this.finished();
2660
			return;
2661
		}
2662
 
2663
		// apps should respond to the "download"
2664
		// event to download their data; when done
2665
		// they must call dojox.off.sync.finishedDownloading()
2666
		this.onSync("download");
2667
	},
2668
 
2669
	finished: function(){ /* void */
2670
		// summary:
2671
		//	For advanced usage; most developers can ignore this.
2672
		//	Called when syncing is finished. Advanced
2673
		//	developers can over-ride this method to clean
2674
		//	up after finishing their own sync
2675
		//	mechanism they might have rolled.
2676
		this.isSyncing = false;
2677
 
2678
		this.successful = (!this.cancelled && !this.error);
2679
 
2680
		this.onSync("finished");
2681
	},
2682
 
2683
	_save: function(callback){
2684
		this.actions._save(function(){
2685
			callback();
2686
		});
2687
	},
2688
 
2689
	_load: function(callback){
2690
		this.actions._load(function(){
2691
			callback();
2692
		});
2693
	}
2694
});
2695
 
2696
 
2697
// summary:
2698
//		A class that records actions taken by a user when they are offline,
2699
//		suitable for replaying when the network reappears.
2700
// description:
2701
//		The basic idea behind this method is to record user actions that would
2702
//		normally have to contact a server into an action log when we are
2703
//		offline, so that later when we are online we can simply replay this log
2704
//		in the order user actions happened so that they can be executed against
2705
//		the server, causing synchronization to happen.
2706
//
2707
//		When we replay, for each of the actions that were added, we call a
2708
//		method named onReplay that applications should connect to and
2709
//		which will be called over and over for each of our actions --
2710
//		applications should take the offline action
2711
//		information and use it to talk to a server to have this action
2712
//		actually happen online, 'syncing' themselves with the server.
2713
//
2714
//		For example, if the action was "update" with the item that was updated, we
2715
//		might call some RESTian server API that exists for updating an item in
2716
//		our application.  The server could either then do sophisticated merging
2717
//		and conflict resolution on the server side, for example, allowing you
2718
//		to pop up a custom merge UI, or could do automatic merging or nothing
2719
//		of the sort. When you are finished with this particular action, your
2720
//		application is then required to call continueReplay() on the actionLog object
2721
//		passed to onReplay() to continue replaying the action log, or haltReplay()
2722
//		with the reason for halting to completely stop the syncing/replaying
2723
//		process.
2724
//
2725
//		For example, imagine that we have a web application that allows us to add
2726
//		contacts. If we are offline, and we update a contact, we would add an action;
2727
//		imagine that the user has to click an Update button after changing the values
2728
//		for a given contact:
2729
//
2730
//		dojox.off.whenOffline(dojo.byId("updateButton"), "onclick", function(evt){
2731
//			// get the updated customer values
2732
//			var customer = getCustomerValues();
2733
//
2734
//			// we are offline -- just record this action
2735
//			var action = {name: "update", customer: customer};
2736
//			dojox.off.sync.actions.add(action)
2737
//
2738
//			// persist this customer data into local storage as well
2739
//			dojox.storage.put(customer.name, customer);
2740
//		})
2741
//
2742
//		Then, when we go back online, the dojox.off.sync.actions.onReplay event
2743
//		will fire over and over, once for each action that was recorded while offline:
2744
//
2745
//		dojo.connect(dojox.off.sync.actions, "onReplay", function(action, actionLog){
2746
//			// called once for each action we added while offline, in the order
2747
//			// they were added
2748
//			if(action.name == "update"){
2749
//				var customer = action.customer;
2750
//
2751
//				// call some network service to update this customer
2752
//				dojo.xhrPost({
2753
//					url: "updateCustomer.php",
2754
//					content: {customer: dojo.toJson(customer)},
2755
//					error: function(err){
2756
//						actionLog.haltReplay(err);
2757
//					},
2758
//					load: function(data){
2759
//						actionLog.continueReplay();
2760
//					}
2761
//				})
2762
//			}
2763
//		})
2764
//
2765
//		Note that the actions log is always automatically persisted locally while using it, so
2766
//		that if the user closes the browser or it crashes the actions will safely be stored
2767
//		for later replaying.
2768
dojo.declare("dojox.off.sync.ActionLog", null, {
2769
		// entries: Array
2770
		//		An array of our action entries, where each one is simply a custom
2771
		//		object literal that were passed to add() when this action entry
2772
		//		was added.
2773
		entries: [],
2774
 
2775
		// reasonHalted: String
2776
		//		If we halted, the reason why
2777
		reasonHalted: null,
2778
 
2779
		// isReplaying: boolean
2780
		//		If true, we are in the middle of replaying a command log; if false,
2781
		//		then we are not
2782
		isReplaying: false,
2783
 
2784
		// autoSave: boolean
2785
		//		Whether we automatically save the action log after each call to
2786
		//		add(); defaults to true. For applications that are rapidly adding
2787
		//		many action log entries in a short period of time, it can be
2788
		//		useful to set this to false and simply call save() yourself when
2789
		//		you are ready to persist your command log -- otherwise performance
2790
		//		could be slow as the default action is to attempt to persist the
2791
		//		actions log constantly with calls to add().
2792
		autoSave: true,
2793
 
2794
		add: function(action /* Object */){ /* void */
2795
			// summary:
2796
			//	Adds an action to our action log
2797
			// description:
2798
			//	This method will add an action to our
2799
			//	action log, later to be replayed when we
2800
			//	go from offline to online. 'action'
2801
			//	will be available when this action is
2802
			//	replayed and will be passed to onReplay.
2803
			//
2804
			//	Example usage:
2805
			//
2806
			//	dojox.off.sync.log.add({actionName: "create", itemType: "document",
2807
			//					  {title: "Message", content: "Hello World"}});
2808
			//
2809
			//	The object literal is simply a custom object appropriate
2810
			//	for our application -- it can be anything that preserves the state
2811
			//	of a user action that will be executed when we go back online
2812
			//	and replay this log. In the above example,
2813
			//	"create" is the name of this action; "documents" is the
2814
			//	type of item this command is operating on, such as documents, contacts,
2815
			//	tasks, etc.; and the final argument is the document that was created.
2816
 
2817
			if(this.isReplaying){
2818
				throw "Programming error: you can not call "
2819
						+ "dojox.off.sync.actions.add() while "
2820
						+ "we are replaying an action log";
2821
			}
2822
 
2823
			this.entries.push(action);
2824
 
2825
			// save our updated state into persistent
2826
			// storage
2827
			if(this.autoSave){
2828
				this._save();
2829
			}
2830
		},
2831
 
2832
		onReplay: function(action /* Object */,
2833
							actionLog /* dojox.off.sync.ActionLog */){ /* void */
2834
			// summary:
2835
			//	Called when we replay our log, for each of our action
2836
			//	entries.
2837
			// action: Object
2838
			//	A custom object literal representing an action for this
2839
			//	application, such as
2840
			//	{actionName: "create", item: {title: "message", content: "hello world"}}
2841
			// actionLog: dojox.off.sync.ActionLog
2842
			//	A reference to the dojox.off.sync.actions log so that developers
2843
			//	can easily call actionLog.continueReplay() or actionLog.haltReplay().
2844
			// description:
2845
			//	This callback should be connected to by applications so that
2846
			//	they can sync themselves when we go back online:
2847
			//
2848
			//		dojo.connect(dojox.off.sync.actions, "onReplay", function(action, actionLog){
2849
			//				// do something
2850
			//		})
2851
			//
2852
			//	When we replay our action log, this callback is called for each
2853
			//	of our action entries in the order they were added. The
2854
			//	'action' entry that was passed to add() for this action will
2855
			//	also be passed in to onReplay, so that applications can use this information
2856
			//	to do their syncing, such as contacting a server web-service
2857
			//	to create a new item, for example.
2858
			//
2859
			//	Inside the method you connected to onReplay, you should either call
2860
			//	actionLog.haltReplay(reason) if an error occurred and you would like to halt
2861
			//	action replaying or actionLog.continueReplay() to have the action log
2862
			//	continue replaying its log and proceed to the next action;
2863
			//	the reason you must call these is the action you execute inside of
2864
			//	onAction will probably be asynchronous, since it will be talking on
2865
			//	the network, and you should call one of these two methods based on
2866
			//	the result of your network call.
2867
		},
2868
 
2869
		length: function(){ /* Number */
2870
			// summary:
2871
			//	Returns the length of this
2872
			//	action log
2873
			return this.entries.length;
2874
		},
2875
 
2876
		haltReplay: function(reason /* String */){ /* void */
2877
			// summary: Halts replaying this command log.
2878
			// reason: String
2879
			//		The reason we halted.
2880
			// description:
2881
			//		This method is called as we are replaying an action log; it
2882
			//		can be called from dojox.off.sync.actions.onReplay, for
2883
			//		example, for an application to indicate an error occurred
2884
			//		while replaying this action, halting further processing of
2885
			//		the action log. Note that any action log entries that
2886
			//		were processed before have their effects retained (i.e.
2887
			//		they are not rolled back), while the action entry that was
2888
			//		halted stays in our list of actions to later be replayed.
2889
			if(!this.isReplaying){
2890
				return;
2891
			}
2892
 
2893
			if(reason){
2894
				this.reasonHalted = reason.toString();
2895
			}
2896
 
2897
			// save the state of our action log, then
2898
			// tell anyone who is interested that we are
2899
			// done when we are finished saving
2900
			if(this.autoSave){
2901
				var self = this;
2902
				this._save(function(){
2903
					self.isReplaying = false;
2904
					self.onReplayFinished();
2905
				});
2906
			}else{
2907
				this.isReplaying = false;
2908
				this.onReplayFinished();
2909
			}
2910
		},
2911
 
2912
		continueReplay: function(){ /* void */
2913
			// summary:
2914
			//		Indicates that we should continue processing out list of
2915
			//		actions.
2916
			// description:
2917
			//		This method is called by applications that have overridden
2918
			//		dojox.off.sync.actions.onReplay() to continue replaying our
2919
			//		action log after the application has finished handling the
2920
			//		current action.
2921
			if(!this.isReplaying){
2922
				return;
2923
			}
2924
 
2925
			// shift off the old action we just ran
2926
			this.entries.shift();
2927
 
2928
			// are we done?
2929
			if(!this.entries.length){
2930
				// save the state of our action log, then
2931
				// tell anyone who is interested that we are
2932
				// done when we are finished saving
2933
				if(this.autoSave){
2934
					var self = this;
2935
					this._save(function(){
2936
						self.isReplaying = false;
2937
						self.onReplayFinished();
2938
					});
2939
					return;
2940
				}else{
2941
					this.isReplaying = false;
2942
					this.onReplayFinished();
2943
					return;
2944
				}
2945
			}
2946
 
2947
			// get the next action
2948
			var nextAction = this.entries[0];
2949
			this.onReplay(nextAction, this);
2950
		},
2951
 
2952
		clear: function(){ /* void */
2953
			// summary:
2954
			//	Completely clears this action log of its entries
2955
 
2956
			if(this.isReplaying){
2957
				return;
2958
			}
2959
 
2960
			this.entries = [];
2961
 
2962
			// save our updated state into persistent
2963
			// storage
2964
			if(this.autoSave){
2965
				this._save();
2966
			}
2967
		},
2968
 
2969
		replay: function(){ /* void */
2970
			// summary:
2971
			//	For advanced usage; most developers can ignore this.
2972
			//	Replays all of the commands that have been
2973
			//	cached in this command log when we go back online;
2974
			//	onCommand will be called for each command we have
2975
 
2976
			if(this.isReplaying){
2977
				return;
2978
			}
2979
 
2980
			this.reasonHalted = null;
2981
 
2982
			if(!this.entries.length){
2983
				this.onReplayFinished();
2984
				return;
2985
			}
2986
 
2987
			this.isReplaying = true;
2988
 
2989
			var nextAction = this.entries[0];
2990
			this.onReplay(nextAction, this);
2991
		},
2992
 
2993
		// onReplayFinished: Function
2994
		//	For advanced usage; most developers can ignore this.
2995
		//	Called when we are finished replaying our commands;
2996
		//	called if we have successfully exhausted all of our
2997
		//	commands, or if an error occurred during replaying.
2998
		//	The default implementation simply continues the
2999
		//	synchronization process. Connect to this to register
3000
		//	for the event:
3001
		//
3002
		//		dojo.connect(dojox.off.sync.actions, "onReplayFinished",
3003
		//					someFunc)
3004
		onReplayFinished: function(){
3005
		},
3006
 
3007
		toString: function(){
3008
			var results = "";
3009
			results += "[";
3010
 
3011
			for(var i = 0; i < this.entries.length; i++){
3012
				results += "{";
3013
				for(var j in this.entries[i]){
3014
					results += j + ": \"" + this.entries[i][j] + "\"";
3015
					results += ", ";
3016
				}
3017
				results += "}, ";
3018
			}
3019
 
3020
			results += "]";
3021
 
3022
			return results;
3023
		},
3024
 
3025
		_save: function(callback){
3026
			if(!callback){
3027
				callback = function(){};
3028
			}
3029
 
3030
			try{
3031
				var self = this;
3032
				var resultsHandler = function(status, key, message){
3033
					//console.debug("resultsHandler, status="+status+", key="+key+", message="+message);
3034
					if(status == dojox.storage.FAILED){
3035
						dojox.off.onFrameworkEvent("save",
3036
											{status: dojox.storage.FAILED,
3037
											isCoreSave: true,
3038
											key: key,
3039
											value: message,
3040
											namespace: dojox.off.STORAGE_NAMESPACE});
3041
						callback();
3042
					}else if(status == dojox.storage.SUCCESS){
3043
						callback();
3044
					}
3045
				};
3046
 
3047
				dojox.storage.put("actionlog", this.entries, resultsHandler,
3048
									dojox.off.STORAGE_NAMESPACE);
3049
			}catch(exp){
3050
				console.debug("dojox.off.sync._save: " + exp.message||exp);
3051
				dojox.off.onFrameworkEvent("save",
3052
							{status: dojox.storage.FAILED,
3053
							isCoreSave: true,
3054
							key: "actionlog",
3055
							value: this.entries,
3056
							namespace: dojox.off.STORAGE_NAMESPACE});
3057
				callback();
3058
			}
3059
		},
3060
 
3061
		_load: function(callback){
3062
			var entries = dojox.storage.get("actionlog", dojox.off.STORAGE_NAMESPACE);
3063
 
3064
			if(!entries){
3065
				entries = [];
3066
			}
3067
 
3068
			this.entries = entries;
3069
 
3070
			callback();
3071
		}
3072
	}
3073
);
3074
 
3075
dojox.off.sync.actions = new dojox.off.sync.ActionLog();
3076
 
3077
}
3078
 
3079
if(!dojo._hasResource["dojox.off._common"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
3080
dojo._hasResource["dojox.off._common"] = true;
3081
dojo.provide("dojox.off._common");
3082
 
3083
 
3084
 
3085
 
3086
 
3087
// Author: Brad Neuberg, bkn3@columbia.edu, http://codinginparadise.org
3088
 
3089
// summary:
3090
//		dojox.off is the main object for offline applications.
3091
dojo.mixin(dojox.off, {
3092
	// isOnline: boolean
3093
	//	true if we are online, false if not
3094
	isOnline: false,
3095
 
3096
	// NET_CHECK: int
3097
	//		For advanced usage; most developers can ignore this.
3098
	//		Time in seconds on how often we should check the status of the
3099
	//		network with an automatic background timer. The current default
3100
	//		is 5 seconds.
3101
	NET_CHECK: 5,
3102
 
3103
	// STORAGE_NAMESPACE: String
3104
	//		For advanced usage; most developers can ignore this.
3105
	//		The namespace we use to save core data into Dojo Storage.
3106
	STORAGE_NAMESPACE: "_dot",
3107
 
3108
	// enabled: boolean
3109
	//		For advanced usage; most developers can ignore this.
3110
	//		Whether offline ability is enabled or not. Defaults to true.
3111
	enabled: true,
3112
 
3113
	// availabilityURL: String
3114
	//		For advanced usage; most developers can ignore this.
3115
	//		The URL to check for site availability.  We do a GET request on
3116
	//		this URL to check for site availability.  By default we check for a
3117
	//		simple text file in src/off/network_check.txt that has one value
3118
	//		it, the value '1'.
3119
	availabilityURL: dojo.moduleUrl("dojox", "off/network_check.txt"),
3120
 
3121
	// goingOnline: boolean
3122
	//		For advanced usage; most developers can ignore this.
3123
	//		True if we are attempting to go online, false otherwise
3124
	goingOnline: false,
3125
 
3126
	// coreOpFailed: boolean
3127
	//		For advanced usage; most developers can ignore this.
3128
	//		A flag set by the Dojo Offline framework that indicates that the
3129
	//		user denied some operation that required the offline cache or an
3130
	//		operation failed in some critical way that was unrecoverable. For
3131
	//		example, if the offline cache is Google Gears and we try to get a
3132
	//		Gears database, a popup window appears asking the user whether they
3133
	//		will approve or deny this request. If the user denies the request,
3134
	//		and we are doing some operation that is core to Dojo Offline, then
3135
	//		we set this flag to 'true'.  This flag causes a 'fail fast'
3136
	//		condition, turning off offline ability.
3137
	coreOpFailed: false,
3138
 
3139
	// doNetChecking: boolean
3140
	//		For advanced usage; most developers can ignore this.
3141
	//		Whether to have a timing interval in the background doing automatic
3142
	//		network checks at regular intervals; the length of time between
3143
	//		checks is controlled by dojox.off.NET_CHECK. Defaults to true.
3144
	doNetChecking: true,
3145
 
3146
	// hasOfflineCache: boolean
3147
	//		For advanced usage; most developers can ignore this.
3148
	//  	Determines if an offline cache is available or installed; an
3149
	//  	offline cache is a facility that can truely cache offline
3150
	//  	resources, such as JavaScript, HTML, etc. in such a way that they
3151
	//  	won't be removed from the cache inappropriately like a browser
3152
	//  	cache would. If this is false then an offline cache will be
3153
	//  	installed. Only Google Gears is currently supported as an offline
3154
	//  	cache. Future possible offline caches include Firefox 3.
3155
	hasOfflineCache: null,
3156
 
3157
	// browserRestart: boolean
3158
	//		For advanced usage; most developers can ignore this.
3159
	//		If true, the browser must be restarted to register the existence of
3160
	//		a new host added offline (from a call to addHostOffline); if false,
3161
	//		then nothing is needed.
3162
	browserRestart: false,
3163
 
3164
	_STORAGE_APP_NAME: window.location.href.replace(/[^0-9A-Za-z_]/g, "_"),
3165
 
3166
	_initializeCalled: false,
3167
	_storageLoaded: false,
3168
	_pageLoaded: false,
3169
 
3170
	onLoad: function(){
3171
		// summary:
3172
		//	Called when Dojo Offline can be used.
3173
		// description:
3174
		//	Do a dojo.connect to this to know when you can
3175
		//	start using Dojo Offline:
3176
		//		dojo.connect(dojox.off, "onLoad", myFunc);
3177
	},
3178
 
3179
	onNetwork: function(type){
3180
		// summary:
3181
		//	Called when our on- or offline- status changes.
3182
		// description:
3183
		//	If we move online, then this method is called with the
3184
		//	value "online". If we move offline, then this method is
3185
		//	called with the value "offline". You can connect to this
3186
		//	method to do add your own behavior:
3187
		//
3188
		//		dojo.connect(dojox.off, "onNetwork", someFunc)
3189
		//
3190
		//	Note that if you are using the default Dojo Offline UI
3191
		//	widget that most of the on- and off-line notification
3192
		//	and syncing is automatically handled and provided to the
3193
		//	user.
3194
		// type: String
3195
		//	Either "online" or "offline".
3196
	},
3197
 
3198
	initialize: function(){ /* void */
3199
		// summary:
3200
		//		Called when a Dojo Offline-enabled application is finished
3201
		//		configuring Dojo Offline, and is ready for Dojo Offline to
3202
		//		initialize itself.
3203
		// description:
3204
		//		When an application has finished filling out the variables Dojo
3205
		//		Offline needs to work, such as dojox.off.ui.appName, it must
3206
		//		this method to tell Dojo Offline to initialize itself.
3207
 
3208
		//		Note:
3209
		//		This method is needed for a rare edge case. In some conditions,
3210
		//		especially if we are dealing with a compressed Dojo build, the
3211
		//		entire Dojo Offline subsystem might initialize itself and be
3212
		//		running even before the JavaScript for an application has had a
3213
		//		chance to run and configure Dojo Offline, causing Dojo Offline
3214
		//		to have incorrect initialization parameters for a given app,
3215
		//		such as no value for dojox.off.ui.appName. This method is
3216
		//		provided to prevent this scenario, to slightly 'slow down' Dojo
3217
		//		Offline so it can be configured before running off and doing
3218
		//		its thing.
3219
 
3220
		//console.debug("dojox.off.initialize");
3221
		this._initializeCalled = true;
3222
 
3223
		if(this._storageLoaded && this._pageLoaded){
3224
			this._onLoad();
3225
		}
3226
	},
3227
 
3228
	goOffline: function(){ /* void */
3229
		// summary:
3230
		//		For advanced usage; most developers can ignore this.
3231
		//		Manually goes offline, away from the network.
3232
		if((dojox.off.sync.isSyncing)||(this.goingOnline)){ return; }
3233
 
3234
		this.goingOnline = false;
3235
		this.isOnline = false;
3236
	},
3237
 
3238
	goOnline: function(callback){ /* void */
3239
		// summary:
3240
		//		For advanced usage; most developers can ignore this.
3241
		//		Attempts to go online.
3242
		// description:
3243
		//		Attempts to go online, making sure this web application's web
3244
		//		site is available. 'callback' is called asychronously with the
3245
		//		result of whether we were able to go online or not.
3246
		// callback: Function
3247
		//		An optional callback function that will receive one argument:
3248
		//		whether the site is available or not and is boolean. If this
3249
		//		function is not present we call dojo.xoff.onOnline instead if
3250
		//		we are able to go online.
3251
 
3252
		//console.debug("goOnline");
3253
 
3254
		if(dojox.off.sync.isSyncing || dojox.off.goingOnline){
3255
			return;
3256
		}
3257
 
3258
		this.goingOnline = true;
3259
		this.isOnline = false;
3260
 
3261
		// see if can reach our web application's web site
3262
		this._isSiteAvailable(callback);
3263
	},
3264
 
3265
	onFrameworkEvent: function(type /* String */, saveData /* Object? */){
3266
		//	summary:
3267
		//		For advanced usage; most developers can ignore this.
3268
		//		A standard event handler that can be attached to to find out
3269
		//		about low-level framework events. Most developers will not need to
3270
		//		attach to this method; it is meant for low-level information
3271
		//		that can be useful for updating offline user-interfaces in
3272
		//		exceptional circumstances. The default Dojo Offline UI
3273
		//		widget takes care of most of these situations.
3274
		//	type: String
3275
		//		The type of the event:
3276
		//
3277
		//		* "offlineCacheInstalled"
3278
		//			An event that is fired when a user
3279
		//			has installed an offline cache after the page has been loaded.
3280
		//			If a user didn't have an offline cache when the page loaded, a
3281
		//			UI of some kind might have prompted them to download one. This
3282
		//			method is called if they have downloaded and installed an
3283
		//			offline cache so a UI can reinitialize itself to begin using
3284
		//			this offline cache.
3285
		//		* "coreOperationFailed"
3286
		//			Fired when a core operation during interaction with the
3287
		//			offline cache is denied by the user. Some offline caches, such
3288
		//			as Google Gears, prompts the user to approve or deny caching
3289
		//			files, using the database, and more. If the user denies a
3290
		//			request that is core to Dojo Offline's operation, we set
3291
		//			dojox.off.coreOpFailed to true and call this method for
3292
		//			listeners that would like to respond some how to Dojo Offline
3293
		//			'failing fast'.
3294
		//		* "save"
3295
		//			Called whenever the framework saves data into persistent
3296
		//			storage. This could be useful for providing save feedback
3297
		//			or providing appropriate error feedback if saving fails
3298
		//			due to a user not allowing the save to occur
3299
		//	saveData: Object?
3300
		//		If the type was 'save', then a saveData object is provided with
3301
		//		further save information. This object has the following properties:
3302
		//
3303
		//		* status - dojox.storage.SUCCESS, dojox.storage.PENDING, dojox.storage.FAILED
3304
		//		Whether the save succeeded, whether it is pending based on a UI
3305
		//		dialog asking the user for permission, or whether it failed.
3306
		//
3307
		//		* isCoreSave - boolean
3308
		//		If true, then this save was for a core piece of data necessary
3309
		//		for the functioning of Dojo Offline. If false, then it is a
3310
		//		piece of normal data being saved for offline access. Dojo
3311
		//		Offline will 'fail fast' if some core piece of data could not
3312
		//		be saved, automatically setting dojox.off.coreOpFailed to
3313
		//		'true' and dojox.off.enabled to 'false'.
3314
		//
3315
		// 		* key - String
3316
		//		The key that we are attempting to persist
3317
		//
3318
		// 		* value - Object
3319
		//		The object we are trying to persist
3320
		//
3321
		// 		* namespace - String
3322
		//		The Dojo Storage namespace we are saving this key/value pair
3323
		//		into, such as "default", "Documents", "Contacts", etc.
3324
		//		Optional.
3325
		if(type == "save"){
3326
			if(saveData.isCoreSave && (saveData.status == dojox.storage.FAILED)){
3327
				dojox.off.coreOpFailed = true;
3328
				dojox.off.enabled = false;
3329
 
3330
				// FIXME: Stop the background network thread
3331
				dojox.off.onFrameworkEvent("coreOperationFailed");
3332
			}
3333
		}else if(type == "coreOperationFailed"){
3334
			dojox.off.coreOpFailed = true;
3335
			dojox.off.enabled = false;
3336
			// FIXME: Stop the background network thread
3337
		}
3338
	},
3339
 
3340
	_checkOfflineCacheAvailable: function(callback){
3341
		// is a true, offline cache running on this machine?
3342
		this.hasOfflineCache = dojo.isGears;
3343
 
3344
		callback();
3345
	},
3346
 
3347
	_onLoad: function(){
3348
		//console.debug("dojox.off._onLoad");
3349
 
3350
		// both local storage and the page are finished loading
3351
 
3352
		// cache the Dojo JavaScript -- just use the default dojo.js
3353
		// name for the most common scenario
3354
		// FIXME: TEST: Make sure syncing doesn't break if dojo.js
3355
		// can't be found, or report an error to developer
3356
		dojox.off.files.cache(dojo.moduleUrl("dojo", "dojo.js"));
3357
 
3358
		// pull in the files needed by Dojo
3359
		this._cacheDojoResources();
3360
 
3361
		// FIXME: need to pull in the firebug lite files here!
3362
		// workaround or else we will get an error on page load
3363
		// from Dojo that it can't find 'console.debug' for optimized builds
3364
		// dojox.off.files.cache(djConfig.baseRelativePath + "src/debug.js");
3365
 
3366
		// make sure that resources needed by all of our underlying
3367
		// Dojo Storage storage providers will be available
3368
		// offline
3369
		dojox.off.files.cache(dojox.storage.manager.getResourceList());
3370
 
3371
		// slurp the page if the end-developer wants that
3372
		dojox.off.files._slurp();
3373
 
3374
		// see if we have an offline cache; when done, move
3375
		// on to the rest of our startup tasks
3376
		this._checkOfflineCacheAvailable(dojo.hitch(this, "_onOfflineCacheChecked"));
3377
	},
3378
 
3379
	_onOfflineCacheChecked: function(){
3380
		// this method is part of our _onLoad series of startup tasks
3381
 
3382
		// if we have an offline cache, see if we have been added to the
3383
		// list of available offline web apps yet
3384
		if(this.hasOfflineCache && this.enabled){
3385
			// load framework data; when we are finished, continue
3386
			// initializing ourselves
3387
			this._load(dojo.hitch(this, "_finishStartingUp"));
3388
		}else if(this.hasOfflineCache && !this.enabled){
3389
			// we have an offline cache, but it is disabled for some reason
3390
			// perhaps due to the user denying a core operation
3391
			this._finishStartingUp();
3392
		}else{
3393
			this._keepCheckingUntilInstalled();
3394
		}
3395
	},
3396
 
3397
	_keepCheckingUntilInstalled: function(){
3398
		// this method is part of our _onLoad series of startup tasks
3399
 
3400
		// kick off a background interval that keeps
3401
		// checking to see if an offline cache has been
3402
		// installed since this page loaded
3403
 
3404
		// FIXME: Gears: See if we are installed somehow after the
3405
		// page has been loaded
3406
 
3407
		// now continue starting up
3408
		this._finishStartingUp();
3409
	},
3410
 
3411
	_finishStartingUp: function(){
3412
		//console.debug("dojox.off._finishStartingUp");
3413
 
3414
		// this method is part of our _onLoad series of startup tasks
3415
 
3416
		if(!this.hasOfflineCache){
3417
			this.onLoad();
3418
		}else if(this.enabled){
3419
			// kick off a thread to check network status on
3420
			// a regular basis
3421
			this._startNetworkThread();
3422
 
3423
			// try to go online
3424
			this.goOnline(dojo.hitch(this, function(){
3425
				//console.debug("Finished trying to go online");
3426
				// indicate we are ready to be used
3427
				dojox.off.onLoad();
3428
			}));
3429
		}else{ // we are disabled or a core operation failed
3430
			if(this.coreOpFailed){
3431
				this.onFrameworkEvent("coreOperationFailed");
3432
			}else{
3433
				this.onLoad();
3434
			}
3435
		}
3436
	},
3437
 
3438
	_onPageLoad: function(){
3439
		//console.debug("dojox.off._onPageLoad");
3440
		this._pageLoaded = true;
3441
 
3442
		if(this._storageLoaded && this._initializeCalled){
3443
			this._onLoad();
3444
		}
3445
	},
3446
 
3447
	_onStorageLoad: function(){
3448
		//console.debug("dojox.off._onStorageLoad");
3449
		this._storageLoaded = true;
3450
 
3451
		// were we able to initialize storage? if
3452
		// not, then this is a core operation, and
3453
		// let's indicate we will need to fail fast
3454
		if(!dojox.storage.manager.isAvailable()
3455
			&& dojox.storage.manager.isInitialized()){
3456
			this.coreOpFailed = true;
3457
			this.enabled = false;
3458
		}
3459
 
3460
		if(this._pageLoaded && this._initializeCalled){
3461
			this._onLoad();
3462
		}
3463
	},
3464
 
3465
	_isSiteAvailable: function(callback){
3466
		// summary:
3467
		//		Determines if our web application's website is available.
3468
		// description:
3469
		//		This method will asychronously determine if our web
3470
		//		application's web site is available, which is a good proxy for
3471
		//		network availability. The URL dojox.off.availabilityURL is
3472
		//		used, which defaults to this site's domain name (ex:
3473
		//		foobar.com). We check for dojox.off.AVAILABILITY_TIMEOUT (in
3474
		//		seconds) and abort after that
3475
		// callback: Function
3476
		//		An optional callback function that will receive one argument:
3477
		//		whether the site is available or not and is boolean. If this
3478
		//		function is not present we call dojox.off.onNetwork instead if we
3479
		//		are able to go online.
3480
		dojo.xhrGet({
3481
			url:		this._getAvailabilityURL(),
3482
			handleAs:	"text",
3483
			timeout:	this.NET_CHECK * 1000,
3484
			error:		dojo.hitch(this, function(err){
3485
				//console.debug("dojox.off._isSiteAvailable.error: " + err);
3486
				this.goingOnline = false;
3487
				this.isOnline = false;
3488
				if(callback){ callback(false); }
3489
			}),
3490
			load:		dojo.hitch(this, function(data){
3491
				//console.debug("dojox.off._isSiteAvailable.load, data="+data);
3492
				this.goingOnline = false;
3493
				this.isOnline = true;
3494
 
3495
				if(callback){ callback(true);
3496
				}else{ this.onNetwork("online"); }
3497
			})
3498
		});
3499
	},
3500
 
3501
	_startNetworkThread: function(){
3502
		//console.debug("startNetworkThread");
3503
 
3504
		// kick off a thread that does periodic
3505
		// checks on the status of the network
3506
		if(!this.doNetChecking){
3507
			return;
3508
		}
3509
 
3510
		window.setInterval(dojo.hitch(this, function(){
3511
			var d = dojo.xhrGet({
3512
				url:	 	this._getAvailabilityURL(),
3513
				handleAs:	"text",
3514
				timeout: 	this.NET_CHECK * 1000,
3515
				error:		dojo.hitch(this,
3516
								function(err){
3517
									if(this.isOnline){
3518
										this.isOnline = false;
3519
 
3520
										// FIXME: xhrGet() is not
3521
										// correctly calling abort
3522
										// on the XHR object when
3523
										// it times out; fix inside
3524
										// there instead of externally
3525
										// here
3526
										try{
3527
											if(typeof d.ioArgs.xhr.abort == "function"){
3528
												d.ioArgs.xhr.abort();
3529
											}
3530
										}catch(e){}
3531
 
3532
										// if things fell in the middle of syncing,
3533
										// stop syncing
3534
										dojox.off.sync.isSyncing = false;
3535
 
3536
										this.onNetwork("offline");
3537
									}
3538
								}
3539
							),
3540
				load:		dojo.hitch(this,
3541
								function(data){
3542
									if(!this.isOnline){
3543
										this.isOnline = true;
3544
										this.onNetwork("online");
3545
									}
3546
								}
3547
							)
3548
			});
3549
 
3550
		}), this.NET_CHECK * 1000);
3551
	},
3552
 
3553
	_getAvailabilityURL: function(){
3554
		var url = this.availabilityURL.toString();
3555
 
3556
		// bust the browser's cache to make sure we are really talking to
3557
		// the server
3558
		if(url.indexOf("?") == -1){
3559
			url += "?";
3560
		}else{
3561
			url += "&";
3562
		}
3563
		url += "browserbust=" + new Date().getTime();
3564
 
3565
		return url;
3566
	},
3567
 
3568
	_onOfflineCacheInstalled: function(){
3569
		this.onFrameworkEvent("offlineCacheInstalled");
3570
	},
3571
 
3572
	_cacheDojoResources: function(){
3573
		// if we are a non-optimized build, then the core Dojo bootstrap
3574
		// system was loaded as separate JavaScript files;
3575
		// add these to our offline cache list. these are
3576
		// loaded before the dojo.require() system exists
3577
 
3578
		// FIXME: create a better mechanism in the Dojo core to
3579
		// expose whether you are dealing with an optimized build;
3580
		// right now we just scan the SCRIPT tags attached to this
3581
		// page and see if there is one for _base/_loader/bootstrap.js
3582
		var isOptimizedBuild = true;
3583
		dojo.forEach(dojo.query("script"), function(i){
3584
			var src = i.getAttribute("src");
3585
			if(!src){ return; }
3586
 
3587
			if(src.indexOf("_base/_loader/bootstrap.js") != -1){
3588
				isOptimizedBuild = false;
3589
			}
3590
		});
3591
 
3592
		if(!isOptimizedBuild){
3593
			dojox.off.files.cache(dojo.moduleUrl("dojo", "_base.js").uri);
3594
			dojox.off.files.cache(dojo.moduleUrl("dojo", "_base/_loader/loader.js").uri);
3595
			dojox.off.files.cache(dojo.moduleUrl("dojo", "_base/_loader/bootstrap.js").uri);
3596
 
3597
			// FIXME: pull in the host environment file in a more generic way
3598
			// for other host environments
3599
			dojox.off.files.cache(dojo.moduleUrl("dojo", "_base/_loader/hostenv_browser.js").uri);
3600
		}
3601
 
3602
		// add anything that was brought in with a
3603
		// dojo.require() that resulted in a JavaScript
3604
		// URL being fetched
3605
 
3606
		// FIXME: modify dojo/_base/_loader/loader.js to
3607
		// expose a public API to get this information
3608
 
3609
		for(var i = 0; i < dojo._loadedUrls.length; i++){
3610
			dojox.off.files.cache(dojo._loadedUrls[i]);
3611
		}
3612
 
3613
		// FIXME: add the standard Dojo CSS file
3614
	},
3615
 
3616
	_save: function(){
3617
		// summary:
3618
		//		Causes the Dojo Offline framework to save its configuration
3619
		//		data into local storage.
3620
	},
3621
 
3622
	_load: function(callback){
3623
		// summary:
3624
		//		Causes the Dojo Offline framework to load its configuration
3625
		//		data from local storage
3626
		dojox.off.sync._load(callback);
3627
	}
3628
});
3629
 
3630
 
3631
// wait until the storage system is finished loading
3632
dojox.storage.manager.addOnLoad(dojo.hitch(dojox.off, "_onStorageLoad"));
3633
 
3634
// wait until the page is finished loading
3635
dojo.addOnLoad(dojox.off, "_onPageLoad");
3636
 
3637
}
3638
 
3639
if(!dojo._hasResource["dojox.off"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
3640
dojo._hasResource["dojox.off"] = true;
3641
dojo.provide("dojox.off");
3642
 
3643
 
3644
}
3645
 
3646
if(!dojo._hasResource["dojox.off.ui"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
3647
dojo._hasResource["dojox.off.ui"] = true;
3648
dojo.provide("dojox.off.ui");
3649
 
3650
 
3651
 
3652
 
3653
 
3654
// Author: Brad Neuberg, bkn3@columbia.edu, http://codinginparadise.org
3655
 
3656
// summary:
3657
//	dojox.off.ui provides a standard,
3658
//	default user-interface for a
3659
//	Dojo Offline Widget that can easily
3660
//	be dropped into applications that would
3661
//	like to work offline.
3662
dojo.mixin(dojox.off.ui, {
3663
	// appName: String
3664
	//	This application's name, such as "Foobar". Note that
3665
	//	this is a string, not HTML, so embedded markup will
3666
	//	not work, including entities. Only the following
3667
	//	characters are allowed: numbers, letters, and spaces.
3668
	//	You must set this property.
3669
	appName: "setme",
3670
 
3671
	// autoEmbed: boolean
3672
	//	For advanced usage; most developers can ignore this.
3673
	//	Whether to automatically auto-embed the default Dojo Offline
3674
	//	widget into this page; default is true.
3675
	autoEmbed: true,
3676
 
3677
	// autoEmbedID: String
3678
	//	For advanced usage; most developers can ignore this.
3679
	//	The ID of the DOM element that will contain our
3680
	//	Dojo Offline widget; defaults to the ID 'dot-widget'.
3681
	autoEmbedID: "dot-widget",
3682
 
3683
	// runLink: String
3684
	//	For advanced usage; most developers can ignore this.
3685
	//	The URL that should be navigated to to run this
3686
	//	application offline; this will be placed inside of a
3687
	//	link that the user can drag to their desktop and double
3688
	//	click. Note that this URL must exactly match the URL
3689
	//	of the main page of our resource that is offline for
3690
	//	it to be retrieved from the offline cache correctly.
3691
	//	For example, if you have cached your main page as
3692
	//	http://foobar.com/index.html, and you set this to
3693
	//	http://www.foobar.com/index.html, the run link will
3694
	//	not work. By default this value is automatically set to
3695
	//	the URL of this page, so it does not need to be set
3696
	//	manually unless you have unusual needs.
3697
	runLink: window.location.href,
3698
 
3699
	// runLinkTitle: String
3700
	//	For advanced usage; most developers can ignore this.
3701
	//	The text that will be inside of the link that a user
3702
	//	can drag to their desktop to run this application offline.
3703
	//	By default this is automatically set to "Run " plus your
3704
	//	application's name.
3705
	runLinkTitle: "Run Application",
3706
 
3707
	// learnHowPath: String
3708
	//	For advanced usage; most developers can ignore this.
3709
	//	The path to a web page that has information on
3710
	//	how to use this web app offline; defaults to
3711
	//	src/off/ui-template/learnhow.html, relative to
3712
	//	your Dojo installation. Make sure to set
3713
	//	dojo.to.ui.customLearnHowPath to true if you want
3714
	//	a custom Learn How page.
3715
	learnHowPath: dojo.moduleUrl("dojox", "off/resources/learnhow.html"),
3716
 
3717
	// customLearnHowPath: boolean
3718
	//	For advanced usage; most developers can ignore this.
3719
	//	Whether the developer is using their own custom page
3720
	//	for the Learn How instructional page; defaults to false.
3721
	//	Use in conjunction with dojox.off.ui.learnHowPath.
3722
	customLearnHowPath: false,
3723
 
3724
	htmlTemplatePath: dojo.moduleUrl("dojox", "off/resources/offline-widget.html").uri,
3725
	cssTemplatePath: dojo.moduleUrl("dojox", "off/resources/offline-widget.css").uri,
3726
	onlineImagePath: dojo.moduleUrl("dojox", "off/resources/greenball.png").uri,
3727
	offlineImagePath: dojo.moduleUrl("dojox", "off/resources/redball.png").uri,
3728
	rollerImagePath: dojo.moduleUrl("dojox", "off/resources/roller.gif").uri,
3729
	checkmarkImagePath: dojo.moduleUrl("dojox", "off/resources/checkmark.png").uri,
3730
	learnHowJSPath: dojo.moduleUrl("dojox", "off/resources/learnhow.js").uri,
3731
 
3732
	_initialized: false,
3733
 
3734
	onLoad: function(){
3735
		// summary:
3736
		//	A function that should be connected to allow your
3737
		//	application to know when Dojo Offline, the page, and
3738
		//	the Offline Widget are all initialized and ready to be
3739
		//	used:
3740
		//
3741
		//		dojo.connect(dojox.off.ui, "onLoad", someFunc)
3742
	},
3743
 
3744
	_initialize: function(){
3745
		//console.debug("dojox.off.ui._initialize");
3746
 
3747
		// make sure our app name is correct
3748
		if(this._validateAppName(this.appName) == false){
3749
			alert("You must set dojox.off.ui.appName; it can only contain "
3750
					+ "letters, numbers, and spaces; right now it "
3751
					+ "is incorrectly set to '" + dojox.off.ui.appName + "'");
3752
			dojox.off.enabled = false;
3753
			return;
3754
		}
3755
 
3756
		// set our run link text to its default
3757
		this.runLinkText = "Run " + this.appName;
3758
 
3759
		// setup our event listeners for Dojo Offline events
3760
		// to update our UI
3761
		dojo.connect(dojox.off, "onNetwork", this, "_onNetwork");
3762
		dojo.connect(dojox.off.sync, "onSync", this, "_onSync");
3763
 
3764
		// cache our default UI resources
3765
		dojox.off.files.cache([
3766
							this.htmlTemplatePath,
3767
							this.cssTemplatePath,
3768
							this.onlineImagePath,
3769
							this.offlineImagePath,
3770
							this.rollerImagePath,
3771
							this.checkmarkImagePath
3772
							]);
3773
 
3774
		// embed the offline widget UI
3775
		if(this.autoEmbed){
3776
			this._doAutoEmbed();
3777
		}
3778
	},
3779
 
3780
	_doAutoEmbed: function(){
3781
		// fetch our HTML for the offline widget
3782
 
3783
		// dispatch the request
3784
		dojo.xhrGet({
3785
			url:	 this.htmlTemplatePath,
3786
			handleAs:	"text",
3787
			error:		function(err){
3788
				dojox.off.enabled = false;
3789
				err = err.message||err;
3790
				alert("Error loading the Dojo Offline Widget from "
3791
						+ this.htmlTemplatePath + ": " + err);
3792
			},
3793
			load:		dojo.hitch(this, this._templateLoaded)
3794
		});
3795
	},
3796
 
3797
	_templateLoaded: function(data){
3798
		//console.debug("dojox.off.ui._templateLoaded");
3799
		// inline our HTML
3800
		var container = dojo.byId(this.autoEmbedID);
3801
		if(container){ container.innerHTML = data; }
3802
 
3803
		// fill out our image paths
3804
		this._initImages();
3805
 
3806
		// update our network indicator status ball
3807
		this._updateNetIndicator();
3808
 
3809
		// update our 'Learn How' text
3810
		this._initLearnHow();
3811
 
3812
		this._initialized = true;
3813
 
3814
		// check offline cache settings
3815
		if(!dojox.off.hasOfflineCache){
3816
			this._showNeedsOfflineCache();
3817
			return;
3818
		}
3819
 
3820
		// check to see if we need a browser restart
3821
		// to be able to use this web app offline
3822
		if(dojox.off.hasOfflineCache && dojox.off.browserRestart){
3823
			this._needsBrowserRestart();
3824
			return;
3825
		}else{
3826
			var browserRestart = dojo.byId("dot-widget-browser-restart");
3827
			if(browserRestart){ browserRestart.style.display = "none"; }
3828
		}
3829
 
3830
		// update our sync UI
3831
		this._updateSyncUI();
3832
 
3833
		// register our event listeners for our main buttons
3834
		this._initMainEvtHandlers();
3835
 
3836
		// if offline functionality is disabled, disable everything
3837
		this._setOfflineEnabled(dojox.off.enabled);
3838
 
3839
		// update our UI based on the state of the network
3840
		this._onNetwork(dojox.off.isOnline ? "online" : "offline");
3841
 
3842
		// try to go online
3843
		this._testNet();
3844
	},
3845
 
3846
	_testNet: function(){
3847
		dojox.off.goOnline(dojo.hitch(this, function(isOnline){
3848
			//console.debug("testNet callback, isOnline="+isOnline);
3849
 
3850
			// display our online/offline results
3851
			this._onNetwork(isOnline ? "online" : "offline");
3852
 
3853
			// indicate that our default UI
3854
			// and Dojo Offline are now ready to
3855
			// be used
3856
			this.onLoad();
3857
		}));
3858
	},
3859
 
3860
	_updateNetIndicator: function(){
3861
		var onlineImg = dojo.byId("dot-widget-network-indicator-online");
3862
		var offlineImg = dojo.byId("dot-widget-network-indicator-offline");
3863
		var titleText = dojo.byId("dot-widget-title-text");
3864
 
3865
		if(onlineImg && offlineImg){
3866
			if(dojox.off.isOnline == true){
3867
				onlineImg.style.display = "inline";
3868
				offlineImg.style.display = "none";
3869
			}else{
3870
				onlineImg.style.display = "none";
3871
				offlineImg.style.display = "inline";
3872
			}
3873
		}
3874
 
3875
		if(titleText){
3876
			if(dojox.off.isOnline){
3877
				titleText.innerHTML = "Online";
3878
			}else{
3879
				titleText.innerHTML = "Offline";
3880
			}
3881
		}
3882
	},
3883
 
3884
	_initLearnHow: function(){
3885
		var learnHow = dojo.byId("dot-widget-learn-how-link");
3886
 
3887
		if(!learnHow){ return; }
3888
 
3889
		if(!this.customLearnHowPath){
3890
			// add parameters to URL so the Learn How page
3891
			// can customize itself and display itself
3892
			// correctly based on framework settings
3893
			var dojoPath = djConfig.baseRelativePath;
3894
			this.learnHowPath += "?appName=" + encodeURIComponent(this.appName)
3895
									+ "&hasOfflineCache=" + dojox.off.hasOfflineCache
3896
									+ "&runLink=" + encodeURIComponent(this.runLink)
3897
									+ "&runLinkText=" + encodeURIComponent(this.runLinkText)
3898
									+ "&baseRelativePath=" + encodeURIComponent(dojoPath);
3899
 
3900
			// cache our Learn How JavaScript page and
3901
			// the HTML version with full query parameters
3902
			// so it is available offline without a cache miss
3903
			dojox.off.files.cache(this.learnHowJSPath);
3904
			dojox.off.files.cache(this.learnHowPath);
3905
		}
3906
 
3907
		learnHow.setAttribute("href", this.learnHowPath);
3908
 
3909
		var appName = dojo.byId("dot-widget-learn-how-app-name");
3910
 
3911
		if(!appName){ return; }
3912
 
3913
		appName.innerHTML = "";
3914
		appName.appendChild(document.createTextNode(this.appName));
3915
	},
3916
 
3917
	_validateAppName: function(appName){
3918
		if(!appName){ return false; }
3919
 
3920
		return (/^[a-z0-9 ]*$/i.test(appName));
3921
	},
3922
 
3923
	_updateSyncUI: function(){
3924
		var roller = dojo.byId("dot-roller");
3925
		var checkmark = dojo.byId("dot-success-checkmark");
3926
		var syncMessages = dojo.byId("dot-sync-messages");
3927
		var details = dojo.byId("dot-sync-details");
3928
		var cancel = dojo.byId("dot-sync-cancel");
3929
 
3930
		if(dojox.off.sync.isSyncing){
3931
			this._clearSyncMessage();
3932
 
3933
			if(roller){ roller.style.display = "inline"; }
3934
 
3935
			if(checkmark){ checkmark.style.display = "none"; }
3936
 
3937
			if(syncMessages){
3938
				dojo.removeClass(syncMessages, "dot-sync-error");
3939
			}
3940
 
3941
			if(details){ details.style.display = "none"; }
3942
 
3943
			if(cancel){ cancel.style.display = "inline"; }
3944
		}else{
3945
			if(roller){ roller.style.display = "none"; }
3946
 
3947
			if(cancel){ cancel.style.display = "none"; }
3948
 
3949
			if(syncMessages){
3950
				dojo.removeClass(syncMessages, "dot-sync-error");
3951
			}
3952
		}
3953
	},
3954
 
3955
	_setSyncMessage: function(message){
3956
		var syncMessage = dojo.byId("dot-sync-messages");
3957
		if(syncMessage){
3958
			// when used with Google Gears pre-release in Firefox/Mac OS X,
3959
			// the browser would crash when testing in Moxie
3960
			// if we set the message this way for some reason.
3961
			// Brad Neuberg, bkn3@columbia.edu
3962
			//syncMessage.innerHTML = message;
3963
 
3964
			while(syncMessage.firstChild){
3965
				syncMessage.removeChild(syncMessage.firstChild);
3966
			}
3967
			syncMessage.appendChild(document.createTextNode(message));
3968
		}
3969
	},
3970
 
3971
	_clearSyncMessage: function(){
3972
		this._setSyncMessage("");
3973
	},
3974
 
3975
	_initImages: function(){
3976
		var onlineImg = dojo.byId("dot-widget-network-indicator-online");
3977
		if(onlineImg){
3978
			onlineImg.setAttribute("src", this.onlineImagePath);
3979
		}
3980
 
3981
		var offlineImg = dojo.byId("dot-widget-network-indicator-offline");
3982
		if(offlineImg){
3983
			offlineImg.setAttribute("src", this.offlineImagePath);
3984
		}
3985
 
3986
		var roller = dojo.byId("dot-roller");
3987
		if(roller){
3988
			roller.setAttribute("src", this.rollerImagePath);
3989
		}
3990
 
3991
		var checkmark = dojo.byId("dot-success-checkmark");
3992
		if(checkmark){
3993
			checkmark.setAttribute("src", this.checkmarkImagePath);
3994
		}
3995
	},
3996
 
3997
	_showDetails: function(evt){
3998
		// cancel the button's default behavior
3999
		evt.preventDefault();
4000
		evt.stopPropagation();
4001
 
4002
		if(!dojox.off.sync.details.length){
4003
			return;
4004
		}
4005
 
4006
		// determine our HTML message to display
4007
		var html = "";
4008
		html += "<html><head><title>Sync Details</title><head><body>";
4009
		html += "<h1>Sync Details</h1>\n";
4010
		html += "<ul>\n";
4011
		for(var i = 0; i < dojox.off.sync.details.length; i++){
4012
			html += "<li>";
4013
			html += dojox.off.sync.details[i];
4014
			html += "</li>";
4015
		}
4016
		html += "</ul>\n";
4017
		html += "<a href='javascript:window.close()' "
4018
				 + "style='text-align: right; padding-right: 2em;'>"
4019
				 + "Close Window"
4020
				 + "</a>\n";
4021
		html += "</body></html>";
4022
 
4023
		// open a popup window with this message
4024
		var windowParams = "height=400,width=600,resizable=true,"
4025
							+ "scrollbars=true,toolbar=no,menubar=no,"
4026
							+ "location=no,directories=no,dependent=yes";
4027
 
4028
		var popup = window.open("", "SyncDetails", windowParams);
4029
 
4030
		if(!popup){ // aggressive popup blocker
4031
			alert("Please allow popup windows for this domain; can't display sync details window");
4032
			return;
4033
		}
4034
 
4035
		popup.document.open();
4036
		popup.document.write(html);
4037
		popup.document.close();
4038
 
4039
		// put the focus on the popup window
4040
		if(popup.focus){
4041
			popup.focus();
4042
		}
4043
	},
4044
 
4045
	_cancel: function(evt){
4046
		// cancel the button's default behavior
4047
		evt.preventDefault();
4048
		evt.stopPropagation();
4049
 
4050
		dojox.off.sync.cancel();
4051
	},
4052
 
4053
	_needsBrowserRestart: function(){
4054
		var browserRestart = dojo.byId("dot-widget-browser-restart");
4055
		if(browserRestart){
4056
			dojo.addClass(browserRestart, "dot-needs-browser-restart");
4057
		}
4058
 
4059
		var appName = dojo.byId("dot-widget-browser-restart-app-name");
4060
		if(appName){
4061
			appName.innerHTML = "";
4062
			appName.appendChild(document.createTextNode(this.appName));
4063
		}
4064
 
4065
		var status = dojo.byId("dot-sync-status");
4066
		if(status){
4067
			status.style.display = "none";
4068
		}
4069
	},
4070
 
4071
	_showNeedsOfflineCache: function(){
4072
		var widgetContainer = dojo.byId("dot-widget-container");
4073
		if(widgetContainer){
4074
			dojo.addClass(widgetContainer, "dot-needs-offline-cache");
4075
		}
4076
	},
4077
 
4078
	_hideNeedsOfflineCache: function(){
4079
		var widgetContainer = dojo.byId("dot-widget-container");
4080
		if(widgetContainer){
4081
			dojo.removeClass(widgetContainer, "dot-needs-offline-cache");
4082
		}
4083
	},
4084
 
4085
	_initMainEvtHandlers: function(){
4086
		var detailsButton = dojo.byId("dot-sync-details-button");
4087
		if(detailsButton){
4088
			dojo.connect(detailsButton, "onclick", this, this._showDetails);
4089
		}
4090
		var cancelButton = dojo.byId("dot-sync-cancel-button");
4091
		if(cancelButton){
4092
			dojo.connect(cancelButton, "onclick", this, this._cancel);
4093
		}
4094
	},
4095
 
4096
	_setOfflineEnabled: function(enabled){
4097
		var elems = [];
4098
		elems.push(dojo.byId("dot-sync-status"));
4099
 
4100
		for(var i = 0; i < elems.length; i++){
4101
			if(elems[i]){
4102
				elems[i].style.visibility =
4103
							(enabled ? "visible" : "hidden");
4104
			}
4105
		}
4106
	},
4107
 
4108
	_syncFinished: function(){
4109
		this._updateSyncUI();
4110
 
4111
		var checkmark = dojo.byId("dot-success-checkmark");
4112
		var details = dojo.byId("dot-sync-details");
4113
 
4114
		if(dojox.off.sync.successful == true){
4115
			this._setSyncMessage("Sync Successful");
4116
			if(checkmark){ checkmark.style.display = "inline"; }
4117
		}else if(dojox.off.sync.cancelled == true){
4118
			this._setSyncMessage("Sync Cancelled");
4119
 
4120
			if(checkmark){ checkmark.style.display = "none"; }
4121
		}else{
4122
			this._setSyncMessage("Sync Error");
4123
 
4124
			var messages = dojo.byId("dot-sync-messages");
4125
			if(messages){
4126
				dojo.addClass(messages, "dot-sync-error");
4127
			}
4128
 
4129
			if(checkmark){ checkmark.style.display = "none"; }
4130
		}
4131
 
4132
		if(dojox.off.sync.details.length && details){
4133
			details.style.display = "inline";
4134
		}
4135
	},
4136
 
4137
	_onFrameworkEvent: function(type, saveData){
4138
		if(type == "save"){
4139
			if(saveData.status == dojox.storage.FAILED && !saveData.isCoreSave){
4140
				alert("Please increase the amount of local storage available "
4141
						+ "to this application");
4142
				if(dojox.storage.hasSettingsUI()){
4143
					dojox.storage.showSettingsUI();
4144
				}
4145
 
4146
				// FIXME: Be able to know if storage size has changed
4147
				// due to user configuration
4148
			}
4149
		}else if(type == "coreOperationFailed"){
4150
			console.log("Application does not have permission to use Dojo Offline");
4151
 
4152
			if(!this._userInformed){
4153
				alert("This application will not work if Google Gears is not allowed to run");
4154
				this._userInformed = true;
4155
			}
4156
		}else if(type == "offlineCacheInstalled"){
4157
			// clear out the 'needs offline cache' info
4158
			this._hideNeedsOfflineCache();
4159
 
4160
			// check to see if we need a browser restart
4161
			// to be able to use this web app offline
4162
			if(dojox.off.hasOfflineCache == true
4163
				&& dojox.off.browserRestart == true){
4164
				this._needsBrowserRestart();
4165
				return;
4166
			}else{
4167
				var browserRestart = dojo.byId("dot-widget-browser-restart");
4168
				if(browserRestart){
4169
					browserRestart.style.display = "none";
4170
				}
4171
			}
4172
 
4173
			// update our sync UI
4174
			this._updateSyncUI();
4175
 
4176
			// register our event listeners for our main buttons
4177
			this._initMainEvtHandlers();
4178
 
4179
			// if offline is disabled, disable everything
4180
			this._setOfflineEnabled(dojox.off.enabled);
4181
 
4182
			// try to go online
4183
			this._testNet();
4184
		}
4185
	},
4186
 
4187
	_onSync: function(type){
4188
		//console.debug("ui, onSync="+type);
4189
		switch(type){
4190
			case "start":
4191
				this._updateSyncUI();
4192
				break;
4193
 
4194
			case "refreshFiles":
4195
				this._setSyncMessage("Downloading UI...");
4196
				break;
4197
 
4198
			case "upload":
4199
				this._setSyncMessage("Uploading new data...");
4200
				break;
4201
 
4202
			case "download":
4203
				this._setSyncMessage("Downloading new data...");
4204
				break;
4205
 
4206
			case "finished":
4207
				this._syncFinished();
4208
				break;
4209
 
4210
			case "cancel":
4211
				this._setSyncMessage("Canceling Sync...");
4212
				break;
4213
 
4214
			default:
4215
				dojo.warn("Programming error: "
4216
							+ "Unknown sync type in dojox.off.ui: " + type);
4217
				break;
4218
		}
4219
	},
4220
 
4221
	_onNetwork: function(type){
4222
		// summary:
4223
		//	Called when we go on- or off-line
4224
		// description:
4225
		//	When we go online or offline, this method is called to update
4226
		//	our UI. Default behavior is to update the Offline
4227
		//	Widget UI and to attempt a synchronization.
4228
		// type: String
4229
		//	"online" if we just moved online, and "offline" if we just
4230
		//	moved offline.
4231
 
4232
		if(!this._initialized){ return; }
4233
 
4234
		// update UI
4235
		this._updateNetIndicator();
4236
 
4237
		if(type == "offline"){
4238
			this._setSyncMessage("You are working offline");
4239
 
4240
			// clear old details
4241
			var details = dojo.byId("dot-sync-details");
4242
			if(details){ details.style.display = "none"; }
4243
 
4244
			// if we fell offline during a sync, hide
4245
			// the sync info
4246
			this._updateSyncUI();
4247
		}else{ // online
4248
			// synchronize, but pause for a few seconds
4249
			// so that the user can orient themselves
4250
			if(dojox.off.sync.autoSync){
4251
				window.setTimeout("dojox.off.sync.synchronize()", 1000);
4252
			}
4253
		}
4254
	}
4255
});
4256
 
4257
// register ourselves for low-level framework events
4258
dojo.connect(dojox.off, "onFrameworkEvent", dojox.off.ui, "_onFrameworkEvent");
4259
 
4260
// start our magic when the Dojo Offline framework is ready to go
4261
dojo.connect(dojox.off, "onLoad", dojox.off.ui, dojox.off.ui._initialize);
4262
 
4263
}
4264
 
4265
if(!dojo._hasResource["dojox.off.offline"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
4266
dojo._hasResource["dojox.off.offline"] = true;
4267
dojo.provide("dojox.off.offline");
4268
 
4269
 
4270
 
4271
 
4272
 
4273
 
4274
 
4275
}
4276