8a80973b212c5acad3d4d1f1854b4b26999ed032.svn-base 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. if (!dojo._hasResource["dojox.off.ui"]) { // _hasResource checks added by
  2. // build. Do not use _hasResource
  3. // directly in your code.
  4. dojo._hasResource["dojox.off.ui"] = true;
  5. dojo.provide("dojox.off.ui");
  6. dojo.require("dojox.storage.Provider");
  7. dojo.require("dojox.storage.manager");
  8. dojo.require("dojox.storage.GearsStorageProvider");
  9. // Author: Brad Neuberg, bkn3@columbia.edu, http://codinginparadise.org
  10. // summary:
  11. // dojox.off.ui provides a standard,
  12. // default user-interface for a
  13. // Dojo Offline Widget that can easily
  14. // be dropped into applications that would
  15. // like to work offline.
  16. dojo.mixin(dojox.off.ui, {
  17. // appName: String
  18. // This application's name, such as "Foobar". Note that
  19. // this is a string, not HTML, so embedded markup will
  20. // not work, including entities. Only the following
  21. // characters are allowed: numbers, letters, and spaces.
  22. // You must set this property.
  23. appName : "setme",
  24. // autoEmbed: boolean
  25. // For advanced usage; most developers can ignore this.
  26. // Whether to automatically auto-embed the default Dojo Offline
  27. // widget into this page; default is true.
  28. autoEmbed : true,
  29. // autoEmbedID: String
  30. // For advanced usage; most developers can ignore this.
  31. // The ID of the DOM element that will contain our
  32. // Dojo Offline widget; defaults to the ID 'dot-widget'.
  33. autoEmbedID : "dot-widget",
  34. // runLink: String
  35. // For advanced usage; most developers can ignore this.
  36. // The URL that should be navigated to to run this
  37. // application offline; this will be placed inside of a
  38. // link that the user can drag to their desktop and double
  39. // click. Note that this URL must exactly match the URL
  40. // of the main page of our resource that is offline for
  41. // it to be retrieved from the offline cache correctly.
  42. // For example, if you have cached your main page as
  43. // http://foobar.com/index.html, and you set this to
  44. // http://www.foobar.com/index.html, the run link will
  45. // not work. By default this value is automatically set to
  46. // the URL of this page, so it does not need to be set
  47. // manually unless you have unusual needs.
  48. runLink : window.location.href,
  49. // runLinkTitle: String
  50. // For advanced usage; most developers can ignore this.
  51. // The text that will be inside of the link that a user
  52. // can drag to their desktop to run this application offline.
  53. // By default this is automatically set to "Run " plus your
  54. // application's name.
  55. runLinkTitle : "Run Application",
  56. // learnHowPath: String
  57. // For advanced usage; most developers can ignore this.
  58. // The path to a web page that has information on
  59. // how to use this web app offline; defaults to
  60. // src/off/ui-template/learnhow.html, relative to
  61. // your Dojo installation. Make sure to set
  62. // dojo.to.ui.customLearnHowPath to true if you want
  63. // a custom Learn How page.
  64. learnHowPath : dojo.moduleUrl("dojox", "off/resources/learnhow.html"),
  65. // customLearnHowPath: boolean
  66. // For advanced usage; most developers can ignore this.
  67. // Whether the developer is using their own custom page
  68. // for the Learn How instructional page; defaults to false.
  69. // Use in conjunction with dojox.off.ui.learnHowPath.
  70. customLearnHowPath : false,
  71. htmlTemplatePath : dojo.moduleUrl("dojox",
  72. "off/resources/offline-widget.html").uri,
  73. cssTemplatePath : dojo.moduleUrl("dojox",
  74. "off/resources/offline-widget.css").uri,
  75. onlineImagePath : dojo
  76. .moduleUrl("dojox", "off/resources/greenball.png").uri,
  77. offlineImagePath : dojo.moduleUrl("dojox", "off/resources/redball.png").uri,
  78. rollerImagePath : dojo.moduleUrl("dojox", "off/resources/roller.gif").uri,
  79. checkmarkImagePath : dojo.moduleUrl("dojox",
  80. "off/resources/checkmark.png").uri,
  81. learnHowJSPath : dojo.moduleUrl("dojox", "off/resources/learnhow.js").uri,
  82. _initialized : false,
  83. onLoad : function() {
  84. // summary:
  85. // A function that should be connected to allow your
  86. // application to know when Dojo Offline, the page, and
  87. // the Offline Widget are all initialized and ready to be
  88. // used:
  89. //
  90. // dojo.connect(dojox.off.ui, "onLoad", someFunc)
  91. },
  92. _initialize : function() {
  93. // console.debug("dojox.off.ui._initialize");
  94. // make sure our app name is correct
  95. if (this._validateAppName(this.appName) == false) {
  96. alert("You must set dojox.off.ui.appName; it can only contain "
  97. + "letters, numbers, and spaces; right now it "
  98. + "is incorrectly set to '" + dojox.off.ui.appName
  99. + "'");
  100. dojox.off.enabled = false;
  101. return;
  102. }
  103. // set our run link text to its default
  104. this.runLinkText = "Run " + this.appName;
  105. // setup our event listeners for Dojo Offline events
  106. // to update our UI
  107. dojo.connect(dojox.off, "onNetwork", this, "_onNetwork");
  108. dojo.connect(dojox.off.sync, "onSync", this, "_onSync");
  109. // cache our default UI resources
  110. dojox.off.files.cache([this.htmlTemplatePath, this.cssTemplatePath,
  111. this.onlineImagePath, this.offlineImagePath,
  112. this.rollerImagePath, this.checkmarkImagePath]);
  113. // embed the offline widget UI
  114. if (this.autoEmbed) {
  115. this._doAutoEmbed();
  116. }
  117. },
  118. _doAutoEmbed : function() {
  119. // fetch our HTML for the offline widget
  120. // dispatch the request
  121. dojo.xhrGet({
  122. url : this.htmlTemplatePath,
  123. handleAs : "text",
  124. error : function(err) {
  125. dojox.off.enabled = false;
  126. err = err.message || err;
  127. alert("Error loading the Dojo Offline Widget from "
  128. + this.htmlTemplatePath + ": " + err);
  129. },
  130. load : dojo.hitch(this, this._templateLoaded)
  131. });
  132. },
  133. _templateLoaded : function(data) {
  134. // console.debug("dojox.off.ui._templateLoaded");
  135. // inline our HTML
  136. var container = dojo.byId(this.autoEmbedID);
  137. if (container) {
  138. container.innerHTML = data;
  139. }
  140. // fill out our image paths
  141. this._initImages();
  142. // update our network indicator status ball
  143. this._updateNetIndicator();
  144. // update our 'Learn How' text
  145. this._initLearnHow();
  146. this._initialized = true;
  147. // check offline cache settings
  148. if (!dojox.off.hasOfflineCache) {
  149. this._showNeedsOfflineCache();
  150. return;
  151. }
  152. // check to see if we need a browser restart
  153. // to be able to use this web app offline
  154. if (dojox.off.hasOfflineCache && dojox.off.browserRestart) {
  155. this._needsBrowserRestart();
  156. return;
  157. } else {
  158. var browserRestart = dojo.byId("dot-widget-browser-restart");
  159. if (browserRestart) {
  160. browserRestart.style.display = "none";
  161. }
  162. }
  163. // update our sync UI
  164. this._updateSyncUI();
  165. // register our event listeners for our main buttons
  166. this._initMainEvtHandlers();
  167. // if offline functionality is disabled, disable everything
  168. this._setOfflineEnabled(dojox.off.enabled);
  169. // update our UI based on the state of the network
  170. this._onNetwork(dojox.off.isOnline ? "online" : "offline");
  171. // try to go online
  172. this._testNet();
  173. },
  174. _testNet : function() {
  175. dojox.off.goOnline(dojo.hitch(this, function(isOnline) {
  176. // console.debug("testNet callback,
  177. // isOnline="+isOnline);
  178. // display our online/offline results
  179. this._onNetwork(isOnline ? "online" : "offline");
  180. // indicate that our default UI
  181. // and Dojo Offline are now ready to
  182. // be used
  183. this.onLoad();
  184. }));
  185. },
  186. _updateNetIndicator : function() {
  187. var onlineImg = dojo.byId("dot-widget-network-indicator-online");
  188. var offlineImg = dojo.byId("dot-widget-network-indicator-offline");
  189. var titleText = dojo.byId("dot-widget-title-text");
  190. if (onlineImg && offlineImg) {
  191. if (dojox.off.isOnline == true) {
  192. onlineImg.style.display = "inline";
  193. offlineImg.style.display = "none";
  194. } else {
  195. onlineImg.style.display = "none";
  196. offlineImg.style.display = "inline";
  197. }
  198. }
  199. if (titleText) {
  200. if (dojox.off.isOnline) {
  201. titleText.innerHTML = "Online";
  202. } else {
  203. titleText.innerHTML = "Offline";
  204. }
  205. }
  206. },
  207. _initLearnHow : function() {
  208. var learnHow = dojo.byId("dot-widget-learn-how-link");
  209. if (!learnHow) {
  210. return;
  211. }
  212. if (!this.customLearnHowPath) {
  213. // add parameters to URL so the Learn How page
  214. // can customize itself and display itself
  215. // correctly based on framework settings
  216. var dojoPath = djConfig.baseRelativePath;
  217. this.learnHowPath += "?appName="
  218. + encodeURIComponent(this.appName)
  219. + "&hasOfflineCache=" + dojox.off.hasOfflineCache
  220. + "&runLink=" + encodeURIComponent(this.runLink)
  221. + "&runLinkText="
  222. + encodeURIComponent(this.runLinkText)
  223. + "&baseRelativePath=" + encodeURIComponent(dojoPath);
  224. // cache our Learn How JavaScript page and
  225. // the HTML version with full query parameters
  226. // so it is available offline without a cache miss
  227. dojox.off.files.cache(this.learnHowJSPath);
  228. dojox.off.files.cache(this.learnHowPath);
  229. }
  230. learnHow.setAttribute("href", this.learnHowPath);
  231. var appName = dojo.byId("dot-widget-learn-how-app-name");
  232. if (!appName) {
  233. return;
  234. }
  235. appName.innerHTML = "";
  236. appName.appendChild(document.createTextNode(this.appName));
  237. },
  238. _validateAppName : function(appName) {
  239. if (!appName) {
  240. return false;
  241. }
  242. return (/^[a-z0-9 ]*$/i.test(appName));
  243. },
  244. _updateSyncUI : function() {
  245. var roller = dojo.byId("dot-roller");
  246. var checkmark = dojo.byId("dot-success-checkmark");
  247. var syncMessages = dojo.byId("dot-sync-messages");
  248. var details = dojo.byId("dot-sync-details");
  249. var cancel = dojo.byId("dot-sync-cancel");
  250. if (dojox.off.sync.isSyncing) {
  251. this._clearSyncMessage();
  252. if (roller) {
  253. roller.style.display = "inline";
  254. }
  255. if (checkmark) {
  256. checkmark.style.display = "none";
  257. }
  258. if (syncMessages) {
  259. dojo.removeClass(syncMessages, "dot-sync-error");
  260. }
  261. if (details) {
  262. details.style.display = "none";
  263. }
  264. if (cancel) {
  265. cancel.style.display = "inline";
  266. }
  267. } else {
  268. if (roller) {
  269. roller.style.display = "none";
  270. }
  271. if (cancel) {
  272. cancel.style.display = "none";
  273. }
  274. if (syncMessages) {
  275. dojo.removeClass(syncMessages, "dot-sync-error");
  276. }
  277. }
  278. },
  279. _setSyncMessage : function(message) {
  280. var syncMessage = dojo.byId("dot-sync-messages");
  281. if (syncMessage) {
  282. // when used with Google Gears pre-release in Firefox/Mac OS X,
  283. // the browser would crash when testing in Moxie
  284. // if we set the message this way for some reason.
  285. // Brad Neuberg, bkn3@columbia.edu
  286. // syncMessage.innerHTML = message;
  287. while (syncMessage.firstChild) {
  288. syncMessage.removeChild(syncMessage.firstChild);
  289. }
  290. syncMessage.appendChild(document.createTextNode(message));
  291. }
  292. },
  293. _clearSyncMessage : function() {
  294. this._setSyncMessage("");
  295. },
  296. _initImages : function() {
  297. var onlineImg = dojo.byId("dot-widget-network-indicator-online");
  298. if (onlineImg) {
  299. onlineImg.setAttribute("src", this.onlineImagePath);
  300. }
  301. var offlineImg = dojo.byId("dot-widget-network-indicator-offline");
  302. if (offlineImg) {
  303. offlineImg.setAttribute("src", this.offlineImagePath);
  304. }
  305. var roller = dojo.byId("dot-roller");
  306. if (roller) {
  307. roller.setAttribute("src", this.rollerImagePath);
  308. }
  309. var checkmark = dojo.byId("dot-success-checkmark");
  310. if (checkmark) {
  311. checkmark.setAttribute("src", this.checkmarkImagePath);
  312. }
  313. },
  314. _showDetails : function(evt) {
  315. // cancel the button's default behavior
  316. evt.preventDefault();
  317. evt.stopPropagation();
  318. if (!dojox.off.sync.details.length) {
  319. return;
  320. }
  321. // determine our HTML message to display
  322. var html = "";
  323. html += "<html><head><title>Sync Details</title><head><body>";
  324. html += "<h1>Sync Details</h1>\n";
  325. html += "<ul>\n";
  326. for (var i = 0; i < dojox.off.sync.details.length; i++) {
  327. html += "<li>";
  328. html += dojox.off.sync.details[i];
  329. html += "</li>";
  330. }
  331. html += "</ul>\n";
  332. html += "<a href='javascript:window.close()' "
  333. + "style='text-align: right; padding-right: 2em;'>"
  334. + "Close Window" + "</a>\n";
  335. html += "</body></html>";
  336. // open a popup window with this message
  337. var windowParams = "height=400,width=600,resizable=true,"
  338. + "scrollbars=true,toolbar=no,menubar=no,"
  339. + "location=no,directories=no,dependent=yes";
  340. var popup = window.open("", "SyncDetails", windowParams);
  341. if (!popup) { // aggressive popup blocker
  342. alert("Please allow popup windows for this domain; can't display sync details window");
  343. return;
  344. }
  345. popup.document.open();
  346. popup.document.write(html);
  347. popup.document.close();
  348. // put the focus on the popup window
  349. if (popup.focus) {
  350. popup.focus();
  351. }
  352. },
  353. _cancel : function(evt) {
  354. // cancel the button's default behavior
  355. evt.preventDefault();
  356. evt.stopPropagation();
  357. dojox.off.sync.cancel();
  358. },
  359. _needsBrowserRestart : function() {
  360. var browserRestart = dojo.byId("dot-widget-browser-restart");
  361. if (browserRestart) {
  362. dojo.addClass(browserRestart, "dot-needs-browser-restart");
  363. }
  364. var appName = dojo.byId("dot-widget-browser-restart-app-name");
  365. if (appName) {
  366. appName.innerHTML = "";
  367. appName.appendChild(document.createTextNode(this.appName));
  368. }
  369. var status = dojo.byId("dot-sync-status");
  370. if (status) {
  371. status.style.display = "none";
  372. }
  373. },
  374. _showNeedsOfflineCache : function() {
  375. var widgetContainer = dojo.byId("dot-widget-container");
  376. if (widgetContainer) {
  377. dojo.addClass(widgetContainer, "dot-needs-offline-cache");
  378. }
  379. },
  380. _hideNeedsOfflineCache : function() {
  381. var widgetContainer = dojo.byId("dot-widget-container");
  382. if (widgetContainer) {
  383. dojo.removeClass(widgetContainer, "dot-needs-offline-cache");
  384. }
  385. },
  386. _initMainEvtHandlers : function() {
  387. var detailsButton = dojo.byId("dot-sync-details-button");
  388. if (detailsButton) {
  389. dojo.connect(detailsButton, "onclick", this, this._showDetails);
  390. }
  391. var cancelButton = dojo.byId("dot-sync-cancel-button");
  392. if (cancelButton) {
  393. dojo.connect(cancelButton, "onclick", this, this._cancel);
  394. }
  395. },
  396. _setOfflineEnabled : function(enabled) {
  397. var elems = [];
  398. elems.push(dojo.byId("dot-sync-status"));
  399. for (var i = 0; i < elems.length; i++) {
  400. if (elems[i]) {
  401. elems[i].style.visibility = (enabled ? "visible" : "hidden");
  402. }
  403. }
  404. },
  405. _syncFinished : function() {
  406. this._updateSyncUI();
  407. var checkmark = dojo.byId("dot-success-checkmark");
  408. var details = dojo.byId("dot-sync-details");
  409. if (dojox.off.sync.successful == true) {
  410. this._setSyncMessage("Sync Successful");
  411. if (checkmark) {
  412. checkmark.style.display = "inline";
  413. }
  414. } else if (dojox.off.sync.cancelled == true) {
  415. this._setSyncMessage("Sync Cancelled");
  416. if (checkmark) {
  417. checkmark.style.display = "none";
  418. }
  419. } else {
  420. this._setSyncMessage("Sync Error");
  421. var messages = dojo.byId("dot-sync-messages");
  422. if (messages) {
  423. dojo.addClass(messages, "dot-sync-error");
  424. }
  425. if (checkmark) {
  426. checkmark.style.display = "none";
  427. }
  428. }
  429. if (dojox.off.sync.details.length && details) {
  430. details.style.display = "inline";
  431. }
  432. },
  433. _onFrameworkEvent : function(type, saveData) {
  434. if (type == "save") {
  435. if (saveData.status == dojox.storage.FAILED
  436. && !saveData.isCoreSave) {
  437. alert("Please increase the amount of local storage available "
  438. + "to this application");
  439. if (dojox.storage.hasSettingsUI()) {
  440. dojox.storage.showSettingsUI();
  441. }
  442. // FIXME: Be able to know if storage size has changed
  443. // due to user configuration
  444. }
  445. } else if (type == "coreOperationFailed") {
  446. console
  447. .log("Application does not have permission to use Dojo Offline");
  448. if (!this._userInformed) {
  449. alert("This application will not work if Google Gears is not allowed to run");
  450. this._userInformed = true;
  451. }
  452. } else if (type == "offlineCacheInstalled") {
  453. // clear out the 'needs offline cache' info
  454. this._hideNeedsOfflineCache();
  455. // check to see if we need a browser restart
  456. // to be able to use this web app offline
  457. if (dojox.off.hasOfflineCache == true
  458. && dojox.off.browserRestart == true) {
  459. this._needsBrowserRestart();
  460. return;
  461. } else {
  462. var browserRestart = dojo
  463. .byId("dot-widget-browser-restart");
  464. if (browserRestart) {
  465. browserRestart.style.display = "none";
  466. }
  467. }
  468. // update our sync UI
  469. this._updateSyncUI();
  470. // register our event listeners for our main buttons
  471. this._initMainEvtHandlers();
  472. // if offline is disabled, disable everything
  473. this._setOfflineEnabled(dojox.off.enabled);
  474. // try to go online
  475. this._testNet();
  476. }
  477. },
  478. _onSync : function(type) {
  479. // console.debug("ui, onSync="+type);
  480. switch (type) {
  481. case "start" :
  482. this._updateSyncUI();
  483. break;
  484. case "refreshFiles" :
  485. this._setSyncMessage("Downloading UI...");
  486. break;
  487. case "upload" :
  488. this._setSyncMessage("Uploading new data...");
  489. break;
  490. case "download" :
  491. this._setSyncMessage("Downloading new data...");
  492. break;
  493. case "finished" :
  494. this._syncFinished();
  495. break;
  496. case "cancel" :
  497. this._setSyncMessage("Canceling Sync...");
  498. break;
  499. default :
  500. dojo.warn("Programming error: "
  501. + "Unknown sync type in dojox.off.ui: " + type);
  502. break;
  503. }
  504. },
  505. _onNetwork : function(type) {
  506. // summary:
  507. // Called when we go on- or off-line
  508. // description:
  509. // When we go online or offline, this method is called to update
  510. // our UI. Default behavior is to update the Offline
  511. // Widget UI and to attempt a synchronization.
  512. // type: String
  513. // "online" if we just moved online, and "offline" if we just
  514. // moved offline.
  515. if (!this._initialized) {
  516. return;
  517. }
  518. // update UI
  519. this._updateNetIndicator();
  520. if (type == "offline") {
  521. this._setSyncMessage("You are working offline");
  522. // clear old details
  523. var details = dojo.byId("dot-sync-details");
  524. if (details) {
  525. details.style.display = "none";
  526. }
  527. // if we fell offline during a sync, hide
  528. // the sync info
  529. this._updateSyncUI();
  530. } else { // online
  531. // synchronize, but pause for a few seconds
  532. // so that the user can orient themselves
  533. if (dojox.off.sync.autoSync) {
  534. window.setTimeout("dojox.off.sync.synchronize()", 1000);
  535. }
  536. }
  537. }
  538. });
  539. // register ourselves for low-level framework events
  540. dojo.connect(dojox.off, "onFrameworkEvent", dojox.off.ui,
  541. "_onFrameworkEvent");
  542. // start our magic when the Dojo Offline framework is ready to go
  543. dojo.connect(dojox.off, "onLoad", dojox.off.ui, dojox.off.ui._initialize);
  544. }