Fixed failing test
[phpmyadmin.git] / js / navigation.js
blobcdbccf6a495dce73d87655ae9b21dee9ceed9e55
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  * Executed on page load
10  */
11 $(function () {
12     if (! $('#pma_navigation').length) {
13         // Don't bother running any code if the navigation is not even on the page
14         return;
15     }
17     // Do not let the page reload on submitting the fast filter
18     $(document).on('submit', '.fast_filter', function (event) {
19         event.preventDefault();
20     });
22     // Fire up the resize handlers
23     new ResizeHandler();
25     /**
26      * opens/closes (hides/shows) tree elements
27      * loads data via ajax
28      */
29     $('#pma_navigation_tree a.expander').live('click', function (event) {
30         event.preventDefault();
31         event.stopImmediatePropagation();
32         var $icon = $(this).find('img');
33         if ($icon.is('.ic_b_plus')) {
34             expandTreeNode($(this));
35         } else {
36             collapseTreeNode($(this));
37         }
38     });
40     /**
41      * Register event handler for click on the reload
42      * navigation icon at the top of the panel
43      */
44     $('#pma_navigation_reload').live('click', function (event) {
45         event.preventDefault();
46         $('#pma_navigation .throbber')
47             .first()
48             .css('visibility', 'visible');
49         PMA_reloadNavigation();
50     });
52     /**
53      * Bind all "fast filter" events
54      */
55     $('#pma_navigation_tree li.fast_filter span')
56         .live('click', PMA_fastFilter.events.clear);
57     $('#pma_navigation_tree li.fast_filter input.searchClause')
58         .live('focus', PMA_fastFilter.events.focus)
59         .live('blur', PMA_fastFilter.events.blur)
60         .live('keyup', PMA_fastFilter.events.keyup);
62     /**
63      * Ajax handler for pagination
64      */
65     $('#pma_navigation_tree div.pageselector a.ajax').live('click', function (event) {
66         event.preventDefault();
67         PMA_navigationTreePagination($(this));
68     });
70     /**
71      * Node highlighting
72      */
73     $('#pma_navigation_tree.highlight li:not(.fast_filter)').live(
74         'mouseover',
75         function () {
76             if ($('li:visible', this).length === 0) {
77                 $(this).addClass('activePointer');
78             }
79         }
80     );
81     $('#pma_navigation_tree.highlight li:not(.fast_filter)').live(
82         'mouseout',
83         function () {
84             $(this).removeClass('activePointer');
85         }
86     );
88     /**
89      * Jump to recent table
90      */
91     $('#recentTable').live('change', function () {
92         if (this.value !== '') {
93             var arr = jQuery.parseJSON(this.value);
94             var $form = $(this).closest('form');
95             $form.find('input[name=db]').val(arr.db);
96             $form.find('input[name=table]').val(arr.table);
97             $form.submit();
98         }
99     });
101     /** Create a Routine, Trigger or Event */
102     $('li.new_procedure a.ajax, li.new_function a.ajax').live('click', function (event) {
103         event.preventDefault();
104         var dialog = new RTE.object('routine');
105         dialog.editorDialog(1, $(this));
106     });
107     $('li.new_trigger a.ajax').live('click', function (event) {
108         event.preventDefault();
109         var dialog = new RTE.object('trigger');
110         dialog.editorDialog(1, $(this));
111     });
112     $('li.new_event a.ajax').live('click', function (event) {
113         event.preventDefault();
114         var dialog = new RTE.object('event');
115         dialog.editorDialog(1, $(this));
116     });
118     /** Edit Routines, Triggers and Events */
119     $('li.procedure > a.ajax, li.function > a.ajax').live('click', function (event) {
120         event.preventDefault();
121         var dialog = new RTE.object('routine');
122         dialog.editorDialog(0, $(this));
123     });
124     $('li.trigger > a.ajax').live('click', function (event) {
125         event.preventDefault();
126         var dialog = new RTE.object('trigger');
127         dialog.editorDialog(0, $(this));
128     });
129     $('li.event > a.ajax').live('click', function (event) {
130         event.preventDefault();
131         var dialog = new RTE.object('event');
132         dialog.editorDialog(0, $(this));
133     });
135     /** Export Routines, Triggers and Events */
136     $('li.procedure div:eq(1) a.ajax img,' +
137         ' li.function div:eq(1) a.ajax img,' +
138         ' li.trigger div:eq(1) a.ajax img,' +
139         ' li.event div:eq(1) a.ajax img'
140         ).live('click', function (event) {
141         event.preventDefault();
142         var dialog = new RTE.object();
143         dialog.exportDialog($(this).parent());
144     });
146     /** New index */
147     $('li.new_index a.ajax').live('click', function (event) {
148         event.preventDefault();
149         var url = $(this).attr('href').substr(
150             $(this).attr('href').indexOf('?') + 1
151         ) + '&ajax_request=true';
152         var title = PMA_messages.strAddIndex;
153         indexEditorDialog(url, title);
154     });
156     /** Edit index */
157     $('li.index a.ajax').live('click', function (event) {
158         event.preventDefault();
159         var url = $(this).attr('href').substr(
160             $(this).attr('href').indexOf('?') + 1
161         ) + '&ajax_request=true';
162         var title = PMA_messages.strEditIndex;
163         indexEditorDialog(url, title);
164     });
166     /** New view */
167     $('li.new_view a.ajax').live('click', function (event) {
168         event.preventDefault();
169         PMA_createViewDialog($(this));
170     });
172     /** Hide navigation tree item */
173     $('a.hideNavItem.ajax').live('click', function (event) {
174         event.preventDefault();
175         $.ajax({
176             url: $(this).attr('href') + '&ajax_request=true',
177             success: function (data) {
178                 if (data.success === true) {
179                     PMA_reloadNavigation();
180                 } else {
181                     PMA_ajaxShowMessage(data.error);
182                 }
183             }
184         });
185     });
187     /** Display a dialog to choose hidden navigation items to show */
188     $('a.showUnhide.ajax').live('click', function (event) {
189         event.preventDefault();
190         var $msg = PMA_ajaxShowMessage();
191         $.get($(this).attr('href') + '&ajax_request=1', function (data) {
192             if (data.success === true) {
193                 PMA_ajaxRemoveMessage($msg);
194                 var buttonOptions = {};
195                 buttonOptions[PMA_messages.strClose] = function () {
196                     $(this).dialog("close");
197                 };
198                 var $dialog = $('<div/>')
199                     .attr('id', 'unhideNavItemDialog')
200                     .append(data.message)
201                     .dialog({
202                         width: 400,
203                         minWidth: 200,
204                         modal: true,
205                         buttons: buttonOptions,
206                         title: PMA_messages.strUnhideNavItem,
207                         close: function () {
208                             $(this).remove();
209                         }
210                     });
211             } else {
212                 PMA_ajaxShowMessage(data.error);
213             }
214         });
215     });
217     /** Show a hidden navigation tree item */
218     $('a.unhideNavItem.ajax').live('click', function (event) {
219         event.preventDefault();
220         var $tr = $(this).parents('tr');
221         var $msg = PMA_ajaxShowMessage();
222         $.ajax({
223             url: $(this).attr('href') + '&ajax_request=true',
224             success: function (data) {
225                 PMA_ajaxRemoveMessage($msg);
226                 if (data.success === true) {
227                     $tr.remove();
228                     PMA_reloadNavigation();
229                 } else {
230                     PMA_ajaxShowMessage(data.error);
231                 }
232             }
233         });
234     });
236     PMA_showCurrentNavigation();
240  * Expands a node in navigation tree.
242  * @param $expandElem expander
243  * @param callback    callback function
245  * @returns void
246  */
247 function expandTreeNode($expandElem, callback)
249     var $children = $expandElem.closest('li').children('div.list_container');
250     var $icon = $expandElem.find('img');
251     if ($expandElem.hasClass('loaded')) {
252         if ($icon.is('.ic_b_plus')) {
253             $icon.removeClass('ic_b_plus').addClass('ic_b_minus');
254             $children.show('fast');
255         }
256         if (callback && typeof callback == 'function') {
257             callback.call();
258         }
259     } else {
260         var $throbber = $('#pma_navigation .throbber')
261             .first()
262             .clone()
263             .css('visibility', 'visible')
264             .click(false);
265         $icon.hide();
266         $throbber.insertBefore($icon);
268         loadChildNodes($expandElem, function (data) {
269             if (data.success === true) {
270                 var $destination = $expandElem.closest('li');
271                 $icon.removeClass('ic_b_plus').addClass('ic_b_minus');
272                 $destination
273                     .children('div.list_container')
274                     .show('fast');
275                 if ($destination.find('ul > li').length == 1) {
276                     $destination.find('ul > li')
277                         .find('a.expander.container')
278                         .click();
279                 }
280                 if (callback && typeof callback == 'function') {
281                     callback.call();
282                 }
283             } else {
284                 PMA_ajaxShowMessage(data.error, false);
285             }
286             $icon.show();
287             $throbber.remove();
288         });
289     }
290     $expandElem.blur();
294  * Auto-scrolls the newly chosen database
296  * @param  object   $element    The element to set to view
297  * @param  object   $container  The container srollable element
299  */
300 function scrollToView($element, $container) {
301     var pushToOffset = $element.offset().top - $container.offset().top + $container.scrollTop();
302     $('#pma_navigation_tree_content').stop().animate({
303         scrollTop: pushToOffset
304     });
308  * Collapses a node in navigation tree.
310  * @param $expandElem expander
312  * @returns void
313  */
314 function collapseTreeNode($expandElem) {
315     var $children = $expandElem.closest('li').children('div.list_container');
316     var $icon = $expandElem.find('img');
317     if ($expandElem.hasClass('loaded')) {
318         if ($icon.is('.ic_b_minus')) {
319             $icon.removeClass('ic_b_minus').addClass('ic_b_plus');
320             $children.hide('fast');
321         }
322     }
323     $expandElem.blur();
327  * Loads child items of a node and executes a given callback
329  * @param $expandElem expander
330  * @param callback    callback function
332  * @returns void
333  */
334 function loadChildNodes($expandElem, callback) {
335     var $destination = $expandElem.closest('li');
337     var searchClause = PMA_fastFilter.getSearchClause();
338     var searchClause2 = PMA_fastFilter.getSearchClause2($expandElem);
340     var params = {
341         aPath: $expandElem.find('span.aPath').text(),
342         vPath: $expandElem.find('span.vPath').text(),
343         pos: $expandElem.find('span.pos').text(),
344         pos2_name: $expandElem.find('span.pos2_name').text(),
345         pos2_value: $expandElem.find('span.pos2_value').text(),
346         searchClause: searchClause,
347         searchClause2: searchClause2
348     };
350     var url = $('#pma_navigation').find('a.navigation_url').attr('href');
351     $.get(url, params, function (data) {
352         if (data.success === true) {
353             $expandElem.addClass('loaded');
354             $destination.find('div.list_container').remove(); // FIXME: Hack, there shouldn't be a list container there
355             $destination.append(data.message);
356             if (callback && typeof callback == 'function') {
357                 callback(data);
358             }
359         }
360     });
364  * Expand the navigation and highlight the current database or table/view
366  * @returns void
367  */
368 function PMA_showCurrentNavigation()
370     var db = PMA_commonParams.get('db');
371     var table = PMA_commonParams.get('table');
372     $('#pma_navigation_tree')
373         .find('li.selected')
374         .removeClass('selected');
375     if (db) {
376         var $dbItem = findLoadedItem(
377             $('#pma_navigation_tree > div'), db, 'database', !table
378         );
379         if ($dbItem) {
380             var $expander = $dbItem.children('div:first').children('a.expander');
381             // if not loaded or loaded but collapsed
382             if (! $expander.hasClass('loaded') ||
383                 $expander.find('img').is('.ic_b_plus')
384             ) {
385                 expandTreeNode($expander, function () {
386                     handleTableOrDb(table, $dbItem);
387                 });
388             } else {
389                 handleTableOrDb(table, $dbItem);
390             }
391         }
392     }
394     function handleTableOrDb(table, $dbItem) {
395         if (table) {
396             loadAndHighlightTableOrView($dbItem, table);
397         } else {
398             var $container = $dbItem.children('div.list_container');
399             var $tableContainer = $container.children('ul').children('li.tableContainer');
400             if ($tableContainer.length > 0) {
401                 var $expander = $tableContainer.children('div:first').children('a.expander');
402                 expandTreeNode($expander, function () {
403                     scrollToView($dbItem, $('#pma_navigation_tree_content'));
404                 });
405             } else {
406                 scrollToView($dbItem, $('#pma_navigation_tree_content'));
407             }
408         }
409     }
411     function findLoadedItem($container, name, clazz, doSelect) {
412         var ret = false;
413         $container.children('ul').children('li').each(function () {
414             var $li = $(this);
415             // this is a navigation group, recurse
416             if ($li.is('.navGroup')) {
417                 var $container = $li.children('div.list_container');
418                 var $childRet = findLoadedItem(
419                     $container, name, clazz, doSelect
420                 );
421                 if ($childRet) {
422                     ret = $childRet;
423                     return false;
424                 }
425             } else { // this is a real navigation item
426                 // name and class matches
427                 if (((clazz && $li.is('.' + clazz)) || ! clazz) &&
428                         $li.children('a').text() == name) {
429                     if (doSelect) {
430                         $li.addClass('selected');
431                     }
432                     // taverse up and expand and parent navigation groups
433                     $li.parents('.navGroup').each(function () {
434                         $cont = $(this).children('div.list_container');
435                         if (! $cont.is(':visible')) {
436                             $(this)
437                                 .children('div:first')
438                                 .children('a.expander')
439                                 .click();
440                         }
441                     });
442                     ret = $li;
443                     return false;
444                 }
445             }
446         });
447         return ret;
448     }
450     function loadAndHighlightTableOrView($dbItem, table) {
451         var $container = $dbItem.children('div.list_container');
452         var $tableContainer = $container
453             .children('ul')
454             .children('li.tableContainer');
455         var $viewContainer = $container
456             .children('ul')
457             .children('li.viewContainer');
459         if ($tableContainer.length > 0) {
460             var $expander = $tableContainer
461                 .children('div:first')
462                 .children('a.expander');
464             if (! $expander.hasClass('loaded')) {
465                 loadChildNodes($expander, function (data) {
466                     highlightTableOrView($tableContainer, $viewContainer, table);
467                 });
468             } else {
469                 highlightTableOrView($tableContainer, $viewContainer, table);
470             }
471         } else if ($viewContainer.length > 0) {
472             highlightView($viewContainer, table);
473         } else {
474             // no containers, highlight the item
475             var $tableOrView = findLoadedItem($container, table, null, true);
476             if ($tableOrView){
477                 scrollToView($tableOrView, $('#pma_navigation_tree_content'));
478             }
479         }
480     }
482     function highlightTableOrView($tableContainer, $viewContainer, table)
483     {
484         if (isItemInContainer($tableContainer, table, 'table')) {
485             var $expander = $tableContainer
486                 .children('div:first')
487                 .children('a.expander');
488             if ($expander.find('img').is('.ic_b_plus')) {
489                 expandTreeNode($expander);
490             }
491             var $table = findLoadedItem(
492                 $tableContainer.children('div.list_container'),
493                 table, 'table', true
494             );
495             if ($table) {
496                 scrollToView($table, $('#pma_navigation_tree_content'));
497             }
498         } else if ($viewContainer.length > 0) {
499             highlightView($viewContainer, table);
500         }
501     }
503     function isItemInContainer($container, name, clazz)
504     {
505         $items = $container.find('li.' + clazz);
506         var found = false;
507         $items.each(function () {
508             if ($(this).children('a').text() == name) {
509                 found = true;
510                 return false;
511             }
512         });
513         return found;
514     }
516     function highlightView($viewContainer, view) {
517         var $expander = $viewContainer
518             .children('div:first')
519             .children('a.expander');
520         if (! $expander.hasClass('loaded') ||
521             $expander.find('img').is('.ic_b_plus')
522         ) {
523             expandTreeNode($expander, function () {
524                 var $view = findLoadedItem(
525                     $viewContainer.children('div.list_container'),
526                     view, 'view', true
527                 );
528                 if ($view) {
529                     scrollToView($view, $('#pma_navigation_tree_content'));
530                 }
531             });
532         } else {
533             var $view = findLoadedItem(
534                 $viewContainer.children('div.list_container'),
535                 view, 'view', true
536             );
537             if ($view) {
538                 scrollToView($view, $('#pma_navigation_tree_content'));
539             }
540         }
541     }
545  * Reloads the whole navigation tree while preserving its state
547  * @param  function     the callback function
548  * @return void
549  */
550 function PMA_reloadNavigation(callback) {
551     var $throbber = $('#pma_navigation .throbber')
552         .first()
553         .css({
554             'visibility' : 'visible',
555             'display' : 'block'
556         });
557     var params = {
558         reload: true,
559         pos: $('#pma_navigation_tree').find('a.expander:first > span.pos').text()
560     };
561     // Traverse the navigation tree backwards to generate all the actual
562     // and virtual paths, as well as the positions in the pagination at
563     // various levels, if necessary.
564     var count = 0;
565     $('#pma_navigation_tree').find('a.expander:visible').each(function () {
566         if ($(this).find('img').is('.ic_b_minus') &&
567             $(this).closest('li').find('div.list_container .ic_b_minus').length === 0
568         ) {
569             params['n' + count + '_aPath'] = $(this).find('span.aPath').text();
570             params['n' + count + '_vPath'] = $(this).find('span.vPath').text();
572             var pos2_name = $(this).find('span.pos2_name').text();
573             if (! pos2_name) {
574                 pos2_name = $(this)
575                     .parent()
576                     .parent()
577                     .find('span.pos2_name:last')
578                     .text();
579             }
580             var pos2_value = $(this).find('span.pos2_value').text();
581             if (! pos2_value) {
582                 pos2_value = $(this)
583                     .parent()
584                     .parent()
585                     .find('span.pos2_value:last')
586                     .text();
587             }
589             params['n' + count + '_pos2_name'] = pos2_name;
590             params['n' + count + '_pos2_value'] = pos2_value;
592             params['n' + count + '_pos3_name'] = $(this).find('span.pos3_name').text();
593             params['n' + count + '_pos3_value'] = $(this).find('span.pos3_value').text();
594             count++;
595         }
596     });
597     var url = $('#pma_navigation').find('a.navigation_url').attr('href');
598     $.post(url, params, function (data) {
599         // Hide throbber if it's visible
600         $('#pma_navigation .throbber')
601             .first()
602             .css('visibility', 'hidden');
603         if (data.success) {
604             $('#pma_navigation_tree').html(data.message).children('div').show();
605             PMA_showCurrentNavigation();
606             // Fire the callback, if any
607             if (typeof callback === 'function') {
608                 callback.call();
609             }
610         } else {
611             PMA_ajaxShowMessage(data.error);
612         }
613     });
617  * Handles any requests to change the page in a branch of a tree
619  * This can be called from link click or select change event handlers
621  * @param object $this A jQuery object that points to the element that
622  * initiated the action of changing the page
624  * @return void
625  */
626 function PMA_navigationTreePagination($this)
628     var $msgbox = PMA_ajaxShowMessage();
629     var isDbSelector = $this.closest('div.pageselector').is('.dbselector');
630     var url, params;
631     if ($this[0].tagName == 'A') {
632         url = $this.attr('href');
633         params = 'ajax_request=true';
634     } else { // tagName == 'SELECT'
635         url = 'navigation.php';
636         params = $this.closest("form").serialize() + '&ajax_request=true';
637     }
638     var searchClause = PMA_fastFilter.getSearchClause();
639     if (searchClause) {
640         params += '&searchClause=' + encodeURIComponent(searchClause);
641     }
642     if (isDbSelector) {
643         params += '&full=true';
644     } else {
645         var searchClause2 = PMA_fastFilter.getSearchClause2($this);
646         if (searchClause2) {
647             params += '&searchClause2=' + encodeURIComponent(searchClause2);
648         }
649     }
650     $.post(url, params, function (data) {
651         PMA_ajaxRemoveMessage($msgbox);
652         if (data.success) {
653             if (isDbSelector) {
654                 var val = PMA_fastFilter.getSearchClause();
655                 $('#pma_navigation_tree')
656                     .html(data.message)
657                     .children('div')
658                     .show();
659                 if (val) {
660                     $('#pma_navigation_tree')
661                         .find('li.fast_filter input.searchClause')
662                         .val(val);
663                 }
664             } else {
665                 var $parent = $this.closest('div.list_container').parent();
666                 var val = PMA_fastFilter.getSearchClause2($this);
667                 $this.closest('div.list_container').html(
668                     $(data.message).children().show()
669                 );
670                 if (val) {
671                     $parent.find('li.fast_filter input.searchClause').val(val);
672                 }
673                 $parent.find('span.pos2_value:first').text(
674                     $parent.find('span.pos2_value:last').text()
675                 );
676                 $parent.find('span.pos3_value:first').text(
677                     $parent.find('span.pos3_value:last').text()
678                 );
679             }
680         } else {
681             PMA_ajaxShowMessage(data.error);
682         }
683     });
687  * @var ResizeHandler Custom object that manages the resizing of the navigation
689  * XXX: Must only be ever instanciated once
690  * XXX: Inside event handlers the 'this' object is accessed as 'event.data.resize_handler'
691  */
692 var ResizeHandler = function () {
693     /**
694      * Whether the user has initiated a resize operation
695      */
696     this.active = false;
697     /**
698      * @var int panel_width Used by the collapser to know where to go
699      *                      back to when uncollapsing the panel
700      */
701     this.panel_width = 0;
702     /**
703      * @var string left Used to provide support for RTL languages
704      */
705     this.left = $('html').attr('dir') == 'ltr' ? 'left' : 'right';
706     /**
707      * Adjusts the width of the navigation panel to the specified value
708      *
709      * @param int pos Navigation width in pixels
710      *
711      * @return void
712      */
713     this.setWidth = function (pos) {
714         var $resizer = $('#pma_navigation_resizer');
715         var resizer_width = $resizer.width();
716         var $collapser = $('#pma_navigation_collapser');
717         $('#pma_navigation').width(pos);
718         $('body').css('margin-' + this.left, pos + 'px');
719         $("#floating_menubar")
720             .css('margin-' + this.left, (pos + resizer_width) + 'px');
721         $resizer.css(this.left, pos + 'px');
722         if (pos === 0) {
723             $collapser
724                 .css(this.left, pos + resizer_width)
725                 .html(this.getSymbol(pos))
726                 .prop('title', PMA_messages.strShowPanel);
727         } else {
728             $collapser
729                 .css(this.left, pos)
730                 .html(this.getSymbol(pos))
731                 .prop('title', PMA_messages.strHidePanel);
732         }
733         setTimeout(function () {
734             $(window).trigger('resize');
735         }, 4);
736     };
737     /**
738      * Returns the horizontal position of the mouse,
739      * relative to the outer side of the navigation panel
740      *
741      * @param int pos Navigation width in pixels
742      *
743      * @return void
744      */
745     this.getPos = function (event) {
746         var pos = event.pageX;
747         var windowWidth = $(window).width();
748         if (this.left != 'left') {
749             pos = windowWidth - event.pageX;
750         }
751         if (pos < 0) {
752             pos = 0;
753         } else if (pos + 100 >= windowWidth) {
754             pos = windowWidth - 100;
755         } else {
756             this.panel_width = 0;
757         }
758         return pos;
759     };
760     /**
761      * Returns the HTML code for the arrow symbol used in the collapser
762      *
763      * @param int width The width of the panel
764      *
765      * @return string
766      */
767     this.getSymbol = function (width) {
768         if (this.left == 'left') {
769             if (width === 0) {
770                 return '&rarr;';
771             } else {
772                 return '&larr;';
773             }
774         } else {
775             if (width === 0) {
776                 return '&larr;';
777             } else {
778                 return '&rarr;';
779             }
780         }
781     };
782     /**
783      * Event handler for initiating a resize of the panel
784      *
785      * @param object e Event data (contains a reference to resizeHandler)
786      *
787      * @return void
788      */
789     this.mousedown = function (event) {
790         event.preventDefault();
791         event.data.resize_handler.active = true;
792         $('body').css('cursor', 'col-resize');
793     };
794     /**
795      * Event handler for terminating a resize of the panel
796      *
797      * @param object e Event data (contains a reference to resizeHandler)
798      *
799      * @return void
800      */
801     this.mouseup = function (event) {
802         if (event.data.resize_handler.active) {
803             event.data.resize_handler.active = false;
804             $('body').css('cursor', '');
805             $.cookie('pma_navi_width', event.data.resize_handler.getPos(event));
806             $('#topmenu').menuResizer('resize');
807         }
808     };
809     /**
810      * Event handler for updating the panel during a resize operation
811      *
812      * @param object e Event data (contains a reference to resizeHandler)
813      *
814      * @return void
815      */
816     this.mousemove = function (event) {
817         if (event.data && event.data.resize_handler && event.data.resize_handler.active) {
818             event.preventDefault();
819             var pos = event.data.resize_handler.getPos(event);
820             event.data.resize_handler.setWidth(pos);
821         }
822     };
823     /**
824      * Event handler for collapsing the panel
825      *
826      * @param object e Event data (contains a reference to resizeHandler)
827      *
828      * @return void
829      */
830     this.collapse = function (event) {
831         event.preventDefault();
832         event.data.active = false;
833         var panel_width = event.data.resize_handler.panel_width;
834         var width = $('#pma_navigation').width();
835         if (width === 0 && panel_width === 0) {
836             panel_width = 240;
837         }
838         event.data.resize_handler.setWidth(panel_width);
839         event.data.resize_handler.panel_width = width;
840     };
841     /**
842      * Event handler for resizing the navigation tree height on window resize
843      *
844      * @return void
845      */
846     this.treeResize = function (event) {
847         var $nav        = $("#pma_navigation"),
848             $nav_tree   = $("#pma_navigation_tree"),
849             $nav_header = $("#pma_navigation_header"),
850             $nav_tree_content = $("#pma_navigation_tree_content");
851         $nav_tree.height($nav.height() - $nav_header.height());
852         $nav_tree_content.height($nav_tree.height() - $nav_tree_content.position().top);
853     };
854     /* Initialisation section begins here */
855     if ($.cookie('pma_navi_width')) {
856         // If we have a cookie, set the width of the panel to its value
857         var pos = Math.abs(parseInt($.cookie('pma_navi_width'), 10) || 0);
858         this.setWidth(pos);
859         $('#topmenu').menuResizer('resize');
860     }
861     // Register the events for the resizer and the collapser
862     $('#pma_navigation_resizer')
863         .live('mousedown', {'resize_handler': this}, this.mousedown);
864     $(document)
865         .bind('mouseup', {'resize_handler': this}, this.mouseup)
866         .bind('mousemove', {'resize_handler': this}, $.throttle(this.mousemove, 4));
867     var $collapser = $('#pma_navigation_collapser');
868     $collapser.live('click', {'resize_handler': this}, this.collapse);
869     // Add the correct arrow symbol to the collapser
870     $collapser.html(this.getSymbol($('#pma_navigation').width()));
871     // Fix navigation tree height
872     $(window).on('resize', this.treeResize);
873     // need to call this now and then, browser might decide
874     // to show/hide horizontal scrollbars depending on page content width
875     setInterval(this.treeResize, 2000);
876 }; // End of ResizeHandler
879  * @var object PMA_fastFilter Handles the functionality that allows filtering
880  *                            of the items in a branch of the navigation tree
881  */
882 var PMA_fastFilter = {
883     /**
884      * Construct for the asynchronous fast filter functionality
885      *
886      * @param object $this        A jQuery object pointing to the list container
887      *                            which is the nearest parent of the fast filter
888      * @param string searchClause The query string for the filter
889      *
890      * @return new PMA_fastFilter.filter object
891      */
892     filter: function ($this, searchClause) {
893         /**
894          * @var object $this A jQuery object pointing to the list container
895          *                   which is the nearest parent of the fast filter
896          */
897         this.$this = $this;
898         /**
899          * @var bool searchClause The query string for the filter
900          */
901         this.searchClause = searchClause;
902         /**
903          * @var object $clone A clone of the original contents
904          *                    of the navigation branch before
905          *                    the fast filter was applied
906          */
907         this.$clone = $this.clone();
908         /**
909          * @var bool swapped Whether the user clicked on the "N other results" link
910          */
911         this.swapped = false;
912         /**
913          * @var object xhr A reference to the ajax request that is currently running
914          */
915         this.xhr = null;
916         /**
917          * @var int timeout Used to delay the request for asynchronous search
918          */
919         this.timeout = null;
921         var $filterInput = $this.find('li.fast_filter input.searchClause');
922         if ($filterInput.length !== 0 &&
923             $filterInput.val() !== '' &&
924             $filterInput.val() != $filterInput[0].defaultValue
925         ) {
926             this.request();
927         }
928     },
929     /**
930      * Gets the query string from the database fast filter form
931      *
932      * @return string
933      */
934     getSearchClause: function () {
935         var retval = '';
936         var $input = $('#pma_navigation_tree')
937             .find('li.fast_filter.db_fast_filter input.searchClause');
938         if ($input.length && $input.val() != $input[0].defaultValue) {
939             retval = $input.val();
940         }
941         return retval;
942     },
943     /**
944      * Gets the query string from a second level item's fast filter form
945      * The retrieval is done by trasversing the navigation tree backwards
946      *
947      * @return string
948      */
949     getSearchClause2: function ($this) {
950         var $filterContainer = $this.closest('div.list_container');
951         var $filterInput = $([]);
952         while (1) {
953             if ($filterContainer.find('li.fast_filter:not(.db_fast_filter) input.searchClause').length !== 0) {
954                 $filterInput = $filterContainer.find('li.fast_filter:not(.db_fast_filter) input.searchClause');
955                 break;
956             } else if (! $filterContainer.is('div.list_container')) {
957                 break;
958             }
959             $filterContainer = $filterContainer
960                 .parent()
961                 .closest('div.list_container');
962         }
963         var searchClause2 = '';
964         if ($filterInput.length !== 0 &&
965             $filterInput.first().val() != $filterInput[0].defaultValue
966         ) {
967             searchClause2 = $filterInput.val();
968         }
969         return searchClause2;
970     },
971     /**
972      * @var hash events A list of functions that are bound to DOM events
973      *                  at the top of this file
974      */
975     events: {
976         focus: function (event) {
977             var $obj = $(this).closest('div.list_container');
978             if (! $obj.data('fastFilter')) {
979                 $obj.data(
980                     'fastFilter',
981                     new PMA_fastFilter.filter($obj, $(this).val())
982                 );
983             }
984             if ($(this).val() == this.defaultValue) {
985                 $(this).val('');
986             } else {
987                 $(this).select();
988             }
989         },
990         blur: function (event) {
991             if ($(this).val() === '') {
992                 $(this).val(this.defaultValue);
993             }
994             var $obj = $(this).closest('div.list_container');
995             if ($(this).val() == this.defaultValue && $obj.data('fastFilter')) {
996                 $obj.data('fastFilter').restore();
997             }
998         },
999         keyup: function (event) {
1000             var $obj = $(this).closest('div.list_container');
1001             var str = '';
1002             if ($(this).val() != this.defaultValue && $(this).val() !== '') {
1003                 $obj.find('div.pageselector').hide();
1004                 str = $(this).val();
1005             }
1007             /**
1008              * FIXME at the server level a value match is done while on
1009              * the client side it is a regex match. These two should be aligned
1010              */
1012             // regex used for filtering.
1013             var regex;
1014             try {
1015                 regex = new RegExp(str, 'i');
1016             } catch (err) {
1017                 return;
1018             }
1020             // this is the div that houses the items to be filtered by this filter.
1021             var outerContainer;
1022             if ($(this).closest('li.fast_filter').is('.db_fast_filter')) {
1023                 outerContainer = $('#pma_navigation_tree_content');
1024             } else {
1025                 outerContainer = $obj;
1026             }
1028             // filters items that are directly under the div as well as grouped in
1029             // groups. Does not filter child items (i.e. a database search does
1030             // not filter tables)
1031             var item_filter = function($curr) {
1032                 $curr.children('ul').children('li.navGroup').each(function() {
1033                     $(this).children('div.list_container').each(function() {
1034                         item_filter($(this)); // recursive
1035                     });
1036                 });
1037                 $curr.children('ul').children('li').children('a').not('.container').each(function() {
1038                     if (regex.test($(this).text())) {
1039                         $(this).parent().show().removeClass('hidden');
1040                     } else {
1041                         $(this).parent().hide().addClass('hidden');
1042                     }
1043                 });
1044             };
1045             item_filter(outerContainer);
1047             // hides containers that does not have any visible children
1048             var container_filter = function ($curr) {
1049                 $curr.children('ul').children('li.navGroup').each(function() {
1050                     var $group = $(this);
1051                     $group.children('div.list_container').each(function() {
1052                         container_filter($(this)); // recursive
1053                     });
1054                     $group.show().removeClass('hidden');
1055                     if ($group.children('div.list_container').children('ul')
1056                             .children('li').not('.hidden').length === 0) {
1057                         $group.hide().addClass('hidden');
1058                     }
1059                 });
1060             };
1061             container_filter(outerContainer);
1063             if ($(this).val() != this.defaultValue && $(this).val() !== '') {
1064                 if (! $obj.data('fastFilter')) {
1065                     $obj.data(
1066                         'fastFilter',
1067                         new PMA_fastFilter.filter($obj, $(this).val())
1068                     );
1069                 } else {
1070                     $obj.data('fastFilter').update($(this).val());
1071                 }
1072             } else if ($obj.data('fastFilter')) {
1073                 $obj.data('fastFilter').restore(true);
1074             }
1075         },
1076         clear: function (event) {
1077             event.stopPropagation();
1078             // Clear the input and apply the fast filter with empty input
1079             var filter = $(this).closest('div.list_container').data('fastFilter');
1080             if (filter) {
1081                 filter.restore();
1082             }
1083             var value = $(this).prev()[0].defaultValue;
1084             $(this).prev().val(value).trigger('keyup');
1085         }
1086     }
1089  * Handles a change in the search clause
1091  * @param string searchClause The query string for the filter
1093  * @return void
1094  */
1095 PMA_fastFilter.filter.prototype.update = function (searchClause)
1097     if (this.searchClause != searchClause) {
1098         this.searchClause = searchClause;
1099         this.$this.find('.moreResults').remove();
1100         this.request();
1101     }
1104  * After a delay of 250mS, initiates a request to retrieve search results
1105  * Multiple calls to this function will always abort the previous request
1107  * @return void
1108  */
1109 PMA_fastFilter.filter.prototype.request = function ()
1111     var self = this;
1112     clearTimeout(self.timeout);
1113     if (self.$this.find('li.fast_filter').find('img.throbber').length === 0) {
1114         self.$this.find('li.fast_filter').append(
1115             $('<div class="throbber"></div>').append(
1116                 $('#pma_navigation_content')
1117                     .find('img.throbber')
1118                     .clone()
1119                     .css('visibility', 'visible')
1120             )
1121         );
1122     }
1123     self.timeout = setTimeout(function () {
1124         if (self.xhr) {
1125             self.xhr.abort();
1126         }
1127         var url = $('#pma_navigation').find('a.navigation_url').attr('href');
1128         var results = self.$this.find('li:not(.hidden):not(.fast_filter):not(.navGroup)').not('[class^=new]').length;
1129         var params = self.$this.find('> ul > li > form.fast_filter').first().serialize() + "&results=" + results;
1130         if (self.$this.find('> ul > li > form.fast_filter:first input[name=searchClause]').length === 0) {
1131             var $input = $('#pma_navigation_tree').find('li.fast_filter.db_fast_filter input.searchClause');
1132             if ($input.length && $input.val() != $input[0].defaultValue) {
1133                 params += '&searchClause=' + encodeURIComponent($input.val());
1134             }
1135         }
1136         self.xhr = $.ajax({
1137             url: url,
1138             type: 'post',
1139             dataType: 'json',
1140             data: params,
1141             complete: function (jqXHR) {
1142                 var data = $.parseJSON(jqXHR.responseText);
1143                 self.$this.find('li.fast_filter').find('div.throbber').remove();
1144                 if (data && data.results) {
1145                     var $listItem = $('<li />', {'class': 'moreResults'})
1146                         .appendTo(self.$this.find('li.fast_filter'));
1147                     var $link = $('<a />', {href: '#'})
1148                         .text(data.results)
1149                         .appendTo($listItem)
1150                         .click(function (event) {
1151                             event.preventDefault();
1152                             self.swap.apply(self, [data.message]);
1153                         });
1154                 }
1155             }
1156         });
1157     }, 250);
1160  * Replaces the contents of the navigation branch with the search results
1162  * @param string list The search results
1164  * @return void
1165  */
1166 PMA_fastFilter.filter.prototype.swap = function (list)
1168     this.swapped = true;
1169     this.$this
1170         .html($(list).html())
1171         .children()
1172         .show()
1173         .end()
1174         .find('li.fast_filter input.searchClause')
1175         .val(this.searchClause);
1176     this.$this.data('fastFilter', this);
1179  * Restores the navigation to the original state after the fast filter is cleared
1181  * @param bool focus Whether to also focus the input box of the fast filter
1183  * @return void
1184  */
1185 PMA_fastFilter.filter.prototype.restore = function (focus)
1187     if (this.swapped) {
1188         this.swapped = false;
1189         this.$this.html(this.$clone.html()).children().show();
1190         this.$this.data('fastFilter', this);
1191         if (focus) {
1192             this.$this.find('li.fast_filter input.searchClause').focus();
1193         }
1194     }
1195     this.searchClause = '';
1196     this.$this.find('.moreResults').remove();
1197     this.$this.find('div.pageselector').show();
1198     this.$this.find('div.throbber').remove();