summaryrefslogtreecommitdiff
path: root/includes/js/dojox/off/_common.js
blob: 005cd31a375439d69da70a0aa37e313f1b9768ca (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
if(!dojo._hasResource["dojox.off._common"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.off._common"] = true;
dojo.provide("dojox.off._common");

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

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

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

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

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

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


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

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

}