1 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 * function used in or for navigation panel
5 * @package phpMyAdmin-Navigation
9 * updates the tree state in sessionStorage
13 function navTreeStateUpdate() {
14 // update if session storage is supported
15 if (isStorageSupported('sessionStorage')) {
16 var storage = window.sessionStorage;
17 // try catch necessary here to detect whether
18 // content to be stored exceeds storage capacity
20 storage.setItem('navTreePaths', JSON.stringify(traverseNavigationForPaths()));
21 storage.setItem('server', PMA_commonParams.get('server'));
22 storage.setItem('token', PMA_commonParams.get('token'));
24 // storage capacity exceeded & old navigation tree
25 // state is no more valid, so remove it
26 storage.removeItem('navTreePaths');
27 storage.removeItem('server');
28 storage.removeItem('token');
35 * updates the filter state in sessionStorage
39 function navFilterStateUpdate(filterName, filterValue) {
40 if (isStorageSupported('sessionStorage')) {
41 var storage = window.sessionStorage;
43 var currentFilter = $.extend({}, JSON.parse(storage.getItem('navTreeSearchFilters')));
45 filter[filterName] = filterValue;
46 currentFilter = $.extend(currentFilter, filter);
47 storage.setItem('navTreeSearchFilters', JSON.stringify(currentFilter));
49 storage.removeItem('navTreeSearchFilters');
56 * restores the filter state on navigation reload
60 function navFilterStateRestore() {
61 if (isStorageSupported('sessionStorage')
62 && typeof window.sessionStorage.navTreeSearchFilters !== 'undefined'
64 var searchClauses = JSON.parse(window.sessionStorage.navTreeSearchFilters);
65 if (Object.keys(searchClauses).length < 1) {
68 // restore database filter if present and not empty
69 if (searchClauses.hasOwnProperty("dbFilter")
70 && searchClauses.dbFilter.length
72 $obj = $('#pma_navigation_tree');
73 if (! $obj.data('fastFilter')) {
76 new PMA_fastFilter.filter($obj, "")
79 $obj.find('li.fast_filter.db_fast_filter input.searchClause')
80 .val(searchClauses.dbFilter)
83 // find all table filters present in the tree
84 $tableFilters = $('#pma_navigation_tree li.database')
85 .children('div.list_container')
86 .find('li.fast_filter input.searchClause');
87 // restore table filters
88 $tableFilters.each(function () {
89 $obj = $(this).closest('div.list_container');
90 // aPath associated with this filter
91 var filterName = $(this).siblings('input[name=aPath]').val();
92 // if this table's filter has a state stored in storage
93 if (searchClauses.hasOwnProperty(filterName)
94 && searchClauses[filterName].length
96 // clear state if item is not visible,
97 // happens when table filter becomes invisible
98 // as db filter has already been applied
99 if (! $obj.is(":visible")) {
100 navFilterStateUpdate(filterName, "");
103 if (! $obj.data('fastFilter')) {
106 new PMA_fastFilter.filter($obj, "")
109 $(this).val(searchClauses[filterName])
117 * Loads child items of a node and executes a given callback
120 * @param $expandElem expander
121 * @param callback callback function
125 function loadChildNodes(isNode, $expandElem, callback) {
127 var $destination = null;
131 if (!$expandElem.hasClass('expander')) {
134 $destination = $expandElem.closest('li');
136 aPath: $expandElem.find('span.aPath').text(),
137 vPath: $expandElem.find('span.vPath').text(),
138 pos: $expandElem.find('span.pos').text(),
139 pos2_name: $expandElem.find('span.pos2_name').text(),
140 pos2_value: $expandElem.find('span.pos2_value').text(),
144 if ($expandElem.closest('ul').hasClass('search_results')) {
145 params.searchClause = PMA_fastFilter.getSearchClause();
146 params.searchClause2 = PMA_fastFilter.getSearchClause2($expandElem);
149 $destination = $('#pma_navigation_tree_content');
151 aPath: $expandElem.attr('aPath'),
152 vPath: $expandElem.attr('vPath'),
153 pos: $expandElem.attr('pos'),
161 var url = $('#pma_navigation').find('a.navigation_url').attr('href');
162 $.get(url, params, function (data) {
163 if (typeof data !== 'undefined' && data.success === true) {
164 $destination.find('div.list_container').remove(); // FIXME: Hack, there shouldn't be a list container there
166 $destination.append(data.message);
167 $expandElem.addClass('loaded');
169 $destination.html(data.message);
170 $destination.children()
180 var $errors = $(data._errors);
181 if ($errors.children().length > 0) {
182 $('#pma_errors').replaceWith(data._errors);
185 if (callback && typeof callback == 'function') {
188 } else if(data.redirect_flag == "1") {
189 if (window.location.href.indexOf('?') === -1) {
190 window.location.href += '?session_expired=1';
192 window.location.href += '&session_expired=1';
194 window.location.reload();
196 var $throbber = $expandElem.find('img.throbber');
198 var $icon = $expandElem.find('img.ic_b_plus');
200 PMA_ajaxShowMessage(data.error, false);
206 * Collapses a node in navigation tree.
208 * @param $expandElem expander
212 function collapseTreeNode($expandElem) {
213 var $children = $expandElem.closest('li').children('div.list_container');
214 var $icon = $expandElem.find('img');
215 if ($expandElem.hasClass('loaded')) {
216 if ($icon.is('.ic_b_minus')) {
217 $icon.removeClass('ic_b_minus').addClass('ic_b_plus');
218 $children.slideUp('fast');
222 $children.promise().done(navTreeStateUpdate);
226 * Traverse the navigation tree backwards to generate all the actual
227 * and virtual paths, as well as the positions in the pagination at
228 * various levels, if necessary.
232 function traverseNavigationForPaths() {
234 pos: $('#pma_navigation_tree').find('div.dbselector select').val()
236 if ($('#navi_db_select').length) {
240 $('#pma_navigation_tree').find('a.expander:visible').each(function () {
241 if ($(this).find('img').is('.ic_b_minus') &&
242 $(this).closest('li').find('div.list_container .ic_b_minus').length === 0
244 params['n' + count + '_aPath'] = $(this).find('span.aPath').text();
245 params['n' + count + '_vPath'] = $(this).find('span.vPath').text();
247 var pos2_name = $(this).find('span.pos2_name').text();
252 .find('span.pos2_name:last')
255 var pos2_value = $(this).find('span.pos2_value').text();
260 .find('span.pos2_value:last')
264 params['n' + count + '_pos2_name'] = pos2_name;
265 params['n' + count + '_pos2_value'] = pos2_value;
267 params['n' + count + '_pos3_name'] = $(this).find('span.pos3_name').text();
268 params['n' + count + '_pos3_value'] = $(this).find('span.pos3_value').text();
276 * Executed on page load
279 if (! $('#pma_navigation').length) {
280 // Don't bother running any code if the navigation is not even on the page
284 // Do not let the page reload on submitting the fast filter
285 $(document).on('submit', '.fast_filter', function (event) {
286 event.preventDefault();
289 // Fire up the resize handlers
293 * opens/closes (hides/shows) tree elements
294 * loads data via ajax
296 $(document).on('click', '#pma_navigation_tree a.expander', function (event) {
297 event.preventDefault();
298 event.stopImmediatePropagation();
299 var $icon = $(this).find('img');
300 if ($icon.is('.ic_b_plus')) {
301 expandTreeNode($(this));
303 collapseTreeNode($(this));
308 * Register event handler for click on the reload
309 * navigation icon at the top of the panel
311 $(document).on('click', '#pma_navigation_reload', function (event) {
312 event.preventDefault();
313 // reload icon object
314 var $icon = $(this).find('img');
315 // source of the hidden throbber icon
316 var icon_throbber_src = $('#pma_navigation').find('.throbber').attr('src');
317 // source of the reload icon
318 var icon_reload_src = $icon.attr('src');
319 // replace the source of the reload icon with the one for throbber
320 $icon.attr('src', icon_throbber_src);
321 PMA_reloadNavigation();
322 // after one second, put back the reload icon
323 setTimeout(function () {
324 $icon.attr('src', icon_reload_src);
328 $(document).on("change", '#navi_db_select', function (event) {
329 if (! $(this).val()) {
330 PMA_commonParams.set('db', '');
331 PMA_reloadNavigation();
333 $(this).closest('form').trigger('submit');
337 * Register event handler for click on the collapse all
338 * navigation icon at the top of the navigation tree
340 $(document).on('click', '#pma_navigation_collapse', function (event) {
341 event.preventDefault();
342 $('#pma_navigation_tree').find('a.expander').each(function() {
343 var $icon = $(this).find('img');
344 if ($icon.is('.ic_b_minus')) {
351 * Register event handler to toggle
352 * the 'link with main panel' icon on mouseenter.
354 $(document).on('mouseenter', '#pma_navigation_sync', function (event) {
355 event.preventDefault();
356 var synced = $('#pma_navigation_tree').hasClass('synced');
357 var $img = $('#pma_navigation_sync').children('img');
359 $img.removeClass('ic_s_link').addClass('ic_s_unlink');
361 $img.removeClass('ic_s_unlink').addClass('ic_s_link');
366 * Register event handler to toggle
367 * the 'link with main panel' icon on mouseout.
369 $(document).on('mouseout', '#pma_navigation_sync', function (event) {
370 event.preventDefault();
371 var synced = $('#pma_navigation_tree').hasClass('synced');
372 var $img = $('#pma_navigation_sync').children('img');
374 $img.removeClass('ic_s_unlink').addClass('ic_s_link');
376 $img.removeClass('ic_s_link').addClass('ic_s_unlink');
381 * Register event handler to toggle
382 * the linking with main panel behavior
384 $(document).on('click', '#pma_navigation_sync', function (event) {
385 event.preventDefault();
386 var synced = $('#pma_navigation_tree').hasClass('synced');
387 var $img = $('#pma_navigation_sync').children('img');
390 .removeClass('ic_s_unlink')
391 .addClass('ic_s_link')
392 .attr('alt', PMA_messages.linkWithMain)
393 .attr('title', PMA_messages.linkWithMain);
394 $('#pma_navigation_tree')
395 .removeClass('synced')
397 .removeClass('selected');
400 .removeClass('ic_s_link')
401 .addClass('ic_s_unlink')
402 .attr('alt', PMA_messages.unlinkWithMain)
403 .attr('title', PMA_messages.unlinkWithMain);
404 $('#pma_navigation_tree').addClass('synced');
405 PMA_showCurrentNavigation();
410 * Bind all "fast filter" events
412 $(document).on('click', '#pma_navigation_tree li.fast_filter span', PMA_fastFilter.events.clear);
413 $(document).on('focus', '#pma_navigation_tree li.fast_filter input.searchClause', PMA_fastFilter.events.focus);
414 $(document).on('blur', '#pma_navigation_tree li.fast_filter input.searchClause', PMA_fastFilter.events.blur);
415 $(document).on('keyup', '#pma_navigation_tree li.fast_filter input.searchClause', PMA_fastFilter.events.keyup);
418 * Ajax handler for pagination
420 $(document).on('click', '#pma_navigation_tree div.pageselector a.ajax', function (event) {
421 event.preventDefault();
422 PMA_navigationTreePagination($(this));
430 '#pma_navigation_tree.highlight li:not(.fast_filter)',
432 if ($('li:visible', this).length === 0) {
433 $(this).addClass('activePointer');
439 '#pma_navigation_tree.highlight li:not(.fast_filter)',
441 $(this).removeClass('activePointer');
445 /** Create a Routine, Trigger or Event */
446 $(document).on('click', 'li.new_procedure a.ajax, li.new_function a.ajax', function (event) {
447 event.preventDefault();
448 var dialog = new RTE.object('routine');
449 dialog.editorDialog(1, $(this));
451 $(document).on('click', 'li.new_trigger a.ajax', function (event) {
452 event.preventDefault();
453 var dialog = new RTE.object('trigger');
454 dialog.editorDialog(1, $(this));
456 $(document).on('click', 'li.new_event a.ajax', function (event) {
457 event.preventDefault();
458 var dialog = new RTE.object('event');
459 dialog.editorDialog(1, $(this));
462 /** Edit Routines, Triggers or Events */
463 $(document).on('click', 'li.procedure > a.ajax, li.function > a.ajax', function (event) {
464 event.preventDefault();
465 var dialog = new RTE.object('routine');
466 dialog.editorDialog(0, $(this));
468 $(document).on('click', 'li.trigger > a.ajax', function (event) {
469 event.preventDefault();
470 var dialog = new RTE.object('trigger');
471 dialog.editorDialog(0, $(this));
473 $(document).on('click', 'li.event > a.ajax', function (event) {
474 event.preventDefault();
475 var dialog = new RTE.object('event');
476 dialog.editorDialog(0, $(this));
479 /** Execute Routines */
480 $(document).on('click', 'li.procedure div a.ajax img,' +
481 ' li.function div a.ajax img', function (event) {
482 event.preventDefault();
483 var dialog = new RTE.object('routine');
484 dialog.executeDialog($(this).parent());
486 /** Export Triggers and Events */
487 $(document).on('click', 'li.trigger div:eq(1) a.ajax img,' +
488 ' li.event div:eq(1) a.ajax img', function (event) {
489 event.preventDefault();
490 var dialog = new RTE.object();
491 dialog.exportDialog($(this).parent());
495 $(document).on('click', '#pma_navigation_tree li.new_index a.ajax', function (event) {
496 event.preventDefault();
497 var url = $(this).attr('href').substr(
498 $(this).attr('href').indexOf('?') + 1
499 ) + '&ajax_request=true';
500 var title = PMA_messages.strAddIndex;
501 indexEditorDialog(url, title);
505 $(document).on('click', 'li.index a.ajax', function (event) {
506 event.preventDefault();
507 var url = $(this).attr('href').substr(
508 $(this).attr('href').indexOf('?') + 1
509 ) + '&ajax_request=true';
510 var title = PMA_messages.strEditIndex;
511 indexEditorDialog(url, title);
515 $(document).on('click', 'li.new_view a.ajax', function (event) {
516 event.preventDefault();
517 PMA_createViewDialog($(this));
520 /** Hide navigation tree item */
521 $(document).on('click', 'a.hideNavItem.ajax', function (event) {
522 event.preventDefault();
525 url: $(this).attr('href') + '&ajax_request=true',
526 success: function (data) {
527 if (typeof data !== 'undefined' && data.success === true) {
528 PMA_reloadNavigation();
530 PMA_ajaxShowMessage(data.error);
536 /** Display a dialog to choose hidden navigation items to show */
537 $(document).on('click', 'a.showUnhide.ajax', function (event) {
538 event.preventDefault();
539 var $msg = PMA_ajaxShowMessage();
540 $.get($(this).attr('href') + '&ajax_request=1', function (data) {
541 if (typeof data !== 'undefined' && data.success === true) {
542 PMA_ajaxRemoveMessage($msg);
543 var buttonOptions = {};
544 buttonOptions[PMA_messages.strClose] = function () {
545 $(this).dialog("close");
548 .attr('id', 'unhideNavItemDialog')
549 .append(data.message)
554 buttons: buttonOptions,
555 title: PMA_messages.strUnhideNavItem,
561 PMA_ajaxShowMessage(data.error);
566 /** Show a hidden navigation tree item */
567 $(document).on('click', 'a.unhideNavItem.ajax', function (event) {
568 event.preventDefault();
569 var $tr = $(this).parents('tr');
570 var $msg = PMA_ajaxShowMessage();
573 url: $(this).attr('href') + '&ajax_request=true',
574 success: function (data) {
575 PMA_ajaxRemoveMessage($msg);
576 if (typeof data !== 'undefined' && data.success === true) {
578 PMA_reloadNavigation();
580 PMA_ajaxShowMessage(data.error);
586 // Add/Remove favorite table using Ajax.
587 $(document).on("click", ".favorite_table_anchor", function (event) {
588 event.preventDefault();
590 var anchor_id = $self.attr("id");
591 if($self.data("favtargetn") !== null) {
592 if($('a[data-favtargets="' + $self.data("favtargetn") + '"]').length > 0)
594 $('a[data-favtargets="' + $self.data("favtargetn") + '"]').trigger('click');
600 url: $self.attr('href'),
604 favorite_tables: (isStorageSupported('localStorage') && typeof window.localStorage.favorite_tables !== 'undefined')
605 ? window.localStorage.favorite_tables
608 success: function (data) {
610 $('#pma_favorite_list').html(data.list);
611 $('#' + anchor_id).parent().html(data.anchor);
615 $('#' + anchor_id).attr("title")
617 // Update localStorage.
618 if (isStorageSupported('localStorage')) {
619 window.localStorage.favorite_tables = data.favorite_tables;
622 PMA_ajaxShowMessage(data.message);
627 // Check if session storage is supported
628 if (isStorageSupported('sessionStorage')) {
629 var storage = window.sessionStorage;
630 // remove tree from storage if Navi_panel config form is submitted
631 $(document).on('submit', 'form.config-form', function(event) {
632 storage.removeItem('navTreePaths');
634 // Initialize if no previous state is defined
635 if ($('#pma_navigation_tree_content').length &&
636 typeof storage.navTreePaths === 'undefined'
638 navTreeStateUpdate();
639 } else if (PMA_commonParams.get('server') === storage.server &&
640 PMA_commonParams.get('token') === storage.token
642 // Reload the tree to the state before page refresh
643 PMA_reloadNavigation(navFilterStateRestore, JSON.parse(storage.navTreePaths));
649 * Expands a node in navigation tree.
651 * @param $expandElem expander
652 * @param callback callback function
656 function expandTreeNode($expandElem, callback) {
657 var $children = $expandElem.closest('li').children('div.list_container');
658 var $icon = $expandElem.find('img');
659 if ($expandElem.hasClass('loaded')) {
660 if ($icon.is('.ic_b_plus')) {
661 $icon.removeClass('ic_b_plus').addClass('ic_b_minus');
662 $children.slideDown('fast');
664 if (callback && typeof callback == 'function') {
667 $children.promise().done(navTreeStateUpdate);
669 var $throbber = $('#pma_navigation').find('.throbber')
672 .css({visibility: 'visible', display: 'block'})
675 $throbber.insertBefore($icon);
677 loadChildNodes(true, $expandElem, function (data) {
678 if (typeof data !== 'undefined' && data.success === true) {
679 var $destination = $expandElem.closest('li');
680 $icon.removeClass('ic_b_plus').addClass('ic_b_minus');
681 $children = $destination.children('div.list_container');
682 $children.slideDown('fast');
683 if ($destination.find('ul > li').length == 1) {
684 $destination.find('ul > li')
685 .find('a.expander.container')
688 if (callback && typeof callback == 'function') {
691 PMA_showFullName($destination);
693 PMA_ajaxShowMessage(data.error, false);
697 $children.promise().done(navTreeStateUpdate);
704 * Auto-scrolls the newly chosen database
706 * @param object $element The element to set to view
707 * @param boolean $forceToTop Whether to force scroll to top
710 function scrollToView($element, $forceToTop) {
711 navFilterStateRestore();
712 var $container = $('#pma_navigation_tree_content');
713 var elemTop = $element.offset().top - $container.offset().top;
715 var scrollPadding = 20; // extra padding from top of bottom when scrolling to view
716 if (elemTop < 0 || $forceToTop) {
717 $container.stop().animate({
718 scrollTop: elemTop + $container.scrollTop() - scrollPadding
720 } else if (elemTop + textHeight > $container.height()) {
721 $container.stop().animate({
722 scrollTop: elemTop + textHeight - $container.height() + $container.scrollTop() + scrollPadding
728 * Expand the navigation and highlight the current database or table/view
732 function PMA_showCurrentNavigation() {
733 var db = PMA_commonParams.get('db');
734 var table = PMA_commonParams.get('table');
735 $('#pma_navigation_tree')
737 .removeClass('selected');
739 var $dbItem = findLoadedItem(
740 $('#pma_navigation_tree').find('> div'), db, 'database', !table
742 if ($('#navi_db_select').length &&
743 $('option:selected', $('#navi_db_select')).length
745 if (! PMA_selectCurrentDb()) {
748 // If loaded database in navigation is not same as current one
749 if ($('#pma_navigation_tree_content').find('span.loaded_db:first').text()
750 !== $('#navi_db_select').val()
752 loadChildNodes(false, $('option:selected', $('#navi_db_select')), function (data) {
753 handleTableOrDb(table, $('#pma_navigation_tree_content'));
754 var $children = $('#pma_navigation_tree_content').children('div.list_container');
755 $children.promise().done(navTreeStateUpdate);
758 handleTableOrDb(table, $('#pma_navigation_tree_content'));
760 } else if ($dbItem) {
761 var $expander = $dbItem.children('div:first').children('a.expander');
762 // if not loaded or loaded but collapsed
763 if (! $expander.hasClass('loaded') ||
764 $expander.find('img').is('.ic_b_plus')
766 expandTreeNode($expander, function () {
767 handleTableOrDb(table, $dbItem);
770 handleTableOrDb(table, $dbItem);
773 } else if ($('#navi_db_select').length && $('#navi_db_select').val()) {
774 $('#navi_db_select').val('').hide().trigger('change');
776 PMA_showFullName($('#pma_navigation_tree'));
778 function handleTableOrDb(table, $dbItem) {
780 loadAndHighlightTableOrView($dbItem, table);
782 var $container = $dbItem.children('div.list_container');
783 var $tableContainer = $container.children('ul').children('li.tableContainer');
784 if ($tableContainer.length > 0) {
785 var $expander = $tableContainer.children('div:first').children('a.expander');
786 $tableContainer.addClass('selected');
787 expandTreeNode($expander, function () {
788 scrollToView($dbItem, true);
791 scrollToView($dbItem, true);
796 function findLoadedItem($container, name, clazz, doSelect) {
798 $container.children('ul').children('li').each(function () {
800 // this is a navigation group, recurse
801 if ($li.is('.navGroup')) {
802 var $container = $li.children('div.list_container');
803 var $childRet = findLoadedItem(
804 $container, name, clazz, doSelect
810 } else { // this is a real navigation item
811 // name and class matches
812 if (((clazz && $li.is('.' + clazz)) || ! clazz) &&
813 $li.children('a').text() == name) {
815 $li.addClass('selected');
817 // taverse up and expand and parent navigation groups
818 $li.parents('.navGroup').each(function () {
819 var $cont = $(this).children('div.list_container');
820 if (! $cont.is(':visible')) {
822 .children('div:first')
823 .children('a.expander')
835 function loadAndHighlightTableOrView($dbItem, itemName) {
836 var $container = $dbItem.children('div.list_container');
838 var $whichItem = isItemInContainer($container, itemName, 'li.table, li.view');
839 //If item already there in some container
841 //get the relevant container while may also be a subcontainer
842 var $relatedContainer = $whichItem.closest('li.subContainer').length
843 ? $whichItem.closest('li.subContainer')
845 $whichItem = findLoadedItem(
846 $relatedContainer.children('div.list_container'),
850 showTableOrView($whichItem, $relatedContainer.children('div:first').children('a.expander'));
851 //else if item not there, try loading once
853 var $sub_containers = $dbItem.find('.subContainer');
854 //If there are subContainers i.e. tableContainer or viewContainer
855 if($sub_containers.length > 0) {
856 var $containers = [];
857 $sub_containers.each(function (index) {
858 $containers[index] = $(this);
859 $expander = $containers[index]
860 .children('div:first')
861 .children('a.expander');
862 if (! $expander.hasClass('loaded')) {
863 loadAndShowTableOrView($expander, $containers[index], itemName);
866 // else if no subContainers
869 .children('div:first')
870 .children('a.expander');
871 if (! $expander.hasClass('loaded')) {
872 loadAndShowTableOrView($expander, $dbItem, itemName);
878 function loadAndShowTableOrView($expander, $relatedContainer, itemName) {
879 loadChildNodes(true, $expander, function (data) {
880 var $whichItem = findLoadedItem(
881 $relatedContainer.children('div.list_container'),
885 showTableOrView($whichItem, $expander);
890 function showTableOrView($whichItem, $expander) {
891 expandTreeNode($expander, function (data) {
893 scrollToView($whichItem, false);
898 function isItemInContainer($container, name, clazz)
900 var $whichItem = null;
901 $items = $container.find(clazz);
903 $items.each(function () {
904 if ($(this).children('a').text() == name) {
905 $whichItem = $(this);
914 * Disable navigation panel settings
918 function PMA_disableNaviSettings() {
919 $('#pma_navigation_settings_icon').addClass('hide');
920 $('#pma_navigation_settings').remove();
924 * Ensure that navigation panel settings is properly setup.
929 function PMA_ensureNaviSettings(selflink) {
930 $('#pma_navigation_settings_icon').removeClass('hide');
932 if (!$('#pma_navigation_settings').length) {
934 getNaviSettings: true
936 var url = $('#pma_navigation').find('a.navigation_url').attr('href');
937 $.post(url, params, function (data) {
938 if (typeof data !== 'undefined' && data.success) {
939 $('#pma_navi_settings_container').html(data.message);
943 $('#pma_navigation_settings').find('form').attr('action', selflink);
945 PMA_ajaxShowMessage(data.error);
949 $('#pma_navigation_settings').find('form').attr('action', selflink);
954 * Reloads the whole navigation tree while preserving its state
956 * @param function the callback function
957 * @param Object stored navigation paths
961 function PMA_reloadNavigation(callback, paths) {
966 paths = paths || traverseNavigationForPaths();
967 $.extend(params, paths);
968 if ($('#navi_db_select').length) {
969 params.db = PMA_commonParams.get('db');
970 requestNaviReload(params);
973 requestNaviReload(params);
975 function requestNaviReload(params) {
976 var url = $('#pma_navigation').find('a.navigation_url').attr('href');
977 $.post(url, params, function (data) {
978 if (typeof data !== 'undefined' && data.success) {
979 $('#pma_navigation_tree').html(data.message).children('div').show();
980 if ($('#pma_navigation_tree').hasClass('synced')) {
981 PMA_selectCurrentDb();
982 PMA_showCurrentNavigation();
984 // Fire the callback, if any
985 if (typeof callback === 'function') {
988 navTreeStateUpdate();
990 PMA_ajaxShowMessage(data.error);
996 function PMA_selectCurrentDb() {
997 var $naviDbSelect = $('#navi_db_select');
999 if (!$naviDbSelect.length) {
1003 if (PMA_commonParams.get('db')) { // db selected
1004 $naviDbSelect.show();
1007 $naviDbSelect.val(PMA_commonParams.get('db'));
1008 return $naviDbSelect.val() === PMA_commonParams.get('db');
1013 * Handles any requests to change the page in a branch of a tree
1015 * This can be called from link click or select change event handlers
1017 * @param object $this A jQuery object that points to the element that
1018 * initiated the action of changing the page
1022 function PMA_navigationTreePagination($this) {
1023 var $msgbox = PMA_ajaxShowMessage();
1024 var isDbSelector = $this.closest('div.pageselector').is('.dbselector');
1026 if ($this[0].tagName == 'A') {
1027 url = $this.attr('href');
1028 params = 'ajax_request=true';
1029 } else { // tagName == 'SELECT'
1030 url = 'navigation.php';
1031 params = $this.closest("form").serialize() + '&ajax_request=true';
1033 var searchClause = PMA_fastFilter.getSearchClause();
1035 params += '&searchClause=' + encodeURIComponent(searchClause);
1038 params += '&full=true';
1040 var searchClause2 = PMA_fastFilter.getSearchClause2($this);
1041 if (searchClause2) {
1042 params += '&searchClause2=' + encodeURIComponent(searchClause2);
1045 $.post(url, params, function (data) {
1046 if (typeof data !== 'undefined' && data.success) {
1047 PMA_ajaxRemoveMessage($msgbox);
1049 var val = PMA_fastFilter.getSearchClause();
1050 $('#pma_navigation_tree')
1055 $('#pma_navigation_tree')
1056 .find('li.fast_filter input.searchClause')
1060 var $parent = $this.closest('div.list_container').parent();
1061 var val = PMA_fastFilter.getSearchClause2($this);
1062 $this.closest('div.list_container').html(
1063 $(data.message).children().show()
1066 $parent.find('li.fast_filter input.searchClause').val(val);
1068 $parent.find('span.pos2_value:first').text(
1069 $parent.find('span.pos2_value:last').text()
1071 $parent.find('span.pos3_value:first').text(
1072 $parent.find('span.pos3_value:last').text()
1076 PMA_ajaxShowMessage(data.error);
1077 PMA_handleRedirectAndReload(data);
1079 navTreeStateUpdate();
1084 * @var ResizeHandler Custom object that manages the resizing of the navigation
1086 * XXX: Must only be ever instanciated once
1087 * XXX: Inside event handlers the 'this' object is accessed as 'event.data.resize_handler'
1089 var ResizeHandler = function () {
1091 * @var int panel_width Used by the collapser to know where to go
1092 * back to when uncollapsing the panel
1094 this.panel_width = 0;
1096 * @var string left Used to provide support for RTL languages
1098 this.left = $('html').attr('dir') == 'ltr' ? 'left' : 'right';
1100 * Adjusts the width of the navigation panel to the specified value
1102 * @param int pos Navigation width in pixels
1106 this.setWidth = function (pos) {
1107 var $resizer = $('#pma_navigation_resizer');
1108 var resizer_width = $resizer.width();
1109 var $collapser = $('#pma_navigation_collapser');
1110 $('#pma_navigation').width(pos);
1111 $('body').css('margin-' + this.left, pos + 'px');
1112 $("#floating_menubar, #pma_console")
1113 .css('margin-' + this.left, (pos + resizer_width) + 'px');
1114 $resizer.css(this.left, pos + 'px');
1117 .css(this.left, pos + resizer_width)
1118 .html(this.getSymbol(pos))
1119 .prop('title', PMA_messages.strShowPanel);
1122 .css(this.left, pos)
1123 .html(this.getSymbol(pos))
1124 .prop('title', PMA_messages.strHidePanel);
1126 setTimeout(function () {
1127 $(window).trigger('resize');
1131 * Returns the horizontal position of the mouse,
1132 * relative to the outer side of the navigation panel
1134 * @param int pos Navigation width in pixels
1138 this.getPos = function (event) {
1139 var pos = event.pageX;
1140 var windowWidth = $(window).width();
1141 var windowScroll = $(window).scrollLeft();
1142 pos = pos - windowScroll;
1143 if (this.left != 'left') {
1144 pos = windowWidth - event.pageX;
1148 } else if (pos + 100 >= windowWidth) {
1149 pos = windowWidth - 100;
1151 this.panel_width = 0;
1156 * Returns the HTML code for the arrow symbol used in the collapser
1158 * @param int width The width of the panel
1162 this.getSymbol = function (width) {
1163 if (this.left == 'left') {
1178 * Event handler for initiating a resize of the panel
1180 * @param object e Event data (contains a reference to resizeHandler)
1184 this.mousedown = function (event) {
1185 event.preventDefault();
1187 .bind('mousemove', {'resize_handler': event.data.resize_handler},
1188 $.throttle(event.data.resize_handler.mousemove, 4))
1189 .bind('mouseup', {'resize_handler': event.data.resize_handler},
1190 event.data.resize_handler.mouseup);
1191 $('body').css('cursor', 'col-resize');
1194 * Event handler for terminating a resize of the panel
1196 * @param object e Event data (contains a reference to resizeHandler)
1200 this.mouseup = function (event) {
1201 $('body').css('cursor', '');
1202 $.cookie('pma_navi_width', event.data.resize_handler.getPos(event));
1203 $('#topmenu').menuResizer('resize');
1205 .unbind('mousemove')
1209 * Event handler for updating the panel during a resize operation
1211 * @param object e Event data (contains a reference to resizeHandler)
1215 this.mousemove = function (event) {
1216 event.preventDefault();
1217 var pos = event.data.resize_handler.getPos(event);
1218 event.data.resize_handler.setWidth(pos);
1219 if ($('.sticky_columns').length !== 0) {
1220 handleAllStickyColumns();
1224 * Event handler for collapsing the panel
1226 * @param object e Event data (contains a reference to resizeHandler)
1230 this.collapse = function (event) {
1231 event.preventDefault();
1232 var panel_width = event.data.resize_handler.panel_width;
1233 var width = $('#pma_navigation').width();
1234 if (width === 0 && panel_width === 0) {
1237 event.data.resize_handler.setWidth(panel_width);
1238 event.data.resize_handler.panel_width = width;
1241 * Event handler for resizing the navigation tree height on window resize
1245 this.treeResize = function (event) {
1246 var $nav = $("#pma_navigation"),
1247 $nav_tree = $("#pma_navigation_tree"),
1248 $nav_header = $("#pma_navigation_header"),
1249 $nav_tree_content = $("#pma_navigation_tree_content");
1250 $nav_tree.height($nav.height() - $nav_header.height());
1251 if ($nav_tree_content.length > 0) {
1252 $nav_tree_content.height($nav_tree.height() - $nav_tree_content.position().top);
1254 //TODO: in fast filter search response there is no #pma_navigation_tree_content, needs to be added in php
1256 'overflow-y': 'auto'
1259 // Set content bottom space beacuse of console
1260 $('body').css('margin-bottom', $('#pma_console').height() + 'px');
1262 /* Initialisation section begins here */
1263 if ($.cookie('pma_navi_width')) {
1264 // If we have a cookie, set the width of the panel to its value
1265 var pos = Math.abs(parseInt($.cookie('pma_navi_width'), 10) || 0);
1267 $('#topmenu').menuResizer('resize');
1269 // Register the events for the resizer and the collapser
1270 $(document).on('mousedown', '#pma_navigation_resizer', {'resize_handler': this}, this.mousedown);
1271 $(document).on('click', '#pma_navigation_collapser', {'resize_handler': this}, this.collapse);
1273 // Add the correct arrow symbol to the collapser
1274 $('#pma_navigation_collapser').html(this.getSymbol($('#pma_navigation').width()));
1275 // Fix navigation tree height
1276 $(window).on('resize', this.treeResize);
1277 // need to call this now and then, browser might decide
1278 // to show/hide horizontal scrollbars depending on page content width
1279 setInterval(this.treeResize, 2000);
1281 }; // End of ResizeHandler
1284 * @var object PMA_fastFilter Handles the functionality that allows filtering
1285 * of the items in a branch of the navigation tree
1287 var PMA_fastFilter = {
1289 * Construct for the asynchronous fast filter functionality
1291 * @param object $this A jQuery object pointing to the list container
1292 * which is the nearest parent of the fast filter
1293 * @param string searchClause The query string for the filter
1295 * @return new PMA_fastFilter.filter object
1297 filter: function ($this, searchClause) {
1299 * @var object $this A jQuery object pointing to the list container
1300 * which is the nearest parent of the fast filter
1304 * @var bool searchClause The query string for the filter
1306 this.searchClause = searchClause;
1308 * @var object $clone A clone of the original contents
1309 * of the navigation branch before
1310 * the fast filter was applied
1312 this.$clone = $this.clone();
1314 * @var object xhr A reference to the ajax request that is currently running
1318 * @var int timeout Used to delay the request for asynchronous search
1320 this.timeout = null;
1322 var $filterInput = $this.find('li.fast_filter input.searchClause');
1323 if ($filterInput.length !== 0 &&
1324 $filterInput.val() !== '' &&
1325 $filterInput.val() != $filterInput[0].defaultValue
1331 * Gets the query string from the database fast filter form
1335 getSearchClause: function () {
1337 var $input = $('#pma_navigation_tree')
1338 .find('li.fast_filter.db_fast_filter input.searchClause');
1339 if ($input.length && $input.val() != $input[0].defaultValue) {
1340 retval = $input.val();
1345 * Gets the query string from a second level item's fast filter form
1346 * The retrieval is done by trasversing the navigation tree backwards
1350 getSearchClause2: function ($this) {
1351 var $filterContainer = $this.closest('div.list_container');
1352 var $filterInput = $([]);
1353 if ($filterContainer
1354 .find('li.fast_filter:not(.db_fast_filter) input.searchClause')
1356 $filterInput = $filterContainer
1357 .find('li.fast_filter:not(.db_fast_filter) input.searchClause');
1359 var searchClause2 = '';
1360 if ($filterInput.length !== 0 &&
1361 $filterInput.first().val() != $filterInput[0].defaultValue
1363 searchClause2 = $filterInput.val();
1365 return searchClause2;
1368 * @var hash events A list of functions that are bound to DOM events
1369 * at the top of this file
1372 focus: function (event) {
1373 var $obj = $(this).closest('div.list_container');
1374 if (! $obj.data('fastFilter')) {
1377 new PMA_fastFilter.filter($obj, $(this).val())
1380 if ($(this).val() == this.defaultValue) {
1386 blur: function (event) {
1387 if ($(this).val() === '') {
1388 $(this).val(this.defaultValue);
1390 var $obj = $(this).closest('div.list_container');
1391 if ($(this).val() == this.defaultValue && $obj.data('fastFilter')) {
1392 $obj.data('fastFilter').restore();
1395 keyup: function (event) {
1396 var $obj = $(this).closest('div.list_container');
1398 if ($(this).val() != this.defaultValue && $(this).val() !== '') {
1399 $obj.find('div.pageselector').hide();
1400 str = $(this).val();
1404 * FIXME at the server level a value match is done while on
1405 * the client side it is a regex match. These two should be aligned
1408 // regex used for filtering.
1411 regex = new RegExp(str, 'i');
1416 // this is the div that houses the items to be filtered by this filter.
1418 if ($(this).closest('li.fast_filter').is('.db_fast_filter')) {
1419 outerContainer = $('#pma_navigation_tree_content');
1421 outerContainer = $obj;
1424 // filters items that are directly under the div as well as grouped in
1425 // groups. Does not filter child items (i.e. a database search does
1426 // not filter tables)
1427 var item_filter = function($curr) {
1428 $curr.children('ul').children('li.navGroup').each(function() {
1429 $(this).children('div.list_container').each(function() {
1430 item_filter($(this)); // recursive
1433 $curr.children('ul').children('li').children('a').not('.container').each(function() {
1434 if (regex.test($(this).text())) {
1435 $(this).parent().show().removeClass('hidden');
1437 $(this).parent().hide().addClass('hidden');
1441 item_filter(outerContainer);
1443 // hides containers that does not have any visible children
1444 var container_filter = function ($curr) {
1445 $curr.children('ul').children('li.navGroup').each(function() {
1446 var $group = $(this);
1447 $group.children('div.list_container').each(function() {
1448 container_filter($(this)); // recursive
1450 $group.show().removeClass('hidden');
1451 if ($group.children('div.list_container').children('ul')
1452 .children('li').not('.hidden').length === 0) {
1453 $group.hide().addClass('hidden');
1457 container_filter(outerContainer);
1459 if ($(this).val() != this.defaultValue && $(this).val() !== '') {
1460 if (! $obj.data('fastFilter')) {
1463 new PMA_fastFilter.filter($obj, $(this).val())
1466 if (event.keyCode == 13) {
1467 $obj.data('fastFilter').update($(this).val());
1470 } else if ($obj.data('fastFilter')) {
1471 $obj.data('fastFilter').restore(true);
1473 // update filter state
1475 if ($(this).attr('name') == 'searchClause2') {
1476 filterName = $(this).siblings('input[name=aPath]').val();
1478 filterName = 'dbFilter';
1480 navFilterStateUpdate(filterName, $(this).val());
1482 clear: function (event) {
1483 event.stopPropagation();
1484 // Clear the input and apply the fast filter with empty input
1485 var filter = $(this).closest('div.list_container').data('fastFilter');
1489 var value = $(this).prev()[0].defaultValue;
1490 $(this).prev().val(value).trigger('keyup');
1495 * Handles a change in the search clause
1497 * @param string searchClause The query string for the filter
1501 PMA_fastFilter.filter.prototype.update = function (searchClause) {
1502 if (this.searchClause != searchClause) {
1503 this.searchClause = searchClause;
1508 * After a delay of 250mS, initiates a request to retrieve search results
1509 * Multiple calls to this function will always abort the previous request
1513 PMA_fastFilter.filter.prototype.request = function () {
1515 if (self.$this.find('li.fast_filter').find('img.throbber').length === 0) {
1516 self.$this.find('li.fast_filter').append(
1517 $('<div class="throbber"></div>').append(
1518 $('#pma_navigation_content')
1519 .find('img.throbber')
1521 .css({visibility: 'visible', display: 'block'})
1528 var url = $('#pma_navigation').find('a.navigation_url').attr('href');
1529 var params = self.$this.find('> ul > li > form.fast_filter').first().serialize();
1530 if (self.$this.find('> ul > li > form.fast_filter:first input[name=searchClause]').length === 0) {
1531 var $input = $('#pma_navigation_tree').find('li.fast_filter.db_fast_filter input.searchClause');
1532 if ($input.length && $input.val() != $input[0].defaultValue) {
1533 params += '&searchClause=' + encodeURIComponent($input.val());
1541 complete: function (jqXHR, status) {
1542 if (status != 'abort') {
1543 var data = $.parseJSON(jqXHR.responseText);
1544 self.$this.find('li.fast_filter').find('div.throbber').remove();
1545 if (data && data.results) {
1546 self.swap.apply(self, [data.message]);
1553 * Replaces the contents of the navigation branch with the search results
1555 * @param string list The search results
1559 PMA_fastFilter.filter.prototype.swap = function (list) {
1561 .html($(list).html())
1565 .find('li.fast_filter input.searchClause')
1566 .val(this.searchClause);
1567 this.$this.data('fastFilter', this);
1570 * Restores the navigation to the original state after the fast filter is cleared
1572 * @param bool focus Whether to also focus the input box of the fast filter
1576 PMA_fastFilter.filter.prototype.restore = function (focus) {
1577 if(this.$this.children('ul').first().hasClass('search_results')) {
1578 this.$this.html(this.$clone.html()).children().show();
1579 this.$this.data('fastFilter', this);
1581 this.$this.find('li.fast_filter input.searchClause').focus();
1584 this.searchClause = '';
1585 this.$this.find('div.pageselector').show();
1586 this.$this.find('div.throbber').remove();
1590 * Show full name when cursor hover and name not shown completely
1592 * @param object $containerELem Container element
1596 function PMA_showFullName($containerELem) {
1598 $containerELem.find('.hover_show_full').mouseenter(function() {
1600 var $this = $(this);
1601 var thisOffset = $this.offset();
1602 if($this.text() === '') {
1605 var $parent = $this.parent();
1606 if( ($parent.offset().left + $parent.outerWidth())
1607 < (thisOffset.left + $this.outerWidth()))
1609 var $fullNameLayer = $('#full_name_layer');
1610 if($fullNameLayer.length === 0)
1612 $('body').append('<div id="full_name_layer" class="hide"></div>');
1613 $('#full_name_layer').mouseleave(function() {
1615 $(this).addClass('hide')
1616 .removeClass('hovering');
1617 }).mouseenter(function() {
1619 $(this).addClass('hovering');
1621 $fullNameLayer = $('#full_name_layer');
1623 $fullNameLayer.removeClass('hide');
1624 $fullNameLayer.css({left: thisOffset.left, top: thisOffset.top});
1625 $fullNameLayer.html($this.clone());
1626 setTimeout(function() {
1627 if(! $fullNameLayer.hasClass('hovering'))
1629 $fullNameLayer.trigger('mouseleave');