microhistory.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. "use strict";
  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. var 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 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 add(hash, scripts, menu, params, rel) {
  47. if (this.pages.length > 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' || typeof rel === 'undefined' && (typeof this.pages[this.current - 1] === 'undefined' || this.pages[this.current - 1].hash !== hash)) {
  60. this.pages.push({
  61. hash: hash,
  62. content: $('#page_content').html(),
  63. scripts: scripts,
  64. selflink: $('#selflink').html(),
  65. menu: menu,
  66. params: params
  67. });
  68. MicroHistory.setUrlHash(this.current, hash);
  69. this.current++;
  70. }
  71. },
  72. /**
  73. * Restores a page from the cache. This is called when the hash
  74. * part of the url changes and it's structure appears to be valid
  75. *
  76. * @param string index Which page from the history to load
  77. *
  78. * @return void
  79. */
  80. navigate: function navigate(index) {
  81. var localIndex = index;
  82. if (typeof this.pages[localIndex] === 'undefined' || typeof this.pages[localIndex].content === 'undefined' || typeof this.pages[localIndex].menu === 'undefined' || !MicroHistory.menus.get(this.pages[localIndex].menu)) {
  83. Functions.ajaxShowMessage('<div class="alert alert-danger" role="alert">' + Messages.strInvalidPage + '</div>', false);
  84. } else {
  85. AJAX.active = true;
  86. var record = this.pages[localIndex];
  87. AJAX.scriptHandler.reset(function () {
  88. $('#page_content').html(record.content);
  89. $('#selflink').html(record.selflink);
  90. MicroHistory.menus.replace(MicroHistory.menus.get(record.menu));
  91. CommonParams.setAll(record.params);
  92. AJAX.scriptHandler.load(record.scripts);
  93. MicroHistory.current = ++localIndex;
  94. });
  95. }
  96. },
  97. /**
  98. * Resaves the content of the current page in the cache.
  99. * Necessary in order not to show the user some outdated version of the page
  100. *
  101. * @return void
  102. */
  103. update: function update() {
  104. var page = this.pages[this.current - 1];
  105. if (page) {
  106. page.content = $('#page_content').html();
  107. }
  108. },
  109. /**
  110. * @var object Dedicated menu cache
  111. */
  112. menus: {
  113. /**
  114. * Returns the number of items in an associative array
  115. *
  116. * @return int
  117. */
  118. size: function size(obj) {
  119. var size = 0;
  120. var key;
  121. for (key in obj) {
  122. if (obj.hasOwnProperty(key)) {
  123. size++;
  124. }
  125. }
  126. return size;
  127. },
  128. /**
  129. * @var hash Stores the content of the cached menus
  130. */
  131. data: {},
  132. /**
  133. * Saves a new menu in the cache
  134. *
  135. * @param string hash The hash (trimmed md5) of the menu to be saved
  136. * @param string content The HTML code of the menu to be saved
  137. *
  138. * @return void
  139. */
  140. add: function add(hash, content) {
  141. if (this.size(this.data) > MicroHistory.MAX) {
  142. // when the cache grows, we remove the oldest entry
  143. var oldest;
  144. var key;
  145. var init = 0;
  146. for (var i in this.data) {
  147. if (this.data[i]) {
  148. if (!init || this.data[i].timestamp.getTime() < oldest.getTime()) {
  149. oldest = this.data[i].timestamp;
  150. key = i;
  151. init = 1;
  152. }
  153. }
  154. }
  155. delete this.data[key];
  156. }
  157. this.data[hash] = {
  158. content: content,
  159. timestamp: new Date()
  160. };
  161. },
  162. /**
  163. * Retrieves a menu given its hash
  164. *
  165. * @param string hash The hash of the menu to be retrieved
  166. *
  167. * @return string
  168. */
  169. get: function get(hash) {
  170. if (this.data[hash]) {
  171. return this.data[hash].content;
  172. } else {
  173. // This should never happen as long as the number of stored menus
  174. // is larger or equal to the number of pages in the page cache
  175. return '';
  176. }
  177. },
  178. /**
  179. * Prepares part of the parameter string used during page requests,
  180. * this is necessary to tell the server which menus we have in the cache
  181. *
  182. * @return string
  183. */
  184. getRequestParam: function getRequestParam() {
  185. var param = '';
  186. var menuHashes = [];
  187. for (var i in this.data) {
  188. menuHashes.push(i);
  189. }
  190. var menuHashesParam = menuHashes.join('-');
  191. if (menuHashesParam) {
  192. param = CommonParams.get('arg_separator') + 'menuHashes=' + menuHashesParam;
  193. }
  194. return param;
  195. },
  196. /**
  197. * Replaces the menu with new content
  198. *
  199. * @return void
  200. */
  201. replace: function replace(content) {
  202. $('#floating_menubar').html(content) // Remove duplicate wrapper
  203. // TODO: don't send it in the response
  204. .children().first().remove();
  205. $('#topmenu').menuResizer(Functions.mainMenuResizerCallback);
  206. }
  207. }
  208. };
  209. /**
  210. * URL hash management module.
  211. * Allows direct bookmarking and microhistory.
  212. */
  213. MicroHistory.setUrlHash = function (jQuery, window) {
  214. 'use strict';
  215. /**
  216. * Indicates whether we have already completed
  217. * the initialization of the hash
  218. *
  219. * @access private
  220. */
  221. var ready = false;
  222. /**
  223. * Stores a hash that needed to be set when we were not ready
  224. *
  225. * @access private
  226. */
  227. var savedHash = '';
  228. /**
  229. * Flag to indicate if the change of hash was triggered
  230. * by a user pressing the back/forward button or if
  231. * the change was triggered internally
  232. *
  233. * @access private
  234. */
  235. var userChange = true; // Fix favicon disappearing in Firefox when setting location.hash
  236. function resetFavicon() {
  237. if (navigator.userAgent.indexOf('Firefox') > -1) {
  238. // Move the link tags for the favicon to the bottom
  239. // of the head element to force a reload of the favicon
  240. $('head > link[href="favicon\\.ico"]').appendTo('head');
  241. }
  242. }
  243. /**
  244. * Sets the hash part of the URL
  245. *
  246. * @access public
  247. */
  248. function setUrlHash(index, hash) {
  249. /*
  250. * Known problem:
  251. * Setting hash leads to reload in webkit:
  252. * https://www.quirksmode.org/bugreports/archives/2005/05/Safari_13_visual_anomaly_with_windowlocationhref.html
  253. *
  254. * so we expect that users are not running an ancient Safari version
  255. */
  256. userChange = false;
  257. if (ready) {
  258. window.location.hash = 'PMAURL-' + index + ':' + hash;
  259. resetFavicon();
  260. } else {
  261. savedHash = 'PMAURL-' + index + ':' + hash;
  262. }
  263. }
  264. /**
  265. * Start initialization
  266. */
  267. var urlHash = window.location.hash;
  268. if (urlHash.substring(0, 8) === '#PMAURL-') {
  269. // We have a valid hash, let's redirect the user
  270. // to the page that it's pointing to
  271. var colonPosition = urlHash.indexOf(':');
  272. var questionMarkPosition = urlHash.indexOf('?');
  273. if (colonPosition !== -1 && questionMarkPosition !== -1 && colonPosition < questionMarkPosition) {
  274. var hashUrl = urlHash.substring(colonPosition + 1, questionMarkPosition);
  275. if (hashUrl === 'index.php') {
  276. window.location = urlHash.substring(colonPosition + 1);
  277. }
  278. }
  279. } else {
  280. // We don't have a valid hash, so we'll set it up
  281. // when the page finishes loading
  282. jQuery(function () {
  283. /* Check if we should set URL */
  284. if (savedHash !== '') {
  285. window.location.hash = savedHash;
  286. savedHash = '';
  287. resetFavicon();
  288. } // Indicate that we're done initializing
  289. ready = true;
  290. });
  291. }
  292. /**
  293. * Register an event handler for when the url hash changes
  294. */
  295. jQuery(function () {
  296. jQuery(window).hashchange(function () {
  297. if (userChange === false) {
  298. // Ignore internally triggered hash changes
  299. userChange = true;
  300. } else if (/^#PMAURL-\d+:/.test(window.location.hash)) {
  301. // Change page if the hash changed was triggered by a user action
  302. var index = window.location.hash.substring(8, window.location.hash.indexOf(':'));
  303. MicroHistory.navigate(index);
  304. }
  305. });
  306. });
  307. /**
  308. * Publicly exposes a reference to the otherwise private setUrlHash function
  309. */
  310. return setUrlHash;
  311. }(jQuery, window);