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