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