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 $('#session_debug').replaceWith(data._debug);
75 $errors = $(data._errors);
76 if ($errors.children().length > 0) {
77 $('#pma_errors').replaceWith(data._errors);
80 if (callback && typeof callback == 'function') {
83 } else if(data.redirect_flag == "1") {
84 window.location.href += '&session_expired=1';
85 window.location.reload();
87 var $throbber = $expandElem.find('img.throbber');
89 $icon = $expandElem.find('img.ic_b_plus');
91 PMA_ajaxShowMessage(data.error, false);
97 * Collapses a node in navigation tree.
99 * @param $expandElem expander
103 function collapseTreeNode($expandElem) {
104 var $children = $expandElem.closest('li').children('div.list_container');
105 var $icon = $expandElem.find('img');
106 if ($expandElem.hasClass('loaded')) {
107 if ($icon.is('.ic_b_minus')) {
108 $icon.removeClass('ic_b_minus').addClass('ic_b_plus');
109 $children.slideUp('fast');
113 $children.promise().done(navTreeStateUpdate);
117 * Traverse the navigation tree backwards to generate all the actual
118 * and virtual paths, as well as the positions in the pagination at
119 * various levels, if necessary.
123 function traverseNavigationForPaths() {
125 pos: $('#pma_navigation_tree div.dbselector select').val()
127 if ($('#navi_db_select').length) {
131 $('#pma_navigation_tree').find('a.expander:visible').each(function () {
132 if ($(this).find('img').is('.ic_b_minus') &&
133 $(this).closest('li').find('div.list_container .ic_b_minus').length === 0
135 params['n' + count + '_aPath'] = $(this).find('span.aPath').text();
136 params['n' + count + '_vPath'] = $(this).find('span.vPath').text();
138 var pos2_name = $(this).find('span.pos2_name').text();
143 .find('span.pos2_name:last')
146 var pos2_value = $(this).find('span.pos2_value').text();
151 .find('span.pos2_value:last')
155 params['n' + count + '_pos2_name'] = pos2_name;
156 params['n' + count + '_pos2_value'] = pos2_value;
158 params['n' + count + '_pos3_name'] = $(this).find('span.pos3_name').text();
159 params['n' + count + '_pos3_value'] = $(this).find('span.pos3_value').text();
167 * Executed on page load
170 if (! $('#pma_navigation').length) {
171 // Don't bother running any code if the navigation is not even on the page
175 // Do not let the page reload on submitting the fast filter
176 $(document).on('submit', '.fast_filter', function (event) {
177 event.preventDefault();
180 // Fire up the resize handlers
184 * opens/closes (hides/shows) tree elements
185 * loads data via ajax
187 $(document).on('click', '#pma_navigation_tree a.expander', function (event) {
188 event.preventDefault();
189 event.stopImmediatePropagation();
190 var $icon = $(this).find('img');
191 if ($icon.is('.ic_b_plus')) {
192 expandTreeNode($(this));
194 collapseTreeNode($(this));
199 * Register event handler for click on the reload
200 * navigation icon at the top of the panel
202 $(document).on('click', '#pma_navigation_reload', function (event) {
203 event.preventDefault();
204 // reload icon object
205 var $icon = $(this).find('img');
206 // source of the hidden throbber icon
207 var icon_throbber_src = $('#pma_navigation .throbber').attr('src');
208 // source of the reload icon
209 var icon_reload_src = $icon.attr('src');
210 // replace the source of the reload icon with the one for throbber
211 $icon.attr('src', icon_throbber_src);
212 PMA_reloadNavigation();
213 // after one second, put back the reload icon
214 setTimeout(function () {
215 $icon.attr('src', icon_reload_src);
219 $(document).on("change", '#navi_db_select', function (event) {
220 $(this).closest('form').trigger('submit');
224 * Register event handler for click on the collapse all
225 * navigation icon at the top of the navigation tree
227 $(document).on('click', '#pma_navigation_collapse', function (event) {
228 event.preventDefault();
229 $('#pma_navigation_tree a.expander').each(function() {
230 var $icon = $(this).find('img');
231 if ($icon.is('.ic_b_minus')) {
238 * Register event handler to toggle
239 * the 'link with main panel' icon on mouseenter.
241 $(document).on('mouseenter', '#pma_navigation_sync', function (event) {
242 event.preventDefault();
243 var synced = $('#pma_navigation_tree').hasClass('synced');
244 var $img = $('#pma_navigation_sync').children('img');
246 $img.removeClass('ic_s_link').addClass('ic_s_unlink');
248 $img.removeClass('ic_s_unlink').addClass('ic_s_link');
253 * Register event handler to toggle
254 * the 'link with main panel' icon on mouseout.
256 $(document).on('mouseout', '#pma_navigation_sync', function (event) {
257 event.preventDefault();
258 var synced = $('#pma_navigation_tree').hasClass('synced');
259 var $img = $('#pma_navigation_sync').children('img');
261 $img.removeClass('ic_s_unlink').addClass('ic_s_link');
263 $img.removeClass('ic_s_link').addClass('ic_s_unlink');
268 * Register event handler to toggle
269 * the linking with main panel behavior
271 $(document).on('click', '#pma_navigation_sync', function (event) {
272 event.preventDefault();
273 var synced = $('#pma_navigation_tree').hasClass('synced');
274 var $img = $('#pma_navigation_sync').children('img');
277 .removeClass('ic_s_unlink')
278 .addClass('ic_s_link')
279 .attr('alt', PMA_messages.linkWithMain)
280 .attr('title', PMA_messages.linkWithMain);
281 $('#pma_navigation_tree')
282 .removeClass('synced')
284 .removeClass('selected');
287 .removeClass('ic_s_link')
288 .addClass('ic_s_unlink')
289 .attr('alt', PMA_messages.unlinkWithMain)
290 .attr('title', PMA_messages.unlinkWithMain);
291 $('#pma_navigation_tree').addClass('synced');
292 PMA_showCurrentNavigation();
297 * Bind all "fast filter" events
299 $(document).on('click', '#pma_navigation_tree li.fast_filter span', PMA_fastFilter.events.clear);
300 $(document).on('focus', '#pma_navigation_tree li.fast_filter input.searchClause', PMA_fastFilter.events.focus);
301 $(document).on('blur', '#pma_navigation_tree li.fast_filter input.searchClause', PMA_fastFilter.events.blur);
302 $(document).on('keyup', '#pma_navigation_tree li.fast_filter input.searchClause', PMA_fastFilter.events.keyup);
303 $(document).on('mouseover', '#pma_navigation_tree li.fast_filter input.searchClause', PMA_fastFilter.events.mouseover);
306 * Ajax handler for pagination
308 $(document).on('click', '#pma_navigation_tree div.pageselector a.ajax', function (event) {
309 event.preventDefault();
310 PMA_navigationTreePagination($(this));
318 '#pma_navigation_tree.highlight li:not(.fast_filter)',
320 if ($('li:visible', this).length === 0) {
321 $(this).addClass('activePointer');
327 '#pma_navigation_tree.highlight li:not(.fast_filter)',
329 $(this).removeClass('activePointer');
333 /** Create a Routine, Trigger or Event */
334 $(document).on('click', 'li.new_procedure a.ajax, li.new_function a.ajax', function (event) {
335 event.preventDefault();
336 var dialog = new RTE.object('routine');
337 dialog.editorDialog(1, $(this));
339 $(document).on('click', 'li.new_trigger a.ajax', function (event) {
340 event.preventDefault();
341 var dialog = new RTE.object('trigger');
342 dialog.editorDialog(1, $(this));
344 $(document).on('click', 'li.new_event a.ajax', function (event) {
345 event.preventDefault();
346 var dialog = new RTE.object('event');
347 dialog.editorDialog(1, $(this));
350 /** Execute Routines */
351 $(document).on('click', 'li.procedure > a.ajax, li.function > a.ajax', function (event) {
352 event.preventDefault();
353 var dialog = new RTE.object('routine');
354 dialog.executeDialog($(this));
356 /** Edit Triggers and Events */
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));
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.editorDialog(0, $(this).parent());
375 /** Export Triggers and Events */
376 $(document).on('click', 'li.trigger div:eq(1) a.ajax img, li.event div:eq(1) a.ajax img', function (event) {
377 event.preventDefault();
378 var dialog = new RTE.object();
379 dialog.exportDialog($(this).parent());
383 $(document).on('click', '#pma_navigation_tree li.new_index a.ajax', function (event) {
384 event.preventDefault();
385 var url = $(this).attr('href').substr(
386 $(this).attr('href').indexOf('?') + 1
387 ) + '&ajax_request=true';
388 var title = PMA_messages.strAddIndex;
389 indexEditorDialog(url, title);
393 $(document).on('click', 'li.index a.ajax', function (event) {
394 event.preventDefault();
395 var url = $(this).attr('href').substr(
396 $(this).attr('href').indexOf('?') + 1
397 ) + '&ajax_request=true';
398 var title = PMA_messages.strEditIndex;
399 indexEditorDialog(url, title);
403 $(document).on('click', 'li.new_view a.ajax', function (event) {
404 event.preventDefault();
405 PMA_createViewDialog($(this));
408 /** Hide navigation tree item */
409 $(document).on('click', 'a.hideNavItem.ajax', function (event) {
410 event.preventDefault();
412 url: $(this).attr('href') + '&ajax_request=true',
413 success: function (data) {
414 if (typeof data !== 'undefined' && data.success === true) {
415 PMA_reloadNavigation();
417 PMA_ajaxShowMessage(data.error);
423 /** Display a dialog to choose hidden navigation items to show */
424 $(document).on('click', 'a.showUnhide.ajax', function (event) {
425 event.preventDefault();
426 var $msg = PMA_ajaxShowMessage();
427 $.get($(this).attr('href') + '&ajax_request=1', function (data) {
428 if (typeof data !== 'undefined' && data.success === true) {
429 PMA_ajaxRemoveMessage($msg);
430 var buttonOptions = {};
431 buttonOptions[PMA_messages.strClose] = function () {
432 $(this).dialog("close");
435 .attr('id', 'unhideNavItemDialog')
436 .append(data.message)
441 buttons: buttonOptions,
442 title: PMA_messages.strUnhideNavItem,
448 PMA_ajaxShowMessage(data.error);
453 /** Show a hidden navigation tree item */
454 $(document).on('click', 'a.unhideNavItem.ajax', function (event) {
455 event.preventDefault();
456 var $tr = $(this).parents('tr');
457 var $msg = PMA_ajaxShowMessage();
459 url: $(this).attr('href') + '&ajax_request=true',
460 success: function (data) {
461 PMA_ajaxRemoveMessage($msg);
462 if (typeof data !== 'undefined' && data.success === true) {
464 PMA_reloadNavigation();
466 PMA_ajaxShowMessage(data.error);
472 // Add/Remove favorite table using Ajax.
473 $(document).on("click", ".favorite_table_anchor", function (event) {
474 event.preventDefault();
476 var anchor_id = $self.attr("id");
477 if($self.data("favtargetn") !== null) {
478 if($('a[data-favtargets="' + $self.data("favtargetn") + '"]').length > 0)
480 $('a[data-favtargets="' + $self.data("favtargetn") + '"]').trigger('click');
486 url: $self.attr('href'),
490 favorite_tables: (isStorageSupported('localStorage'))
491 ? window.localStorage.favorite_tables
494 success: function (data) {
496 $('#pma_favorite_list').html(data.list);
497 $('#' + anchor_id).parent().html(data.anchor);
501 $('#' + anchor_id).attr("title")
503 // Update localStorage.
504 if (isStorageSupported('localStorage')) {
505 window.localStorage.favorite_tables = data.favorite_tables;
508 PMA_ajaxShowMessage(data.message);
514 // Check if session storage is supported
515 if (isStorageSupported('sessionStorage')) {
516 var storage = window.sessionStorage;
517 // remove tree from storage if Navi_panel config form is submitted
518 $(document).on('submit', 'form.config-form', function(event) {
519 storage.removeItem('navTreePaths');
521 // Initialize if no previous state is defined
522 if ($('#pma_navigation_tree_content').length
523 && typeof storage.navTreePaths === 'undefined'
525 navTreeStateUpdate();
526 } else if (PMA_commonParams.get('server') === storage.server &&
527 PMA_commonParams.get('token') === storage.token
529 // Reload the tree to the state before page refresh
530 PMA_reloadNavigation(null, JSON.parse(storage.navTreePaths));
536 * updates the tree state in sessionStorage
540 function navTreeStateUpdate() {
541 // update if session storage is supported
542 if (isStorageSupported('sessionStorage')) {
543 var storage = window.sessionStorage;
544 // try catch necessary here to detect whether
545 // content to be stored exceeds storage capacity
547 storage.setItem('navTreePaths', JSON.stringify(traverseNavigationForPaths()));
548 storage.setItem('server', PMA_commonParams.get('server'));
549 storage.setItem('token', PMA_commonParams.get('token'));
551 // storage capacity exceeded & old navigation tree
552 // state is no more valid, so remove it
553 storage.removeItem('navTreePaths');
554 storage.removeItem('server');
555 storage.removeItem('token');
561 * Expands a node in navigation tree.
563 * @param $expandElem expander
564 * @param callback callback function
568 function expandTreeNode($expandElem, callback) {
569 var $children = $expandElem.closest('li').children('div.list_container');
570 var $icon = $expandElem.find('img');
571 if ($expandElem.hasClass('loaded')) {
572 if ($icon.is('.ic_b_plus')) {
573 $icon.removeClass('ic_b_plus').addClass('ic_b_minus');
574 $children.slideDown('fast');
576 if (callback && typeof callback == 'function') {
579 $children.promise().done(navTreeStateUpdate);
581 var $throbber = $('#pma_navigation .throbber')
584 .css({visibility: 'visible', display: 'block'})
587 $throbber.insertBefore($icon);
589 loadChildNodes(true, $expandElem, function (data) {
590 if (typeof data !== 'undefined' && data.success === true) {
591 var $destination = $expandElem.closest('li');
592 $icon.removeClass('ic_b_plus').addClass('ic_b_minus');
593 $children = $destination.children('div.list_container');
594 $children.slideDown('fast');
595 if ($destination.find('ul > li').length == 1) {
596 $destination.find('ul > li')
597 .find('a.expander.container')
600 if (callback && typeof callback == 'function') {
603 PMA_showFullName($destination);
605 PMA_ajaxShowMessage(data.error, false);
609 $children.promise().done(navTreeStateUpdate);
616 * Auto-scrolls the newly chosen database
618 * @param object $element The element to set to view
619 * @param boolean $forceToTop Whether to force scroll to top
622 function scrollToView($element, $forceToTop) {
623 var $container = $('#pma_navigation_tree_content');
624 var elemTop = $element.offset().top - $container.offset().top;
626 var scrollPadding = 20; // extra padding from top of bottom when scrolling to view
627 if (elemTop < 0 || $forceToTop) {
628 $container.stop().animate({
629 scrollTop: elemTop + $container.scrollTop() - scrollPadding
631 } else if (elemTop + textHeight > $container.height()) {
632 $container.stop().animate({
633 scrollTop: elemTop + textHeight - $container.height() + $container.scrollTop() + scrollPadding
639 * Expand the navigation and highlight the current database or table/view
643 function PMA_showCurrentNavigation() {
644 var db = PMA_commonParams.get('db');
645 var table = PMA_commonParams.get('table');
646 $('#pma_navigation_tree')
648 .removeClass('selected');
650 var $dbItem = findLoadedItem(
651 $('#pma_navigation_tree > div'), db, 'database', !table
654 var $expander = $dbItem.children('div:first').children('a.expander');
655 // if not loaded or loaded but collapsed
656 if (! $expander.hasClass('loaded') ||
657 $expander.find('img').is('.ic_b_plus')
659 expandTreeNode($expander, function () {
660 handleTableOrDb(table, $dbItem);
663 handleTableOrDb(table, $dbItem);
665 } else if ($('#navi_db_select').length
666 && $('option:selected', $('#navi_db_select')).length
668 if (! PMA_selectCurrentDb()) {
671 // If loaded database in navigation is not same as current one
672 if ( $('#pma_navigation_tree_content span.loaded_db:first').text()
673 !== $('#navi_db_select').val()
675 loadChildNodes(false, $('option:selected', $('#navi_db_select')), function (data) {
676 handleTableOrDb(table, $('#pma_navigation_tree_content'));
677 var $children = $('#pma_navigation_tree_content').children('div.list_container');
678 $children.promise().done(navTreeStateUpdate);
681 handleTableOrDb(table, $('#pma_navigation_tree_content'));
685 PMA_showFullName($('#pma_navigation_tree'));
687 function handleTableOrDb(table, $dbItem) {
689 loadAndHighlightTableOrView($dbItem, table);
691 var $container = $dbItem.children('div.list_container');
692 var $tableContainer = $container.children('ul').children('li.tableContainer');
693 if ($tableContainer.length > 0) {
694 var $expander = $tableContainer.children('div:first').children('a.expander');
695 $tableContainer.addClass('selected');
696 expandTreeNode($expander, function () {
697 scrollToView($dbItem, true);
700 scrollToView($dbItem, true);
705 function findLoadedItem($container, name, clazz, doSelect) {
707 $container.children('ul').children('li').each(function () {
709 // this is a navigation group, recurse
710 if ($li.is('.navGroup')) {
711 var $container = $li.children('div.list_container');
712 var $childRet = findLoadedItem(
713 $container, name, clazz, doSelect
719 } else { // this is a real navigation item
720 // name and class matches
721 if (((clazz && $li.is('.' + clazz)) || ! clazz) &&
722 $li.children('a').text() == name) {
724 $li.addClass('selected');
726 // taverse up and expand and parent navigation groups
727 $li.parents('.navGroup').each(function () {
728 $cont = $(this).children('div.list_container');
729 if (! $cont.is(':visible')) {
731 .children('div:first')
732 .children('a.expander')
744 function loadAndHighlightTableOrView($dbItem, itemName) {
745 var $container = $dbItem.children('div.list_container');
747 var $whichItem = isItemInContainer($container, itemName, 'li.table, li.view');
748 //If item already there in some container
750 //get the relevant container while may also be a subcontainer
751 var $relatedContainer = $whichItem.closest('li.subContainer').length
752 ? $whichItem.closest('li.subContainer')
754 $whichItem = findLoadedItem(
755 $relatedContainer.children('div.list_container'),
759 showTableOrView($whichItem, $relatedContainer.children('div:first').children('a.expander'));
760 //else if item not there, try loading once
762 var $sub_containers = $dbItem.find('.subContainer');
763 //If there are subContainers i.e. tableContainer or viewContainer
764 if($sub_containers.length > 0) {
765 var $containers = [];
766 $sub_containers.each(function (index) {
767 $containers[index] = $(this);
768 $expander = $containers[index]
769 .children('div:first')
770 .children('a.expander');
771 collapseTreeNode($expander);
772 loadAndShowTableOrView($expander, $containers[index], itemName);
774 // else if no subContainers
777 .children('div:first')
778 .children('a.expander');
779 collapseTreeNode($expander);
780 loadAndShowTableOrView($expander, $dbItem, itemName);
785 function loadAndShowTableOrView($expander, $relatedContainer, itemName) {
786 loadChildNodes(true, $expander, function (data) {
787 var $whichItem = findLoadedItem(
788 $relatedContainer.children('div.list_container'),
792 showTableOrView($whichItem, $expander);
797 function showTableOrView($whichItem, $expander) {
798 expandTreeNode($expander, function (data) {
800 scrollToView($whichItem, false);
805 function isItemInContainer($container, name, clazz)
807 var $whichItem = null;
808 $items = $container.find(clazz);
810 $items.each(function () {
811 if ($(this).children('a').text() == name) {
812 $whichItem = $(this);
821 * Reloads the whole navigation tree while preserving its state
823 * @param function the callback function
824 * @param Object stored navigation paths
828 function PMA_reloadNavigation(callback, paths) {
832 paths = paths || traverseNavigationForPaths();
833 $.extend(params, paths);
834 if ($('#navi_db_select').length) {
835 params.db = PMA_commonParams.get('db');
836 requestNaviReload(params);
839 requestNaviReload(params);
841 function requestNaviReload(params) {
842 var url = $('#pma_navigation').find('a.navigation_url').attr('href');
843 $.post(url, params, function (data) {
844 if (typeof data !== 'undefined' && data.success) {
845 $('#pma_navigation_tree').html(data.message).children('div').show();
846 if ($('#pma_navigation_tree').hasClass('synced')) {
847 PMA_selectCurrentDb();
848 PMA_showCurrentNavigation();
850 // Fire the callback, if any
851 if (typeof callback === 'function') {
854 navTreeStateUpdate();
856 PMA_ajaxShowMessage(data.error);
862 function PMA_selectCurrentDb() {
863 if ($('#navi_db_select').length) {
864 $('#navi_db_select').val(PMA_commonParams.get('db'));
865 if ($('#navi_db_select').val() !== PMA_commonParams.get('db')) {
873 * Handles any requests to change the page in a branch of a tree
875 * This can be called from link click or select change event handlers
877 * @param object $this A jQuery object that points to the element that
878 * initiated the action of changing the page
882 function PMA_navigationTreePagination($this) {
883 var $msgbox = PMA_ajaxShowMessage();
884 var isDbSelector = $this.closest('div.pageselector').is('.dbselector');
886 if ($this[0].tagName == 'A') {
887 url = $this.attr('href');
888 params = 'ajax_request=true';
889 } else { // tagName == 'SELECT'
890 url = 'navigation.php';
891 params = $this.closest("form").serialize() + '&ajax_request=true';
893 var searchClause = PMA_fastFilter.getSearchClause();
895 params += '&searchClause=' + encodeURIComponent(searchClause);
898 params += '&full=true';
900 var searchClause2 = PMA_fastFilter.getSearchClause2($this);
902 params += '&searchClause2=' + encodeURIComponent(searchClause2);
905 $.post(url, params, function (data) {
906 PMA_ajaxRemoveMessage($msgbox);
907 if (typeof data !== 'undefined' && data.success) {
909 var val = PMA_fastFilter.getSearchClause();
910 $('#pma_navigation_tree')
915 $('#pma_navigation_tree')
916 .find('li.fast_filter input.searchClause')
920 var $parent = $this.closest('div.list_container').parent();
921 var val = PMA_fastFilter.getSearchClause2($this);
922 $this.closest('div.list_container').html(
923 $(data.message).children().show()
926 $parent.find('li.fast_filter input.searchClause').val(val);
928 $parent.find('span.pos2_value:first').text(
929 $parent.find('span.pos2_value:last').text()
931 $parent.find('span.pos3_value:first').text(
932 $parent.find('span.pos3_value:last').text()
936 PMA_ajaxShowMessage(data.error);
938 navTreeStateUpdate();
943 * @var ResizeHandler Custom object that manages the resizing of the navigation
945 * XXX: Must only be ever instanciated once
946 * XXX: Inside event handlers the 'this' object is accessed as 'event.data.resize_handler'
948 var ResizeHandler = function () {
950 * @var int panel_width Used by the collapser to know where to go
951 * back to when uncollapsing the panel
953 this.panel_width = 0;
955 * @var string left Used to provide support for RTL languages
957 this.left = $('html').attr('dir') == 'ltr' ? 'left' : 'right';
959 * Adjusts the width of the navigation panel to the specified value
961 * @param int pos Navigation width in pixels
965 this.setWidth = function (pos) {
966 var $resizer = $('#pma_navigation_resizer');
967 var resizer_width = $resizer.width();
968 var $collapser = $('#pma_navigation_collapser');
969 $('#pma_navigation').width(pos);
970 $('body').css('margin-' + this.left, pos + 'px');
971 $("#floating_menubar, #pma_console")
972 .css('margin-' + this.left, (pos + resizer_width) + 'px');
973 $resizer.css(this.left, pos + 'px');
976 .css(this.left, pos + resizer_width)
977 .html(this.getSymbol(pos))
978 .prop('title', PMA_messages.strShowPanel);
982 .html(this.getSymbol(pos))
983 .prop('title', PMA_messages.strHidePanel);
985 setTimeout(function () {
986 $(window).trigger('resize');
990 * Returns the horizontal position of the mouse,
991 * relative to the outer side of the navigation panel
993 * @param int pos Navigation width in pixels
997 this.getPos = function (event) {
998 var pos = event.pageX;
999 var windowWidth = $(window).width();
1000 var windowScroll = $(window).scrollLeft();
1001 pos = pos - windowScroll;
1002 if (this.left != 'left') {
1003 pos = windowWidth - event.pageX;
1007 } else if (pos + 100 >= windowWidth) {
1008 pos = windowWidth - 100;
1010 this.panel_width = 0;
1015 * Returns the HTML code for the arrow symbol used in the collapser
1017 * @param int width The width of the panel
1021 this.getSymbol = function (width) {
1022 if (this.left == 'left') {
1037 * Event handler for initiating a resize of the panel
1039 * @param object e Event data (contains a reference to resizeHandler)
1043 this.mousedown = function (event) {
1044 event.preventDefault();
1046 .bind('mousemove', {'resize_handler': event.data.resize_handler},
1047 $.throttle(event.data.resize_handler.mousemove, 4))
1048 .bind('mouseup', {'resize_handler': event.data.resize_handler},
1049 event.data.resize_handler.mouseup);
1050 $('body').css('cursor', 'col-resize');
1053 * Event handler for terminating a resize of the panel
1055 * @param object e Event data (contains a reference to resizeHandler)
1059 this.mouseup = function (event) {
1060 $('body').css('cursor', '');
1061 $.cookie('pma_navi_width', event.data.resize_handler.getPos(event));
1062 $('#topmenu').menuResizer('resize');
1064 .unbind('mousemove')
1068 * Event handler for updating the panel during a resize operation
1070 * @param object e Event data (contains a reference to resizeHandler)
1074 this.mousemove = function (event) {
1075 event.preventDefault();
1076 var pos = event.data.resize_handler.getPos(event);
1077 event.data.resize_handler.setWidth(pos);
1078 if ($('.sticky_columns').length !== 0) {
1079 handleAllStickyColumns();
1083 * Event handler for collapsing the panel
1085 * @param object e Event data (contains a reference to resizeHandler)
1089 this.collapse = function (event) {
1090 event.preventDefault();
1091 var panel_width = event.data.resize_handler.panel_width;
1092 var width = $('#pma_navigation').width();
1093 if (width === 0 && panel_width === 0) {
1096 event.data.resize_handler.setWidth(panel_width);
1097 event.data.resize_handler.panel_width = width;
1100 * Event handler for resizing the navigation tree height on window resize
1104 this.treeResize = function (event) {
1105 var $nav = $("#pma_navigation"),
1106 $nav_tree = $("#pma_navigation_tree"),
1107 $nav_header = $("#pma_navigation_header"),
1108 $nav_tree_content = $("#pma_navigation_tree_content");
1109 $nav_tree.height($nav.height() - $nav_header.height());
1110 if ($nav_tree_content.length > 0) {
1111 $nav_tree_content.height($nav_tree.height() - $nav_tree_content.position().top);
1113 //TODO: in fast filter search response there is no #pma_navigation_tree_content, needs to be added in php
1115 'overflow-y': 'auto'
1118 // Set content bottom space beacuse of console
1119 $('body').css('margin-bottom', $('#pma_console').height() + 'px');
1121 /* Initialisation section begins here */
1122 if ($.cookie('pma_navi_width')) {
1123 // If we have a cookie, set the width of the panel to its value
1124 var pos = Math.abs(parseInt($.cookie('pma_navi_width'), 10) || 0);
1126 $('#topmenu').menuResizer('resize');
1128 // Register the events for the resizer and the collapser
1129 $(document).on('mousedown', '#pma_navigation_resizer', {'resize_handler': this}, this.mousedown);
1130 $(document).on('click', '#pma_navigation_collapser', {'resize_handler': this}, this.collapse);
1132 // Add the correct arrow symbol to the collapser
1133 $('#pma_navigation_collapser').html(this.getSymbol($('#pma_navigation').width()));
1134 // Fix navigation tree height
1135 $(window).on('resize', this.treeResize);
1136 // need to call this now and then, browser might decide
1137 // to show/hide horizontal scrollbars depending on page content width
1138 setInterval(this.treeResize, 2000);
1140 }; // End of ResizeHandler
1143 * @var object PMA_fastFilter Handles the functionality that allows filtering
1144 * of the items in a branch of the navigation tree
1146 var PMA_fastFilter = {
1148 * Construct for the asynchronous fast filter functionality
1150 * @param object $this A jQuery object pointing to the list container
1151 * which is the nearest parent of the fast filter
1152 * @param string searchClause The query string for the filter
1154 * @return new PMA_fastFilter.filter object
1156 filter: function ($this, searchClause) {
1158 * @var object $this A jQuery object pointing to the list container
1159 * which is the nearest parent of the fast filter
1163 * @var bool searchClause The query string for the filter
1165 this.searchClause = searchClause;
1167 * @var object $clone A clone of the original contents
1168 * of the navigation branch before
1169 * the fast filter was applied
1171 this.$clone = $this.clone();
1173 * @var object xhr A reference to the ajax request that is currently running
1177 * @var int timeout Used to delay the request for asynchronous search
1179 this.timeout = null;
1181 var $filterInput = $this.find('li.fast_filter input.searchClause');
1182 if ($filterInput.length !== 0 &&
1183 $filterInput.val() !== '' &&
1184 $filterInput.val() != $filterInput[0].defaultValue
1190 * Gets the query string from the database fast filter form
1194 getSearchClause: function () {
1196 var $input = $('#pma_navigation_tree')
1197 .find('li.fast_filter.db_fast_filter input.searchClause');
1198 if ($input.length && $input.val() != $input[0].defaultValue) {
1199 retval = $input.val();
1204 * Gets the query string from a second level item's fast filter form
1205 * The retrieval is done by trasversing the navigation tree backwards
1209 getSearchClause2: function ($this) {
1210 var $filterContainer = $this.closest('div.list_container');
1211 var $filterInput = $([]);
1212 if ($filterContainer
1213 .find('li.fast_filter:not(.db_fast_filter) input.searchClause')
1215 $filterInput = $filterContainer
1216 .find('li.fast_filter:not(.db_fast_filter) input.searchClause');
1218 var searchClause2 = '';
1219 if ($filterInput.length !== 0 &&
1220 $filterInput.first().val() != $filterInput[0].defaultValue
1222 searchClause2 = $filterInput.val();
1224 return searchClause2;
1227 * @var hash events A list of functions that are bound to DOM events
1228 * at the top of this file
1231 focus: function (event) {
1232 var $obj = $(this).closest('div.list_container');
1233 if (! $obj.data('fastFilter')) {
1236 new PMA_fastFilter.filter($obj, $(this).val())
1239 if ($(this).val() == this.defaultValue) {
1245 blur: function (event) {
1246 if ($(this).val() === '') {
1247 $(this).val(this.defaultValue);
1249 var $obj = $(this).closest('div.list_container');
1250 if ($(this).val() == this.defaultValue && $obj.data('fastFilter')) {
1251 $obj.data('fastFilter').restore();
1254 mouseover: function (event) {
1256 if ($(this).closest('li.fast_filter').is('.db_fast_filter')) {
1257 message = PMA_messages.strHoverDbFastFilter;
1259 var node_type = $(this).siblings("input[name='pos2_name']").val();
1260 var node_name = PMA_messages.strTables;
1261 if (node_type == 'views') {
1262 node_name = PMA_messages.strViews;
1263 } else if (node_type == 'procedures') {
1264 node_name = PMA_messages.strProcedures;
1265 } else if (node_type == 'functions') {
1266 node_name = PMA_messages.strFunctions;
1267 } else if (node_type == 'events') {
1268 node_name = PMA_messages.strEvents;
1270 message = PMA_sprintf(PMA_messages.strHoverFastFilter, node_name);
1272 PMA_tooltip($(this), 'input', message);
1274 keyup: function (event) {
1275 var $obj = $(this).closest('div.list_container');
1277 if ($(this).val() != this.defaultValue && $(this).val() !== '') {
1278 $obj.find('div.pageselector').hide();
1279 str = $(this).val();
1283 * FIXME at the server level a value match is done while on
1284 * the client side it is a regex match. These two should be aligned
1287 // regex used for filtering.
1290 regex = new RegExp(str, 'i');
1295 // this is the div that houses the items to be filtered by this filter.
1297 if ($(this).closest('li.fast_filter').is('.db_fast_filter')) {
1298 outerContainer = $('#pma_navigation_tree_content');
1300 outerContainer = $obj;
1303 // filters items that are directly under the div as well as grouped in
1304 // groups. Does not filter child items (i.e. a database search does
1305 // not filter tables)
1306 var item_filter = function($curr) {
1307 $curr.children('ul').children('li.navGroup').each(function() {
1308 $(this).children('div.list_container').each(function() {
1309 item_filter($(this)); // recursive
1312 $curr.children('ul').children('li').children('a').not('.container').each(function() {
1313 if (regex.test($(this).text())) {
1314 $(this).parent().show().removeClass('hidden');
1316 $(this).parent().hide().addClass('hidden');
1320 item_filter(outerContainer);
1322 // hides containers that does not have any visible children
1323 var container_filter = function ($curr) {
1324 $curr.children('ul').children('li.navGroup').each(function() {
1325 var $group = $(this);
1326 $group.children('div.list_container').each(function() {
1327 container_filter($(this)); // recursive
1329 $group.show().removeClass('hidden');
1330 if ($group.children('div.list_container').children('ul')
1331 .children('li').not('.hidden').length === 0) {
1332 $group.hide().addClass('hidden');
1336 container_filter(outerContainer);
1338 if ($(this).val() != this.defaultValue && $(this).val() !== '') {
1339 if (! $obj.data('fastFilter')) {
1342 new PMA_fastFilter.filter($obj, $(this).val())
1345 if (event.keyCode == 13) {
1346 $obj.data('fastFilter').update($(this).val());
1349 } else if ($obj.data('fastFilter')) {
1350 $obj.data('fastFilter').restore(true);
1353 clear: function (event) {
1354 event.stopPropagation();
1355 // Clear the input and apply the fast filter with empty input
1356 var filter = $(this).closest('div.list_container').data('fastFilter');
1360 var value = $(this).prev()[0].defaultValue;
1361 $(this).prev().val(value).trigger('keyup');
1366 * Handles a change in the search clause
1368 * @param string searchClause The query string for the filter
1372 PMA_fastFilter.filter.prototype.update = function (searchClause) {
1373 if (this.searchClause != searchClause) {
1374 this.searchClause = searchClause;
1379 * After a delay of 250mS, initiates a request to retrieve search results
1380 * Multiple calls to this function will always abort the previous request
1384 PMA_fastFilter.filter.prototype.request = function () {
1386 if (self.$this.find('li.fast_filter').find('img.throbber').length === 0) {
1387 self.$this.find('li.fast_filter').append(
1388 $('<div class="throbber"></div>').append(
1389 $('#pma_navigation_content')
1390 .find('img.throbber')
1392 .css({visibility: 'visible', display: 'block'})
1399 var url = $('#pma_navigation').find('a.navigation_url').attr('href');
1400 var params = self.$this.find('> ul > li > form.fast_filter').first().serialize();
1401 if (self.$this.find('> ul > li > form.fast_filter:first input[name=searchClause]').length === 0) {
1402 var $input = $('#pma_navigation_tree').find('li.fast_filter.db_fast_filter input.searchClause');
1403 if ($input.length && $input.val() != $input[0].defaultValue) {
1404 params += '&searchClause=' + encodeURIComponent($input.val());
1412 complete: function (jqXHR, status) {
1413 if (status != 'abort') {
1414 var data = $.parseJSON(jqXHR.responseText);
1415 self.$this.find('li.fast_filter').find('div.throbber').remove();
1416 if (data && data.results) {
1417 self.swap.apply(self, [data.message]);
1424 * Replaces the contents of the navigation branch with the search results
1426 * @param string list The search results
1430 PMA_fastFilter.filter.prototype.swap = function (list) {
1432 .html($(list).html())
1436 .find('li.fast_filter input.searchClause')
1437 .val(this.searchClause);
1438 this.$this.data('fastFilter', this);
1441 * Restores the navigation to the original state after the fast filter is cleared
1443 * @param bool focus Whether to also focus the input box of the fast filter
1447 PMA_fastFilter.filter.prototype.restore = function (focus) {
1448 if(this.$this.children('ul').first().hasClass('search_results')) {
1449 this.$this.html(this.$clone.html()).children().show();
1450 this.$this.data('fastFilter', this);
1452 this.$this.find('li.fast_filter input.searchClause').focus();
1455 this.searchClause = '';
1456 this.$this.find('div.pageselector').show();
1457 this.$this.find('div.throbber').remove();
1461 * Show full name when cursor hover and name not shown completely
1463 * @param object $containerELem Container element
1467 function PMA_showFullName($containerELem) {
1469 $containerELem.find('.hover_show_full').mouseenter(function() {
1471 var $this = $(this);
1472 var thisOffset = $this.offset();
1473 if($this.text() === '') {
1476 var $parent = $this.parent();
1477 if( ($parent.offset().left + $parent.outerWidth())
1478 < (thisOffset.left + $this.outerWidth()))
1480 var $fullNameLayer = $('#full_name_layer');
1481 if($fullNameLayer.length === 0)
1483 $('body').append('<div id="full_name_layer" class="hide"></div>');
1484 $('#full_name_layer').mouseleave(function() {
1486 $(this).addClass('hide')
1487 .removeClass('hovering');
1488 }).mouseenter(function() {
1490 $(this).addClass('hovering');
1492 $fullNameLayer = $('#full_name_layer');
1494 $fullNameLayer.removeClass('hide');
1495 $fullNameLayer.css({left: thisOffset.left, top: thisOffset.top});
1496 $fullNameLayer.html($this.clone());
1497 setTimeout(function() {
1498 if(! $fullNameLayer.hasClass('hovering'))
1500 $fullNameLayer.trigger('mouseleave');