SemanticScuttle/includes/js/dojox/off/files.js

455 lines
13 KiB
JavaScript
Raw Normal View History

if(!dojo._hasResource["dojox.off.files"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.off.files"] = true;
dojo.provide("dojox.off.files");
// Author: Brad Neuberg, bkn3@columbia.edu, http://codinginparadise.org
// summary:
// Helps maintain resources that should be
// available offline, such as CSS files.
// description:
// dojox.off.files makes it easy to indicate
// what resources should be available offline,
// such as CSS files, JavaScript, HTML, etc.
dojox.off.files = {
// versionURL: String
// An optional file, that if present, records the version
// of our bundle of files to make available offline. If this
// file is present, and we are not currently debugging,
// then we only refresh our offline files if the version has
// changed.
versionURL: "version.js",
// listOfURLs: Array
// For advanced usage; most developers can ignore this.
// Our list of URLs that will be cached and made available
// offline.
listOfURLs: [],
// refreshing: boolean
// For advanced usage; most developers can ignore this.
// Whether we are currently in the middle
// of refreshing our list of offline files.
refreshing: false,
_cancelID: null,
_error: false,
_errorMessages: [],
_currentFileIndex: 0,
_store: null,
_doSlurp: false,
slurp: function(){
// summary:
// Autoscans the page to find all resources to
// cache. This includes scripts, images, CSS, and hyperlinks
// to pages that are in the same scheme/port/host as this
// page. We also scan the embedded CSS of any stylesheets
// to find @import statements and url()'s.
// You should call this method from the top-level, outside of
// any functions and before the page loads:
//
// <script>
// dojo.require("dojox.sql");
// dojo.require("dojox.off");
// dojo.require("dojox.off.ui");
// dojo.require("dojox.off.sync");
//
// // configure how we should work offline
//
// // set our application name
// dojox.off.ui.appName = "Moxie";
//
// // automatically "slurp" the page and
// // capture the resources we need offline
// dojox.off.files.slurp();
//
// // tell Dojo Offline we are ready for it to initialize itself now
// // that we have finished configuring it for our application
// dojox.off.initialize();
// </script>
//
// Note that inline styles on elements are not handled (i.e.
// if you somehow have an inline style that uses a URL);
// object and embed tags are not scanned since their format
// differs based on type; and elements created by JavaScript
// after page load are not found. For these you must manually
// add them with a dojox.off.files.cache() method call.
// just schedule the slurp once the page is loaded and
// Dojo Offline is ready to slurp; dojox.off will call
// our _slurp() method before indicating it is finished
// loading
this._doSlurp = true;
},
cache: function(urlOrList){ /* void */
// summary:
// Caches a file or list of files to be available offline. This
// can either be a full URL, such as http://foobar.com/index.html,
// or a relative URL, such as ../index.html. This URL is not
// actually cached until dojox.off.sync.synchronize() is called.
// urlOrList: String or Array[]
// A URL of a file to cache or an Array of Strings of files to
// cache
//console.debug("dojox.off.files.cache, urlOrList="+urlOrList);
if(dojo.isString(urlOrList)){
var url = this._trimAnchor(urlOrList+"");
if(!this.isAvailable(url)){
this.listOfURLs.push(url);
}
}else if(urlOrList instanceof dojo._Url){
var url = this._trimAnchor(urlOrList.uri);
if(!this.isAvailable(url)){
this.listOfURLs.push(url);
}
}else{
dojo.forEach(urlOrList, function(url){
url = this._trimAnchor(url);
if(!this.isAvailable(url)){
this.listOfURLs.push(url);
}
}, this);
}
},
printURLs: function(){
// summary:
// A helper function that will dump and print out
// all of the URLs that are cached for offline
// availability. This can help with debugging if you
// are trying to make sure that all of your URLs are
// available offline
console.debug("The following URLs are cached for offline use:");
dojo.forEach(this.listOfURLs, function(i){
console.debug(i);
});
},
remove: function(url){ /* void */
// summary:
// Removes a URL from the list of files to cache.
// description:
// Removes a URL from the list of URLs to cache. Note that this
// does not actually remove the file from the offline cache;
// instead, it just prevents us from refreshing this file at a
// later time, so that it will naturally time out and be removed
// from the offline cache
// url: String
// The URL to remove
for(var i = 0; i < this.listOfURLs.length; i++){
if(this.listOfURLs[i] == url){
this.listOfURLs = this.listOfURLs.splice(i, 1);
break;
}
}
},
isAvailable: function(url){ /* boolean */
// summary:
// Determines whether the given resource is available offline.
// url: String
// The URL to check
for(var i = 0; i < this.listOfURLs.length; i++){
if(this.listOfURLs[i] == url){
return true;
}
}
return false;
},
refresh: function(callback){ /* void */
//console.debug("dojox.off.files.refresh");
// summary:
// For advanced usage; most developers can ignore this.
// Refreshes our list of offline resources,
// making them available offline.
// callback: Function
// A callback that receives two arguments: whether an error
// occurred, which is a boolean; and an array of error message strings
// with details on errors encountered. If no error occured then message is
// empty array with length 0.
try{
if(dojo.config.isDebug){
this.printURLs();
}
this.refreshing = true;
if(this.versionURL){
this._getVersionInfo(function(oldVersion, newVersion, justDebugged){
//console.warn("getVersionInfo, oldVersion="+oldVersion+", newVersion="+newVersion
// + ", justDebugged="+justDebugged+", isDebug="+dojo.config.isDebug);
if(dojo.config.isDebug || !newVersion || justDebugged
|| !oldVersion || oldVersion != newVersion){
console.warn("Refreshing offline file list");
this._doRefresh(callback, newVersion);
}else{
console.warn("No need to refresh offline file list");
callback(false, []);
}
});
}else{
console.warn("Refreshing offline file list");
this._doRefresh(callback);
}
}catch(e){
this.refreshing = false;
// can't refresh files -- core operation --
// fail fast
dojox.off.coreOpFailed = true;
dojox.off.enabled = false;
dojox.off.onFrameworkEvent("coreOperationFailed");
}
},
abortRefresh: function(){
// summary:
// For advanced usage; most developers can ignore this.
// Aborts and cancels a refresh.
if(!this.refreshing){
return;
}
this._store.abortCapture(this._cancelID);
this.refreshing = false;
},
_slurp: function(){
if(!this._doSlurp){
return;
}
var handleUrl = dojo.hitch(this, function(url){
if(this._sameLocation(url)){
this.cache(url);
}
});
handleUrl(window.location.href);
dojo.query("script").forEach(function(i){
try{
handleUrl(i.getAttribute("src"));
}catch(exp){
//console.debug("dojox.off.files.slurp 'script' error: "
// + exp.message||exp);
}
});
dojo.query("link").forEach(function(i){
try{
if(!i.getAttribute("rel")
|| i.getAttribute("rel").toLowerCase() != "stylesheet"){
return;
}
handleUrl(i.getAttribute("href"));
}catch(exp){
//console.debug("dojox.off.files.slurp 'link' error: "
// + exp.message||exp);
}
});
dojo.query("img").forEach(function(i){
try{
handleUrl(i.getAttribute("src"));
}catch(exp){
//console.debug("dojox.off.files.slurp 'img' error: "
// + exp.message||exp);
}
});
dojo.query("a").forEach(function(i){
try{
handleUrl(i.getAttribute("href"));
}catch(exp){
//console.debug("dojox.off.files.slurp 'a' error: "
// + exp.message||exp);
}
});
// FIXME: handle 'object' and 'embed' tag
// parse our style sheets for inline URLs and imports
dojo.forEach(document.styleSheets, function(sheet){
try{
if(sheet.cssRules){ // Firefox
dojo.forEach(sheet.cssRules, function(rule){
var text = rule.cssText;
if(text){
var matches = text.match(/url\(\s*([^\) ]*)\s*\)/i);
if(!matches){
return;
}
for(var i = 1; i < matches.length; i++){
handleUrl(matches[i])
}
}
});
}else if(sheet.cssText){ // IE
var matches;
var text = sheet.cssText.toString();
// unfortunately, using RegExp.exec seems to be flakey
// for looping across multiple lines on IE using the
// global flag, so we have to simulate it
var lines = text.split(/\f|\r|\n/);
for(var i = 0; i < lines.length; i++){
matches = lines[i].match(/url\(\s*([^\) ]*)\s*\)/i);
if(matches && matches.length){
handleUrl(matches[1]);
}
}
}
}catch(exp){
//console.debug("dojox.off.files.slurp stylesheet parse error: "
// + exp.message||exp);
}
});
//this.printURLs();
},
_sameLocation: function(url){
if(!url){ return false; }
// filter out anchors
if(url.length && url.charAt(0) == "#"){
return false;
}
// FIXME: dojo._Url should be made public;
// it's functionality is very useful for
// parsing URLs correctly, which is hard to
// do right
url = new dojo._Url(url);
// totally relative -- ../../someFile.html
if(!url.scheme && !url.port && !url.host){
return true;
}
// scheme relative with port specified -- brad.com:8080
if(!url.scheme && url.host && url.port
&& window.location.hostname == url.host
&& window.location.port == url.port){
return true;
}
// scheme relative with no-port specified -- brad.com
if(!url.scheme && url.host && !url.port
&& window.location.hostname == url.host
&& window.location.port == 80){
return true;
}
// else we have everything
return window.location.protocol == (url.scheme + ":")
&& window.location.hostname == url.host
&& (window.location.port == url.port || !window.location.port && !url.port);
},
_trimAnchor: function(url){
return url.replace(/\#.*$/, "");
},
_doRefresh: function(callback, newVersion){
// get our local server
var localServer;
try{
localServer = google.gears.factory.create("beta.localserver", "1.0");
}catch(exp){
dojo.setObject("google.gears.denied", true);
dojox.off.onFrameworkEvent("coreOperationFailed");
throw "Google Gears must be allowed to run";
}
var storeName = "dot_store_"
+ window.location.href.replace(/[^0-9A-Za-z_]/g, "_");
// clip at 64 characters, the max length of a resource store name
if(storeName.length >= 64){
storeName = storeName.substring(0, 63);
}
// refresh everything by simply removing
// any older stores
localServer.removeStore(storeName);
// open/create the resource store
localServer.openStore(storeName);
var store = localServer.createStore(storeName);
this._store = store;
// add our list of files to capture
var self = this;
this._currentFileIndex = 0;
this._cancelID = store.capture(this.listOfURLs, function(url, success, captureId){
//console.debug("store.capture, url="+url+", success="+success);
if(!success && self.refreshing){
self._cancelID = null;
self.refreshing = false;
var errorMsgs = [];
errorMsgs.push("Unable to capture: " + url);
callback(true, errorMsgs);
return;
}else if(success){
self._currentFileIndex++;
}
if(success && self._currentFileIndex >= self.listOfURLs.length){
self._cancelID = null;
self.refreshing = false;
if(newVersion){
dojox.storage.put("oldVersion", newVersion, null,
dojox.off.STORAGE_NAMESPACE);
}
dojox.storage.put("justDebugged", dojo.config.isDebug, null,
dojox.off.STORAGE_NAMESPACE);
callback(false, []);
}
});
},
_getVersionInfo: function(callback){
var justDebugged = dojox.storage.get("justDebugged",
dojox.off.STORAGE_NAMESPACE);
var oldVersion = dojox.storage.get("oldVersion",
dojox.off.STORAGE_NAMESPACE);
var newVersion = null;
callback = dojo.hitch(this, callback);
dojo.xhrGet({
url: this.versionURL + "?browserbust=" + new Date().getTime(),
timeout: 5 * 1000,
handleAs: "javascript",
error: function(err){
//console.warn("dojox.off.files._getVersionInfo, err=",err);
dojox.storage.remove("oldVersion", dojox.off.STORAGE_NAMESPACE);
dojox.storage.remove("justDebugged", dojox.off.STORAGE_NAMESPACE);
callback(oldVersion, newVersion, justDebugged);
},
load: function(data){
//console.warn("dojox.off.files._getVersionInfo, load=",data);
// some servers incorrectly return 404's
// as a real page
if(data){
newVersion = data;
}
callback(oldVersion, newVersion, justDebugged);
}
});
}
}
}