Merge #15685 - Fix #15682 - Timestamp method not taking current time
[phpmyadmin.git] / js / ajax.js
blob81976128cc97dddef24fa10aa7e73b265f0190a7
1 /* vim: set expandtab sw=4 ts=4 sts=4: */
2 /**
3  * This object handles ajax requests for pages. It also
4  * handles the reloading of the main menu and scripts.
5  */
6 var AJAX = {
7     /**
8      * @var bool active Whether we are busy
9      */
10     active: false,
11     /**
12      * @var object source The object whose event initialized the request
13      */
14     source: null,
15     /**
16      * @var object xhr A reference to the ajax request that is currently running
17      */
18     xhr: null,
19     /**
20      * @var object lockedTargets, list of locked targets
21      */
22     lockedTargets: {},
23     /**
24      * @var function Callback to execute after a successful request
25      *               Used by PMA_commonFunctions from common.js
26      */
27     _callback: function () {},
28     /**
29      * @var bool _debug Makes noise in your Firebug console
30      */
31     _debug: false,
32     /**
33      * @var object $msgbox A reference to a jQuery object that links to a message
34      *                     box that is generated by PMA_ajaxShowMessage()
35      */
36     $msgbox: null,
37     /**
38      * Given the filename of a script, returns a hash to be
39      * used to refer to all the events registered for the file
40      *
41      * @param key string key The filename for which to get the event name
42      *
43      * @return int
44      */
45     hash: function (key) {
46         /* http://burtleburtle.net/bob/hash/doobs.html#one */
47         key += '';
48         var len = key.length;
49         var hash = 0;
50         var i = 0;
51         for (; i < len; ++i) {
52             hash += key.charCodeAt(i);
53             hash += (hash << 10);
54             hash ^= (hash >> 6);
55         }
56         hash += (hash << 3);
57         hash ^= (hash >> 11);
58         hash += (hash << 15);
59         return Math.abs(hash);
60     },
61     /**
62      * Registers an onload event for a file
63      *
64      * @param file string   file The filename for which to register the event
65      * @param func function func The function to execute when the page is ready
66      *
67      * @return self For chaining
68      */
69     registerOnload: function (file, func) {
70         var eventName = 'onload_' + AJAX.hash(file);
71         $(document).on(eventName, func);
72         if (this._debug) {
73             console.log(
74                 // no need to translate
75                 'Registered event ' + eventName + ' for file ' + file
76             );
77         }
78         return this;
79     },
80     /**
81      * Registers a teardown event for a file. This is useful to execute functions
82      * that unbind events for page elements that are about to be removed.
83      *
84      * @param string   file The filename for which to register the event
85      * @param function func The function to execute when
86      *                      the page is about to be torn down
87      *
88      * @return self For chaining
89      */
90     registerTeardown: function (file, func) {
91         var eventName = 'teardown_' + AJAX.hash(file);
92         $(document).on(eventName, func);
93         if (this._debug) {
94             console.log(
95                 // no need to translate
96                 'Registered event ' + eventName + ' for file ' + file
97             );
98         }
99         return this;
100     },
101     /**
102      * Called when a page has finished loading, once for every
103      * file that registered to the onload event of that file.
104      *
105      * @param string file The filename for which to fire the event
106      *
107      * @return void
108      */
109     fireOnload: function (file) {
110         var eventName = 'onload_' + AJAX.hash(file);
111         $(document).trigger(eventName);
112         if (this._debug) {
113             console.log(
114                 // no need to translate
115                 'Fired event ' + eventName + ' for file ' + file
116             );
117         }
118     },
119     /**
120      * Called just before a page is torn down, once for every
121      * file that registered to the teardown event of that file.
122      *
123      * @param string file The filename for which to fire the event
124      *
125      * @return void
126      */
127     fireTeardown: function (file) {
128         var eventName = 'teardown_' + AJAX.hash(file);
129         $(document).triggerHandler(eventName);
130         if (this._debug) {
131             console.log(
132                 // no need to translate
133                 'Fired event ' + eventName + ' for file ' + file
134             );
135         }
136     },
137     /**
138      * function to handle lock page mechanism
139      *
140      * @param event the event object
141      *
142      * @return void
143      */
144     lockPageHandler: function (event) {
145         var newHash = null;
146         var oldHash = null;
147         var lockId;
148         // CodeMirror lock
149         if (event.data.value === 3) {
150             newHash = event.data.content;
151             oldHash = true;
152             lockId = 'cm';
153         } else {
154             // Don't lock on enter.
155             if (0 === event.charCode) {
156                 return;
157             }
159             lockId = $(this).data('lock-id');
160             if (typeof lockId === 'undefined') {
161                 return;
162             }
163             /*
164              * @todo Fix Code mirror does not give correct full value (query)
165              * in textarea, it returns only the change in content.
166              */
167             if (event.data.value === 1) {
168                 newHash = AJAX.hash($(this).val());
169             } else {
170                 newHash = AJAX.hash($(this).is(':checked'));
171             }
172             oldHash = $(this).data('val-hash');
173         }
174         // Set lock if old value !== new value
175         // otherwise release lock
176         if (oldHash !== newHash) {
177             AJAX.lockedTargets[lockId] = true;
178         } else {
179             delete AJAX.lockedTargets[lockId];
180         }
181         // Show lock icon if locked targets is not empty.
182         // otherwise remove lock icon
183         if (!jQuery.isEmptyObject(AJAX.lockedTargets)) {
184             $('#lock_page_icon').html(PMA_getImage('s_lock', PMA_messages.strLockToolTip).toString());
185         } else {
186             $('#lock_page_icon').html('');
187         }
188     },
189     /**
190      * resets the lock
191      *
192      * @return void
193      */
194     resetLock: function () {
195         AJAX.lockedTargets = {};
196         $('#lock_page_icon').html('');
197     },
198     handleMenu: {
199         replace: function (content) {
200             $('#floating_menubar').html(content)
201                 // Remove duplicate wrapper
202                 // TODO: don't send it in the response
203                 .children().first().remove();
204             $('#topmenu').menuResizer(PMA_mainMenuResizerCallback);
205         }
206     },
207     /**
208      * Event handler for clicks on links and form submissions
209      *
210      * @param object e Event data
211      *
212      * @return void
213      */
214     requestHandler: function (event) {
215         // In some cases we don't want to handle the request here and either
216         // leave the browser deal with it natively (e.g: file download)
217         // or leave an existing ajax event handler present elsewhere deal with it
218         var href = $(this).attr('href');
219         if (typeof event !== 'undefined' && (event.shiftKey || event.ctrlKey)) {
220             return true;
221         } else if ($(this).attr('target')) {
222             return true;
223         } else if ($(this).hasClass('ajax') || $(this).hasClass('disableAjax')) {
224             // reset the lockedTargets object, as specified AJAX operation has finished
225             AJAX.resetLock();
226             return true;
227         } else if (href && href.match(/^#/)) {
228             return true;
229         } else if (href && href.match(/^mailto/)) {
230             return true;
231         } else if ($(this).hasClass('ui-datepicker-next') ||
232             $(this).hasClass('ui-datepicker-prev')
233         ) {
234             return true;
235         }
237         if (typeof event !== 'undefined') {
238             event.preventDefault();
239             event.stopImmediatePropagation();
240         }
242         // triggers a confirm dialog if:
243         // the user has performed some operations on loaded page
244         // the user clicks on some link, (won't trigger for buttons)
245         // the click event is not triggered by script
246         if (typeof event !== 'undefined' && event.type === 'click' &&
247             event.isTrigger !== true &&
248             !jQuery.isEmptyObject(AJAX.lockedTargets) &&
249             confirm(PMA_messages.strConfirmNavigation) === false
250         ) {
251             return false;
252         }
253         AJAX.resetLock();
254         var isLink = !! href || false;
255         var previousLinkAborted = false;
257         if (AJAX.active === true) {
258             // Cancel the old request if abortable, when the user requests
259             // something else. Otherwise silently bail out, as there is already
260             // a request well in progress.
261             if (AJAX.xhr) {
262                 // In case of a link request, attempt aborting
263                 AJAX.xhr.abort();
264                 if (AJAX.xhr.status === 0 && AJAX.xhr.statusText === 'abort') {
265                     // If aborted
266                     AJAX.$msgbox = PMA_ajaxShowMessage(PMA_messages.strAbortedRequest);
267                     AJAX.active = false;
268                     AJAX.xhr = null;
269                     previousLinkAborted = true;
270                 } else {
271                     // If can't abort
272                     return false;
273                 }
274             } else {
275                 // In case submitting a form, don't attempt aborting
276                 return false;
277             }
278         }
280         AJAX.source = $(this);
282         $('html, body').animate({ scrollTop: 0 }, 'fast');
284         var url = isLink ? href : $(this).attr('action');
285         var argsep = PMA_commonParams.get('arg_separator');
286         var params = 'ajax_request=true' + argsep + 'ajax_page_request=true';
287         var dataPost = AJAX.source.getPostData();
288         if (! isLink) {
289             params += argsep + $(this).serialize();
290         } else if (dataPost) {
291             params += argsep + dataPost;
292             isLink = false;
293         }
294         if (! (history && history.pushState)) {
295             // Add a list of menu hashes that we have in the cache to the request
296             params += PMA_MicroHistory.menus.getRequestParam();
297         }
299         if (AJAX._debug) {
300             console.log('Loading: ' + url); // no need to translate
301         }
303         if (isLink) {
304             AJAX.active = true;
305             AJAX.$msgbox = PMA_ajaxShowMessage();
306             // Save reference for the new link request
307             AJAX.xhr = $.get(url, params, AJAX.responseHandler);
308             if (history && history.pushState) {
309                 var state = {
310                     url : href
311                 };
312                 if (previousLinkAborted) {
313                     // hack: there is already an aborted entry on stack
314                     // so just modify the aborted one
315                     history.replaceState(state, null, href);
316                 } else {
317                     history.pushState(state, null, href);
318                 }
319             }
320         } else {
321             /**
322              * Manually fire the onsubmit event for the form, if any.
323              * The event was saved in the jQuery data object by an onload
324              * handler defined below. Workaround for bug #3583316
325              */
326             var onsubmit = $(this).data('onsubmit');
327             // Submit the request if there is no onsubmit handler
328             // or if it returns a value that evaluates to true
329             if (typeof onsubmit !== 'function' || onsubmit.apply(this, [event])) {
330                 AJAX.active = true;
331                 AJAX.$msgbox = PMA_ajaxShowMessage();
332                 $.post(url, params, AJAX.responseHandler);
333             }
334         }
335     },
336     /**
337      * Called after the request that was initiated by this.requestHandler()
338      * has completed successfully or with a caught error. For completely
339      * failed requests or requests with uncaught errors, see the .ajaxError
340      * handler at the bottom of this file.
341      *
342      * To refer to self use 'AJAX', instead of 'this' as this function
343      * is called in the jQuery context.
344      *
345      * @param object e Event data
346      *
347      * @return void
348      */
349     responseHandler: function (data) {
350         if (typeof data === 'undefined' || data === null) {
351             return;
352         }
353         if (typeof data.success !== 'undefined' && data.success) {
354             $('html, body').animate({ scrollTop: 0 }, 'fast');
355             PMA_ajaxRemoveMessage(AJAX.$msgbox);
357             if (data._redirect) {
358                 PMA_ajaxShowMessage(data._redirect, false);
359                 AJAX.active = false;
360                 AJAX.xhr = null;
361                 return;
362             }
364             AJAX.scriptHandler.reset(function () {
365                 if (data._reloadNavigation) {
366                     PMA_reloadNavigation();
367                 }
368                 if (data._title) {
369                     $('title').replaceWith(data._title);
370                 }
371                 if (data._menu) {
372                     if (history && history.pushState) {
373                         var state = {
374                             url : data._selflink,
375                             menu : data._menu
376                         };
377                         history.replaceState(state, null);
378                         AJAX.handleMenu.replace(data._menu);
379                     } else {
380                         PMA_MicroHistory.menus.replace(data._menu);
381                         PMA_MicroHistory.menus.add(data._menuHash, data._menu);
382                     }
383                 } else if (data._menuHash) {
384                     if (! (history && history.pushState)) {
385                         PMA_MicroHistory.menus.replace(PMA_MicroHistory.menus.get(data._menuHash));
386                     }
387                 }
388                 if (data._disableNaviSettings) {
389                     PMA_disableNaviSettings();
390                 } else {
391                     PMA_ensureNaviSettings(data._selflink);
392                 }
394                 // Remove all containers that may have
395                 // been added outside of #page_content
396                 $('body').children()
397                     .not('#pma_navigation')
398                     .not('#floating_menubar')
399                     .not('#page_nav_icons')
400                     .not('#page_content')
401                     .not('#selflink')
402                     .not('#pma_header')
403                     .not('#pma_footer')
404                     .not('#pma_demo')
405                     .not('#pma_console_container')
406                     .not('#prefs_autoload')
407                     .remove();
408                 // Replace #page_content with new content
409                 if (data.message && data.message.length > 0) {
410                     $('#page_content').replaceWith(
411                         '<div id=\'page_content\'>' + data.message + '</div>'
412                     );
413                     PMA_highlightSQL($('#page_content'));
414                     checkNumberOfFields();
415                 }
417                 if (data._selflink) {
418                     var source = data._selflink.split('?')[0];
419                     // Check for faulty links
420                     $selflink_replace = {
421                         'import.php': 'tbl_sql.php',
422                         'tbl_chart.php': 'sql.php',
423                         'tbl_gis_visualization.php': 'sql.php'
424                     };
425                     if ($selflink_replace[source]) {
426                         var replacement = $selflink_replace[source];
427                         data._selflink = data._selflink.replace(source, replacement);
428                     }
429                     $('#selflink').find('> a').attr('href', data._selflink);
430                 }
431                 if (data._params) {
432                     PMA_commonParams.setAll(data._params);
433                 }
434                 if (data._scripts) {
435                     AJAX.scriptHandler.load(data._scripts);
436                 }
437                 if (data._selflink && data._scripts && data._menuHash && data._params) {
438                     if (! (history && history.pushState)) {
439                         PMA_MicroHistory.add(
440                             data._selflink,
441                             data._scripts,
442                             data._menuHash,
443                             data._params,
444                             AJAX.source.attr('rel')
445                         );
446                     }
447                 }
448                 if (data._displayMessage) {
449                     $('#page_content').prepend(data._displayMessage);
450                     PMA_highlightSQL($('#page_content'));
451                 }
453                 $('#pma_errors').remove();
455                 var msg = '';
456                 if (data._errSubmitMsg) {
457                     msg = data._errSubmitMsg;
458                 }
459                 if (data._errors) {
460                     $('<div/>', { id : 'pma_errors', class : 'clearfloat' })
461                         .insertAfter('#selflink')
462                         .append(data._errors);
463                     // bind for php error reporting forms (bottom)
464                     $('#pma_ignore_errors_bottom').on('click', function (e) {
465                         e.preventDefault();
466                         PMA_ignorePhpErrors();
467                     });
468                     $('#pma_ignore_all_errors_bottom').on('click', function (e) {
469                         e.preventDefault();
470                         PMA_ignorePhpErrors(false);
471                     });
472                     // In case of 'sendErrorReport'='always'
473                     // submit the hidden error reporting form.
474                     if (data._sendErrorAlways === '1' &&
475                         data._stopErrorReportLoop !== '1'
476                     ) {
477                         $('#pma_report_errors_form').submit();
478                         PMA_ajaxShowMessage(PMA_messages.phpErrorsBeingSubmitted, false);
479                         $('html, body').animate({ scrollTop:$(document).height() }, 'slow');
480                     } else if (data._promptPhpErrors) {
481                         // otherwise just prompt user if it is set so.
482                         msg = msg + PMA_messages.phpErrorsFound;
483                         // scroll to bottom where all the errors are displayed.
484                         $('html, body').animate({ scrollTop:$(document).height() }, 'slow');
485                     }
486                 }
487                 PMA_ajaxShowMessage(msg, false);
488                 // bind for php error reporting forms (popup)
489                 $('#pma_ignore_errors_popup').on('click', function () {
490                     PMA_ignorePhpErrors();
491                 });
492                 $('#pma_ignore_all_errors_popup').on('click', function () {
493                     PMA_ignorePhpErrors(false);
494                 });
496                 if (typeof AJAX._callback === 'function') {
497                     AJAX._callback.call();
498                 }
499                 AJAX._callback = function () {};
500             });
501         } else {
502             PMA_ajaxShowMessage(data.error, false);
503             AJAX.active = false;
504             AJAX.xhr = null;
505             PMA_handleRedirectAndReload(data);
506             if (data.fieldWithError) {
507                 $(':input.error').removeClass('error');
508                 $('#' + data.fieldWithError).addClass('error');
509             }
510         }
511     },
512     /**
513      * This object is in charge of downloading scripts,
514      * keeping track of what's downloaded and firing
515      * the onload event for them when the page is ready.
516      */
517     scriptHandler: {
518         /**
519          * @var array _scripts The list of files already downloaded
520          */
521         _scripts: [],
522         /**
523          * @var string _scriptsVersion version of phpMyAdmin from which the
524          *                             scripts have been loaded
525          */
526         _scriptsVersion: null,
527         /**
528          * @var array _scriptsToBeLoaded The list of files that
529          *                               need to be downloaded
530          */
531         _scriptsToBeLoaded: [],
532         /**
533          * @var array _scriptsToBeFired The list of files for which
534          *                              to fire the onload and unload events
535          */
536         _scriptsToBeFired: [],
537         _scriptsCompleted: false,
538         /**
539          * Records that a file has been downloaded
540          *
541          * @param string file The filename
542          * @param string fire Whether this file will be registering
543          *                    onload/teardown events
544          *
545          * @return self For chaining
546          */
547         add: function (file, fire) {
548             this._scripts.push(file);
549             if (fire) {
550                 // Record whether to fire any events for the file
551                 // This is necessary to correctly tear down the initial page
552                 this._scriptsToBeFired.push(file);
553             }
554             return this;
555         },
556         /**
557          * Download a list of js files in one request
558          *
559          * @param array files An array of filenames and flags
560          *
561          * @return void
562          */
563         load: function (files, callback) {
564             var self = this;
565             var i;
566             // Clear loaded scripts if they are from another version of phpMyAdmin.
567             // Depends on common params being set before loading scripts in responseHandler
568             if (self._scriptsVersion === null) {
569                 self._scriptsVersion = PMA_commonParams.get('PMA_VERSION');
570             } else if (self._scriptsVersion !== PMA_commonParams.get('PMA_VERSION')) {
571                 self._scripts = [];
572                 self._scriptsVersion = PMA_commonParams.get('PMA_VERSION');
573             }
574             self._scriptsCompleted = false;
575             self._scriptsToBeFired = [];
576             // We need to first complete list of files to load
577             // as next loop will directly fire requests to load them
578             // and that triggers removal of them from
579             // self._scriptsToBeLoaded
580             for (i in files) {
581                 self._scriptsToBeLoaded.push(files[i].name);
582                 if (files[i].fire) {
583                     self._scriptsToBeFired.push(files[i].name);
584                 }
585             }
586             for (i in files) {
587                 var script = files[i].name;
588                 // Only for scripts that we don't already have
589                 if ($.inArray(script, self._scripts) === -1) {
590                     this.add(script);
591                     this.appendScript(script, callback);
592                 } else {
593                     self.done(script, callback);
594                 }
595             }
596             // Trigger callback if there is nothing else to load
597             self.done(null, callback);
598         },
599         /**
600          * Called whenever all files are loaded
601          *
602          * @return void
603          */
604         done: function (script, callback) {
605             if (typeof ErrorReport !== 'undefined') {
606                 ErrorReport.wrap_global_functions();
607             }
608             if ($.inArray(script, this._scriptsToBeFired)) {
609                 AJAX.fireOnload(script);
610             }
611             if ($.inArray(script, this._scriptsToBeLoaded)) {
612                 this._scriptsToBeLoaded.splice($.inArray(script, this._scriptsToBeLoaded), 1);
613             }
614             if (script === null) {
615                 this._scriptsCompleted = true;
616             }
617             /* We need to wait for last signal (with null) or last script load */
618             AJAX.active = (this._scriptsToBeLoaded.length > 0) || ! this._scriptsCompleted;
619             /* Run callback on last script */
620             if (! AJAX.active && $.isFunction(callback)) {
621                 callback();
622             }
623         },
624         /**
625          * Appends a script element to the head to load the scripts
626          *
627          * @return void
628          */
629         appendScript: function (name, callback) {
630             var head = document.head || document.getElementsByTagName('head')[0];
631             var script = document.createElement('script');
632             var self = this;
634             script.type = 'text/javascript';
635             script.src = 'js/' + name + '?' + 'v=' + encodeURIComponent(PMA_commonParams.get('PMA_VERSION'));
636             script.async = false;
637             script.onload = function () {
638                 self.done(name, callback);
639             };
640             head.appendChild(script);
641         },
642         /**
643          * Fires all the teardown event handlers for the current page
644          * and rebinds all forms and links to the request handler
645          *
646          * @param function callback The callback to call after resetting
647          *
648          * @return void
649          */
650         reset: function (callback) {
651             for (var i in this._scriptsToBeFired) {
652                 AJAX.fireTeardown(this._scriptsToBeFired[i]);
653             }
654             this._scriptsToBeFired = [];
655             /**
656              * Re-attach a generic event handler to clicks
657              * on pages and submissions of forms
658              */
659             $(document).off('click', 'a').on('click', 'a', AJAX.requestHandler);
660             $(document).off('submit', 'form').on('submit', 'form', AJAX.requestHandler);
661             if (! (history && history.pushState)) {
662                 PMA_MicroHistory.update();
663             }
664             callback();
665         }
666     }
670  * Here we register a function that will remove the onsubmit event from all
671  * forms that will be handled by the generic page loader. We then save this
672  * event handler in the "jQuery data", so that we can fire it up later in
673  * AJAX.requestHandler().
675  * See bug #3583316
676  */
677 AJAX.registerOnload('functions.js', function () {
678     // Registering the onload event for functions.js
679     // ensures that it will be fired for all pages
680     $('form').not('.ajax').not('.disableAjax').each(function () {
681         if ($(this).attr('onsubmit')) {
682             $(this).data('onsubmit', this.onsubmit).attr('onsubmit', '');
683         }
684     });
686     var $page_content = $('#page_content');
687     /**
688      * Workaround for passing submit button name,value on ajax form submit
689      * by appending hidden element with submit button name and value.
690      */
691     $page_content.on('click', 'form input[type=submit]', function () {
692         var buttonName = $(this).attr('name');
693         if (typeof buttonName === 'undefined') {
694             return;
695         }
696         $(this).closest('form').append($('<input/>', {
697             'type' : 'hidden',
698             'name' : buttonName,
699             'value': $(this).val()
700         }));
701     });
703     /**
704      * Attach event listener to events when user modify visible
705      * Input,Textarea and select fields to make changes in forms
706      */
707     $page_content.on(
708         'keyup change',
709         'form.lock-page textarea, ' +
710         'form.lock-page input[type="text"], ' +
711         'form.lock-page input[type="number"], ' +
712         'form.lock-page select',
713         { value:1 },
714         AJAX.lockPageHandler
715     );
716     $page_content.on(
717         'change',
718         'form.lock-page input[type="checkbox"], ' +
719         'form.lock-page input[type="radio"]',
720         { value:2 },
721         AJAX.lockPageHandler
722     );
723     /**
724      * Reset lock when lock-page form reset event is fired
725      * Note: reset does not bubble in all browser so attach to
726      * form directly.
727      */
728     $('form.lock-page').on('reset', function (event) {
729         AJAX.resetLock();
730     });
734  * Page load event handler
735  */
736 $(function () {
737     var menuContent = $('<div></div>')
738         .append($('#serverinfo').clone())
739         .append($('#topmenucontainer').clone())
740         .html();
741     if (history && history.pushState) {
742         // set initial state reload
743         var initState = ('state' in window.history && window.history.state !== null);
744         var initURL = $('#selflink').find('> a').attr('href') || location.href;
745         var state = {
746             url : initURL,
747             menu : menuContent
748         };
749         history.replaceState(state, null);
751         $(window).on('popstate', function (event) {
752             var initPop = (! initState && location.href === initURL);
753             initState = true;
754             // check if popstate fired on first page itself
755             if (initPop) {
756                 return;
757             }
758             var state = event.originalEvent.state;
759             if (state && state.menu) {
760                 AJAX.$msgbox = PMA_ajaxShowMessage();
761                 var params = 'ajax_request=true' + PMA_commonParams.get('arg_separator') + 'ajax_page_request=true';
762                 var url = state.url || location.href;
763                 $.get(url, params, AJAX.responseHandler);
764                 // TODO: Check if sometimes menu is not retrieved from server,
765                 // Not sure but it seems menu was missing only for printview which
766                 // been removed lately, so if it's right some dead menu checks/fallbacks
767                 // may need to be removed from this file and Header.php
768                 // AJAX.handleMenu.replace(event.originalEvent.state.menu);
769             }
770         });
771     } else {
772         // Fallback to microhistory mechanism
773         AJAX.scriptHandler
774             .load([{ 'name' : 'microhistory.js', 'fire' : 1 }], function () {
775                 // The cache primer is set by the footer class
776                 if (PMA_MicroHistory.primer.url) {
777                     PMA_MicroHistory.menus.add(
778                         PMA_MicroHistory.primer.menuHash,
779                         menuContent
780                     );
781                 }
782                 $(function () {
783                     // Queue up this event twice to make sure that we get a copy
784                     // of the page after all other onload events have been fired
785                     if (PMA_MicroHistory.primer.url) {
786                         PMA_MicroHistory.add(
787                             PMA_MicroHistory.primer.url,
788                             PMA_MicroHistory.primer.scripts,
789                             PMA_MicroHistory.primer.menuHash
790                         );
791                     }
792                 });
793             });
794     }
798  * Attach a generic event handler to clicks
799  * on pages and submissions of forms
800  */
801 $(document).on('click', 'a', AJAX.requestHandler);
802 $(document).on('submit', 'form', AJAX.requestHandler);
805  * Gracefully handle fatal server errors
806  * (e.g: 500 - Internal server error)
807  */
808 $(document).ajaxError(function (event, request, settings) {
809     if (AJAX._debug) {
810         console.log('AJAX error: status=' + request.status + ', text=' + request.statusText);
811     }
812     // Don't handle aborted requests
813     if (request.status !== 0 || request.statusText !== 'abort') {
814         var details = '';
815         var state = request.state();
817         if (request.status !== 0) {
818             details += '<div>' + escapeHtml(PMA_sprintf(PMA_messages.strErrorCode, request.status)) + '</div>';
819         }
820         details += '<div>' + escapeHtml(PMA_sprintf(PMA_messages.strErrorText, request.statusText + ' (' + state + ')')) + '</div>';
821         if (state === 'rejected' || state === 'timeout') {
822             details += '<div>' + escapeHtml(PMA_messages.strErrorConnection) + '</div>';
823         }
824         PMA_ajaxShowMessage(
825             '<div class="error">' +
826             PMA_messages.strErrorProcessingRequest +
827             details +
828             '</div>',
829             false
830         );
831         AJAX.active = false;
832         AJAX.xhr = null;
833     }