1 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 * function used in or for navigation panel
5 * @package phpMyAdmin-Navigation
9 * Loads child items of a node and executes a given callback
12 * @param $expandElem expander
13 * @param callback callback function
17 function loadChildNodes(isNode, $expandElem, callback) {
19 var $destination = null;
23 if (!$expandElem.hasClass('expander')) {
26 $destination = $expandElem.closest('li');
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(),
36 if ($expandElem.closest('ul').hasClass('search_results')) {
37 params.searchClause = PMA_fastFilter.getSearchClause();
38 params.searchClause2 = PMA_fastFilter.getSearchClause2($expandElem);
41 $destination = $('#pma_navigation_tree_content');
43 aPath: $expandElem.attr('aPath'),
44 vPath: $expandElem.attr('vPath'),
45 pos: $expandElem.attr('pos'),
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
58 $destination.append(data.message);
59 $expandElem.addClass('loaded');
61 $destination.html(data.message);
62 $destination.children()
72 var $errors = $(data._errors);
73 if ($errors.children().length > 0) {
74 $('#pma_errors').replaceWith(data._errors);
77 if (callback && typeof callback == 'function') {
80 } else if(data.redirect_flag == "1") {
81 window.location.href += '&session_expired=1';
82 window.location.reload();
84 var $throbber = $expandElem.find('img.throbber');
86 var $icon = $expandElem.find('img.ic_b_plus');
88 PMA_ajaxShowMessage(data.error, false);
94 * Collapses a node in navigation tree.
96 * @param $expandElem expander
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');
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.
120 function traverseNavigationForPaths() {
122 pos: $('#pma_navigation_tree').find('div.dbselector select').val()
124 if ($('#navi_db_select').length) {
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
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();
140 .find('span.pos2_name:last')
143 var pos2_value = $(this).find('span.pos2_value').text();
148 .find('span.pos2_value:last')
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();
164 * Executed on page load
167 if (! $('#pma_navigation').length) {
168 // Don't bother running any code if the navigation is not even on the page
172 // Do not let the page reload on submitting the fast filter
173 $(document).on('submit', '.fast_filter', function (event) {
174 event.preventDefault();
177 // Fire up the resize handlers
181 * opens/closes (hides/shows) tree elements
182 * loads data via ajax
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));
191 collapseTreeNode($(this));
196 * Register event handler for click on the reload
197 * navigation icon at the top of the panel
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);
216 $(document).on("change", '#navi_db_select', function (event) {
217 if (! $(this).val()) {
218 PMA_commonParams.set('db', '');
219 PMA_reloadNavigation();
221 $(this).closest('form').trigger('submit');
225 * Register event handler for click on the collapse all
226 * navigation icon at the top of the navigation tree
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')) {
239 * Register event handler to toggle
240 * the 'link with main panel' icon on mouseenter.
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');
247 $img.removeClass('ic_s_link').addClass('ic_s_unlink');
249 $img.removeClass('ic_s_unlink').addClass('ic_s_link');
254 * Register event handler to toggle
255 * the 'link with main panel' icon on mouseout.
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');
262 $img.removeClass('ic_s_unlink').addClass('ic_s_link');
264 $img.removeClass('ic_s_link').addClass('ic_s_unlink');
269 * Register event handler to toggle
270 * the linking with main panel behavior
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');
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')
285 .removeClass('selected');
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();
298 * Bind all "fast filter" events
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);
307 * Ajax handler for pagination
309 $(document).on('click', '#pma_navigation_tree div.pageselector a.ajax', function (event) {
310 event.preventDefault();
311 PMA_navigationTreePagination($(this));
319 '#pma_navigation_tree.highlight li:not(.fast_filter)',
321 if ($('li:visible', this).length === 0) {
322 $(this).addClass('activePointer');
328 '#pma_navigation_tree.highlight li:not(.fast_filter)',
330 $(this).removeClass('activePointer');
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));
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));
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));
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));
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));
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));
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());
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());
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);
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);
404 $(document).on('click', 'li.new_view a.ajax', function (event) {
405 event.preventDefault();
406 PMA_createViewDialog($(this));
409 /** Hide navigation tree item */
410 $(document).on('click', 'a.hideNavItem.ajax', function (event) {
411 event.preventDefault();
413 url: $(this).attr('href') + '&ajax_request=true',
414 success: function (data) {
415 if (typeof data !== 'undefined' && data.success === true) {
416 PMA_reloadNavigation();
418 PMA_ajaxShowMessage(data.error);
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");
436 .attr('id', 'unhideNavItemDialog')
437 .append(data.message)
442 buttons: buttonOptions,
443 title: PMA_messages.strUnhideNavItem,
449 PMA_ajaxShowMessage(data.error);
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();
460 url: $(this).attr('href') + '&ajax_request=true',
461 success: function (data) {
462 PMA_ajaxRemoveMessage($msg);
463 if (typeof data !== 'undefined' && data.success === true) {
465 PMA_reloadNavigation();
467 PMA_ajaxShowMessage(data.error);
473 // Add/Remove favorite table using Ajax.
474 $(document).on("click", ".favorite_table_anchor", function (event) {
475 event.preventDefault();
477 var anchor_id = $self.attr("id");
478 if($self.data("favtargetn") !== null) {
479 if($('a[data-favtargets="' + $self.data("favtargetn") + '"]').length > 0)
481 $('a[data-favtargets="' + $self.data("favtargetn") + '"]').trigger('click');
487 url: $self.attr('href'),
491 favorite_tables: (isStorageSupported('localStorage') && typeof window.localStorage.favorite_tables !== 'undefined')
492 ? window.localStorage.favorite_tables
495 success: function (data) {
497 $('#pma_favorite_list').html(data.list);
498 $('#' + anchor_id).parent().html(data.anchor);
502 $('#' + anchor_id).attr("title")
504 // Update localStorage.
505 if (isStorageSupported('localStorage')) {
506 window.localStorage.favorite_tables = data.favorite_tables;
509 PMA_ajaxShowMessage(data.message);
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');
522 // Initialize if no previous state is defined
523 if ($('#pma_navigation_tree_content').length &&
524 typeof storage.navTreePaths === 'undefined'
526 navTreeStateUpdate();
527 } else if (PMA_commonParams.get('server') === storage.server &&
528 PMA_commonParams.get('token') === storage.token
530 // Reload the tree to the state before page refresh
531 PMA_reloadNavigation(null, JSON.parse(storage.navTreePaths));
537 * updates the tree state in sessionStorage
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
548 storage.setItem('navTreePaths', JSON.stringify(traverseNavigationForPaths()));
549 storage.setItem('server', PMA_commonParams.get('server'));
550 storage.setItem('token', PMA_commonParams.get('token'));
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');
562 * Expands a node in navigation tree.
564 * @param $expandElem expander
565 * @param callback callback function
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');
577 if (callback && typeof callback == 'function') {
580 $children.promise().done(navTreeStateUpdate);
582 var $throbber = $('#pma_navigation').find('.throbber')
585 .css({visibility: 'visible', display: 'block'})
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')
601 if (callback && typeof callback == 'function') {
604 PMA_showFullName($destination);
606 PMA_ajaxShowMessage(data.error, false);
610 $children.promise().done(navTreeStateUpdate);
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
623 function scrollToView($element, $forceToTop) {
624 var $container = $('#pma_navigation_tree_content');
625 var elemTop = $element.offset().top - $container.offset().top;
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
632 } else if (elemTop + textHeight > $container.height()) {
633 $container.stop().animate({
634 scrollTop: elemTop + textHeight - $container.height() + $container.scrollTop() + scrollPadding
640 * Expand the navigation and highlight the current database or table/view
644 function PMA_showCurrentNavigation() {
645 var db = PMA_commonParams.get('db');
646 var table = PMA_commonParams.get('table');
647 $('#pma_navigation_tree')
649 .removeClass('selected');
651 var $dbItem = findLoadedItem(
652 $('#pma_navigation_tree').find('> div'), db, 'database', !table
654 if ($('#navi_db_select').length &&
655 $('option:selected', $('#navi_db_select')).length
657 if (! PMA_selectCurrentDb()) {
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()
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);
670 handleTableOrDb(table, $('#pma_navigation_tree_content'));
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')
678 expandTreeNode($expander, function () {
679 handleTableOrDb(table, $dbItem);
682 handleTableOrDb(table, $dbItem);
685 } else if ($('#navi_db_select').length && $('#navi_db_select').val()) {
686 $('#navi_db_select').val('').hide().trigger('change');
688 PMA_showFullName($('#pma_navigation_tree'));
690 function handleTableOrDb(table, $dbItem) {
692 loadAndHighlightTableOrView($dbItem, table);
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);
703 scrollToView($dbItem, true);
708 function findLoadedItem($container, name, clazz, doSelect) {
710 $container.children('ul').children('li').each(function () {
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
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) {
727 $li.addClass('selected');
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')) {
734 .children('div:first')
735 .children('a.expander')
747 function loadAndHighlightTableOrView($dbItem, itemName) {
748 var $container = $dbItem.children('div.list_container');
750 var $whichItem = isItemInContainer($container, itemName, 'li.table, li.view');
751 //If item already there in some container
753 //get the relevant container while may also be a subcontainer
754 var $relatedContainer = $whichItem.closest('li.subContainer').length
755 ? $whichItem.closest('li.subContainer')
757 $whichItem = findLoadedItem(
758 $relatedContainer.children('div.list_container'),
762 showTableOrView($whichItem, $relatedContainer.children('div:first').children('a.expander'));
763 //else if item not there, try loading once
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);
778 // else if no subContainers
781 .children('div:first')
782 .children('a.expander');
783 if (! $expander.hasClass('loaded')) {
784 loadAndShowTableOrView($expander, $dbItem, itemName);
790 function loadAndShowTableOrView($expander, $relatedContainer, itemName) {
791 loadChildNodes(true, $expander, function (data) {
792 var $whichItem = findLoadedItem(
793 $relatedContainer.children('div.list_container'),
797 showTableOrView($whichItem, $expander);
802 function showTableOrView($whichItem, $expander) {
803 expandTreeNode($expander, function (data) {
805 scrollToView($whichItem, false);
810 function isItemInContainer($container, name, clazz)
812 var $whichItem = null;
813 $items = $container.find(clazz);
815 $items.each(function () {
816 if ($(this).children('a').text() == name) {
817 $whichItem = $(this);
826 * Disable navigation panel settings
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.
841 function PMA_ensureNaviSettings(selflink) {
842 $('#pma_navigation_settings_icon').removeClass('hide');
844 if (!$('#pma_navigation_settings').length) {
846 getNaviSettings: true
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);
855 $('#pma_navigation_settings').find('form').attr('action', selflink);
857 PMA_ajaxShowMessage(data.error);
861 $('#pma_navigation_settings').find('form').attr('action', selflink);
866 * Reloads the whole navigation tree while preserving its state
868 * @param function the callback function
869 * @param Object stored navigation paths
873 function PMA_reloadNavigation(callback, paths) {
878 paths = paths || traverseNavigationForPaths();
879 $.extend(params, paths);
880 if ($('#navi_db_select').length) {
881 params.db = PMA_commonParams.get('db');
882 requestNaviReload(params);
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();
896 // Fire the callback, if any
897 if (typeof callback === 'function') {
900 navTreeStateUpdate();
902 PMA_ajaxShowMessage(data.error);
908 function PMA_selectCurrentDb() {
909 var $naviDbSelect = $('#navi_db_select');
911 if (!$naviDbSelect.length) {
915 if (PMA_commonParams.get('db')) { // db selected
916 $naviDbSelect.show();
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
934 function PMA_navigationTreePagination($this) {
935 var $msgbox = PMA_ajaxShowMessage();
936 var isDbSelector = $this.closest('div.pageselector').is('.dbselector');
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';
945 var searchClause = PMA_fastFilter.getSearchClause();
947 params += '&searchClause=' + encodeURIComponent(searchClause);
950 params += '&full=true';
952 var searchClause2 = PMA_fastFilter.getSearchClause2($this);
954 params += '&searchClause2=' + encodeURIComponent(searchClause2);
957 $.post(url, params, function (data) {
958 PMA_ajaxRemoveMessage($msgbox);
959 if (typeof data !== 'undefined' && data.success) {
961 var val = PMA_fastFilter.getSearchClause();
962 $('#pma_navigation_tree')
967 $('#pma_navigation_tree')
968 .find('li.fast_filter input.searchClause')
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()
978 $parent.find('li.fast_filter input.searchClause').val(val);
980 $parent.find('span.pos2_value:first').text(
981 $parent.find('span.pos2_value:last').text()
983 $parent.find('span.pos3_value:first').text(
984 $parent.find('span.pos3_value:last').text()
988 PMA_ajaxShowMessage(data.error);
990 navTreeStateUpdate();
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'
1000 var ResizeHandler = function () {
1002 * @var int panel_width Used by the collapser to know where to go
1003 * back to when uncollapsing the panel
1005 this.panel_width = 0;
1007 * @var string left Used to provide support for RTL languages
1009 this.left = $('html').attr('dir') == 'ltr' ? 'left' : 'right';
1011 * Adjusts the width of the navigation panel to the specified value
1013 * @param int pos Navigation width in pixels
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');
1028 .css(this.left, pos + resizer_width)
1029 .html(this.getSymbol(pos))
1030 .prop('title', PMA_messages.strShowPanel);
1033 .css(this.left, pos)
1034 .html(this.getSymbol(pos))
1035 .prop('title', PMA_messages.strHidePanel);
1037 setTimeout(function () {
1038 $(window).trigger('resize');
1042 * Returns the horizontal position of the mouse,
1043 * relative to the outer side of the navigation panel
1045 * @param int pos Navigation width in pixels
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;
1059 } else if (pos + 100 >= windowWidth) {
1060 pos = windowWidth - 100;
1062 this.panel_width = 0;
1067 * Returns the HTML code for the arrow symbol used in the collapser
1069 * @param int width The width of the panel
1073 this.getSymbol = function (width) {
1074 if (this.left == 'left') {
1089 * Event handler for initiating a resize of the panel
1091 * @param object e Event data (contains a reference to resizeHandler)
1095 this.mousedown = function (event) {
1096 event.preventDefault();
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');
1105 * Event handler for terminating a resize of the panel
1107 * @param object e Event data (contains a reference to resizeHandler)
1111 this.mouseup = function (event) {
1112 $('body').css('cursor', '');
1113 $.cookie('pma_navi_width', event.data.resize_handler.getPos(event));
1114 $('#topmenu').menuResizer('resize');
1116 .unbind('mousemove')
1120 * Event handler for updating the panel during a resize operation
1122 * @param object e Event data (contains a reference to resizeHandler)
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();
1135 * Event handler for collapsing the panel
1137 * @param object e Event data (contains a reference to resizeHandler)
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) {
1148 event.data.resize_handler.setWidth(panel_width);
1149 event.data.resize_handler.panel_width = width;
1152 * Event handler for resizing the navigation tree height on window resize
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);
1165 //TODO: in fast filter search response there is no #pma_navigation_tree_content, needs to be added in php
1167 'overflow-y': 'auto'
1170 // Set content bottom space beacuse of console
1171 $('body').css('margin-bottom', $('#pma_console').height() + 'px');
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);
1178 $('#topmenu').menuResizer('resize');
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);
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
1198 var PMA_fastFilter = {
1200 * Construct for the asynchronous fast filter functionality
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
1206 * @return new PMA_fastFilter.filter object
1208 filter: function ($this, searchClause) {
1210 * @var object $this A jQuery object pointing to the list container
1211 * which is the nearest parent of the fast filter
1215 * @var bool searchClause The query string for the filter
1217 this.searchClause = searchClause;
1219 * @var object $clone A clone of the original contents
1220 * of the navigation branch before
1221 * the fast filter was applied
1223 this.$clone = $this.clone();
1225 * @var object xhr A reference to the ajax request that is currently running
1229 * @var int timeout Used to delay the request for asynchronous search
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
1242 * Gets the query string from the database fast filter form
1246 getSearchClause: function () {
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();
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
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')
1267 $filterInput = $filterContainer
1268 .find('li.fast_filter:not(.db_fast_filter) input.searchClause');
1270 var searchClause2 = '';
1271 if ($filterInput.length !== 0 &&
1272 $filterInput.first().val() != $filterInput[0].defaultValue
1274 searchClause2 = $filterInput.val();
1276 return searchClause2;
1279 * @var hash events A list of functions that are bound to DOM events
1280 * at the top of this file
1283 focus: function (event) {
1284 var $obj = $(this).closest('div.list_container');
1285 if (! $obj.data('fastFilter')) {
1288 new PMA_fastFilter.filter($obj, $(this).val())
1291 if ($(this).val() == this.defaultValue) {
1297 blur: function (event) {
1298 if ($(this).val() === '') {
1299 $(this).val(this.defaultValue);
1301 var $obj = $(this).closest('div.list_container');
1302 if ($(this).val() == this.defaultValue && $obj.data('fastFilter')) {
1303 $obj.data('fastFilter').restore();
1306 mouseover: function (event) {
1308 if ($(this).closest('li.fast_filter').is('.db_fast_filter')) {
1309 message = PMA_messages.strHoverDbFastFilter;
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;
1322 message = PMA_sprintf(PMA_messages.strHoverFastFilter, node_name);
1324 PMA_tooltip($(this), 'input', message);
1326 keyup: function (event) {
1327 var $obj = $(this).closest('div.list_container');
1329 if ($(this).val() != this.defaultValue && $(this).val() !== '') {
1330 $obj.find('div.pageselector').hide();
1331 str = $(this).val();
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
1339 // regex used for filtering.
1342 regex = new RegExp(str, 'i');
1347 // this is the div that houses the items to be filtered by this filter.
1349 if ($(this).closest('li.fast_filter').is('.db_fast_filter')) {
1350 outerContainer = $('#pma_navigation_tree_content');
1352 outerContainer = $obj;
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
1364 $curr.children('ul').children('li').children('a').not('.container').each(function() {
1365 if (regex.test($(this).text())) {
1366 $(this).parent().show().removeClass('hidden');
1368 $(this).parent().hide().addClass('hidden');
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
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');
1388 container_filter(outerContainer);
1390 if ($(this).val() != this.defaultValue && $(this).val() !== '') {
1391 if (! $obj.data('fastFilter')) {
1394 new PMA_fastFilter.filter($obj, $(this).val())
1397 if (event.keyCode == 13) {
1398 $obj.data('fastFilter').update($(this).val());
1401 } else if ($obj.data('fastFilter')) {
1402 $obj.data('fastFilter').restore(true);
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');
1412 var value = $(this).prev()[0].defaultValue;
1413 $(this).prev().val(value).trigger('keyup');
1418 * Handles a change in the search clause
1420 * @param string searchClause The query string for the filter
1424 PMA_fastFilter.filter.prototype.update = function (searchClause) {
1425 if (this.searchClause != searchClause) {
1426 this.searchClause = searchClause;
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
1436 PMA_fastFilter.filter.prototype.request = function () {
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')
1444 .css({visibility: 'visible', display: 'block'})
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());
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]);
1476 * Replaces the contents of the navigation branch with the search results
1478 * @param string list The search results
1482 PMA_fastFilter.filter.prototype.swap = function (list) {
1484 .html($(list).html())
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
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);
1504 this.$this.find('li.fast_filter input.searchClause').focus();
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
1519 function PMA_showFullName($containerELem) {
1521 $containerELem.find('.hover_show_full').mouseenter(function() {
1523 var $this = $(this);
1524 var thisOffset = $this.offset();
1525 if($this.text() === '') {
1528 var $parent = $this.parent();
1529 if( ($parent.offset().left + $parent.outerWidth())
1530 < (thisOffset.left + $this.outerWidth()))
1532 var $fullNameLayer = $('#full_name_layer');
1533 if($fullNameLayer.length === 0)
1535 $('body').append('<div id="full_name_layer" class="hide"></div>');
1536 $('#full_name_layer').mouseleave(function() {
1538 $(this).addClass('hide')
1539 .removeClass('hovering');
1540 }).mouseenter(function() {
1542 $(this).addClass('hovering');
1544 $fullNameLayer = $('#full_name_layer');
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'))
1552 $fullNameLayer.trigger('mouseleave');