1 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 /* global GotoWhitelist */ // js/whitelist.php
6 * An implementation of a client-side page cache.
7 * This object also uses the cache to provide a simple microhistory,
8 * that is the ability to use the back and forward buttons in the browser
12 * @var int The maximum number of pages to keep in the cache
16 * @var object A hash used to prime the cache with data about the initially
17 * loaded page. This is set in the footer, and then loaded
18 * by a double-queued event further down this file.
22 * @var array Stores the content of the cached pages
26 * @var int The index of the currently loaded page
27 * This is used to know at which point in the history we are
31 * Saves a new page in the cache
33 * @param string hash The hash part of the url that is being loaded
34 * @param array scripts A list of scripts that is required for the page
35 * @param string menu A hash that links to a menu stored
36 * in a dedicated menu cache
37 * @param array params A list of parameters used by CommonParams()
38 * @param string rel A relationship to the current page:
39 * 'samepage': Forces the response to be treated as
40 * the same page as the current one
41 * 'newpage': Forces the response to be treated as
43 * undefined: Default behaviour, 'samepage' if the
44 * selflinks of the two pages are the same.
49 add: function (hash, scripts, menu, params, rel) {
50 if (this.pages.length > MicroHistory.MAX) {
51 // Trim the cache, to the maximum number of allowed entries
52 // This way we will have a cached menu for every page
53 for (var i = 0; i < this.pages.length - this.MAX; i++) {
57 while (this.current < this.pages.length) {
58 // trim the cache if we went back in the history
59 // and are now going forward again
62 if (rel === 'newpage' ||
64 typeof rel === 'undefined' && (
65 typeof this.pages[this.current - 1] === 'undefined' ||
66 this.pages[this.current - 1].hash !== hash
72 content: $('#page_content').html(),
74 selflink: $('#selflink').html(),
78 MicroHistory.setUrlHash(this.current, hash);
83 * Restores a page from the cache. This is called when the hash
84 * part of the url changes and it's structure appears to be valid
86 * @param string index Which page from the history to load
90 navigate: function (index) {
91 var localIndex = index;
92 if (typeof this.pages[localIndex] === 'undefined' ||
93 typeof this.pages[localIndex].content === 'undefined' ||
94 typeof this.pages[localIndex].menu === 'undefined' ||
95 ! MicroHistory.menus.get(this.pages[localIndex].menu)
97 Functions.ajaxShowMessage(
98 '<div class="error">' + Messages.strInvalidPage + '</div>',
103 var record = this.pages[localIndex];
104 AJAX.scriptHandler.reset(function () {
105 $('#page_content').html(record.content);
106 $('#selflink').html(record.selflink);
107 MicroHistory.menus.replace(MicroHistory.menus.get(record.menu));
108 CommonParams.setAll(record.params);
109 AJAX.scriptHandler.load(record.scripts);
110 MicroHistory.current = ++localIndex;
115 * Resaves the content of the current page in the cache.
116 * Necessary in order not to show the user some outdated version of the page
120 update: function () {
121 var page = this.pages[this.current - 1];
123 page.content = $('#page_content').html();
127 * @var object Dedicated menu cache
131 * Returns the number of items in an associative array
135 size: function (obj) {
139 if (obj.hasOwnProperty(key)) {
146 * @var hash Stores the content of the cached menus
150 * Saves a new menu in the cache
152 * @param string hash The hash (trimmed md5) of the menu to be saved
153 * @param string content The HTML code of the menu to be saved
157 add: function (hash, content) {
158 if (this.size(this.data) > MicroHistory.MAX) {
159 // when the cache grows, we remove the oldest entry
163 for (var i in this.data) {
165 if (! init || this.data[i].timestamp.getTime() < oldest.getTime()) {
166 oldest = this.data[i].timestamp;
172 delete this.data[key];
176 timestamp: new Date()
180 * Retrieves a menu given its hash
182 * @param string hash The hash of the menu to be retrieved
186 get: function (hash) {
187 if (this.data[hash]) {
188 return this.data[hash].content;
190 // This should never happen as long as the number of stored menus
191 // is larger or equal to the number of pages in the page cache
196 * Prepares part of the parameter string used during page requests,
197 * this is necessary to tell the server which menus we have in the cache
201 getRequestParam: function () {
204 for (var i in this.data) {
207 var menuHashesParam = menuHashes.join('-');
208 if (menuHashesParam) {
209 param = CommonParams.get('arg_separator') + 'menuHashes=' + menuHashesParam;
214 * Replaces the menu with new content
218 replace: function (content) {
219 $('#floating_menubar').html(content)
220 // Remove duplicate wrapper
221 // TODO: don't send it in the response
222 .children().first().remove();
223 $('#topmenu').menuResizer(Functions.mainMenuResizerCallback);
229 * URL hash management module.
230 * Allows direct bookmarking and microhistory.
232 MicroHistory.setUrlHash = (function (jQuery, window) {
235 * Indictaes whether we have already completed
236 * the initialisation of the hash
242 * Stores a hash that needed to be set when we were not ready
248 * Flag to indicate if the change of hash was triggered
249 * by a user pressing the back/forward button or if
250 * the change was triggered internally
254 var userChange = true;
256 // Fix favicon disappearing in Firefox when setting location.hash
257 function resetFavicon () {
258 if (navigator.userAgent.indexOf('Firefox') > -1) {
259 // Move the link tags for the favicon to the bottom
260 // of the head element to force a reload of the favicon
261 $('head > link[href="favicon\\.ico"]').appendTo('head');
266 * Sets the hash part of the URL
270 function setUrlHash (index, hash) {
273 * Setting hash leads to reload in webkit:
274 * http://www.quirksmode.org/bugreports/archives/2005/05/Safari_13_visual_anomaly_with_windowlocationhref.html
276 * so we expect that users are not running an ancient Safari version
281 window.location.hash = 'PMAURL-' + index + ':' + hash;
284 savedHash = 'PMAURL-' + index + ':' + hash;
288 * Start initialisation
290 var urlHash = window.location.hash;
291 if (urlHash.substring(0, 8) === '#PMAURL-') {
292 // We have a valid hash, let's redirect the user
293 // to the page that it's pointing to
294 var colonPosition = urlHash.indexOf(':');
295 var questionMarkPosition = urlHash.indexOf('?');
296 if (colonPosition !== -1 && questionMarkPosition !== -1 && colonPosition < questionMarkPosition) {
297 var hashUrl = urlHash.substring(colonPosition + 1, questionMarkPosition);
298 if (GotoWhitelist.indexOf(hashUrl) !== -1) {
299 window.location = urlHash.substring(
305 // We don't have a valid hash, so we'll set it up
306 // when the page finishes loading
308 /* Check if we should set URL */
309 if (savedHash !== '') {
310 window.location.hash = savedHash;
314 // Indicate that we're done initialising
319 * Register an event handler for when the url hash changes
322 jQuery(window).hashchange(function () {
323 if (userChange === false) {
324 // Ignore internally triggered hash changes
326 } else if (/^#PMAURL-\d+:/.test(window.location.hash)) {
327 // Change page if the hash changed was triggered by a user action
328 var index = window.location.hash.substring(
329 8, window.location.hash.indexOf(':')
331 MicroHistory.navigate(index);
336 * Publicly exposes a reference to the otherwise private setUrlHash function