Subversion Repositories Applications.papyrus

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2150 mathias 1
if(!dojo._hasResource["dojox.off.files"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2
dojo._hasResource["dojox.off.files"] = true;
3
dojo.provide("dojox.off.files");
4
 
5
// Author: Brad Neuberg, bkn3@columbia.edu, http://codinginparadise.org
6
 
7
// summary:
8
//	Helps maintain resources that should be
9
//	available offline, such as CSS files.
10
// description:
11
//	dojox.off.files makes it easy to indicate
12
//	what resources should be available offline,
13
//	such as CSS files, JavaScript, HTML, etc.
14
dojox.off.files = {
15
	// versionURL: String
16
	//	An optional file, that if present, records the version
17
	//	of our bundle of files to make available offline. If this
18
	//	file is present, and we are not currently debugging,
19
	//	then we only refresh our offline files if the version has
20
	//	changed.
21
	versionURL: "version.js",
22
 
23
	// listOfURLs: Array
24
	//	For advanced usage; most developers can ignore this.
25
	//	Our list of URLs that will be cached and made available
26
	//	offline.
27
	listOfURLs: [],
28
 
29
	// refreshing: boolean
30
	//	For advanced usage; most developers can ignore this.
31
	//	Whether we are currently in the middle
32
	//	of refreshing our list of offline files.
33
	refreshing: false,
34
 
35
	_cancelID: null,
36
 
37
	_error: false,
38
	_errorMessages: [],
39
	_currentFileIndex: 0,
40
	_store: null,
41
	_doSlurp: false,
42
 
43
	slurp: function(){
44
		// summary:
45
		//	Autoscans the page to find all resources to
46
		//	cache. This includes scripts, images, CSS, and hyperlinks
47
		//	to pages that are in the same scheme/port/host as this
48
		//	page. We also scan the embedded CSS of any stylesheets
49
		//	to find @import statements and url()'s.
50
		//  You should call this method from the top-level, outside of
51
		//	any functions and before the page loads:
52
		//
53
		//	<script>
54
		//		dojo.require("dojox.sql");
55
		//		dojo.require("dojox.off");
56
		//		dojo.require("dojox.off.ui");
57
		//		dojo.require("dojox.off.sync");
58
		//
59
		//		// configure how we should work offline
60
		//
61
		//		// set our application name
62
		//		dojox.off.ui.appName = "Moxie";
63
		//
64
		//		// automatically "slurp" the page and
65
		//		// capture the resources we need offline
66
		//		dojox.off.files.slurp();
67
		//
68
		// 		// tell Dojo Offline we are ready for it to initialize itself now
69
		//		// that we have finished configuring it for our application
70
		//		dojox.off.initialize();
71
		//	</script>
72
		//
73
		//	Note that inline styles on elements are not handled (i.e.
74
		//	if you somehow have an inline style that uses a URL);
75
		//	object and embed tags are not scanned since their format
76
		//	differs based on type; and elements created by JavaScript
77
		//	after page load are not found. For these you must manually
78
		//	add them with a dojox.off.files.cache() method call.
79
 
80
		// just schedule the slurp once the page is loaded and
81
		// Dojo Offline is ready to slurp; dojox.off will call
82
		// our _slurp() method before indicating it is finished
83
		// loading
84
		this._doSlurp = true;
85
	},
86
 
87
	cache: function(urlOrList){ /* void */
88
		// summary:
89
		//		Caches a file or list of files to be available offline. This
90
		//		can either be a full URL, such as http://foobar.com/index.html,
91
		//		or a relative URL, such as ../index.html. This URL is not
92
		//		actually cached until dojox.off.sync.synchronize() is called.
93
		// urlOrList: String or Array[]
94
		//		A URL of a file to cache or an Array of Strings of files to
95
		//		cache
96
 
97
		//console.debug("dojox.off.files.cache, urlOrList="+urlOrList);
98
 
99
		if(dojo.isString(urlOrList)){
100
			var url = this._trimAnchor(urlOrList+"");
101
			if(!this.isAvailable(url)){
102
				this.listOfURLs.push(url);
103
			}
104
		}else if(urlOrList instanceof dojo._Url){
105
			var url = this._trimAnchor(urlOrList.uri);
106
			if(!this.isAvailable(url)){
107
				this.listOfURLs.push(url);
108
			}
109
		}else{
110
			dojo.forEach(urlOrList, function(url){
111
				url = this._trimAnchor(url);
112
				if(!this.isAvailable(url)){
113
					this.listOfURLs.push(url);
114
				}
115
			}, this);
116
		}
117
	},
118
 
119
	printURLs: function(){
120
		// summary:
121
		//	A helper function that will dump and print out
122
		//	all of the URLs that are cached for offline
123
		//	availability. This can help with debugging if you
124
		//	are trying to make sure that all of your URLs are
125
		//	available offline
126
		console.debug("The following URLs are cached for offline use:");
127
		dojo.forEach(this.listOfURLs, function(i){
128
			console.debug(i);
129
		});
130
	},
131
 
132
	remove: function(url){ /* void */
133
		// summary:
134
		//		Removes a URL from the list of files to cache.
135
		// description:
136
		//		Removes a URL from the list of URLs to cache. Note that this
137
		//		does not actually remove the file from the offline cache;
138
		//		instead, it just prevents us from refreshing this file at a
139
		//		later time, so that it will naturally time out and be removed
140
		//		from the offline cache
141
		// url: String
142
		//		The URL to remove
143
		for(var i = 0; i < this.listOfURLs.length; i++){
144
			if(this.listOfURLs[i] == url){
145
				this.listOfURLs = this.listOfURLs.splice(i, 1);
146
				break;
147
			}
148
		}
149
	},
150
 
151
	isAvailable: function(url){ /* boolean */
152
		// summary:
153
		//		Determines whether the given resource is available offline.
154
		// url: String
155
		//	The URL to check
156
		for(var i = 0; i < this.listOfURLs.length; i++){
157
			if(this.listOfURLs[i] == url){
158
				return true;
159
			}
160
		}
161
 
162
		return false;
163
	},
164
 
165
	refresh: function(callback){ /* void */
166
		//console.debug("dojox.off.files.refresh");
167
		// summary:
168
		//	For advanced usage; most developers can ignore this.
169
		//	Refreshes our list of offline resources,
170
		//	making them available offline.
171
		// callback: Function
172
		//	A callback that receives two arguments: whether an error
173
		//	occurred, which is a boolean; and an array of error message strings
174
		//	with details on errors encountered. If no error occured then message is
175
		//	empty array with length 0.
176
		try{
177
			if(djConfig.isDebug){
178
				this.printURLs();
179
			}
180
 
181
			this.refreshing = true;
182
 
183
			if(this.versionURL){
184
				this._getVersionInfo(function(oldVersion, newVersion, justDebugged){
185
					//console.warn("getVersionInfo, oldVersion="+oldVersion+", newVersion="+newVersion
186
					//				+ ", justDebugged="+justDebugged+", isDebug="+djConfig.isDebug);
187
					if(djConfig.isDebug || !newVersion || justDebugged
188
							|| !oldVersion || oldVersion != newVersion){
189
						console.warn("Refreshing offline file list");
190
						this._doRefresh(callback, newVersion);
191
					}else{
192
						console.warn("No need to refresh offline file list");
193
						callback(false, []);
194
					}
195
				});
196
			}else{
197
				console.warn("Refreshing offline file list");
198
				this._doRefresh(callback);
199
			}
200
		}catch(e){
201
			this.refreshing = false;
202
 
203
			// can't refresh files -- core operation --
204
			// fail fast
205
			dojox.off.coreOpFailed = true;
206
			dojox.off.enabled = false;
207
			dojox.off.onFrameworkEvent("coreOperationFailed");
208
		}
209
	},
210
 
211
	abortRefresh: function(){
212
		// summary:
213
		//	For advanced usage; most developers can ignore this.
214
		//	Aborts and cancels a refresh.
215
		if(!this.refreshing){
216
			return;
217
		}
218
 
219
		this._store.abortCapture(this._cancelID);
220
		this.refreshing = false;
221
	},
222
 
223
	_slurp: function(){
224
		if(!this._doSlurp){
225
			return;
226
		}
227
 
228
		var handleUrl = dojo.hitch(this, function(url){
229
			if(this._sameLocation(url)){
230
				this.cache(url);
231
			}
232
		});
233
 
234
		handleUrl(window.location.href);
235
 
236
		dojo.query("script").forEach(function(i){
237
			try{
238
				handleUrl(i.getAttribute("src"));
239
			}catch(exp){
240
				//console.debug("dojox.off.files.slurp 'script' error: "
241
				//				+ exp.message||exp);
242
			}
243
		});
244
 
245
		dojo.query("link").forEach(function(i){
246
			try{
247
				if(!i.getAttribute("rel")
248
					|| i.getAttribute("rel").toLowerCase() != "stylesheet"){
249
					return;
250
				}
251
 
252
				handleUrl(i.getAttribute("href"));
253
			}catch(exp){
254
				//console.debug("dojox.off.files.slurp 'link' error: "
255
				//				+ exp.message||exp);
256
			}
257
		});
258
 
259
		dojo.query("img").forEach(function(i){
260
			try{
261
				handleUrl(i.getAttribute("src"));
262
			}catch(exp){
263
				//console.debug("dojox.off.files.slurp 'img' error: "
264
				//				+ exp.message||exp);
265
			}
266
		});
267
 
268
		dojo.query("a").forEach(function(i){
269
			try{
270
				handleUrl(i.getAttribute("href"));
271
			}catch(exp){
272
				//console.debug("dojox.off.files.slurp 'a' error: "
273
				//				+ exp.message||exp);
274
			}
275
		});
276
 
277
		// FIXME: handle 'object' and 'embed' tag
278
 
279
		// parse our style sheets for inline URLs and imports
280
		dojo.forEach(document.styleSheets, function(sheet){
281
			try{
282
				if(sheet.cssRules){ // Firefox
283
					dojo.forEach(sheet.cssRules, function(rule){
284
						var text = rule.cssText;
285
						if(text){
286
							var matches = text.match(/url\(\s*([^\) ]*)\s*\)/i);
287
							if(!matches){
288
								return;
289
							}
290
 
291
							for(var i = 1; i < matches.length; i++){
292
								handleUrl(matches[i])
293
							}
294
						}
295
					});
296
				}else if(sheet.cssText){ // IE
297
					var matches;
298
					var text = sheet.cssText.toString();
299
					// unfortunately, using RegExp.exec seems to be flakey
300
					// for looping across multiple lines on IE using the
301
					// global flag, so we have to simulate it
302
					var lines = text.split(/\f|\r|\n/);
303
					for(var i = 0; i < lines.length; i++){
304
						matches = lines[i].match(/url\(\s*([^\) ]*)\s*\)/i);
305
						if(matches && matches.length){
306
							handleUrl(matches[1]);
307
						}
308
					}
309
				}
310
			}catch(exp){
311
				//console.debug("dojox.off.files.slurp stylesheet parse error: "
312
				//				+ exp.message||exp);
313
			}
314
		});
315
 
316
		//this.printURLs();
317
	},
318
 
319
	_sameLocation: function(url){
320
		if(!url){ return false; }
321
 
322
		// filter out anchors
323
		if(url.length && url.charAt(0) == "#"){
324
			return false;
325
		}
326
 
327
		// FIXME: dojo._Url should be made public;
328
		// it's functionality is very useful for
329
		// parsing URLs correctly, which is hard to
330
		// do right
331
		url = new dojo._Url(url);
332
 
333
		// totally relative -- ../../someFile.html
334
		if(!url.scheme && !url.port && !url.host){
335
			return true;
336
		}
337
 
338
		// scheme relative with port specified -- brad.com:8080
339
		if(!url.scheme && url.host && url.port
340
				&& window.location.hostname == url.host
341
				&& window.location.port == url.port){
342
			return true;
343
		}
344
 
345
		// scheme relative with no-port specified -- brad.com
346
		if(!url.scheme && url.host && !url.port
347
			&& window.location.hostname == url.host
348
			&& window.location.port == 80){
349
			return true;
350
		}
351
 
352
		// else we have everything
353
		return  window.location.protocol == (url.scheme + ":")
354
				&& window.location.hostname == url.host
355
				&& (window.location.port == url.port || !window.location.port && !url.port);
356
	},
357
 
358
	_trimAnchor: function(url){
359
		return url.replace(/\#.*$/, "");
360
	},
361
 
362
	_doRefresh: function(callback, newVersion){
363
		// get our local server
364
		var localServer;
365
		try{
366
			localServer = google.gears.factory.create("beta.localserver", "1.0");
367
		}catch(exp){
368
			dojo.setObject("google.gears.denied", true);
369
			dojox.off.onFrameworkEvent("coreOperationFailed");
370
			throw "Google Gears must be allowed to run";
371
		}
372
 
373
		var storeName = "dot_store_"
374
							+ window.location.href.replace(/[^0-9A-Za-z_]/g, "_");
375
 
376
		// refresh everything by simply removing
377
		// any older stores
378
		localServer.removeStore(storeName);
379
 
380
		// open/create the resource store
381
		localServer.openStore(storeName);
382
		var store = localServer.createStore(storeName);
383
		this._store = store;
384
 
385
		// add our list of files to capture
386
		var self = this;
387
		this._currentFileIndex = 0;
388
		this._cancelID = store.capture(this.listOfURLs, function(url, success, captureId){
389
			//console.debug("store.capture, url="+url+", success="+success);
390
			if(!success && self.refreshing){
391
				self._cancelID = null;
392
				self.refreshing = false;
393
				var errorMsgs = [];
394
				errorMsgs.push("Unable to capture: " + url);
395
				callback(true, errorMsgs);
396
				return;
397
			}else if(success){
398
				self._currentFileIndex++;
399
			}
400
 
401
			if(success && self._currentFileIndex >= self.listOfURLs.length){
402
				self._cancelID = null;
403
				self.refreshing = false;
404
				if(newVersion){
405
					dojox.storage.put("oldVersion", newVersion, null,
406
									dojox.off.STORAGE_NAMESPACE);
407
				}
408
				dojox.storage.put("justDebugged", djConfig.isDebug, null,
409
									dojox.off.STORAGE_NAMESPACE);
410
				callback(false, []);
411
			}
412
		});
413
	},
414
 
415
	_getVersionInfo: function(callback){
416
		var justDebugged = dojox.storage.get("justDebugged",
417
									dojox.off.STORAGE_NAMESPACE);
418
		var oldVersion = dojox.storage.get("oldVersion",
419
									dojox.off.STORAGE_NAMESPACE);
420
		var newVersion = null;
421
 
422
		callback = dojo.hitch(this, callback);
423
 
424
		dojo.xhrGet({
425
				url: this.versionURL + "?browserbust=" + new Date().getTime(),
426
				timeout: 5 * 1000,
427
				handleAs: "javascript",
428
				error: function(err){
429
					//console.warn("dojox.off.files._getVersionInfo, err=",err);
430
					dojox.storage.remove("oldVersion", dojox.off.STORAGE_NAMESPACE);
431
					dojox.storage.remove("justDebugged", dojox.off.STORAGE_NAMESPACE);
432
					callback(oldVersion, newVersion, justDebugged);
433
				},
434
				load: function(data){
435
					//console.warn("dojox.off.files._getVersionInfo, load=",data);
436
 
437
					// some servers incorrectly return 404's
438
					// as a real page
439
					if(data){
440
						newVersion = data;
441
					}
442
 
443
					callback(oldVersion, newVersion, justDebugged);
444
				}
445
		});
446
	}
447
}
448
 
449
}