/tags/release/7020/design/htmlmock/javascript/dojox/off/files.xd.js

http://aipo.googlecode.com/ · JavaScript · 453 lines · 257 code · 54 blank · 142 comment · 68 complexity · 13888b2ada30f5b7728dfaa37c9b29ae MD5 · raw file

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