microhistory.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. /* vim: set expandtab sw=4 ts=4 sts=4: */
  2. /**
  3. * An implementation of a client-side page cache.
  4. * This object also uses the cache to provide a simple microhistory,
  5. * that is the ability to use the back and forward buttons in the browser
  6. */
  7. PMA_MicroHistory = {
  8. /**
  9. * @var int The maximum number of pages to keep in the cache
  10. */
  11. MAX: 6,
  12. /**
  13. * @var object A hash used to prime the cache with data about the initially
  14. * loaded page. This is set in the footer, and then loaded
  15. * by a double-queued event further down this file.
  16. */
  17. primer: {},
  18. /**
  19. * @var array Stores the content of the cached pages
  20. */
  21. pages: [],
  22. /**
  23. * @var int The index of the currently loaded page
  24. * This is used to know at which point in the history we are
  25. */
  26. current: 0,
  27. /**
  28. * Saves a new page in the cache
  29. *
  30. * @param string hash The hash part of the url that is being loaded
  31. * @param array scripts A list of scripts that is required for the page
  32. * @param string menu A hash that links to a menu stored
  33. * in a dedicated menu cache
  34. * @param array params A list of parameters used by PMA_commonParams()
  35. * @param string rel A relationship to the current page:
  36. * 'samepage': Forces the response to be treated as
  37. * the same page as the current one
  38. * 'newpage': Forces the response to be treated as
  39. * a new page
  40. * undefined: Default behaviour, 'samepage' if the
  41. * selflinks of the two pages are the same.
  42. * 'newpage' otherwise
  43. *
  44. * @return void
  45. */
  46. add: function (hash, scripts, menu, params, rel) {
  47. if (this.pages.length > PMA_MicroHistory.MAX) {
  48. // Trim the cache, to the maximum number of allowed entries
  49. // This way we will have a cached menu for every page
  50. for (var i = 0; i < this.pages.length - this.MAX; i++) {
  51. delete this.pages[i];
  52. }
  53. }
  54. while (this.current < this.pages.length) {
  55. // trim the cache if we went back in the history
  56. // and are now going forward again
  57. this.pages.pop();
  58. }
  59. if (rel === 'newpage' ||
  60. (
  61. typeof rel === 'undefined' && (
  62. typeof this.pages[this.current - 1] === 'undefined' ||
  63. this.pages[this.current - 1].hash !== hash
  64. )
  65. )
  66. ) {
  67. this.pages.push({
  68. hash: hash,
  69. content: $('#page_content').html(),
  70. scripts: scripts,
  71. selflink: $('#selflink').html(),
  72. menu: menu,
  73. params: params
  74. });
  75. PMA_SetUrlHash(this.current, hash);
  76. this.current++;
  77. }
  78. },
  79. /**
  80. * Restores a page from the cache. This is called when the hash
  81. * part of the url changes and it's structure appears to be valid
  82. *
  83. * @param string index Which page from the history to load
  84. *
  85. * @return void
  86. */
  87. navigate: function (index) {
  88. if (typeof this.pages[index] === 'undefined' ||
  89. typeof this.pages[index].content === 'undefined' ||
  90. typeof this.pages[index].menu === 'undefined' ||
  91. ! PMA_MicroHistory.menus.get(this.pages[index].menu)
  92. ) {
  93. PMA_ajaxShowMessage(
  94. '<div class="error">' + PMA_messages.strInvalidPage + '</div>',
  95. false
  96. );
  97. } else {
  98. AJAX.active = true;
  99. var record = this.pages[index];
  100. AJAX.scriptHandler.reset(function () {
  101. $('#page_content').html(record.content);
  102. $('#selflink').html(record.selflink);
  103. PMA_MicroHistory.menus.replace(PMA_MicroHistory.menus.get(record.menu));
  104. PMA_commonParams.setAll(record.params);
  105. AJAX.scriptHandler.load(record.scripts);
  106. PMA_MicroHistory.current = ++index;
  107. });
  108. }
  109. },
  110. /**
  111. * Resaves the content of the current page in the cache.
  112. * Necessary in order not to show the user some outdated version of the page
  113. *
  114. * @return void
  115. */
  116. update: function () {
  117. var page = this.pages[this.current - 1];
  118. if (page) {
  119. page.content = $('#page_content').html();
  120. }
  121. },
  122. /**
  123. * @var object Dedicated menu cache
  124. */
  125. menus: {
  126. /**
  127. * Returns the number of items in an associative array
  128. *
  129. * @return int
  130. */
  131. size: function (obj) {
  132. var size = 0, key;
  133. for (key in obj) {
  134. if (obj.hasOwnProperty(key)) {
  135. size++;
  136. }
  137. }
  138. return size;
  139. },
  140. /**
  141. * @var hash Stores the content of the cached menus
  142. */
  143. data: {},
  144. /**
  145. * Saves a new menu in the cache
  146. *
  147. * @param string hash The hash (trimmed md5) of the menu to be saved
  148. * @param string content The HTML code of the menu to be saved
  149. *
  150. * @return void
  151. */
  152. add: function (hash, content) {
  153. if (this.size(this.data) > PMA_MicroHistory.MAX) {
  154. // when the cache grows, we remove the oldest entry
  155. var oldest, key, init = 0;
  156. for (var i in this.data) {
  157. if (this.data[i]) {
  158. if (! init || this.data[i].timestamp.getTime() < oldest.getTime()) {
  159. oldest = this.data[i].timestamp;
  160. key = i;
  161. init = 1;
  162. }
  163. }
  164. }
  165. delete this.data[key];
  166. }
  167. this.data[hash] = {
  168. content: content,
  169. timestamp: new Date()
  170. };
  171. },
  172. /**
  173. * Retrieves a menu given its hash
  174. *
  175. * @param string hash The hash of the menu to be retrieved
  176. *
  177. * @return string
  178. */
  179. get: function (hash) {
  180. if (this.data[hash]) {
  181. return this.data[hash].content;
  182. } else {
  183. // This should never happen as long as the number of stored menus
  184. // is larger or equal to the number of pages in the page cache
  185. return '';
  186. }
  187. },
  188. /**
  189. * Prepares part of the parameter string used during page requests,
  190. * this is necessary to tell the server which menus we have in the cache
  191. *
  192. * @return string
  193. */
  194. getRequestParam: function () {
  195. var param = '';
  196. var menuHashes = [];
  197. for (var i in this.data) {
  198. menuHashes.push(i);
  199. }
  200. var menuHashesParam = menuHashes.join('-');
  201. if (menuHashesParam) {
  202. param = '&menuHashes=' + menuHashesParam;
  203. }
  204. return param;
  205. },
  206. /**
  207. * Replaces the menu with new content
  208. *
  209. * @return void
  210. */
  211. replace: function (content) {
  212. $('#floating_menubar').html(content)
  213. // Remove duplicate wrapper
  214. // TODO: don't send it in the response
  215. .children().first().remove();
  216. $('#topmenu').menuResizer(PMA_mainMenuResizerCallback);
  217. }
  218. }
  219. };
  220. /**
  221. * URL hash management module.
  222. * Allows direct bookmarking and microhistory.
  223. */
  224. PMA_SetUrlHash = (function (jQuery, window) {
  225. "use strict";
  226. /**
  227. * Indictaes whether we have already completed
  228. * the initialisation of the hash
  229. *
  230. * @access private
  231. */
  232. var ready = false;
  233. /**
  234. * Stores a hash that needed to be set when we were not ready
  235. *
  236. * @access private
  237. */
  238. var savedHash = "";
  239. /**
  240. * Flag to indicate if the change of hash was triggered
  241. * by a user pressing the back/forward button or if
  242. * the change was triggered internally
  243. *
  244. * @access private
  245. */
  246. var userChange = true;
  247. // Fix favicon disappearing in Firefox when setting location.hash
  248. function resetFavicon() {
  249. if (navigator.userAgent.indexOf('Firefox') > -1) {
  250. // Move the link tags for the favicon to the bottom
  251. // of the head element to force a reload of the favicon
  252. $('head > link[href=favicon\\.ico]').appendTo('head');
  253. }
  254. }
  255. /**
  256. * Sets the hash part of the URL
  257. *
  258. * @access public
  259. */
  260. function setUrlHash(index, hash) {
  261. /*
  262. * Known problem:
  263. * Setting hash leads to reload in webkit:
  264. * http://www.quirksmode.org/bugreports/archives/2005/05/Safari_13_visual_anomaly_with_windowlocationhref.html
  265. *
  266. * so we expect that users are not running an ancient Safari version
  267. */
  268. userChange = false;
  269. if (ready) {
  270. window.location.hash = "PMAURL-" + index + ":" + hash;
  271. resetFavicon();
  272. } else {
  273. savedHash = "PMAURL-" + index + ":" + hash;
  274. }
  275. }
  276. /**
  277. * Start initialisation
  278. */
  279. if (window.location.hash.substring(0, 8) == '#PMAURL-') {
  280. // We have a valid hash, let's redirect the user
  281. // to the page that it's pointing to
  282. var colon_position = window.location.hash.indexOf(':');
  283. var questionmark_position = window.location.hash.indexOf('?');
  284. if (colon_position != -1 && questionmark_position != -1 && colon_position < questionmark_position) {
  285. var hash_url = window.location.hash.substring(colon_position + 1, questionmark_position);
  286. if (PMA_gotoWhitelist.indexOf(hash_url) != -1) {
  287. window.location = window.location.hash.substring(
  288. colon_position + 1
  289. );
  290. }
  291. }
  292. } else {
  293. // We don't have a valid hash, so we'll set it up
  294. // when the page finishes loading
  295. jQuery(function () {
  296. /* Check if we should set URL */
  297. if (savedHash !== "") {
  298. window.location.hash = savedHash;
  299. savedHash = "";
  300. resetFavicon();
  301. }
  302. // Indicate that we're done initialising
  303. ready = true;
  304. });
  305. }
  306. /**
  307. * Register an event handler for when the url hash changes
  308. */
  309. jQuery(function () {
  310. jQuery(window).hashchange(function () {
  311. if (userChange === false) {
  312. // Ignore internally triggered hash changes
  313. userChange = true;
  314. } else if (/^#PMAURL-\d+:/.test(window.location.hash)) {
  315. // Change page if the hash changed was triggered by a user action
  316. var index = window.location.hash.substring(
  317. 8, window.location.hash.indexOf(':')
  318. );
  319. PMA_MicroHistory.navigate(index);
  320. }
  321. });
  322. });
  323. /**
  324. * Publicly exposes a reference to the otherwise private setUrlHash function
  325. */
  326. return setUrlHash;
  327. })(jQuery, window);