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