123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819 |
- /* vim: set expandtab sw=4 ts=4 sts=4: */
- /**
- * This object handles ajax requests for pages. It also
- * handles the reloading of the main menu and scripts.
- */
- var AJAX = {
- /**
- * @var bool active Whether we are busy
- */
- active: false,
- /**
- * @var object source The object whose event initialized the request
- */
- source: null,
- /**
- * @var function Callback to execute after a successful request
- * Used by PMA_commonFunctions from common.js
- */
- _callback: function () {},
- /**
- * @var bool _debug Makes noise in your Firebug console
- */
- _debug: false,
- /**
- * @var object $msgbox A reference to a jQuery object that links to a message
- * box that is generated by PMA_ajaxShowMessage()
- */
- $msgbox: null,
- /**
- * Given the filename of a script, returns a hash to be
- * used to refer to all the events registered for the file
- *
- * @param string key The filename for which to get the event name
- *
- * @return int
- */
- hash: function (key){
- /* http://burtleburtle.net/bob/hash/doobs.html#one */
- key += "";
- var len = key.length, hash=0, i=0;
- for (; i<len; ++i) {
- hash += key.charCodeAt(i);
- hash += (hash << 10);
- hash ^= (hash >> 6);
- }
- hash += (hash << 3);
- hash ^= (hash >> 11);
- hash += (hash << 15);
- return Math.abs(hash);
- },
- /**
- * Registers an onload event for a file
- *
- * @param string file The filename for which to register the event
- * @param function func The function to execute when the page is ready
- *
- * @return self For chaining
- */
- registerOnload: function (file, func) {
- var eventName = 'onload_' + AJAX.hash(file);
- $(document).bind(eventName, func);
- this._debug && console.log(
- // no need to translate
- "Registered event " + eventName + " for file " + file
- );
- return this;
- },
- /**
- * Registers a teardown event for a file. This is useful to execute functions
- * that unbind events for page elements that are about to be removed.
- *
- * @param string file The filename for which to register the event
- * @param function func The function to execute when
- * the page is about to be torn down
- *
- * @return self For chaining
- */
- registerTeardown: function (file, func) {
- var eventName = 'teardown_' + AJAX.hash(file);
- $(document).bind(eventName, func);
- this._debug && console.log(
- // no need to translate
- "Registered event " + eventName + " for file " + file
- );
- return this;
- },
- /**
- * Called when a page has finished loading, once for every
- * file that registered to the onload event of that file.
- *
- * @param string file The filename for which to fire the event
- *
- * @return void
- */
- fireOnload: function (file) {
- var eventName = 'onload_' + AJAX.hash(file);
- $(document).trigger(eventName);
- this._debug && console.log(
- // no need to translate
- "Fired event " + eventName + " for file " + file
- );
- },
- /**
- * Called just before a page is torn down, once for every
- * file that registered to the teardown event of that file.
- *
- * @param string file The filename for which to fire the event
- *
- * @return void
- */
- fireTeardown: function (file) {
- var eventName = 'teardown_' + AJAX.hash(file);
- $(document).triggerHandler(eventName);
- this._debug && console.log(
- // no need to translate
- "Fired event " + eventName + " for file " + file
- );
- },
- /**
- * Event handler for clicks on links and form submissions
- *
- * @param object e Event data
- *
- * @return void
- */
- requestHandler: function (event) {
- // In some cases we don't want to handle the request here and either
- // leave the browser deal with it natively (e.g: file download)
- // or leave an existing ajax event handler present elsewhere deal with it
- var href = $(this).attr('href');
- if (typeof event != 'undefined' && (event.shiftKey || event.ctrlKey)) {
- return true;
- } else if ($(this).attr('target')) {
- return true;
- } else if ($(this).hasClass('ajax') || $(this).hasClass('disableAjax')) {
- return true;
- } else if (href && href.match(/^#/)) {
- return true;
- } else if (href && href.match(/^mailto/)) {
- return true;
- } else if ($(this).hasClass('ui-datepicker-next') ||
- $(this).hasClass('ui-datepicker-prev')
- ) {
- return true;
- }
- if (typeof event != 'undefined') {
- event.preventDefault();
- event.stopImmediatePropagation();
- }
- if (AJAX.active == true) {
- // Silently bail out, there is already a request in progress.
- // TODO: save a reference to the request and cancel the old request
- // when the user requests something else. Something like this is
- // already implemented in the PMA_fastFilter object in navigation.js
- return false;
- }
- AJAX.source = $(this);
- $('html, body').animate({scrollTop: 0}, 'fast');
- var isLink = !! href || false;
- var url = isLink ? href : $(this).attr('action');
- var params = 'ajax_request=true&ajax_page_request=true';
- if (! isLink) {
- params += '&' + $(this).serialize();
- }
- // Add a list of menu hashes that we have in the cache to the request
- params += AJAX.cache.menus.getRequestParam();
- AJAX._debug && console.log("Loading: " + url); // no need to translate
- if (isLink) {
- AJAX.active = true;
- AJAX.$msgbox = PMA_ajaxShowMessage();
- $.get(url, params, AJAX.responseHandler);
- } else {
- /**
- * Manually fire the onsubmit event for the form, if any.
- * The event was saved in the jQuery data object by an onload
- * handler defined below. Workaround for bug #3583316
- */
- var onsubmit = $(this).data('onsubmit');
- // Submit the request if there is no onsubmit handler
- // or if it returns a value that evaluates to true
- if (typeof onsubmit !== 'function' || onsubmit.apply(this, [event])) {
- AJAX.active = true;
- AJAX.$msgbox = PMA_ajaxShowMessage();
- $.post(url, params, AJAX.responseHandler);
- }
- }
- },
- /**
- * Called after the request that was initiated by this.requestHandler()
- * has completed successfully or with a caught error. For completely
- * failed requests or requests with uncaught errors, see the .ajaxError
- * handler at the bottom of this file.
- *
- * To refer to self use 'AJAX', instead of 'this' as this function
- * is called in the jQuery context.
- *
- * @param object e Event data
- *
- * @return void
- */
- responseHandler: function (data) {
- if (data.success) {
- $table_clone = false;
- PMA_ajaxRemoveMessage(AJAX.$msgbox);
- if (data._redirect) {
- PMA_ajaxShowMessage(data._redirect, false);
- AJAX.active = false;
- return;
- }
- AJAX.scriptHandler.reset(function () {
- if (data._reloadNavigation) {
- PMA_reloadNavigation();
- }
- if (data._reloadQuerywindow) {
- var params = data._reloadQuerywindow;
- PMA_querywindow.reload(
- params.db,
- params.table,
- params.sql_query
- );
- }
- if (data._focusQuerywindow) {
- PMA_querywindow.focus(
- data._focusQuerywindow
- );
- }
- if (data._title) {
- $('title').replaceWith(data._title);
- }
- if (data._menu) {
- AJAX.cache.menus.replace(data._menu);
- AJAX.cache.menus.add(data._menuHash, data._menu);
- } else if (data._menuHash) {
- AJAX.cache.menus.replace(AJAX.cache.menus.get(data._menuHash));
- }
- // Remove all containers that may have
- // been added outside of #page_content
- $('body').children()
- .not('#pma_navigation')
- .not('#floating_menubar')
- .not('#goto_pagetop')
- .not('#page_content')
- .not('#selflink')
- .not('#session_debug')
- .remove();
- // Replace #page_content with new content
- if (data.message && data.message.length > 0) {
- $('#page_content').replaceWith(
- "<div id='page_content'>" + data.message + "</div>"
- );
- }
- if (data._selflink) {
- $('#selflink > a').attr('href', data._selflink);
- }
- if (data._scripts) {
- AJAX.scriptHandler.load(data._scripts);
- }
- if (data._selflink && data._scripts && data._menuHash && data._params) {
- AJAX.cache.add(
- data._selflink,
- data._scripts,
- data._menuHash,
- data._params,
- AJAX.source.attr('rel')
- );
- }
- if (data._params) {
- PMA_commonParams.setAll(data._params);
- }
- if (data._displayMessage) {
- $('#page_content').prepend(data._displayMessage);
- }
- $('#pma_errors').remove();
- if (data._errors) {
- $('<div/>', {id:'pma_errors'})
- .insertAfter('#selflink')
- .append(data._errors);
- }
- if (typeof AJAX._callback === 'function') {
- AJAX._callback.call();
- }
- AJAX._callback = function () {};
- });
- } else {
- PMA_ajaxShowMessage(data.error, false);
- AJAX.active = false;
- }
- },
- /**
- * This object is in charge of downloading scripts,
- * keeping track of what's downloaded and firing
- * the onload event for them when the page is ready.
- */
- scriptHandler: {
- /**
- * @var array _scripts The list of files already downloaded
- */
- _scripts: [],
- /**
- * @var array _scriptsToBeLoaded The list of files that
- * need to be downloaded
- */
- _scriptsToBeLoaded: [],
- /**
- * @var array _scriptsToBeFired The list of files for which
- * to fire the onload event
- */
- _scriptsToBeFired: [],
- /**
- * Records that a file has been downloaded
- *
- * @param string file The filename
- * @param string fire Whether this file will be registering
- * onload/teardown events
- *
- * @return self For chaining
- */
- add: function (file, fire) {
- this._scripts.push(file);
- if (fire) {
- // Record whether to fire any events for the file
- // This is necessary to correctly tear down the initial page
- this._scriptsToBeFired.push(file);
- }
- return this;
- },
- /**
- * Download a list of js files in one request
- *
- * @param array files An array of filenames and flags
- *
- * @return void
- */
- load: function (files) {
- var self = this;
- self._scriptsToBeLoaded = [];
- self._scriptsToBeFired = [];
- for (var i in files) {
- self._scriptsToBeLoaded.push(files[i].name);
- if (files[i].fire) {
- self._scriptsToBeFired.push(files[i].name);
- }
- }
- // Generate a request string
- var request = [];
- var needRequest = false;
- for (var index in self._scriptsToBeLoaded) {
- var script = self._scriptsToBeLoaded[index];
- // Only for scripts that we don't already have
- if ($.inArray(script, self._scripts) == -1) {
- needRequest = true;
- this.add(script);
- request.push("scripts[]=" + script);
- }
- }
- // Download the composite js file, if necessary
- if (needRequest) {
- $.ajax({
- url: "js/get_scripts.js.php?" + request.join("&"),
- cache: true,
- success: function () {
- self.done();
- },
- dataType: "script"
- });
- } else {
- self.done();
- }
- },
- /**
- * Called whenever all files are loaded
- *
- * @return void
- */
- done: function () {
- for (var i in this._scriptsToBeFired) {
- AJAX.fireOnload(this._scriptsToBeFired[i]);
- }
- AJAX.active = false;
- },
- /**
- * Fires all the teardown event handlers for the current page
- * and rebinds all forms and links to the request handler
- *
- * @param function callback The callback to call after resetting
- *
- * @return void
- */
- reset: function (callback) {
- for (var i in this._scriptsToBeFired) {
- AJAX.fireTeardown(this._scriptsToBeFired[i]);
- }
- this._scriptsToBeFired = [];
- /**
- * Re-attach a generic event handler to clicks
- * on pages and submissions of forms
- */
- $('a').die('click').live('click', AJAX.requestHandler);
- $('form').die('submit').live('submit', AJAX.requestHandler);
- AJAX.cache.update();
- callback();
- }
- }
- };
- /**
- * Here we register a function that will remove the onsubmit event from all
- * forms that will be handled by the generic page loader. We then save this
- * event handler in the "jQuery data", so that we can fire it up later in
- * AJAX.requestHandler().
- *
- * See bug #3583316
- */
- AJAX.registerOnload('functions.js', function () {
- // Registering the onload event for functions.js
- // ensures that it will be fired for all pages
- $('form').not('.ajax').not('.disableAjax').each(function () {
- if ($(this).attr('onsubmit')) {
- $(this).data('onsubmit', this.onsubmit).attr('onsubmit', '');
- }
- });
- });
- /**
- * An implementation of a client-side page cache.
- * This object also uses the cache to provide a simple microhistory,
- * that is the ability to use the back and forward buttons in the browser
- */
- AJAX.cache = {
- /**
- * @var int The maximum number of pages to keep in the cache
- */
- MAX: 6,
- /**
- * @var object A hash used to prime the cache with data about the initially
- * loaded page. This is set in the footer, and then loaded
- * by a double-queued event further down this file.
- */
- primer: {},
- /**
- * @var array Stores the content of the cached pages
- */
- pages: [],
- /**
- * @var int The index of the currently loaded page
- * This is used to know at which point in the history we are
- */
- current: 0,
- /**
- * Saves a new page in the cache
- *
- * @param string hash The hash part of the url that is being loaded
- * @param array scripts A list of scripts that is requured for the page
- * @param string menu A hash that links to a menu stored
- * in a dedicated menu cache
- * @param array params A list of parameters used by PMA_commonParams()
- * @param string rel A relationship to the current page:
- * 'samepage': Forces the response to be treated as
- * the same page as the current one
- * 'newpage': Forces the response to be treated as
- * a new page
- * undefined: Default behaviour, 'samepage' if the
- * selflinks of the two pages are the same.
- * 'newpage' otherwise
- *
- * @return void
- */
- add: function (hash, scripts, menu, params, rel) {
- if (this.pages.length > AJAX.cache.MAX) {
- // Trim the cache, to the maximum number of allowed entries
- // This way we will have a cached menu for every page
- for (var i=0; i<this.pages.length-this.MAX; i++) {
- delete this.pages[i];
- }
- }
- while (this.current < this.pages.length) {
- // trim the cache if we went back in the history
- // and are now going forward again
- this.pages.pop();
- }
- if (rel === 'newpage' ||
- (
- typeof rel === 'undefined' && (
- typeof this.pages[this.current - 1] === 'undefined'
- ||
- this.pages[this.current - 1].hash !== hash
- )
- )
- ) {
- this.pages.push({
- hash: hash,
- content: $('#page_content').html(),
- scripts: scripts,
- selflink: $('#selflink').html(),
- menu: menu,
- params: params
- });
- AJAX.setUrlHash(this.current, hash);
- this.current++;
- }
- },
- /**
- * Restores a page from the cache. This is called when the hash
- * part of the url changes and it's structure appears to be valid
- *
- * @param string index Which page from the history to load
- *
- * @return void
- */
- navigate: function (index) {
- if (typeof this.pages[index] === 'undefined') {
- PMA_ajaxShowMessage(
- '<div class="error">' + PMA_messages['strInvalidPage'] + '</div>',
- false
- );
- } else {
- AJAX.active = true;
- var record = this.pages[index];
- AJAX.scriptHandler.reset(function () {
- $('#page_content').html(record.content);
- $('#selflink').html(record.selflink);
- AJAX.cache.menus.replace(AJAX.cache.menus.get(record.menu));
- PMA_commonParams.setAll(record.params);
- AJAX.scriptHandler.load(record.scripts);
- AJAX.cache.current = ++index;
- });
- }
- },
- /**
- * Resaves the content of the current page in the cache.
- * Necessary in order not to show the user some outdated version of the page
- *
- * @return void
- */
- update: function () {
- var page = this.pages[this.current - 1];
- if (page) {
- page.content = $('#page_content').html();
- }
- },
- /**
- * @var object Dedicated menu cache
- */
- menus: {
- /**
- * Returns the number of items in an associative array
- *
- * @return int
- */
- size: function(obj) {
- var size = 0, key;
- for (key in obj) {
- if (obj.hasOwnProperty(key)) {
- size++;
- }
- }
- return size;
- },
- /**
- * @var hash Stores the content of the cached menus
- */
- data: {},
- /**
- * Saves a new menu in the cache
- *
- * @param string hash The hash (trimmed md5) of the menu to be saved
- * @param string content The HTML code of the menu to be saved
- *
- * @return void
- */
- add: function (hash, content) {
- if (this.size(this.data) > AJAX.cache.MAX) {
- // when the cache grows, we remove the oldest entry
- var oldest, key, init = 0;
- for (var i in this.data) {
- if (this.data[i]) {
- if (! init || this.data[i].timestamp.getTime() < oldest.getTime()) {
- oldest = this.data[i].timestamp;
- key = i;
- init = 1;
- }
- }
- }
- delete this.data[key];
- }
- this.data[hash] = {
- content: content,
- timestamp: new Date()
- };
- },
- /**
- * Retrieves a menu given its hash
- *
- * @param string hash The hash of the menu to be retrieved
- *
- * @return string
- */
- get: function (hash) {
- if (this.data[hash]) {
- return this.data[hash].content;
- } else {
- // This should never happen as long as the number of stored menus
- // is larger or equal to the number of pages in the page cache
- return '';
- }
- },
- /**
- * Prepares part of the parameter string used during page requests,
- * this is necessary to tell the server which menus we have in the cache
- *
- * @return string
- */
- getRequestParam: function () {
- var param = '';
- var menuHashes = [];
- for (var i in this.data) {
- menuHashes.push(i);
- }
- var menuHashesParam = menuHashes.join('-');
- if (menuHashesParam) {
- param = '&menuHashes=' + menuHashesParam;
- }
- return param;
- },
- /**
- * Replaces the menu with new content
- *
- * @return void
- */
- replace: function (content) {
- $('#floating_menubar').html(content)
- // Remove duplicate wrapper
- // TODO: don't send it in the response
- .children().first().remove();
- $('#topmenu').menuResizer(PMA_mainMenuResizerCallback);
- }
- }
- };
- /**
- * URL hash management module.
- * Allows direct bookmarking and microhistory.
- */
- AJAX.setUrlHash = (function (jQuery, window) {
- "use strict";
- /**
- * Indictaes whether we have already completed
- * the initialisation of the hash
- *
- * @access private
- */
- var ready = false;
- /**
- * Stores a hash that needed to be set when we were not ready
- *
- * @access private
- */
- var savedHash = "";
- /**
- * Flag to indicate if the change of hash was triggered
- * by a user pressing the back/forward button or if
- * the change was triggered internally
- *
- * @access private
- */
- var userChange = true;
- // Fix favicon disappearing in Firefox when setting location.hash
- function resetFavicon() {
- if (jQuery.browser.mozilla) {
- // Move the link tags for the favicon to the bottom
- // of the head element to force a reload of the favicon
- $('head > link[href=favicon\\.ico]').appendTo('head');
- }
- }
- /**
- * Sets the hash part of the URL
- *
- * @access public
- */
- function setUrlHash(index, hash) {
- /*
- * Known problem:
- * Setting hash leads to reload in webkit:
- * http://www.quirksmode.org/bugreports/archives/2005/05/Safari_13_visual_anomaly_with_windowlocationhref.html
- *
- * so we expect that users are not running an ancient Safari version
- */
- userChange = false;
- if (ready) {
- window.location.hash = "PMAURL-" + index + ":" + hash;
- resetFavicon();
- } else {
- savedHash = "PMAURL-" + index + ":" + hash;
- }
- }
- /**
- * Start initialisation
- */
- var urlhash = window.location.hash;
- if (urlhash.substring(0, 8) == '#PMAURL-') {
- // We have a valid hash, let's redirect the user
- // to the page that it's pointing to
- var colon_position = urlhash.indexOf(':');
- var questionmark_position = urlhash.indexOf('?');
- if (colon_position != -1 && questionmark_position != -1 && colon_position < questionmark_position) {
- var hash_url = urlhash.substring(colon_position + 1, questionmark_position);
- if (PMA_gotoWhitelist.indexOf(hash_url) != -1) {
- window.location = urlhash.substring(
- colon_position + 1
- );
- }
- }
- } else {
- // We don't have a valid hash, so we'll set it up
- // when the page finishes loading
- jQuery(function(){
- /* Check if we should set URL */
- if (savedHash != "") {
- window.location.hash = savedHash;
- savedHash = "";
- resetFavicon();
- }
- // Indicate that we're done initialising
- ready = true;
- });
- }
- /**
- * Register an event handler for when the url hash changes
- */
- jQuery(function(){
- jQuery(window).hashchange(function () {
- if (userChange === false) {
- // Ignore internally triggered hash changes
- userChange = true;
- } else if (/^#PMAURL-\d+:/.test(window.location.hash)) {
- // Change page if the hash changed was triggered by a user action
- var index = window.location.hash.substring(
- 8, window.location.hash.indexOf(':')
- );
- AJAX.cache.navigate(index);
- }
- });
- });
- /**
- * Publicly exposes a reference to the otherwise private setUrlHash function
- */
- return setUrlHash;
- })(jQuery, window);
- /**
- * Page load event handler
- */
- $(function () {
- // Add the menu from the initial page into the cache
- // The cache primer is set by the footer class
- if (AJAX.cache.primer.url) {
- AJAX.cache.menus.add(
- AJAX.cache.primer.menuHash,
- $('<div></div>')
- .append('<div></div>')
- .append($('#serverinfo').clone())
- .append($('#topmenucontainer').clone())
- .html()
- );
- }
- $(function () {
- // Queue up this event twice to make sure that we get a copy
- // of the page after all other onload events have been fired
- if (AJAX.cache.primer.url) {
- AJAX.cache.add(
- AJAX.cache.primer.url,
- AJAX.cache.primer.scripts,
- AJAX.cache.primer.menuHash
- );
- }
- });
- });
- /**
- * Attach a generic event handler to clicks
- * on pages and submissions of forms
- */
- $('a').live('click', AJAX.requestHandler);
- $('form').live('submit', AJAX.requestHandler);
- /**
- * Gracefully handle fatal server errors
- * (e.g: 500 - Internal server error)
- */
- $(document).ajaxError(function(event, request, settings){
- if (request.status !== 0) { // Don't handle aborted requests
- var errorCode = $.sprintf(PMA_messages['strErrorCode'], request.status);
- var errorText = $.sprintf(PMA_messages['strErrorText'], request.statusText);
- PMA_ajaxShowMessage(
- '<div class="error">'
- + PMA_messages['strErrorProcessingRequest']
- + '<div>' + escapeHtml(errorCode) + '</div>'
- + '<div>' + escapeHtml(errorText) + '</div>'
- + '</div>',
- false
- );
- AJAX.active = false;
- }
- });
|