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