Merge remote-tracking branch 'origin/master'
[phpmyadmin.git] / js / navigation.js
blob86f60141b168c243573e9078d631fecd1ed10694
1 /* vim: set expandtab sw=4 ts=4 sts=4: */
2 /**
3  * function used in or for navigation panel
4  *
5  * @package phpMyAdmin-Navigation
6  */
8 /**
9  * updates the tree state in sessionStorage
10  *
11  * @returns void
12  */
13 function navTreeStateUpdate() {
14     // update if session storage is supported
15     if (isStorageSupported('sessionStorage')) {
16         var storage = window.sessionStorage;
17         // try catch necessary here to detect whether
18         // content to be stored exceeds storage capacity
19         try {
20             storage.setItem('navTreePaths', JSON.stringify(traverseNavigationForPaths()));
21             storage.setItem('server', PMA_commonParams.get('server'));
22             storage.setItem('token', PMA_commonParams.get('token'));
23         } catch(error) {
24             // storage capacity exceeded & old navigation tree
25             // state is no more valid, so remove it
26             storage.removeItem('navTreePaths');
27             storage.removeItem('server');
28             storage.removeItem('token');
29         }
30     }
34 /**
35  * updates the filter state in sessionStorage
36  *
37  * @returns void
38  */
39 function navFilterStateUpdate(filterName, filterValue) {
40     if (isStorageSupported('sessionStorage')) {
41         var storage = window.sessionStorage;
42         try {
43             var currentFilter = $.extend({}, JSON.parse(storage.getItem('navTreeSearchFilters')));
44             var filter = {};
45             filter[filterName] = filterValue;
46             currentFilter = $.extend(currentFilter, filter);
47             storage.setItem('navTreeSearchFilters', JSON.stringify(currentFilter));
48         } catch (error) {
49             storage.removeItem('navTreeSearchFilters');
50         }
51     }
55 /**
56  * restores the filter state on navigation reload
57  *
58  * @returns void
59  */
60 function navFilterStateRestore() {
61     if (isStorageSupported('sessionStorage')
62         && typeof window.sessionStorage.navTreeSearchFilters !== 'undefined'
63     ) {
64         var searchClauses = JSON.parse(window.sessionStorage.navTreeSearchFilters);
65         if (Object.keys(searchClauses).length < 1) {
66             return;
67         }
68         // restore database filter if present and not empty
69         if (searchClauses.hasOwnProperty("dbFilter")
70             && searchClauses.dbFilter.length
71         ) {
72             $obj = $('#pma_navigation_tree');
73             if (! $obj.data('fastFilter')) {
74                 $obj.data(
75                     'fastFilter',
76                     new PMA_fastFilter.filter($obj, "")
77                 );
78             }
79             $obj.find('li.fast_filter.db_fast_filter input.searchClause')
80                 .val(searchClauses.dbFilter)
81                 .trigger('keyup');
82         }
83         // find all table filters present in the tree
84         $tableFilters = $('#pma_navigation_tree li.database')
85             .children('div.list_container')
86             .find('li.fast_filter input.searchClause');
87         // restore table filters
88         $tableFilters.each(function () {
89             $obj = $(this).closest('div.list_container');
90             // aPath associated with this filter
91             var filterName = $(this).siblings('input[name=aPath]').val();
92             // if this table's filter has a state stored in storage
93             if (searchClauses.hasOwnProperty(filterName)
94                 && searchClauses[filterName].length
95             ) {
96                 // clear state if item is not visible,
97                 // happens when table filter becomes invisible
98                 // as db filter has already been applied
99                 if (! $obj.is(":visible")) {
100                     navFilterStateUpdate(filterName, "");
101                     return true;
102                 }
103                 if (! $obj.data('fastFilter')) {
104                     $obj.data(
105                         'fastFilter',
106                         new PMA_fastFilter.filter($obj, "")
107                     );
108                 }
109                 $(this).val(searchClauses[filterName])
110                     .trigger('keyup');
111             }
112         });
113     }
117  * Loads child items of a node and executes a given callback
119  * @param isNode
120  * @param $expandElem expander
121  * @param callback    callback function
123  * @returns void
124  */
125 function loadChildNodes(isNode, $expandElem, callback) {
127     var $destination = null;
128     var params = null;
130     if (isNode) {
131         if (!$expandElem.hasClass('expander')) {
132             return;
133         }
134         $destination = $expandElem.closest('li');
135         params = {
136             aPath: $expandElem.find('span.aPath').text(),
137             vPath: $expandElem.find('span.vPath').text(),
138             pos: $expandElem.find('span.pos').text(),
139             pos2_name: $expandElem.find('span.pos2_name').text(),
140             pos2_value: $expandElem.find('span.pos2_value').text(),
141             searchClause: '',
142             searchClause2: ''
143         };
144         if ($expandElem.closest('ul').hasClass('search_results')) {
145             params.searchClause = PMA_fastFilter.getSearchClause();
146             params.searchClause2 = PMA_fastFilter.getSearchClause2($expandElem);
147         }
148     } else {
149         $destination = $('#pma_navigation_tree_content');
150         params = {
151             aPath: $expandElem.attr('aPath'),
152             vPath: $expandElem.attr('vPath'),
153             pos: $expandElem.attr('pos'),
154             pos2_name: '',
155             pos2_value: '',
156             searchClause: '',
157             searchClause2: ''
158         };
159     }
161     var url = $('#pma_navigation').find('a.navigation_url').attr('href');
162     $.get(url, params, function (data) {
163         if (typeof data !== 'undefined' && data.success === true) {
164             $destination.find('div.list_container').remove(); // FIXME: Hack, there shouldn't be a list container there
165             if (isNode) {
166                 $destination.append(data.message);
167                 $expandElem.addClass('loaded');
168             } else {
169                 $destination.html(data.message);
170                 $destination.children()
171                     .first()
172                     .css({
173                         border: '0px',
174                         margin: '0em',
175                         padding : '0em'
176                     })
177                     .slideDown('slow');
178             }
179             if (data._errors) {
180                 var $errors = $(data._errors);
181                 if ($errors.children().length > 0) {
182                     $('#pma_errors').replaceWith(data._errors);
183                 }
184             }
185             if (callback && typeof callback == 'function') {
186                 callback(data);
187             }
188         } else if(data.redirect_flag == "1") {
189             if (window.location.href.indexOf('?') === -1) {
190                 window.location.href += '?session_expired=1';
191             } else {
192                 window.location.href += '&session_expired=1';
193             }
194             window.location.reload();
195         } else {
196             var $throbber = $expandElem.find('img.throbber');
197             $throbber.hide();
198             var $icon = $expandElem.find('img.ic_b_plus');
199             $icon.show();
200             PMA_ajaxShowMessage(data.error, false);
201         }
202     });
206  * Collapses a node in navigation tree.
208  * @param $expandElem expander
210  * @returns void
211  */
212 function collapseTreeNode($expandElem) {
213     var $children = $expandElem.closest('li').children('div.list_container');
214     var $icon = $expandElem.find('img');
215     if ($expandElem.hasClass('loaded')) {
216         if ($icon.is('.ic_b_minus')) {
217             $icon.removeClass('ic_b_minus').addClass('ic_b_plus');
218             $children.slideUp('fast');
219         }
220     }
221     $expandElem.blur();
222     $children.promise().done(navTreeStateUpdate);
226  * Traverse the navigation tree backwards to generate all the actual
227  * and virtual paths, as well as the positions in the pagination at
228  * various levels, if necessary.
230  * @return Object
231  */
232 function traverseNavigationForPaths() {
233     var params = {
234         pos: $('#pma_navigation_tree').find('div.dbselector select').val()
235     };
236     if ($('#navi_db_select').length) {
237         return params;
238     }
239     var count = 0;
240     $('#pma_navigation_tree').find('a.expander:visible').each(function () {
241         if ($(this).find('img').is('.ic_b_minus') &&
242             $(this).closest('li').find('div.list_container .ic_b_minus').length === 0
243         ) {
244             params['n' + count + '_aPath'] = $(this).find('span.aPath').text();
245             params['n' + count + '_vPath'] = $(this).find('span.vPath').text();
247             var pos2_name = $(this).find('span.pos2_name').text();
248             if (! pos2_name) {
249                 pos2_name = $(this)
250                     .parent()
251                     .parent()
252                     .find('span.pos2_name:last')
253                     .text();
254             }
255             var pos2_value = $(this).find('span.pos2_value').text();
256             if (! pos2_value) {
257                 pos2_value = $(this)
258                     .parent()
259                     .parent()
260                     .find('span.pos2_value:last')
261                     .text();
262             }
264             params['n' + count + '_pos2_name'] = pos2_name;
265             params['n' + count + '_pos2_value'] = pos2_value;
267             params['n' + count + '_pos3_name'] = $(this).find('span.pos3_name').text();
268             params['n' + count + '_pos3_value'] = $(this).find('span.pos3_value').text();
269             count++;
270         }
271     });
272     return params;
276  * Executed on page load
277  */
278 $(function () {
279     if (! $('#pma_navigation').length) {
280         // Don't bother running any code if the navigation is not even on the page
281         return;
282     }
284     // Do not let the page reload on submitting the fast filter
285     $(document).on('submit', '.fast_filter', function (event) {
286         event.preventDefault();
287     });
289     // Fire up the resize handlers
290     new ResizeHandler();
292     /**
293      * opens/closes (hides/shows) tree elements
294      * loads data via ajax
295      */
296     $(document).on('click', '#pma_navigation_tree a.expander', function (event) {
297         event.preventDefault();
298         event.stopImmediatePropagation();
299         var $icon = $(this).find('img');
300         if ($icon.is('.ic_b_plus')) {
301             expandTreeNode($(this));
302         } else {
303             collapseTreeNode($(this));
304         }
305     });
307     /**
308      * Register event handler for click on the reload
309      * navigation icon at the top of the panel
310      */
311     $(document).on('click', '#pma_navigation_reload', function (event) {
312         event.preventDefault();
313         // reload icon object
314         var $icon = $(this).find('img');
315         // source of the hidden throbber icon
316         var icon_throbber_src = $('#pma_navigation').find('.throbber').attr('src');
317         // source of the reload icon
318         var icon_reload_src = $icon.attr('src');
319         // replace the source of the reload icon with the one for throbber
320         $icon.attr('src', icon_throbber_src);
321         PMA_reloadNavigation();
322         // after one second, put back the reload icon
323         setTimeout(function () {
324             $icon.attr('src', icon_reload_src);
325         }, 1000);
326     });
328     $(document).on("change", '#navi_db_select',  function (event) {
329         if (! $(this).val()) {
330             PMA_commonParams.set('db', '');
331             PMA_reloadNavigation();
332         }
333         $(this).closest('form').trigger('submit');
334     });
336     /**
337      * Register event handler for click on the collapse all
338      * navigation icon at the top of the navigation tree
339      */
340     $(document).on('click', '#pma_navigation_collapse', function (event) {
341         event.preventDefault();
342         $('#pma_navigation_tree').find('a.expander').each(function() {
343             var $icon = $(this).find('img');
344             if ($icon.is('.ic_b_minus')) {
345                 $(this).click();
346             }
347         });
348     });
350     /**
351      * Register event handler to toggle
352      * the 'link with main panel' icon on mouseenter.
353      */
354     $(document).on('mouseenter', '#pma_navigation_sync', function (event) {
355         event.preventDefault();
356         var synced = $('#pma_navigation_tree').hasClass('synced');
357         var $img = $('#pma_navigation_sync').children('img');
358         if (synced) {
359             $img.removeClass('ic_s_link').addClass('ic_s_unlink');
360         } else {
361             $img.removeClass('ic_s_unlink').addClass('ic_s_link');
362         }
363     });
365     /**
366      * Register event handler to toggle
367      * the 'link with main panel' icon on mouseout.
368      */
369     $(document).on('mouseout', '#pma_navigation_sync', function (event) {
370         event.preventDefault();
371         var synced = $('#pma_navigation_tree').hasClass('synced');
372         var $img = $('#pma_navigation_sync').children('img');
373         if (synced) {
374             $img.removeClass('ic_s_unlink').addClass('ic_s_link');
375         } else {
376             $img.removeClass('ic_s_link').addClass('ic_s_unlink');
377         }
378     });
380     /**
381      * Register event handler to toggle
382      * the linking with main panel behavior
383      */
384     $(document).on('click', '#pma_navigation_sync', function (event) {
385         event.preventDefault();
386         var synced = $('#pma_navigation_tree').hasClass('synced');
387         var $img = $('#pma_navigation_sync').children('img');
388         if (synced) {
389             $img
390                 .removeClass('ic_s_unlink')
391                 .addClass('ic_s_link')
392                 .attr('alt', PMA_messages.linkWithMain)
393                 .attr('title', PMA_messages.linkWithMain);
394             $('#pma_navigation_tree')
395                 .removeClass('synced')
396                 .find('li.selected')
397                 .removeClass('selected');
398         } else {
399             $img
400                 .removeClass('ic_s_link')
401                 .addClass('ic_s_unlink')
402                 .attr('alt', PMA_messages.unlinkWithMain)
403                 .attr('title', PMA_messages.unlinkWithMain);
404             $('#pma_navigation_tree').addClass('synced');
405             PMA_showCurrentNavigation();
406         }
407     });
409     /**
410      * Bind all "fast filter" events
411      */
412     $(document).on('click', '#pma_navigation_tree li.fast_filter span', PMA_fastFilter.events.clear);
413     $(document).on('focus', '#pma_navigation_tree li.fast_filter input.searchClause', PMA_fastFilter.events.focus);
414     $(document).on('blur', '#pma_navigation_tree li.fast_filter input.searchClause', PMA_fastFilter.events.blur);
415     $(document).on('keyup', '#pma_navigation_tree li.fast_filter input.searchClause', PMA_fastFilter.events.keyup);
417     /**
418      * Ajax handler for pagination
419      */
420     $(document).on('click', '#pma_navigation_tree div.pageselector a.ajax', function (event) {
421         event.preventDefault();
422         PMA_navigationTreePagination($(this));
423     });
425     /**
426      * Node highlighting
427      */
428     $(document).on(
429         'mouseover',
430         '#pma_navigation_tree.highlight li:not(.fast_filter)',
431         function () {
432             if ($('li:visible', this).length === 0) {
433                 $(this).addClass('activePointer');
434             }
435         }
436     );
437     $(document).on(
438         'mouseout',
439         '#pma_navigation_tree.highlight li:not(.fast_filter)',
440         function () {
441             $(this).removeClass('activePointer');
442         }
443     );
445     /** Create a Routine, Trigger or Event */
446     $(document).on('click', 'li.new_procedure a.ajax, li.new_function a.ajax', function (event) {
447         event.preventDefault();
448         var dialog = new RTE.object('routine');
449         dialog.editorDialog(1, $(this));
450     });
451     $(document).on('click', 'li.new_trigger a.ajax', function (event) {
452         event.preventDefault();
453         var dialog = new RTE.object('trigger');
454         dialog.editorDialog(1, $(this));
455     });
456     $(document).on('click', 'li.new_event a.ajax', function (event) {
457         event.preventDefault();
458         var dialog = new RTE.object('event');
459         dialog.editorDialog(1, $(this));
460     });
462     /** Edit Routines, Triggers or Events */
463     $(document).on('click', 'li.procedure > a.ajax, li.function > a.ajax', function (event) {
464         event.preventDefault();
465         var dialog = new RTE.object('routine');
466         dialog.editorDialog(0, $(this));
467     });
468     $(document).on('click', 'li.trigger > a.ajax', function (event) {
469         event.preventDefault();
470         var dialog = new RTE.object('trigger');
471         dialog.editorDialog(0, $(this));
472     });
473     $(document).on('click', 'li.event > a.ajax', function (event) {
474         event.preventDefault();
475         var dialog = new RTE.object('event');
476         dialog.editorDialog(0, $(this));
477     });
479     /** Execute Routines */
480     $(document).on('click', 'li.procedure div a.ajax img,' +
481         ' li.function div a.ajax img', function (event) {
482         event.preventDefault();
483         var dialog = new RTE.object('routine');
484         dialog.executeDialog($(this).parent());
485     });
486     /** Export Triggers and Events */
487     $(document).on('click', 'li.trigger div:eq(1) a.ajax img,' +
488         ' li.event div:eq(1) a.ajax img', function (event) {
489         event.preventDefault();
490         var dialog = new RTE.object();
491         dialog.exportDialog($(this).parent());
492     });
494     /** New index */
495     $(document).on('click', '#pma_navigation_tree li.new_index a.ajax', function (event) {
496         event.preventDefault();
497         var url = $(this).attr('href').substr(
498             $(this).attr('href').indexOf('?') + 1
499         ) + '&ajax_request=true';
500         var title = PMA_messages.strAddIndex;
501         indexEditorDialog(url, title);
502     });
504     /** Edit index */
505     $(document).on('click', 'li.index a.ajax', function (event) {
506         event.preventDefault();
507         var url = $(this).attr('href').substr(
508             $(this).attr('href').indexOf('?') + 1
509         ) + '&ajax_request=true';
510         var title = PMA_messages.strEditIndex;
511         indexEditorDialog(url, title);
512     });
514     /** New view */
515     $(document).on('click', 'li.new_view a.ajax', function (event) {
516         event.preventDefault();
517         PMA_createViewDialog($(this));
518     });
520     /** Hide navigation tree item */
521     $(document).on('click', 'a.hideNavItem.ajax', function (event) {
522         event.preventDefault();
523         $.ajax({
524             type: 'POST',
525             url: $(this).attr('href') + '&ajax_request=true',
526             success: function (data) {
527                 if (typeof data !== 'undefined' && data.success === true) {
528                     PMA_reloadNavigation();
529                 } else {
530                     PMA_ajaxShowMessage(data.error);
531                 }
532             }
533         });
534     });
536     /** Display a dialog to choose hidden navigation items to show */
537     $(document).on('click', 'a.showUnhide.ajax', function (event) {
538         event.preventDefault();
539         var $msg = PMA_ajaxShowMessage();
540         $.get($(this).attr('href') + '&ajax_request=1', function (data) {
541             if (typeof data !== 'undefined' && data.success === true) {
542                 PMA_ajaxRemoveMessage($msg);
543                 var buttonOptions = {};
544                 buttonOptions[PMA_messages.strClose] = function () {
545                     $(this).dialog("close");
546                 };
547                 $('<div/>')
548                     .attr('id', 'unhideNavItemDialog')
549                     .append(data.message)
550                     .dialog({
551                         width: 400,
552                         minWidth: 200,
553                         modal: true,
554                         buttons: buttonOptions,
555                         title: PMA_messages.strUnhideNavItem,
556                         close: function () {
557                             $(this).remove();
558                         }
559                     });
560             } else {
561                 PMA_ajaxShowMessage(data.error);
562             }
563         });
564     });
566     /** Show a hidden navigation tree item */
567     $(document).on('click', 'a.unhideNavItem.ajax', function (event) {
568         event.preventDefault();
569         var $tr = $(this).parents('tr');
570         var $msg = PMA_ajaxShowMessage();
571         $.ajax({
572             type: 'POST',
573             url: $(this).attr('href') + '&ajax_request=true',
574             success: function (data) {
575                 PMA_ajaxRemoveMessage($msg);
576                 if (typeof data !== 'undefined' && data.success === true) {
577                     $tr.remove();
578                     PMA_reloadNavigation();
579                 } else {
580                     PMA_ajaxShowMessage(data.error);
581                 }
582             }
583         });
584     });
586     // Add/Remove favorite table using Ajax.
587     $(document).on("click", ".favorite_table_anchor", function (event) {
588         event.preventDefault();
589         $self = $(this);
590         var anchor_id = $self.attr("id");
591         if($self.data("favtargetn") !== null) {
592             if($('a[data-favtargets="' + $self.data("favtargetn") + '"]').length > 0)
593             {
594                 $('a[data-favtargets="' + $self.data("favtargetn") + '"]').trigger('click');
595                 return;
596             }
597         }
599         $.ajax({
600             url: $self.attr('href'),
601             cache: false,
602             type: 'POST',
603             data: {
604                 favorite_tables: (isStorageSupported('localStorage') && typeof window.localStorage.favorite_tables !== 'undefined')
605                     ? window.localStorage.favorite_tables
606                     : ''
607             },
608             success: function (data) {
609                 if (data.changes) {
610                     $('#pma_favorite_list').html(data.list);
611                     $('#' + anchor_id).parent().html(data.anchor);
612                     PMA_tooltip(
613                         $('#' + anchor_id),
614                         'a',
615                         $('#' + anchor_id).attr("title")
616                     );
617                     // Update localStorage.
618                     if (isStorageSupported('localStorage')) {
619                         window.localStorage.favorite_tables = data.favorite_tables;
620                     }
621                 } else {
622                     PMA_ajaxShowMessage(data.message);
623                 }
624             }
625         });
626     });
627     // Check if session storage is supported
628     if (isStorageSupported('sessionStorage')) {
629         var storage = window.sessionStorage;
630         // remove tree from storage if Navi_panel config form is submitted
631         $(document).on('submit', 'form.config-form', function(event) {
632             storage.removeItem('navTreePaths');
633         });
634         // Initialize if no previous state is defined
635         if ($('#pma_navigation_tree_content').length &&
636             typeof storage.navTreePaths === 'undefined'
637         ) {
638             navTreeStateUpdate();
639         } else if (PMA_commonParams.get('server') === storage.server &&
640             PMA_commonParams.get('token') === storage.token
641         ) {
642             // Reload the tree to the state before page refresh
643             PMA_reloadNavigation(navFilterStateRestore, JSON.parse(storage.navTreePaths));
644         }
645     }
649  * Expands a node in navigation tree.
651  * @param $expandElem expander
652  * @param callback    callback function
654  * @returns void
655  */
656 function expandTreeNode($expandElem, callback) {
657     var $children = $expandElem.closest('li').children('div.list_container');
658     var $icon = $expandElem.find('img');
659     if ($expandElem.hasClass('loaded')) {
660         if ($icon.is('.ic_b_plus')) {
661             $icon.removeClass('ic_b_plus').addClass('ic_b_minus');
662             $children.slideDown('fast');
663         }
664         if (callback && typeof callback == 'function') {
665             callback.call();
666         }
667         $children.promise().done(navTreeStateUpdate);
668     } else {
669         var $throbber = $('#pma_navigation').find('.throbber')
670             .first()
671             .clone()
672             .css({visibility: 'visible', display: 'block'})
673             .click(false);
674         $icon.hide();
675         $throbber.insertBefore($icon);
677         loadChildNodes(true, $expandElem, function (data) {
678             if (typeof data !== 'undefined' && data.success === true) {
679                 var $destination = $expandElem.closest('li');
680                 $icon.removeClass('ic_b_plus').addClass('ic_b_minus');
681                 $children = $destination.children('div.list_container');
682                 $children.slideDown('fast');
683                 if ($destination.find('ul > li').length == 1) {
684                     $destination.find('ul > li')
685                         .find('a.expander.container')
686                         .click();
687                 }
688                 if (callback && typeof callback == 'function') {
689                     callback.call();
690                 }
691                 PMA_showFullName($destination);
692             } else {
693                 PMA_ajaxShowMessage(data.error, false);
694             }
695             $icon.show();
696             $throbber.remove();
697             $children.promise().done(navTreeStateUpdate);
698         });
699     }
700     $expandElem.blur();
704  * Auto-scrolls the newly chosen database
706  * @param  object   $element    The element to set to view
707  * @param  boolean  $forceToTop Whether to force scroll to top
709  */
710 function scrollToView($element, $forceToTop) {
711     navFilterStateRestore();
712     var $container = $('#pma_navigation_tree_content');
713     var elemTop = $element.offset().top - $container.offset().top;
714     var textHeight = 20;
715     var scrollPadding = 20; // extra padding from top of bottom when scrolling to view
716     if (elemTop < 0 || $forceToTop) {
717         $container.stop().animate({
718             scrollTop: elemTop + $container.scrollTop() - scrollPadding
719         });
720     } else if (elemTop + textHeight > $container.height()) {
721         $container.stop().animate({
722             scrollTop: elemTop + textHeight - $container.height() + $container.scrollTop() + scrollPadding
723         });
724     }
728  * Expand the navigation and highlight the current database or table/view
730  * @returns void
731  */
732 function PMA_showCurrentNavigation() {
733     var db = PMA_commonParams.get('db');
734     var table = PMA_commonParams.get('table');
735     $('#pma_navigation_tree')
736         .find('li.selected')
737         .removeClass('selected');
738     if (db) {
739         var $dbItem = findLoadedItem(
740             $('#pma_navigation_tree').find('> div'), db, 'database', !table
741         );
742         if ($('#navi_db_select').length &&
743             $('option:selected', $('#navi_db_select')).length
744         ) {
745             if (! PMA_selectCurrentDb()) {
746                 return;
747             }
748             // If loaded database in navigation is not same as current one
749             if ($('#pma_navigation_tree_content').find('span.loaded_db:first').text()
750                 !== $('#navi_db_select').val()
751             ) {
752                 loadChildNodes(false, $('option:selected', $('#navi_db_select')), function (data) {
753                     handleTableOrDb(table, $('#pma_navigation_tree_content'));
754                     var $children = $('#pma_navigation_tree_content').children('div.list_container');
755                     $children.promise().done(navTreeStateUpdate);
756                 });
757             } else {
758                 handleTableOrDb(table, $('#pma_navigation_tree_content'));
759             }
760         } else if ($dbItem) {
761             var $expander = $dbItem.children('div:first').children('a.expander');
762             // if not loaded or loaded but collapsed
763             if (! $expander.hasClass('loaded') ||
764                 $expander.find('img').is('.ic_b_plus')
765             ) {
766                 expandTreeNode($expander, function () {
767                     handleTableOrDb(table, $dbItem);
768                 });
769             } else {
770                 handleTableOrDb(table, $dbItem);
771             }
772         }
773     } else if ($('#navi_db_select').length && $('#navi_db_select').val()) {
774         $('#navi_db_select').val('').hide().trigger('change');
775     }
776     PMA_showFullName($('#pma_navigation_tree'));
778     function handleTableOrDb(table, $dbItem) {
779         if (table) {
780             loadAndHighlightTableOrView($dbItem, table);
781         } else {
782             var $container = $dbItem.children('div.list_container');
783             var $tableContainer = $container.children('ul').children('li.tableContainer');
784             if ($tableContainer.length > 0) {
785                 var $expander = $tableContainer.children('div:first').children('a.expander');
786                 $tableContainer.addClass('selected');
787                 expandTreeNode($expander, function () {
788                     scrollToView($dbItem, true);
789                 });
790             } else {
791                 scrollToView($dbItem, true);
792             }
793         }
794     }
796     function findLoadedItem($container, name, clazz, doSelect) {
797         var ret = false;
798         $container.children('ul').children('li').each(function () {
799             var $li = $(this);
800             // this is a navigation group, recurse
801             if ($li.is('.navGroup')) {
802                 var $container = $li.children('div.list_container');
803                 var $childRet = findLoadedItem(
804                     $container, name, clazz, doSelect
805                 );
806                 if ($childRet) {
807                     ret = $childRet;
808                     return false;
809                 }
810             } else { // this is a real navigation item
811                 // name and class matches
812                 if (((clazz && $li.is('.' + clazz)) || ! clazz) &&
813                         $li.children('a').text() == name) {
814                     if (doSelect) {
815                         $li.addClass('selected');
816                     }
817                     // taverse up and expand and parent navigation groups
818                     $li.parents('.navGroup').each(function () {
819                         var $cont = $(this).children('div.list_container');
820                         if (! $cont.is(':visible')) {
821                             $(this)
822                                 .children('div:first')
823                                 .children('a.expander')
824                                 .click();
825                         }
826                     });
827                     ret = $li;
828                     return false;
829                 }
830             }
831         });
832         return ret;
833     }
835     function loadAndHighlightTableOrView($dbItem, itemName) {
836         var $container = $dbItem.children('div.list_container');
837         var $expander;
838         var $whichItem = isItemInContainer($container, itemName, 'li.table, li.view');
839         //If item already there in some container
840         if ($whichItem) {
841             //get the relevant container while may also be a subcontainer
842             var $relatedContainer = $whichItem.closest('li.subContainer').length
843                 ? $whichItem.closest('li.subContainer')
844                 : $dbItem;
845             $whichItem = findLoadedItem(
846                 $relatedContainer.children('div.list_container'),
847                 itemName, null, true
848             );
849             //Show directly
850             showTableOrView($whichItem, $relatedContainer.children('div:first').children('a.expander'));
851         //else if item not there, try loading once
852         } else {
853             var $sub_containers = $dbItem.find('.subContainer');
854             //If there are subContainers i.e. tableContainer or viewContainer
855             if($sub_containers.length > 0) {
856                 var $containers = [];
857                 $sub_containers.each(function (index) {
858                     $containers[index] = $(this);
859                     $expander = $containers[index]
860                         .children('div:first')
861                         .children('a.expander');
862                     if (! $expander.hasClass('loaded')) {
863                         loadAndShowTableOrView($expander, $containers[index], itemName);
864                     }
865                 });
866             // else if no subContainers
867             } else {
868                 $expander = $dbItem
869                     .children('div:first')
870                     .children('a.expander');
871                 if (! $expander.hasClass('loaded')) {
872                     loadAndShowTableOrView($expander, $dbItem, itemName);
873                 }
874             }
875         }
876     }
878     function loadAndShowTableOrView($expander, $relatedContainer, itemName) {
879         loadChildNodes(true, $expander, function (data) {
880             var $whichItem = findLoadedItem(
881                 $relatedContainer.children('div.list_container'),
882                 itemName, null, true
883             );
884             if ($whichItem) {
885                 showTableOrView($whichItem, $expander);
886             }
887         });
888     }
890     function showTableOrView($whichItem, $expander) {
891         expandTreeNode($expander, function (data) {
892             if ($whichItem) {
893                 scrollToView($whichItem, false);
894             }
895         });
896     }
898     function isItemInContainer($container, name, clazz)
899     {
900         var $whichItem = null;
901         $items = $container.find(clazz);
902         var found = false;
903         $items.each(function () {
904             if ($(this).children('a').text() == name) {
905                 $whichItem = $(this);
906                 return false;
907             }
908         });
909         return $whichItem;
910     }
914  * Disable navigation panel settings
916  * @return void
917  */
918 function PMA_disableNaviSettings() {
919     $('#pma_navigation_settings_icon').addClass('hide');
920     $('#pma_navigation_settings').remove();
924  * Ensure that navigation panel settings is properly setup.
925  * If not, set it up
927  * @return void
928  */
929 function PMA_ensureNaviSettings(selflink) {
930     $('#pma_navigation_settings_icon').removeClass('hide');
932     if (!$('#pma_navigation_settings').length) {
933         var params = {
934             getNaviSettings: true
935         };
936         var url = $('#pma_navigation').find('a.navigation_url').attr('href');
937         $.post(url, params, function (data) {
938             if (typeof data !== 'undefined' && data.success) {
939                 $('#pma_navi_settings_container').html(data.message);
940                 setupRestoreField();
941                 setupValidation();
942                 setupConfigTabs();
943                 $('#pma_navigation_settings').find('form').attr('action', selflink);
944             } else {
945                 PMA_ajaxShowMessage(data.error);
946             }
947         });
948     } else {
949         $('#pma_navigation_settings').find('form').attr('action', selflink);
950     }
954  * Reloads the whole navigation tree while preserving its state
956  * @param  function     the callback function
957  * @param  Object       stored navigation paths
959  * @return void
960  */
961 function PMA_reloadNavigation(callback, paths) {
962     var params = {
963         reload: true,
964         no_debug: true
965     };
966     paths = paths || traverseNavigationForPaths();
967     $.extend(params, paths);
968     if ($('#navi_db_select').length) {
969         params.db = PMA_commonParams.get('db');
970         requestNaviReload(params);
971         return;
972     }
973     requestNaviReload(params);
975     function requestNaviReload(params) {
976         var url = $('#pma_navigation').find('a.navigation_url').attr('href');
977         $.post(url, params, function (data) {
978             if (typeof data !== 'undefined' && data.success) {
979                 $('#pma_navigation_tree').html(data.message).children('div').show();
980                 if ($('#pma_navigation_tree').hasClass('synced')) {
981                     PMA_selectCurrentDb();
982                     PMA_showCurrentNavigation();
983                 }
984                 // Fire the callback, if any
985                 if (typeof callback === 'function') {
986                     callback.call();
987                 }
988                 navTreeStateUpdate();
989             } else {
990                 PMA_ajaxShowMessage(data.error);
991             }
992         });
993     }
996 function PMA_selectCurrentDb() {
997     var $naviDbSelect = $('#navi_db_select');
999     if (!$naviDbSelect.length) {
1000         return false;
1001     }
1003     if (PMA_commonParams.get('db')) { // db selected
1004         $naviDbSelect.show();
1005     }
1007     $naviDbSelect.val(PMA_commonParams.get('db'));
1008     return $naviDbSelect.val() === PMA_commonParams.get('db');
1013  * Handles any requests to change the page in a branch of a tree
1015  * This can be called from link click or select change event handlers
1017  * @param object $this A jQuery object that points to the element that
1018  * initiated the action of changing the page
1020  * @return void
1021  */
1022 function PMA_navigationTreePagination($this) {
1023     var $msgbox = PMA_ajaxShowMessage();
1024     var isDbSelector = $this.closest('div.pageselector').is('.dbselector');
1025     var url, params;
1026     if ($this[0].tagName == 'A') {
1027         url = $this.attr('href');
1028         params = 'ajax_request=true';
1029     } else { // tagName == 'SELECT'
1030         url = 'navigation.php';
1031         params = $this.closest("form").serialize() + '&ajax_request=true';
1032     }
1033     var searchClause = PMA_fastFilter.getSearchClause();
1034     if (searchClause) {
1035         params += '&searchClause=' + encodeURIComponent(searchClause);
1036     }
1037     if (isDbSelector) {
1038         params += '&full=true';
1039     } else {
1040         var searchClause2 = PMA_fastFilter.getSearchClause2($this);
1041         if (searchClause2) {
1042             params += '&searchClause2=' + encodeURIComponent(searchClause2);
1043         }
1044     }
1045     $.post(url, params, function (data) {
1046         if (typeof data !== 'undefined' && data.success) {
1047             PMA_ajaxRemoveMessage($msgbox);
1048             if (isDbSelector) {
1049                 var val = PMA_fastFilter.getSearchClause();
1050                 $('#pma_navigation_tree')
1051                     .html(data.message)
1052                     .children('div')
1053                     .show();
1054                 if (val) {
1055                     $('#pma_navigation_tree')
1056                         .find('li.fast_filter input.searchClause')
1057                         .val(val);
1058                 }
1059             } else {
1060                 var $parent = $this.closest('div.list_container').parent();
1061                 var val = PMA_fastFilter.getSearchClause2($this);
1062                 $this.closest('div.list_container').html(
1063                     $(data.message).children().show()
1064                 );
1065                 if (val) {
1066                     $parent.find('li.fast_filter input.searchClause').val(val);
1067                 }
1068                 $parent.find('span.pos2_value:first').text(
1069                     $parent.find('span.pos2_value:last').text()
1070                 );
1071                 $parent.find('span.pos3_value:first').text(
1072                     $parent.find('span.pos3_value:last').text()
1073                 );
1074             }
1075         } else {
1076             PMA_ajaxShowMessage(data.error);
1077             PMA_handleRedirectAndReload(data);
1078         }
1079         navTreeStateUpdate();
1080     });
1084  * @var ResizeHandler Custom object that manages the resizing of the navigation
1086  * XXX: Must only be ever instanciated once
1087  * XXX: Inside event handlers the 'this' object is accessed as 'event.data.resize_handler'
1088  */
1089 var ResizeHandler = function () {
1090     /**
1091      * @var int panel_width Used by the collapser to know where to go
1092      *                      back to when uncollapsing the panel
1093      */
1094     this.panel_width = 0;
1095     /**
1096      * @var string left Used to provide support for RTL languages
1097      */
1098     this.left = $('html').attr('dir') == 'ltr' ? 'left' : 'right';
1099     /**
1100      * Adjusts the width of the navigation panel to the specified value
1101      *
1102      * @param int pos Navigation width in pixels
1103      *
1104      * @return void
1105      */
1106     this.setWidth = function (pos) {
1107         var $resizer = $('#pma_navigation_resizer');
1108         var resizer_width = $resizer.width();
1109         var $collapser = $('#pma_navigation_collapser');
1110         $('#pma_navigation').width(pos);
1111         $('body').css('margin-' + this.left, pos + 'px');
1112         $("#floating_menubar, #pma_console")
1113             .css('margin-' + this.left, (pos + resizer_width) + 'px');
1114         $resizer.css(this.left, pos + 'px');
1115         if (pos === 0) {
1116             $collapser
1117                 .css(this.left, pos + resizer_width)
1118                 .html(this.getSymbol(pos))
1119                 .prop('title', PMA_messages.strShowPanel);
1120         } else {
1121             $collapser
1122                 .css(this.left, pos)
1123                 .html(this.getSymbol(pos))
1124                 .prop('title', PMA_messages.strHidePanel);
1125         }
1126         setTimeout(function () {
1127             $(window).trigger('resize');
1128         }, 4);
1129     };
1130     /**
1131      * Returns the horizontal position of the mouse,
1132      * relative to the outer side of the navigation panel
1133      *
1134      * @param int pos Navigation width in pixels
1135      *
1136      * @return void
1137      */
1138     this.getPos = function (event) {
1139         var pos = event.pageX;
1140         var windowWidth = $(window).width();
1141         var windowScroll = $(window).scrollLeft();
1142         pos = pos - windowScroll;
1143         if (this.left != 'left') {
1144             pos = windowWidth - event.pageX;
1145         }
1146         if (pos < 0) {
1147             pos = 0;
1148         } else if (pos + 100 >= windowWidth) {
1149             pos = windowWidth - 100;
1150         } else {
1151             this.panel_width = 0;
1152         }
1153         return pos;
1154     };
1155     /**
1156      * Returns the HTML code for the arrow symbol used in the collapser
1157      *
1158      * @param int width The width of the panel
1159      *
1160      * @return string
1161      */
1162     this.getSymbol = function (width) {
1163         if (this.left == 'left') {
1164             if (width === 0) {
1165                 return '&rarr;';
1166             } else {
1167                 return '&larr;';
1168             }
1169         } else {
1170             if (width === 0) {
1171                 return '&larr;';
1172             } else {
1173                 return '&rarr;';
1174             }
1175         }
1176     };
1177     /**
1178      * Event handler for initiating a resize of the panel
1179      *
1180      * @param object e Event data (contains a reference to resizeHandler)
1181      *
1182      * @return void
1183      */
1184     this.mousedown = function (event) {
1185         event.preventDefault();
1186         $(document)
1187             .bind('mousemove', {'resize_handler': event.data.resize_handler},
1188                 $.throttle(event.data.resize_handler.mousemove, 4))
1189             .bind('mouseup', {'resize_handler': event.data.resize_handler},
1190                 event.data.resize_handler.mouseup);
1191         $('body').css('cursor', 'col-resize');
1192     };
1193     /**
1194      * Event handler for terminating a resize of the panel
1195      *
1196      * @param object e Event data (contains a reference to resizeHandler)
1197      *
1198      * @return void
1199      */
1200     this.mouseup = function (event) {
1201         $('body').css('cursor', '');
1202         $.cookie('pma_navi_width', event.data.resize_handler.getPos(event));
1203         $('#topmenu').menuResizer('resize');
1204         $(document)
1205             .unbind('mousemove')
1206             .unbind('mouseup');
1207     };
1208     /**
1209      * Event handler for updating the panel during a resize operation
1210      *
1211      * @param object e Event data (contains a reference to resizeHandler)
1212      *
1213      * @return void
1214      */
1215     this.mousemove = function (event) {
1216         event.preventDefault();
1217         var pos = event.data.resize_handler.getPos(event);
1218         event.data.resize_handler.setWidth(pos);
1219         if ($('.sticky_columns').length !== 0) {
1220             handleAllStickyColumns();
1221         }
1222     };
1223     /**
1224      * Event handler for collapsing the panel
1225      *
1226      * @param object e Event data (contains a reference to resizeHandler)
1227      *
1228      * @return void
1229      */
1230     this.collapse = function (event) {
1231         event.preventDefault();
1232         var panel_width = event.data.resize_handler.panel_width;
1233         var width = $('#pma_navigation').width();
1234         if (width === 0 && panel_width === 0) {
1235             panel_width = 240;
1236         }
1237         event.data.resize_handler.setWidth(panel_width);
1238         event.data.resize_handler.panel_width = width;
1239     };
1240     /**
1241      * Event handler for resizing the navigation tree height on window resize
1242      *
1243      * @return void
1244      */
1245     this.treeResize = function (event) {
1246         var $nav        = $("#pma_navigation"),
1247             $nav_tree   = $("#pma_navigation_tree"),
1248             $nav_header = $("#pma_navigation_header"),
1249             $nav_tree_content = $("#pma_navigation_tree_content");
1250         $nav_tree.height($nav.height() - $nav_header.height());
1251         if ($nav_tree_content.length > 0) {
1252             $nav_tree_content.height($nav_tree.height() - $nav_tree_content.position().top);
1253         } else {
1254             //TODO: in fast filter search response there is no #pma_navigation_tree_content, needs to be added in php
1255             $nav_tree.css({
1256                 'overflow-y': 'auto'
1257             });
1258         }
1259         // Set content bottom space beacuse of console
1260         $('body').css('margin-bottom', $('#pma_console').height() + 'px');
1261     };
1262     /* Initialisation section begins here */
1263     if ($.cookie('pma_navi_width')) {
1264         // If we have a cookie, set the width of the panel to its value
1265         var pos = Math.abs(parseInt($.cookie('pma_navi_width'), 10) || 0);
1266         this.setWidth(pos);
1267         $('#topmenu').menuResizer('resize');
1268     }
1269     // Register the events for the resizer and the collapser
1270     $(document).on('mousedown', '#pma_navigation_resizer', {'resize_handler': this}, this.mousedown);
1271     $(document).on('click', '#pma_navigation_collapser', {'resize_handler': this}, this.collapse);
1273     // Add the correct arrow symbol to the collapser
1274     $('#pma_navigation_collapser').html(this.getSymbol($('#pma_navigation').width()));
1275     // Fix navigation tree height
1276     $(window).on('resize', this.treeResize);
1277     // need to call this now and then, browser might decide
1278     // to show/hide horizontal scrollbars depending on page content width
1279     setInterval(this.treeResize, 2000);
1280     this.treeResize();
1281 }; // End of ResizeHandler
1284  * @var object PMA_fastFilter Handles the functionality that allows filtering
1285  *                            of the items in a branch of the navigation tree
1286  */
1287 var PMA_fastFilter = {
1288     /**
1289      * Construct for the asynchronous fast filter functionality
1290      *
1291      * @param object $this        A jQuery object pointing to the list container
1292      *                            which is the nearest parent of the fast filter
1293      * @param string searchClause The query string for the filter
1294      *
1295      * @return new PMA_fastFilter.filter object
1296      */
1297     filter: function ($this, searchClause) {
1298         /**
1299          * @var object $this A jQuery object pointing to the list container
1300          *                   which is the nearest parent of the fast filter
1301          */
1302         this.$this = $this;
1303         /**
1304          * @var bool searchClause The query string for the filter
1305          */
1306         this.searchClause = searchClause;
1307         /**
1308          * @var object $clone A clone of the original contents
1309          *                    of the navigation branch before
1310          *                    the fast filter was applied
1311          */
1312         this.$clone = $this.clone();
1313         /**
1314          * @var object xhr A reference to the ajax request that is currently running
1315          */
1316         this.xhr = null;
1317         /**
1318          * @var int timeout Used to delay the request for asynchronous search
1319          */
1320         this.timeout = null;
1322         var $filterInput = $this.find('li.fast_filter input.searchClause');
1323         if ($filterInput.length !== 0 &&
1324             $filterInput.val() !== '' &&
1325             $filterInput.val() != $filterInput[0].defaultValue
1326         ) {
1327             this.request();
1328         }
1329     },
1330     /**
1331      * Gets the query string from the database fast filter form
1332      *
1333      * @return string
1334      */
1335     getSearchClause: function () {
1336         var retval = '';
1337         var $input = $('#pma_navigation_tree')
1338             .find('li.fast_filter.db_fast_filter input.searchClause');
1339         if ($input.length && $input.val() != $input[0].defaultValue) {
1340             retval = $input.val();
1341         }
1342         return retval;
1343     },
1344     /**
1345      * Gets the query string from a second level item's fast filter form
1346      * The retrieval is done by trasversing the navigation tree backwards
1347      *
1348      * @return string
1349      */
1350     getSearchClause2: function ($this) {
1351         var $filterContainer = $this.closest('div.list_container');
1352         var $filterInput = $([]);
1353         if ($filterContainer
1354             .find('li.fast_filter:not(.db_fast_filter) input.searchClause')
1355             .length !== 0) {
1356             $filterInput = $filterContainer
1357                 .find('li.fast_filter:not(.db_fast_filter) input.searchClause');
1358         }
1359         var searchClause2 = '';
1360         if ($filterInput.length !== 0 &&
1361             $filterInput.first().val() != $filterInput[0].defaultValue
1362         ) {
1363             searchClause2 = $filterInput.val();
1364         }
1365         return searchClause2;
1366     },
1367     /**
1368      * @var hash events A list of functions that are bound to DOM events
1369      *                  at the top of this file
1370      */
1371     events: {
1372         focus: function (event) {
1373             var $obj = $(this).closest('div.list_container');
1374             if (! $obj.data('fastFilter')) {
1375                 $obj.data(
1376                     'fastFilter',
1377                     new PMA_fastFilter.filter($obj, $(this).val())
1378                 );
1379             }
1380             if ($(this).val() == this.defaultValue) {
1381                 $(this).val('');
1382             } else {
1383                 $(this).select();
1384             }
1385         },
1386         blur: function (event) {
1387             if ($(this).val() === '') {
1388                 $(this).val(this.defaultValue);
1389             }
1390             var $obj = $(this).closest('div.list_container');
1391             if ($(this).val() == this.defaultValue && $obj.data('fastFilter')) {
1392                 $obj.data('fastFilter').restore();
1393             }
1394         },
1395         keyup: function (event) {
1396             var $obj = $(this).closest('div.list_container');
1397             var str = '';
1398             if ($(this).val() != this.defaultValue && $(this).val() !== '') {
1399                 $obj.find('div.pageselector').hide();
1400                 str = $(this).val();
1401             }
1403             /**
1404              * FIXME at the server level a value match is done while on
1405              * the client side it is a regex match. These two should be aligned
1406              */
1408             // regex used for filtering.
1409             var regex;
1410             try {
1411                 regex = new RegExp(str, 'i');
1412             } catch (err) {
1413                 return;
1414             }
1416             // this is the div that houses the items to be filtered by this filter.
1417             var outerContainer;
1418             if ($(this).closest('li.fast_filter').is('.db_fast_filter')) {
1419                 outerContainer = $('#pma_navigation_tree_content');
1420             } else {
1421                 outerContainer = $obj;
1422             }
1424             // filters items that are directly under the div as well as grouped in
1425             // groups. Does not filter child items (i.e. a database search does
1426             // not filter tables)
1427             var item_filter = function($curr) {
1428                 $curr.children('ul').children('li.navGroup').each(function() {
1429                     $(this).children('div.list_container').each(function() {
1430                         item_filter($(this)); // recursive
1431                     });
1432                 });
1433                 $curr.children('ul').children('li').children('a').not('.container').each(function() {
1434                     if (regex.test($(this).text())) {
1435                         $(this).parent().show().removeClass('hidden');
1436                     } else {
1437                         $(this).parent().hide().addClass('hidden');
1438                     }
1439                 });
1440             };
1441             item_filter(outerContainer);
1443             // hides containers that does not have any visible children
1444             var container_filter = function ($curr) {
1445                 $curr.children('ul').children('li.navGroup').each(function() {
1446                     var $group = $(this);
1447                     $group.children('div.list_container').each(function() {
1448                         container_filter($(this)); // recursive
1449                     });
1450                     $group.show().removeClass('hidden');
1451                     if ($group.children('div.list_container').children('ul')
1452                             .children('li').not('.hidden').length === 0) {
1453                         $group.hide().addClass('hidden');
1454                     }
1455                 });
1456             };
1457             container_filter(outerContainer);
1459             if ($(this).val() != this.defaultValue && $(this).val() !== '') {
1460                 if (! $obj.data('fastFilter')) {
1461                     $obj.data(
1462                         'fastFilter',
1463                         new PMA_fastFilter.filter($obj, $(this).val())
1464                     );
1465                 } else {
1466                     if (event.keyCode == 13) {
1467                         $obj.data('fastFilter').update($(this).val());
1468                     }
1469                 }
1470             } else if ($obj.data('fastFilter')) {
1471                 $obj.data('fastFilter').restore(true);
1472             }
1473             // update filter state
1474             var filterName;
1475             if ($(this).attr('name') == 'searchClause2') {
1476                 filterName = $(this).siblings('input[name=aPath]').val();
1477             } else {
1478                 filterName = 'dbFilter';
1479             }
1480             navFilterStateUpdate(filterName, $(this).val());
1481         },
1482         clear: function (event) {
1483             event.stopPropagation();
1484             // Clear the input and apply the fast filter with empty input
1485             var filter = $(this).closest('div.list_container').data('fastFilter');
1486             if (filter) {
1487                 filter.restore();
1488             }
1489             var value = $(this).prev()[0].defaultValue;
1490             $(this).prev().val(value).trigger('keyup');
1491         }
1492     }
1495  * Handles a change in the search clause
1497  * @param string searchClause The query string for the filter
1499  * @return void
1500  */
1501 PMA_fastFilter.filter.prototype.update = function (searchClause) {
1502     if (this.searchClause != searchClause) {
1503         this.searchClause = searchClause;
1504         this.request();
1505     }
1508  * After a delay of 250mS, initiates a request to retrieve search results
1509  * Multiple calls to this function will always abort the previous request
1511  * @return void
1512  */
1513 PMA_fastFilter.filter.prototype.request = function () {
1514     var self = this;
1515     if (self.$this.find('li.fast_filter').find('img.throbber').length === 0) {
1516         self.$this.find('li.fast_filter').append(
1517             $('<div class="throbber"></div>').append(
1518                 $('#pma_navigation_content')
1519                     .find('img.throbber')
1520                     .clone()
1521                     .css({visibility: 'visible', display: 'block'})
1522             )
1523         );
1524     }
1525     if (self.xhr) {
1526         self.xhr.abort();
1527     }
1528     var url = $('#pma_navigation').find('a.navigation_url').attr('href');
1529     var params = self.$this.find('> ul > li > form.fast_filter').first().serialize();
1530     if (self.$this.find('> ul > li > form.fast_filter:first input[name=searchClause]').length === 0) {
1531         var $input = $('#pma_navigation_tree').find('li.fast_filter.db_fast_filter input.searchClause');
1532         if ($input.length && $input.val() != $input[0].defaultValue) {
1533             params += '&searchClause=' + encodeURIComponent($input.val());
1534         }
1535     }
1536     self.xhr = $.ajax({
1537         url: url,
1538         type: 'post',
1539         dataType: 'json',
1540         data: params,
1541         complete: function (jqXHR, status) {
1542             if (status != 'abort') {
1543                 var data = $.parseJSON(jqXHR.responseText);
1544                 self.$this.find('li.fast_filter').find('div.throbber').remove();
1545                 if (data && data.results) {
1546                     self.swap.apply(self, [data.message]);
1547                 }
1548             }
1549         }
1550     });
1553  * Replaces the contents of the navigation branch with the search results
1555  * @param string list The search results
1557  * @return void
1558  */
1559 PMA_fastFilter.filter.prototype.swap = function (list) {
1560     this.$this
1561         .html($(list).html())
1562         .children()
1563         .show()
1564         .end()
1565         .find('li.fast_filter input.searchClause')
1566         .val(this.searchClause);
1567     this.$this.data('fastFilter', this);
1570  * Restores the navigation to the original state after the fast filter is cleared
1572  * @param bool focus Whether to also focus the input box of the fast filter
1574  * @return void
1575  */
1576 PMA_fastFilter.filter.prototype.restore = function (focus) {
1577     if(this.$this.children('ul').first().hasClass('search_results')) {
1578         this.$this.html(this.$clone.html()).children().show();
1579         this.$this.data('fastFilter', this);
1580         if (focus) {
1581             this.$this.find('li.fast_filter input.searchClause').focus();
1582         }
1583     }
1584     this.searchClause = '';
1585     this.$this.find('div.pageselector').show();
1586     this.$this.find('div.throbber').remove();
1590  * Show full name when cursor hover and name not shown completely
1592  * @param object $containerELem Container element
1594  * @return void
1595  */
1596 function PMA_showFullName($containerELem) {
1598     $containerELem.find('.hover_show_full').mouseenter(function() {
1599         /** mouseenter */
1600         var $this = $(this);
1601         var thisOffset = $this.offset();
1602         if($this.text() === '') {
1603             return;
1604         }
1605         var $parent = $this.parent();
1606         if(  ($parent.offset().left + $parent.outerWidth())
1607            < (thisOffset.left + $this.outerWidth()))
1608         {
1609             var $fullNameLayer = $('#full_name_layer');
1610             if($fullNameLayer.length === 0)
1611             {
1612                 $('body').append('<div id="full_name_layer" class="hide"></div>');
1613                 $('#full_name_layer').mouseleave(function() {
1614                     /** mouseleave */
1615                     $(this).addClass('hide')
1616                            .removeClass('hovering');
1617                 }).mouseenter(function() {
1618                     /** mouseenter */
1619                     $(this).addClass('hovering');
1620                 });
1621                 $fullNameLayer = $('#full_name_layer');
1622             }
1623             $fullNameLayer.removeClass('hide');
1624             $fullNameLayer.css({left: thisOffset.left, top: thisOffset.top});
1625             $fullNameLayer.html($this.clone());
1626             setTimeout(function() {
1627                 if(! $fullNameLayer.hasClass('hovering'))
1628                 {
1629                     $fullNameLayer.trigger('mouseleave');
1630                 }
1631             }, 200);
1632         }
1633     });