microhistory.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  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;
  133. var key;
  134. for (key in obj) {
  135. if (obj.hasOwnProperty(key)) {
  136. size++;
  137. }
  138. }
  139. return size;
  140. },
  141. /**
  142. * @var hash Stores the content of the cached menus
  143. */
  144. data: {},
  145. /**
  146. * Saves a new menu in the cache
  147. *
  148. * @param string hash The hash (trimmed md5) of the menu to be saved
  149. * @param string content The HTML code of the menu to be saved
  150. *
  151. * @return void
  152. */
  153. add: function (hash, content) {
  154. if (this.size(this.data) > PMA_MicroHistory.MAX) {
  155. // when the cache grows, we remove the oldest entry
  156. var oldest;
  157. var key;
  158. var init = 0;
  159. for (var i in this.data) {
  160. if (this.data[i]) {
  161. if (! init || this.data[i].timestamp.getTime() < oldest.getTime()) {
  162. oldest = this.data[i].timestamp;
  163. key = i;
  164. init = 1;
  165. }
  166. }
  167. }
  168. delete this.data[key];
  169. }
  170. this.data[hash] = {
  171. content: content,
  172. timestamp: new Date()
  173. };
  174. },
  175. /**
  176. * Retrieves a menu given its hash
  177. *
  178. * @param string hash The hash of the menu to be retrieved
  179. *
  180. * @return string
  181. */
  182. get: function (hash) {
  183. if (this.data[hash]) {
  184. return this.data[hash].content;
  185. } else {
  186. // This should never happen as long as the number of stored menus
  187. // is larger or equal to the number of pages in the page cache
  188. return '';
  189. }
  190. },
  191. /**
  192. * Prepares part of the parameter string used during page requests,
  193. * this is necessary to tell the server which menus we have in the cache
  194. *
  195. * @return string
  196. */
  197. getRequestParam: function () {
  198. var param = '';
  199. var menuHashes = [];
  200. for (var i in this.data) {
  201. menuHashes.push(i);
  202. }
  203. var menuHashesParam = menuHashes.join('-');
  204. if (menuHashesParam) {
  205. param = PMA_commonParams.get('arg_separator') + 'menuHashes=' + menuHashesParam;
  206. }
  207. return param;
  208. },
  209. /**
  210. * Replaces the menu with new content
  211. *
  212. * @return void
  213. */
  214. replace: function (content) {
  215. $('#floating_menubar').html(content)
  216. // Remove duplicate wrapper
  217. // TODO: don't send it in the response
  218. .children().first().remove();
  219. $('#topmenu').menuResizer(PMA_mainMenuResizerCallback);
  220. }
  221. }
  222. };
  223. /**
  224. * URL hash management module.
  225. * Allows direct bookmarking and microhistory.
  226. */
  227. PMA_SetUrlHash = (function (jQuery, window) {
  228. 'use strict';
  229. /**
  230. * Indictaes whether we have already completed
  231. * the initialisation of the hash
  232. *
  233. * @access private
  234. */
  235. var ready = false;
  236. /**
  237. * Stores a hash that needed to be set when we were not ready
  238. *
  239. * @access private
  240. */
  241. var savedHash = '';
  242. /**
  243. * Flag to indicate if the change of hash was triggered
  244. * by a user pressing the back/forward button or if
  245. * the change was triggered internally
  246. *
  247. * @access private
  248. */
  249. var userChange = true;
  250. // Fix favicon disappearing in Firefox when setting location.hash
  251. function resetFavicon () {
  252. if (navigator.userAgent.indexOf('Firefox') > -1) {
  253. // Move the link tags for the favicon to the bottom
  254. // of the head element to force a reload of the favicon
  255. $('head > link[href="favicon\\.ico"]').appendTo('head');
  256. }
  257. }
  258. /**
  259. * Sets the hash part of the URL
  260. *
  261. * @access public
  262. */
  263. function setUrlHash (index, hash) {
  264. /*
  265. * Known problem:
  266. * Setting hash leads to reload in webkit:
  267. * http://www.quirksmode.org/bugreports/archives/2005/05/Safari_13_visual_anomaly_with_windowlocationhref.html
  268. *
  269. * so we expect that users are not running an ancient Safari version
  270. */
  271. userChange = false;
  272. if (ready) {
  273. window.location.hash = 'PMAURL-' + index + ':' + hash;
  274. resetFavicon();
  275. } else {
  276. savedHash = 'PMAURL-' + index + ':' + hash;
  277. }
  278. }
  279. /**
  280. * Start initialisation
  281. */
  282. var urlhash = window.location.hash;
  283. if (urlhash.substring(0, 8) === '#PMAURL-') {
  284. // We have a valid hash, let's redirect the user
  285. // to the page that it's pointing to
  286. var colon_position = urlhash.indexOf(':');
  287. var questionmark_position = urlhash.indexOf('?');
  288. if (colon_position !== -1 && questionmark_position !== -1 && colon_position < questionmark_position) {
  289. var hash_url = urlhash.substring(colon_position + 1, questionmark_position);
  290. if (PMA_gotoWhitelist.indexOf(hash_url) !== -1) {
  291. window.location = urlhash.substring(
  292. colon_position + 1
  293. );
  294. }
  295. }
  296. } else {
  297. // We don't have a valid hash, so we'll set it up
  298. // when the page finishes loading
  299. jQuery(function () {
  300. /* Check if we should set URL */
  301. if (savedHash !== '') {
  302. window.location.hash = savedHash;
  303. savedHash = '';
  304. resetFavicon();
  305. }
  306. // Indicate that we're done initialising
  307. ready = true;
  308. });
  309. }
  310. /**
  311. * Register an event handler for when the url hash changes
  312. */
  313. jQuery(function () {
  314. jQuery(window).hashchange(function () {
  315. if (userChange === false) {
  316. // Ignore internally triggered hash changes
  317. userChange = true;
  318. } else if (/^#PMAURL-\d+:/.test(window.location.hash)) {
  319. // Change page if the hash changed was triggered by a user action
  320. var index = window.location.hash.substring(
  321. 8, window.location.hash.indexOf(':')
  322. );
  323. PMA_MicroHistory.navigate(index);
  324. }
  325. });
  326. });
  327. /**
  328. * Publicly exposes a reference to the otherwise private setUrlHash function
  329. */
  330. return setUrlHash;
  331. }(jQuery, window));