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();
414 url: $(this).attr('href') + '&ajax_request=true',
415 success: function (data) {
416 if (typeof data !== 'undefined' && data.success === true) {
417 PMA_reloadNavigation();
419 PMA_ajaxShowMessage(data.error);
425 /** Display a dialog to choose hidden navigation items to show */
426 $(document).on('click', 'a.showUnhide.ajax', function (event) {
427 event.preventDefault();
428 var $msg = PMA_ajaxShowMessage();
429 $.get($(this).attr('href') + '&ajax_request=1', function (data) {
430 if (typeof data !== 'undefined' && data.success === true) {
431 PMA_ajaxRemoveMessage($msg);
432 var buttonOptions = {};
433 buttonOptions[PMA_messages.strClose] = function () {
434 $(this).dialog("close");
437 .attr('id', 'unhideNavItemDialog')
438 .append(data.message)
443 buttons: buttonOptions,
444 title: PMA_messages.strUnhideNavItem,
450 PMA_ajaxShowMessage(data.error);
455 /** Show a hidden navigation tree item */
456 $(document).on('click', 'a.unhideNavItem.ajax', function (event) {
457 event.preventDefault();
458 var $tr = $(this).parents('tr');
459 var $msg = PMA_ajaxShowMessage();
462 url: $(this).attr('href') + '&ajax_request=true',
463 success: function (data) {
464 PMA_ajaxRemoveMessage($msg);
465 if (typeof data !== 'undefined' && data.success === true) {
467 PMA_reloadNavigation();
469 PMA_ajaxShowMessage(data.error);
475 // Add/Remove favorite table using Ajax.
476 $(document).on("click", ".favorite_table_anchor", function (event) {
477 event.preventDefault();
479 var anchor_id = $self.attr("id");
480 if($self.data("favtargetn") !== null) {
481 if($('a[data-favtargets="' + $self.data("favtargetn") + '"]').length > 0)
483 $('a[data-favtargets="' + $self.data("favtargetn") + '"]').trigger('click');
489 url: $self.attr('href'),
493 favorite_tables: (isStorageSupported('localStorage') && typeof window.localStorage.favorite_tables !== 'undefined')
494 ? window.localStorage.favorite_tables
497 success: function (data) {
499 $('#pma_favorite_list').html(data.list);
500 $('#' + anchor_id).parent().html(data.anchor);
504 $('#' + anchor_id).attr("title")
506 // Update localStorage.
507 if (isStorageSupported('localStorage')) {
508 window.localStorage.favorite_tables = data.favorite_tables;
511 PMA_ajaxShowMessage(data.message);
517 // Check if session storage is supported
518 if (isStorageSupported('sessionStorage')) {
519 var storage = window.sessionStorage;
520 // remove tree from storage if Navi_panel config form is submitted
521 $(document).on('submit', 'form.config-form', function(event) {
522 storage.removeItem('navTreePaths');
524 // Initialize if no previous state is defined
525 if ($('#pma_navigation_tree_content').length &&
526 typeof storage.navTreePaths === 'undefined'
528 navTreeStateUpdate();
529 } else if (PMA_commonParams.get('server') === storage.server &&
530 PMA_commonParams.get('token') === storage.token
532 // Reload the tree to the state before page refresh
533 PMA_reloadNavigation(null, JSON.parse(storage.navTreePaths));
539 * updates the tree state in sessionStorage
543 function navTreeStateUpdate() {
544 // update if session storage is supported
545 if (isStorageSupported('sessionStorage')) {
546 var storage = window.sessionStorage;
547 // try catch necessary here to detect whether
548 // content to be stored exceeds storage capacity
550 storage.setItem('navTreePaths', JSON.stringify(traverseNavigationForPaths()));
551 storage.setItem('server', PMA_commonParams.get('server'));
552 storage.setItem('token', PMA_commonParams.get('token'));
554 // storage capacity exceeded & old navigation tree
555 // state is no more valid, so remove it
556 storage.removeItem('navTreePaths');
557 storage.removeItem('server');
558 storage.removeItem('token');
564 * Expands a node in navigation tree.
566 * @param $expandElem expander
567 * @param callback callback function
571 function expandTreeNode($expandElem, callback) {
572 var $children = $expandElem.closest('li').children('div.list_container');
573 var $icon = $expandElem.find('img');
574 if ($expandElem.hasClass('loaded')) {
575 if ($icon.is('.ic_b_plus')) {
576 $icon.removeClass('ic_b_plus').addClass('ic_b_minus');
577 $children.slideDown('fast');
579 if (callback && typeof callback == 'function') {
582 $children.promise().done(navTreeStateUpdate);
584 var $throbber = $('#pma_navigation').find('.throbber')
587 .css({visibility: 'visible', display: 'block'})
590 $throbber.insertBefore($icon);
592 loadChildNodes(true, $expandElem, function (data) {
593 if (typeof data !== 'undefined' && data.success === true) {
594 var $destination = $expandElem.closest('li');
595 $icon.removeClass('ic_b_plus').addClass('ic_b_minus');
596 $children = $destination.children('div.list_container');
597 $children.slideDown('fast');
598 if ($destination.find('ul > li').length == 1) {
599 $destination.find('ul > li')
600 .find('a.expander.container')
603 if (callback && typeof callback == 'function') {
606 PMA_showFullName($destination);
608 PMA_ajaxShowMessage(data.error, false);
612 $children.promise().done(navTreeStateUpdate);
619 * Auto-scrolls the newly chosen database
621 * @param object $element The element to set to view
622 * @param boolean $forceToTop Whether to force scroll to top
625 function scrollToView($element, $forceToTop) {
626 var $container = $('#pma_navigation_tree_content');
627 var elemTop = $element.offset().top - $container.offset().top;
629 var scrollPadding = 20; // extra padding from top of bottom when scrolling to view
630 if (elemTop < 0 || $forceToTop) {
631 $container.stop().animate({
632 scrollTop: elemTop + $container.scrollTop() - scrollPadding
634 } else if (elemTop + textHeight > $container.height()) {
635 $container.stop().animate({
636 scrollTop: elemTop + textHeight - $container.height() + $container.scrollTop() + scrollPadding
642 * Expand the navigation and highlight the current database or table/view
646 function PMA_showCurrentNavigation() {
647 var db = PMA_commonParams.get('db');
648 var table = PMA_commonParams.get('table');
649 $('#pma_navigation_tree')
651 .removeClass('selected');
653 var $dbItem = findLoadedItem(
654 $('#pma_navigation_tree').find('> div'), db, 'database', !table
656 if ($('#navi_db_select').length &&
657 $('option:selected', $('#navi_db_select')).length
659 if (! PMA_selectCurrentDb()) {
662 // If loaded database in navigation is not same as current one
663 if ($('#pma_navigation_tree_content').find('span.loaded_db:first').text()
664 !== $('#navi_db_select').val()
666 loadChildNodes(false, $('option:selected', $('#navi_db_select')), function (data) {
667 handleTableOrDb(table, $('#pma_navigation_tree_content'));
668 var $children = $('#pma_navigation_tree_content').children('div.list_container');
669 $children.promise().done(navTreeStateUpdate);
672 handleTableOrDb(table, $('#pma_navigation_tree_content'));
674 } else if ($dbItem) {
675 var $expander = $dbItem.children('div:first').children('a.expander');
676 // if not loaded or loaded but collapsed
677 if (! $expander.hasClass('loaded') ||
678 $expander.find('img').is('.ic_b_plus')
680 expandTreeNode($expander, function () {
681 handleTableOrDb(table, $dbItem);
684 handleTableOrDb(table, $dbItem);
687 } else if ($('#navi_db_select').length && $('#navi_db_select').val()) {
688 $('#navi_db_select').val('').hide().trigger('change');
690 PMA_showFullName($('#pma_navigation_tree'));
692 function handleTableOrDb(table, $dbItem) {
694 loadAndHighlightTableOrView($dbItem, table);
696 var $container = $dbItem.children('div.list_container');
697 var $tableContainer = $container.children('ul').children('li.tableContainer');
698 if ($tableContainer.length > 0) {
699 var $expander = $tableContainer.children('div:first').children('a.expander');
700 $tableContainer.addClass('selected');
701 expandTreeNode($expander, function () {
702 scrollToView($dbItem, true);
705 scrollToView($dbItem, true);
710 function findLoadedItem($container, name, clazz, doSelect) {
712 $container.children('ul').children('li').each(function () {
714 // this is a navigation group, recurse
715 if ($li.is('.navGroup')) {
716 var $container = $li.children('div.list_container');
717 var $childRet = findLoadedItem(
718 $container, name, clazz, doSelect
724 } else { // this is a real navigation item
725 // name and class matches
726 if (((clazz && $li.is('.' + clazz)) || ! clazz) &&
727 $li.children('a').text() == name) {
729 $li.addClass('selected');
731 // taverse up and expand and parent navigation groups
732 $li.parents('.navGroup').each(function () {
733 var $cont = $(this).children('div.list_container');
734 if (! $cont.is(':visible')) {
736 .children('div:first')
737 .children('a.expander')
749 function loadAndHighlightTableOrView($dbItem, itemName) {
750 var $container = $dbItem.children('div.list_container');
752 var $whichItem = isItemInContainer($container, itemName, 'li.table, li.view');
753 //If item already there in some container
755 //get the relevant container while may also be a subcontainer
756 var $relatedContainer = $whichItem.closest('li.subContainer').length
757 ? $whichItem.closest('li.subContainer')
759 $whichItem = findLoadedItem(
760 $relatedContainer.children('div.list_container'),
764 showTableOrView($whichItem, $relatedContainer.children('div:first').children('a.expander'));
765 //else if item not there, try loading once
767 var $sub_containers = $dbItem.find('.subContainer');
768 //If there are subContainers i.e. tableContainer or viewContainer
769 if($sub_containers.length > 0) {
770 var $containers = [];
771 $sub_containers.each(function (index) {
772 $containers[index] = $(this);
773 $expander = $containers[index]
774 .children('div:first')
775 .children('a.expander');
776 if (! $expander.hasClass('loaded')) {
777 loadAndShowTableOrView($expander, $containers[index], itemName);
780 // else if no subContainers
783 .children('div:first')
784 .children('a.expander');
785 if (! $expander.hasClass('loaded')) {
786 loadAndShowTableOrView($expander, $dbItem, itemName);
792 function loadAndShowTableOrView($expander, $relatedContainer, itemName) {
793 loadChildNodes(true, $expander, function (data) {
794 var $whichItem = findLoadedItem(
795 $relatedContainer.children('div.list_container'),
799 showTableOrView($whichItem, $expander);
804 function showTableOrView($whichItem, $expander) {
805 expandTreeNode($expander, function (data) {
807 scrollToView($whichItem, false);
812 function isItemInContainer($container, name, clazz)
814 var $whichItem = null;
815 $items = $container.find(clazz);
817 $items.each(function () {
818 if ($(this).children('a').text() == name) {
819 $whichItem = $(this);
828 * Disable navigation panel settings
832 function PMA_disableNaviSettings() {
833 $('#pma_navigation_settings_icon').addClass('hide');
834 $('#pma_navigation_settings').remove();
838 * Ensure that navigation panel settings is properly setup.
843 function PMA_ensureNaviSettings(selflink) {
844 $('#pma_navigation_settings_icon').removeClass('hide');
846 if (!$('#pma_navigation_settings').length) {
848 getNaviSettings: true
850 var url = $('#pma_navigation').find('a.navigation_url').attr('href');
851 $.post(url, params, function (data) {
852 if (typeof data !== 'undefined' && data.success) {
853 $('#pma_navi_settings_container').html(data.message);
857 $('#pma_navigation_settings').find('form').attr('action', selflink);
859 PMA_ajaxShowMessage(data.error);
863 $('#pma_navigation_settings').find('form').attr('action', selflink);
868 * Reloads the whole navigation tree while preserving its state
870 * @param function the callback function
871 * @param Object stored navigation paths
875 function PMA_reloadNavigation(callback, paths) {
880 paths = paths || traverseNavigationForPaths();
881 $.extend(params, paths);
882 if ($('#navi_db_select').length) {
883 params.db = PMA_commonParams.get('db');
884 requestNaviReload(params);
887 requestNaviReload(params);
889 function requestNaviReload(params) {
890 var url = $('#pma_navigation').find('a.navigation_url').attr('href');
891 $.post(url, params, function (data) {
892 if (typeof data !== 'undefined' && data.success) {
893 $('#pma_navigation_tree').html(data.message).children('div').show();
894 if ($('#pma_navigation_tree').hasClass('synced')) {
895 PMA_selectCurrentDb();
896 PMA_showCurrentNavigation();
898 // Fire the callback, if any
899 if (typeof callback === 'function') {
902 navTreeStateUpdate();
904 PMA_ajaxShowMessage(data.error);
910 function PMA_selectCurrentDb() {
911 var $naviDbSelect = $('#navi_db_select');
913 if (!$naviDbSelect.length) {
917 if (PMA_commonParams.get('db')) { // db selected
918 $naviDbSelect.show();
921 $naviDbSelect.val(PMA_commonParams.get('db'));
922 return $naviDbSelect.val() === PMA_commonParams.get('db');
927 * Handles any requests to change the page in a branch of a tree
929 * This can be called from link click or select change event handlers
931 * @param object $this A jQuery object that points to the element that
932 * initiated the action of changing the page
936 function PMA_navigationTreePagination($this) {
937 var $msgbox = PMA_ajaxShowMessage();
938 var isDbSelector = $this.closest('div.pageselector').is('.dbselector');
940 if ($this[0].tagName == 'A') {
941 url = $this.attr('href');
942 params = 'ajax_request=true';
943 } else { // tagName == 'SELECT'
944 url = 'navigation.php';
945 params = $this.closest("form").serialize() + '&ajax_request=true';
947 var searchClause = PMA_fastFilter.getSearchClause();
949 params += '&searchClause=' + encodeURIComponent(searchClause);
952 params += '&full=true';
954 var searchClause2 = PMA_fastFilter.getSearchClause2($this);
956 params += '&searchClause2=' + encodeURIComponent(searchClause2);
959 $.post(url, params, function (data) {
960 PMA_ajaxRemoveMessage($msgbox);
961 if (typeof data !== 'undefined' && data.success) {
963 var val = PMA_fastFilter.getSearchClause();
964 $('#pma_navigation_tree')
969 $('#pma_navigation_tree')
970 .find('li.fast_filter input.searchClause')
974 var $parent = $this.closest('div.list_container').parent();
975 var val = PMA_fastFilter.getSearchClause2($this);
976 $this.closest('div.list_container').html(
977 $(data.message).children().show()
980 $parent.find('li.fast_filter input.searchClause').val(val);
982 $parent.find('span.pos2_value:first').text(
983 $parent.find('span.pos2_value:last').text()
985 $parent.find('span.pos3_value:first').text(
986 $parent.find('span.pos3_value:last').text()
990 PMA_ajaxShowMessage(data.error);
992 navTreeStateUpdate();
997 * @var ResizeHandler Custom object that manages the resizing of the navigation
999 * XXX: Must only be ever instanciated once
1000 * XXX: Inside event handlers the 'this' object is accessed as 'event.data.resize_handler'
1002 var ResizeHandler = function () {
1004 * @var int panel_width Used by the collapser to know where to go
1005 * back to when uncollapsing the panel
1007 this.panel_width = 0;
1009 * @var string left Used to provide support for RTL languages
1011 this.left = $('html').attr('dir') == 'ltr' ? 'left' : 'right';
1013 * Adjusts the width of the navigation panel to the specified value
1015 * @param int pos Navigation width in pixels
1019 this.setWidth = function (pos) {
1020 var $resizer = $('#pma_navigation_resizer');
1021 var resizer_width = $resizer.width();
1022 var $collapser = $('#pma_navigation_collapser');
1023 $('#pma_navigation').width(pos);
1024 $('body').css('margin-' + this.left, pos + 'px');
1025 $("#floating_menubar, #pma_console")
1026 .css('margin-' + this.left, (pos + resizer_width) + 'px');
1027 $resizer.css(this.left, pos + 'px');
1030 .css(this.left, pos + resizer_width)
1031 .html(this.getSymbol(pos))
1032 .prop('title', PMA_messages.strShowPanel);
1035 .css(this.left, pos)
1036 .html(this.getSymbol(pos))
1037 .prop('title', PMA_messages.strHidePanel);
1039 setTimeout(function () {
1040 $(window).trigger('resize');
1044 * Returns the horizontal position of the mouse,
1045 * relative to the outer side of the navigation panel
1047 * @param int pos Navigation width in pixels
1051 this.getPos = function (event) {
1052 var pos = event.pageX;
1053 var windowWidth = $(window).width();
1054 var windowScroll = $(window).scrollLeft();
1055 pos = pos - windowScroll;
1056 if (this.left != 'left') {
1057 pos = windowWidth - event.pageX;
1061 } else if (pos + 100 >= windowWidth) {
1062 pos = windowWidth - 100;
1064 this.panel_width = 0;
1069 * Returns the HTML code for the arrow symbol used in the collapser
1071 * @param int width The width of the panel
1075 this.getSymbol = function (width) {
1076 if (this.left == 'left') {
1091 * Event handler for initiating a resize of the panel
1093 * @param object e Event data (contains a reference to resizeHandler)
1097 this.mousedown = function (event) {
1098 event.preventDefault();
1100 .bind('mousemove', {'resize_handler': event.data.resize_handler},
1101 $.throttle(event.data.resize_handler.mousemove, 4))
1102 .bind('mouseup', {'resize_handler': event.data.resize_handler},
1103 event.data.resize_handler.mouseup);
1104 $('body').css('cursor', 'col-resize');
1107 * Event handler for terminating a resize of the panel
1109 * @param object e Event data (contains a reference to resizeHandler)
1113 this.mouseup = function (event) {
1114 $('body').css('cursor', '');
1115 $.cookie('pma_navi_width', event.data.resize_handler.getPos(event));
1116 $('#topmenu').menuResizer('resize');
1118 .unbind('mousemove')
1122 * Event handler for updating the panel during a resize operation
1124 * @param object e Event data (contains a reference to resizeHandler)
1128 this.mousemove = function (event) {
1129 event.preventDefault();
1130 var pos = event.data.resize_handler.getPos(event);
1131 event.data.resize_handler.setWidth(pos);
1132 if ($('.sticky_columns').length !== 0) {
1133 handleAllStickyColumns();
1137 * Event handler for collapsing the panel
1139 * @param object e Event data (contains a reference to resizeHandler)
1143 this.collapse = function (event) {
1144 event.preventDefault();
1145 var panel_width = event.data.resize_handler.panel_width;
1146 var width = $('#pma_navigation').width();
1147 if (width === 0 && panel_width === 0) {
1150 event.data.resize_handler.setWidth(panel_width);
1151 event.data.resize_handler.panel_width = width;
1154 * Event handler for resizing the navigation tree height on window resize
1158 this.treeResize = function (event) {
1159 var $nav = $("#pma_navigation"),
1160 $nav_tree = $("#pma_navigation_tree"),
1161 $nav_header = $("#pma_navigation_header"),
1162 $nav_tree_content = $("#pma_navigation_tree_content");
1163 $nav_tree.height($nav.height() - $nav_header.height());
1164 if ($nav_tree_content.length > 0) {
1165 $nav_tree_content.height($nav_tree.height() - $nav_tree_content.position().top);
1167 //TODO: in fast filter search response there is no #pma_navigation_tree_content, needs to be added in php
1169 'overflow-y': 'auto'
1172 // Set content bottom space beacuse of console
1173 $('body').css('margin-bottom', $('#pma_console').height() + 'px');
1175 /* Initialisation section begins here */
1176 if ($.cookie('pma_navi_width')) {
1177 // If we have a cookie, set the width of the panel to its value
1178 var pos = Math.abs(parseInt($.cookie('pma_navi_width'), 10) || 0);
1180 $('#topmenu').menuResizer('resize');
1182 // Register the events for the resizer and the collapser
1183 $(document).on('mousedown', '#pma_navigation_resizer', {'resize_handler': this}, this.mousedown);
1184 $(document).on('click', '#pma_navigation_collapser', {'resize_handler': this}, this.collapse);
1186 // Add the correct arrow symbol to the collapser
1187 $('#pma_navigation_collapser').html(this.getSymbol($('#pma_navigation').width()));
1188 // Fix navigation tree height
1189 $(window).on('resize', this.treeResize);
1190 // need to call this now and then, browser might decide
1191 // to show/hide horizontal scrollbars depending on page content width
1192 setInterval(this.treeResize, 2000);
1194 }; // End of ResizeHandler
1197 * @var object PMA_fastFilter Handles the functionality that allows filtering
1198 * of the items in a branch of the navigation tree
1200 var PMA_fastFilter = {
1202 * Construct for the asynchronous fast filter functionality
1204 * @param object $this A jQuery object pointing to the list container
1205 * which is the nearest parent of the fast filter
1206 * @param string searchClause The query string for the filter
1208 * @return new PMA_fastFilter.filter object
1210 filter: function ($this, searchClause) {
1212 * @var object $this A jQuery object pointing to the list container
1213 * which is the nearest parent of the fast filter
1217 * @var bool searchClause The query string for the filter
1219 this.searchClause = searchClause;
1221 * @var object $clone A clone of the original contents
1222 * of the navigation branch before
1223 * the fast filter was applied
1225 this.$clone = $this.clone();
1227 * @var object xhr A reference to the ajax request that is currently running
1231 * @var int timeout Used to delay the request for asynchronous search
1233 this.timeout = null;
1235 var $filterInput = $this.find('li.fast_filter input.searchClause');
1236 if ($filterInput.length !== 0 &&
1237 $filterInput.val() !== '' &&
1238 $filterInput.val() != $filterInput[0].defaultValue
1244 * Gets the query string from the database fast filter form
1248 getSearchClause: function () {
1250 var $input = $('#pma_navigation_tree')
1251 .find('li.fast_filter.db_fast_filter input.searchClause');
1252 if ($input.length && $input.val() != $input[0].defaultValue) {
1253 retval = $input.val();
1258 * Gets the query string from a second level item's fast filter form
1259 * The retrieval is done by trasversing the navigation tree backwards
1263 getSearchClause2: function ($this) {
1264 var $filterContainer = $this.closest('div.list_container');
1265 var $filterInput = $([]);
1266 if ($filterContainer
1267 .find('li.fast_filter:not(.db_fast_filter) input.searchClause')
1269 $filterInput = $filterContainer
1270 .find('li.fast_filter:not(.db_fast_filter) input.searchClause');
1272 var searchClause2 = '';
1273 if ($filterInput.length !== 0 &&
1274 $filterInput.first().val() != $filterInput[0].defaultValue
1276 searchClause2 = $filterInput.val();
1278 return searchClause2;
1281 * @var hash events A list of functions that are bound to DOM events
1282 * at the top of this file
1285 focus: function (event) {
1286 var $obj = $(this).closest('div.list_container');
1287 if (! $obj.data('fastFilter')) {
1290 new PMA_fastFilter.filter($obj, $(this).val())
1293 if ($(this).val() == this.defaultValue) {
1299 blur: function (event) {
1300 if ($(this).val() === '') {
1301 $(this).val(this.defaultValue);
1303 var $obj = $(this).closest('div.list_container');
1304 if ($(this).val() == this.defaultValue && $obj.data('fastFilter')) {
1305 $obj.data('fastFilter').restore();
1308 mouseover: function (event) {
1310 if ($(this).closest('li.fast_filter').is('.db_fast_filter')) {
1311 message = PMA_messages.strHoverDbFastFilter;
1313 var node_type = $(this).siblings("input[name='pos2_name']").val();
1314 var node_name = PMA_messages.strTables;
1315 if (node_type == 'views') {
1316 node_name = PMA_messages.strViews;
1317 } else if (node_type == 'procedures') {
1318 node_name = PMA_messages.strProcedures;
1319 } else if (node_type == 'functions') {
1320 node_name = PMA_messages.strFunctions;
1321 } else if (node_type == 'events') {
1322 node_name = PMA_messages.strEvents;
1324 message = PMA_sprintf(PMA_messages.strHoverFastFilter, node_name);
1326 PMA_tooltip($(this), 'input', message);
1328 keyup: function (event) {
1329 var $obj = $(this).closest('div.list_container');
1331 if ($(this).val() != this.defaultValue && $(this).val() !== '') {
1332 $obj.find('div.pageselector').hide();
1333 str = $(this).val();
1337 * FIXME at the server level a value match is done while on
1338 * the client side it is a regex match. These two should be aligned
1341 // regex used for filtering.
1344 regex = new RegExp(str, 'i');
1349 // this is the div that houses the items to be filtered by this filter.
1351 if ($(this).closest('li.fast_filter').is('.db_fast_filter')) {
1352 outerContainer = $('#pma_navigation_tree_content');
1354 outerContainer = $obj;
1357 // filters items that are directly under the div as well as grouped in
1358 // groups. Does not filter child items (i.e. a database search does
1359 // not filter tables)
1360 var item_filter = function($curr) {
1361 $curr.children('ul').children('li.navGroup').each(function() {
1362 $(this).children('div.list_container').each(function() {
1363 item_filter($(this)); // recursive
1366 $curr.children('ul').children('li').children('a').not('.container').each(function() {
1367 if (regex.test($(this).text())) {
1368 $(this).parent().show().removeClass('hidden');
1370 $(this).parent().hide().addClass('hidden');
1374 item_filter(outerContainer);
1376 // hides containers that does not have any visible children
1377 var container_filter = function ($curr) {
1378 $curr.children('ul').children('li.navGroup').each(function() {
1379 var $group = $(this);
1380 $group.children('div.list_container').each(function() {
1381 container_filter($(this)); // recursive
1383 $group.show().removeClass('hidden');
1384 if ($group.children('div.list_container').children('ul')
1385 .children('li').not('.hidden').length === 0) {
1386 $group.hide().addClass('hidden');
1390 container_filter(outerContainer);
1392 if ($(this).val() != this.defaultValue && $(this).val() !== '') {
1393 if (! $obj.data('fastFilter')) {
1396 new PMA_fastFilter.filter($obj, $(this).val())
1399 if (event.keyCode == 13) {
1400 $obj.data('fastFilter').update($(this).val());
1403 } else if ($obj.data('fastFilter')) {
1404 $obj.data('fastFilter').restore(true);
1407 clear: function (event) {
1408 event.stopPropagation();
1409 // Clear the input and apply the fast filter with empty input
1410 var filter = $(this).closest('div.list_container').data('fastFilter');
1414 var value = $(this).prev()[0].defaultValue;
1415 $(this).prev().val(value).trigger('keyup');
1420 * Handles a change in the search clause
1422 * @param string searchClause The query string for the filter
1426 PMA_fastFilter.filter.prototype.update = function (searchClause) {
1427 if (this.searchClause != searchClause) {
1428 this.searchClause = searchClause;
1433 * After a delay of 250mS, initiates a request to retrieve search results
1434 * Multiple calls to this function will always abort the previous request
1438 PMA_fastFilter.filter.prototype.request = function () {
1440 if (self.$this.find('li.fast_filter').find('img.throbber').length === 0) {
1441 self.$this.find('li.fast_filter').append(
1442 $('<div class="throbber"></div>').append(
1443 $('#pma_navigation_content')
1444 .find('img.throbber')
1446 .css({visibility: 'visible', display: 'block'})
1453 var url = $('#pma_navigation').find('a.navigation_url').attr('href');
1454 var params = self.$this.find('> ul > li > form.fast_filter').first().serialize();
1455 if (self.$this.find('> ul > li > form.fast_filter:first input[name=searchClause]').length === 0) {
1456 var $input = $('#pma_navigation_tree').find('li.fast_filter.db_fast_filter input.searchClause');
1457 if ($input.length && $input.val() != $input[0].defaultValue) {
1458 params += '&searchClause=' + encodeURIComponent($input.val());
1466 complete: function (jqXHR, status) {
1467 if (status != 'abort') {
1468 var data = $.parseJSON(jqXHR.responseText);
1469 self.$this.find('li.fast_filter').find('div.throbber').remove();
1470 if (data && data.results) {
1471 self.swap.apply(self, [data.message]);
1478 * Replaces the contents of the navigation branch with the search results
1480 * @param string list The search results
1484 PMA_fastFilter.filter.prototype.swap = function (list) {
1486 .html($(list).html())
1490 .find('li.fast_filter input.searchClause')
1491 .val(this.searchClause);
1492 this.$this.data('fastFilter', this);
1495 * Restores the navigation to the original state after the fast filter is cleared
1497 * @param bool focus Whether to also focus the input box of the fast filter
1501 PMA_fastFilter.filter.prototype.restore = function (focus) {
1502 if(this.$this.children('ul').first().hasClass('search_results')) {
1503 this.$this.html(this.$clone.html()).children().show();
1504 this.$this.data('fastFilter', this);
1506 this.$this.find('li.fast_filter input.searchClause').focus();
1509 this.searchClause = '';
1510 this.$this.find('div.pageselector').show();
1511 this.$this.find('div.throbber').remove();
1515 * Show full name when cursor hover and name not shown completely
1517 * @param object $containerELem Container element
1521 function PMA_showFullName($containerELem) {
1523 $containerELem.find('.hover_show_full').mouseenter(function() {
1525 var $this = $(this);
1526 var thisOffset = $this.offset();
1527 if($this.text() === '') {
1530 var $parent = $this.parent();
1531 if( ($parent.offset().left + $parent.outerWidth())
1532 < (thisOffset.left + $this.outerWidth()))
1534 var $fullNameLayer = $('#full_name_layer');
1535 if($fullNameLayer.length === 0)
1537 $('body').append('<div id="full_name_layer" class="hide"></div>');
1538 $('#full_name_layer').mouseleave(function() {
1540 $(this).addClass('hide')
1541 .removeClass('hovering');
1542 }).mouseenter(function() {
1544 $(this).addClass('hovering');
1546 $fullNameLayer = $('#full_name_layer');
1548 $fullNameLayer.removeClass('hide');
1549 $fullNameLayer.css({left: thisOffset.left, top: thisOffset.top});
1550 $fullNameLayer.html($this.clone());
1551 setTimeout(function() {
1552 if(! $fullNameLayer.hasClass('hovering'))
1554 $fullNameLayer.trigger('mouseleave');