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 if (window.location.href.indexOf('?') === -1) {
82 window.location.href += '?session_expired=1';
84 window.location.href += '&session_expired=1';
86 window.location.reload();
88 var $throbber = $expandElem.find('img.throbber');
90 var $icon = $expandElem.find('img.ic_b_plus');
92 PMA_ajaxShowMessage(data.error, false);
98 * Collapses a node in navigation tree.
100 * @param $expandElem expander
104 function collapseTreeNode($expandElem) {
105 var $children = $expandElem.closest('li').children('div.list_container');
106 var $icon = $expandElem.find('img');
107 if ($expandElem.hasClass('loaded')) {
108 if ($icon.is('.ic_b_minus')) {
109 $icon.removeClass('ic_b_minus').addClass('ic_b_plus');
110 $children.slideUp('fast');
114 $children.promise().done(navTreeStateUpdate);
118 * Traverse the navigation tree backwards to generate all the actual
119 * and virtual paths, as well as the positions in the pagination at
120 * various levels, if necessary.
124 function traverseNavigationForPaths() {
126 pos: $('#pma_navigation_tree').find('div.dbselector select').val()
128 if ($('#navi_db_select').length) {
132 $('#pma_navigation_tree').find('a.expander:visible').each(function () {
133 if ($(this).find('img').is('.ic_b_minus') &&
134 $(this).closest('li').find('div.list_container .ic_b_minus').length === 0
136 params['n' + count + '_aPath'] = $(this).find('span.aPath').text();
137 params['n' + count + '_vPath'] = $(this).find('span.vPath').text();
139 var pos2_name = $(this).find('span.pos2_name').text();
144 .find('span.pos2_name:last')
147 var pos2_value = $(this).find('span.pos2_value').text();
152 .find('span.pos2_value:last')
156 params['n' + count + '_pos2_name'] = pos2_name;
157 params['n' + count + '_pos2_value'] = pos2_value;
159 params['n' + count + '_pos3_name'] = $(this).find('span.pos3_name').text();
160 params['n' + count + '_pos3_value'] = $(this).find('span.pos3_value').text();
168 * Executed on page load
171 if (! $('#pma_navigation').length) {
172 // Don't bother running any code if the navigation is not even on the page
176 // Do not let the page reload on submitting the fast filter
177 $(document).on('submit', '.fast_filter', function (event) {
178 event.preventDefault();
181 // Fire up the resize handlers
185 * opens/closes (hides/shows) tree elements
186 * loads data via ajax
188 $(document).on('click', '#pma_navigation_tree a.expander', function (event) {
189 event.preventDefault();
190 event.stopImmediatePropagation();
191 var $icon = $(this).find('img');
192 if ($icon.is('.ic_b_plus')) {
193 expandTreeNode($(this));
195 collapseTreeNode($(this));
200 * Register event handler for click on the reload
201 * navigation icon at the top of the panel
203 $(document).on('click', '#pma_navigation_reload', function (event) {
204 event.preventDefault();
205 // reload icon object
206 var $icon = $(this).find('img');
207 // source of the hidden throbber icon
208 var icon_throbber_src = $('#pma_navigation').find('.throbber').attr('src');
209 // source of the reload icon
210 var icon_reload_src = $icon.attr('src');
211 // replace the source of the reload icon with the one for throbber
212 $icon.attr('src', icon_throbber_src);
213 PMA_reloadNavigation();
214 // after one second, put back the reload icon
215 setTimeout(function () {
216 $icon.attr('src', icon_reload_src);
220 $(document).on("change", '#navi_db_select', function (event) {
221 if (! $(this).val()) {
222 PMA_commonParams.set('db', '');
223 PMA_reloadNavigation();
225 $(this).closest('form').trigger('submit');
229 * Register event handler for click on the collapse all
230 * navigation icon at the top of the navigation tree
232 $(document).on('click', '#pma_navigation_collapse', function (event) {
233 event.preventDefault();
234 $('#pma_navigation_tree').find('a.expander').each(function() {
235 var $icon = $(this).find('img');
236 if ($icon.is('.ic_b_minus')) {
243 * Register event handler to toggle
244 * the 'link with main panel' icon on mouseenter.
246 $(document).on('mouseenter', '#pma_navigation_sync', function (event) {
247 event.preventDefault();
248 var synced = $('#pma_navigation_tree').hasClass('synced');
249 var $img = $('#pma_navigation_sync').children('img');
251 $img.removeClass('ic_s_link').addClass('ic_s_unlink');
253 $img.removeClass('ic_s_unlink').addClass('ic_s_link');
258 * Register event handler to toggle
259 * the 'link with main panel' icon on mouseout.
261 $(document).on('mouseout', '#pma_navigation_sync', function (event) {
262 event.preventDefault();
263 var synced = $('#pma_navigation_tree').hasClass('synced');
264 var $img = $('#pma_navigation_sync').children('img');
266 $img.removeClass('ic_s_unlink').addClass('ic_s_link');
268 $img.removeClass('ic_s_link').addClass('ic_s_unlink');
273 * Register event handler to toggle
274 * the linking with main panel behavior
276 $(document).on('click', '#pma_navigation_sync', function (event) {
277 event.preventDefault();
278 var synced = $('#pma_navigation_tree').hasClass('synced');
279 var $img = $('#pma_navigation_sync').children('img');
282 .removeClass('ic_s_unlink')
283 .addClass('ic_s_link')
284 .attr('alt', PMA_messages.linkWithMain)
285 .attr('title', PMA_messages.linkWithMain);
286 $('#pma_navigation_tree')
287 .removeClass('synced')
289 .removeClass('selected');
292 .removeClass('ic_s_link')
293 .addClass('ic_s_unlink')
294 .attr('alt', PMA_messages.unlinkWithMain)
295 .attr('title', PMA_messages.unlinkWithMain);
296 $('#pma_navigation_tree').addClass('synced');
297 PMA_showCurrentNavigation();
302 * Bind all "fast filter" events
304 $(document).on('click', '#pma_navigation_tree li.fast_filter span', PMA_fastFilter.events.clear);
305 $(document).on('focus', '#pma_navigation_tree li.fast_filter input.searchClause', PMA_fastFilter.events.focus);
306 $(document).on('blur', '#pma_navigation_tree li.fast_filter input.searchClause', PMA_fastFilter.events.blur);
307 $(document).on('keyup', '#pma_navigation_tree li.fast_filter input.searchClause', PMA_fastFilter.events.keyup);
308 $(document).on('mouseover', '#pma_navigation_tree li.fast_filter input.searchClause', PMA_fastFilter.events.mouseover);
311 * Ajax handler for pagination
313 $(document).on('click', '#pma_navigation_tree div.pageselector a.ajax', function (event) {
314 event.preventDefault();
315 PMA_navigationTreePagination($(this));
323 '#pma_navigation_tree.highlight li:not(.fast_filter)',
325 if ($('li:visible', this).length === 0) {
326 $(this).addClass('activePointer');
332 '#pma_navigation_tree.highlight li:not(.fast_filter)',
334 $(this).removeClass('activePointer');
338 /** Create a Routine, Trigger or Event */
339 $(document).on('click', 'li.new_procedure a.ajax, li.new_function a.ajax', function (event) {
340 event.preventDefault();
341 var dialog = new RTE.object('routine');
342 dialog.editorDialog(1, $(this));
344 $(document).on('click', 'li.new_trigger a.ajax', function (event) {
345 event.preventDefault();
346 var dialog = new RTE.object('trigger');
347 dialog.editorDialog(1, $(this));
349 $(document).on('click', 'li.new_event a.ajax', function (event) {
350 event.preventDefault();
351 var dialog = new RTE.object('event');
352 dialog.editorDialog(1, $(this));
355 /** Edit Routines, Triggers or Events */
356 $(document).on('click', 'li.procedure > a.ajax, li.function > a.ajax', function (event) {
357 event.preventDefault();
358 var dialog = new RTE.object('routine');
359 dialog.editorDialog(0, $(this));
361 $(document).on('click', 'li.trigger > a.ajax', function (event) {
362 event.preventDefault();
363 var dialog = new RTE.object('trigger');
364 dialog.editorDialog(0, $(this));
366 $(document).on('click', 'li.event > a.ajax', function (event) {
367 event.preventDefault();
368 var dialog = new RTE.object('event');
369 dialog.editorDialog(0, $(this));
372 /** Execute Routines */
373 $(document).on('click', 'li.procedure div a.ajax img,' +
374 ' li.function div a.ajax img', function (event) {
375 event.preventDefault();
376 var dialog = new RTE.object('routine');
377 dialog.executeDialog($(this).parent());
379 /** Export Triggers and Events */
380 $(document).on('click', 'li.trigger div:eq(1) a.ajax img,' +
381 ' li.event div:eq(1) a.ajax img', function (event) {
382 event.preventDefault();
383 var dialog = new RTE.object();
384 dialog.exportDialog($(this).parent());
388 $(document).on('click', '#pma_navigation_tree li.new_index a.ajax', function (event) {
389 event.preventDefault();
390 var url = $(this).attr('href').substr(
391 $(this).attr('href').indexOf('?') + 1
392 ) + '&ajax_request=true';
393 var title = PMA_messages.strAddIndex;
394 indexEditorDialog(url, title);
398 $(document).on('click', 'li.index a.ajax', function (event) {
399 event.preventDefault();
400 var url = $(this).attr('href').substr(
401 $(this).attr('href').indexOf('?') + 1
402 ) + '&ajax_request=true';
403 var title = PMA_messages.strEditIndex;
404 indexEditorDialog(url, title);
408 $(document).on('click', 'li.new_view a.ajax', function (event) {
409 event.preventDefault();
410 PMA_createViewDialog($(this));
413 /** Hide navigation tree item */
414 $(document).on('click', 'a.hideNavItem.ajax', function (event) {
415 event.preventDefault();
417 url: $(this).attr('href') + '&ajax_request=true',
418 success: function (data) {
419 if (typeof data !== 'undefined' && data.success === true) {
420 PMA_reloadNavigation();
422 PMA_ajaxShowMessage(data.error);
428 /** Display a dialog to choose hidden navigation items to show */
429 $(document).on('click', 'a.showUnhide.ajax', function (event) {
430 event.preventDefault();
431 var $msg = PMA_ajaxShowMessage();
432 $.get($(this).attr('href') + '&ajax_request=1', function (data) {
433 if (typeof data !== 'undefined' && data.success === true) {
434 PMA_ajaxRemoveMessage($msg);
435 var buttonOptions = {};
436 buttonOptions[PMA_messages.strClose] = function () {
437 $(this).dialog("close");
440 .attr('id', 'unhideNavItemDialog')
441 .append(data.message)
446 buttons: buttonOptions,
447 title: PMA_messages.strUnhideNavItem,
453 PMA_ajaxShowMessage(data.error);
458 /** Show a hidden navigation tree item */
459 $(document).on('click', 'a.unhideNavItem.ajax', function (event) {
460 event.preventDefault();
461 var $tr = $(this).parents('tr');
462 var $msg = PMA_ajaxShowMessage();
464 url: $(this).attr('href') + '&ajax_request=true',
465 success: function (data) {
466 PMA_ajaxRemoveMessage($msg);
467 if (typeof data !== 'undefined' && data.success === true) {
469 PMA_reloadNavigation();
471 PMA_ajaxShowMessage(data.error);
477 // Add/Remove favorite table using Ajax.
478 $(document).on("click", ".favorite_table_anchor", function (event) {
479 event.preventDefault();
481 var anchor_id = $self.attr("id");
482 if($self.data("favtargetn") !== null) {
483 if($('a[data-favtargets="' + $self.data("favtargetn") + '"]').length > 0)
485 $('a[data-favtargets="' + $self.data("favtargetn") + '"]').trigger('click');
491 url: $self.attr('href'),
495 favorite_tables: (isStorageSupported('localStorage') && typeof window.localStorage.favorite_tables !== 'undefined')
496 ? window.localStorage.favorite_tables
499 success: function (data) {
501 $('#pma_favorite_list').html(data.list);
502 $('#' + anchor_id).parent().html(data.anchor);
506 $('#' + anchor_id).attr("title")
508 // Update localStorage.
509 if (isStorageSupported('localStorage')) {
510 window.localStorage.favorite_tables = data.favorite_tables;
513 PMA_ajaxShowMessage(data.message);
520 AJAX.registerOnload('navigation.js', function () {
521 // Check if session storage is supported
522 if (isStorageSupported('sessionStorage')) {
523 var storage = window.sessionStorage;
524 // remove tree from storage if Navi_panel config form is submitted
525 $(document).on('submit', 'form.config-form', function(event) {
526 storage.removeItem('navTreePaths');
528 // Initialize if no previous state is defined
529 if ($('#pma_navigation_tree_content').length &&
530 typeof storage.navTreePaths === 'undefined'
532 navTreeStateUpdate();
533 } else if (PMA_commonParams.get('server') === storage.server &&
534 PMA_commonParams.get('token') === storage.token
536 // Reload the tree to the state before page refresh
537 PMA_reloadNavigation(null, JSON.parse(storage.navTreePaths));
543 * updates the tree state in sessionStorage
547 function navTreeStateUpdate() {
548 // update if session storage is supported
549 if (isStorageSupported('sessionStorage')) {
550 var storage = window.sessionStorage;
551 // try catch necessary here to detect whether
552 // content to be stored exceeds storage capacity
554 storage.setItem('navTreePaths', JSON.stringify(traverseNavigationForPaths()));
555 storage.setItem('server', PMA_commonParams.get('server'));
556 storage.setItem('token', PMA_commonParams.get('token'));
558 // storage capacity exceeded & old navigation tree
559 // state is no more valid, so remove it
560 storage.removeItem('navTreePaths');
561 storage.removeItem('server');
562 storage.removeItem('token');
568 * Expands a node in navigation tree.
570 * @param $expandElem expander
571 * @param callback callback function
575 function expandTreeNode($expandElem, callback) {
576 var $children = $expandElem.closest('li').children('div.list_container');
577 var $icon = $expandElem.find('img');
578 if ($expandElem.hasClass('loaded')) {
579 if ($icon.is('.ic_b_plus')) {
580 $icon.removeClass('ic_b_plus').addClass('ic_b_minus');
581 $children.slideDown('fast');
583 if (callback && typeof callback == 'function') {
586 $children.promise().done(navTreeStateUpdate);
588 var $throbber = $('#pma_navigation').find('.throbber')
591 .css({visibility: 'visible', display: 'block'})
594 $throbber.insertBefore($icon);
596 loadChildNodes(true, $expandElem, function (data) {
597 if (typeof data !== 'undefined' && data.success === true) {
598 var $destination = $expandElem.closest('li');
599 $icon.removeClass('ic_b_plus').addClass('ic_b_minus');
600 $children = $destination.children('div.list_container');
601 $children.slideDown('fast');
602 if ($destination.find('ul > li').length == 1) {
603 $destination.find('ul > li')
604 .find('a.expander.container')
607 if (callback && typeof callback == 'function') {
610 PMA_showFullName($destination);
612 PMA_ajaxShowMessage(data.error, false);
616 $children.promise().done(navTreeStateUpdate);
623 * Auto-scrolls the newly chosen database
625 * @param object $element The element to set to view
626 * @param boolean $forceToTop Whether to force scroll to top
629 function scrollToView($element, $forceToTop) {
630 var $container = $('#pma_navigation_tree_content');
631 var elemTop = $element.offset().top - $container.offset().top;
633 var scrollPadding = 20; // extra padding from top of bottom when scrolling to view
634 if (elemTop < 0 || $forceToTop) {
635 $container.stop().animate({
636 scrollTop: elemTop + $container.scrollTop() - scrollPadding
638 } else if (elemTop + textHeight > $container.height()) {
639 $container.stop().animate({
640 scrollTop: elemTop + textHeight - $container.height() + $container.scrollTop() + scrollPadding
646 * Expand the navigation and highlight the current database or table/view
650 function PMA_showCurrentNavigation() {
651 var db = PMA_commonParams.get('db');
652 var table = PMA_commonParams.get('table');
653 $('#pma_navigation_tree')
655 .removeClass('selected');
657 var $dbItem = findLoadedItem(
658 $('#pma_navigation_tree').find('> div'), db, 'database', !table
660 if ($('#navi_db_select').length &&
661 $('option:selected', $('#navi_db_select')).length
663 if (! PMA_selectCurrentDb()) {
666 // If loaded database in navigation is not same as current one
667 if ($('#pma_navigation_tree_content').find('span.loaded_db:first').text()
668 !== $('#navi_db_select').val()
670 loadChildNodes(false, $('option:selected', $('#navi_db_select')), function (data) {
671 handleTableOrDb(table, $('#pma_navigation_tree_content'));
672 var $children = $('#pma_navigation_tree_content').children('div.list_container');
673 $children.promise().done(navTreeStateUpdate);
676 handleTableOrDb(table, $('#pma_navigation_tree_content'));
678 } else if ($dbItem) {
679 var $expander = $dbItem.children('div:first').children('a.expander');
680 // if not loaded or loaded but collapsed
681 if (! $expander.hasClass('loaded') ||
682 $expander.find('img').is('.ic_b_plus')
684 expandTreeNode($expander, function () {
685 handleTableOrDb(table, $dbItem);
688 handleTableOrDb(table, $dbItem);
691 } else if ($('#navi_db_select').length && $('#navi_db_select').val()) {
692 $('#navi_db_select').val('').hide().trigger('change');
694 PMA_showFullName($('#pma_navigation_tree'));
696 function handleTableOrDb(table, $dbItem) {
698 loadAndHighlightTableOrView($dbItem, table);
700 var $container = $dbItem.children('div.list_container');
701 var $tableContainer = $container.children('ul').children('li.tableContainer');
702 if ($tableContainer.length > 0) {
703 var $expander = $tableContainer.children('div:first').children('a.expander');
704 $tableContainer.addClass('selected');
705 expandTreeNode($expander, function () {
706 scrollToView($dbItem, true);
709 scrollToView($dbItem, true);
714 function findLoadedItem($container, name, clazz, doSelect) {
716 $container.children('ul').children('li').each(function () {
718 // this is a navigation group, recurse
719 if ($li.is('.navGroup')) {
720 var $container = $li.children('div.list_container');
721 var $childRet = findLoadedItem(
722 $container, name, clazz, doSelect
728 } else { // this is a real navigation item
729 // name and class matches
730 if (((clazz && $li.is('.' + clazz)) || ! clazz) &&
731 $li.children('a').text() == name) {
733 $li.addClass('selected');
735 // taverse up and expand and parent navigation groups
736 $li.parents('.navGroup').each(function () {
737 var $cont = $(this).children('div.list_container');
738 if (! $cont.is(':visible')) {
740 .children('div:first')
741 .children('a.expander')
753 function loadAndHighlightTableOrView($dbItem, itemName) {
754 var $container = $dbItem.children('div.list_container');
756 var $whichItem = isItemInContainer($container, itemName, 'li.table, li.view');
757 //If item already there in some container
759 //get the relevant container while may also be a subcontainer
760 var $relatedContainer = $whichItem.closest('li.subContainer').length
761 ? $whichItem.closest('li.subContainer')
763 $whichItem = findLoadedItem(
764 $relatedContainer.children('div.list_container'),
768 showTableOrView($whichItem, $relatedContainer.children('div:first').children('a.expander'));
769 //else if item not there, try loading once
771 var $sub_containers = $dbItem.find('.subContainer');
772 //If there are subContainers i.e. tableContainer or viewContainer
773 if($sub_containers.length > 0) {
774 var $containers = [];
775 $sub_containers.each(function (index) {
776 $containers[index] = $(this);
777 $expander = $containers[index]
778 .children('div:first')
779 .children('a.expander');
780 if (! $expander.hasClass('loaded')) {
781 loadAndShowTableOrView($expander, $containers[index], itemName);
784 // else if no subContainers
787 .children('div:first')
788 .children('a.expander');
789 if (! $expander.hasClass('loaded')) {
790 loadAndShowTableOrView($expander, $dbItem, itemName);
796 function loadAndShowTableOrView($expander, $relatedContainer, itemName) {
797 loadChildNodes(true, $expander, function (data) {
798 var $whichItem = findLoadedItem(
799 $relatedContainer.children('div.list_container'),
803 showTableOrView($whichItem, $expander);
808 function showTableOrView($whichItem, $expander) {
809 expandTreeNode($expander, function (data) {
811 scrollToView($whichItem, false);
816 function isItemInContainer($container, name, clazz)
818 var $whichItem = null;
819 $items = $container.find(clazz);
821 $items.each(function () {
822 if ($(this).children('a').text() == name) {
823 $whichItem = $(this);
832 * Disable navigation panel settings
836 function PMA_disableNaviSettings() {
837 $('#pma_navigation_settings_icon').addClass('hide');
838 $('#pma_navigation_settings').remove();
842 * Ensure that navigation panel settings is properly setup.
847 function PMA_ensureNaviSettings(selflink) {
848 $('#pma_navigation_settings_icon').removeClass('hide');
850 if (!$('#pma_navigation_settings').length) {
852 getNaviSettings: true
854 var url = $('#pma_navigation').find('a.navigation_url').attr('href');
855 $.post(url, params, function (data) {
856 if (typeof data !== 'undefined' && data.success) {
857 $('#pma_navi_settings_container').html(data.message);
861 $('#pma_navigation_settings').find('form').attr('action', selflink);
863 PMA_ajaxShowMessage(data.error);
867 $('#pma_navigation_settings').find('form').attr('action', selflink);
872 * Reloads the whole navigation tree while preserving its state
874 * @param function the callback function
875 * @param Object stored navigation paths
879 function PMA_reloadNavigation(callback, paths) {
884 paths = paths || traverseNavigationForPaths();
885 $.extend(params, paths);
886 if ($('#navi_db_select').length) {
887 params.db = PMA_commonParams.get('db');
888 requestNaviReload(params);
891 requestNaviReload(params);
893 function requestNaviReload(params) {
894 var url = $('#pma_navigation').find('a.navigation_url').attr('href');
895 $.post(url, params, function (data) {
896 if (typeof data !== 'undefined' && data.success) {
897 $('#pma_navigation_tree').html(data.message).children('div').show();
898 if ($('#pma_navigation_tree').hasClass('synced')) {
899 PMA_selectCurrentDb();
900 PMA_showCurrentNavigation();
902 // Fire the callback, if any
903 if (typeof callback === 'function') {
906 navTreeStateUpdate();
908 PMA_ajaxShowMessage(data.error);
914 function PMA_selectCurrentDb() {
915 var $naviDbSelect = $('#navi_db_select');
917 if (!$naviDbSelect.length) {
921 if (PMA_commonParams.get('db')) { // db selected
922 $naviDbSelect.show();
925 $naviDbSelect.val(PMA_commonParams.get('db'));
926 return $naviDbSelect.val() === PMA_commonParams.get('db');
931 * Handles any requests to change the page in a branch of a tree
933 * This can be called from link click or select change event handlers
935 * @param object $this A jQuery object that points to the element that
936 * initiated the action of changing the page
940 function PMA_navigationTreePagination($this) {
941 var $msgbox = PMA_ajaxShowMessage();
942 var isDbSelector = $this.closest('div.pageselector').is('.dbselector');
944 if ($this[0].tagName == 'A') {
945 url = $this.attr('href');
946 params = 'ajax_request=true';
947 } else { // tagName == 'SELECT'
948 url = 'navigation.php';
949 params = $this.closest("form").serialize() + '&ajax_request=true';
951 var searchClause = PMA_fastFilter.getSearchClause();
953 params += '&searchClause=' + encodeURIComponent(searchClause);
956 params += '&full=true';
958 var searchClause2 = PMA_fastFilter.getSearchClause2($this);
960 params += '&searchClause2=' + encodeURIComponent(searchClause2);
963 $.post(url, params, function (data) {
964 if (typeof data !== 'undefined' && data.success) {
965 PMA_ajaxRemoveMessage($msgbox);
967 var val = PMA_fastFilter.getSearchClause();
968 $('#pma_navigation_tree')
973 $('#pma_navigation_tree')
974 .find('li.fast_filter input.searchClause')
978 var $parent = $this.closest('div.list_container').parent();
979 var val = PMA_fastFilter.getSearchClause2($this);
980 $this.closest('div.list_container').html(
981 $(data.message).children().show()
984 $parent.find('li.fast_filter input.searchClause').val(val);
986 $parent.find('span.pos2_value:first').text(
987 $parent.find('span.pos2_value:last').text()
989 $parent.find('span.pos3_value:first').text(
990 $parent.find('span.pos3_value:last').text()
994 PMA_ajaxShowMessage(data.error);
995 PMA_handleRedirectAndReload(data);
997 navTreeStateUpdate();
1002 * @var ResizeHandler Custom object that manages the resizing of the navigation
1004 * XXX: Must only be ever instanciated once
1005 * XXX: Inside event handlers the 'this' object is accessed as 'event.data.resize_handler'
1007 var ResizeHandler = function () {
1009 * @var int panel_width Used by the collapser to know where to go
1010 * back to when uncollapsing the panel
1012 this.panel_width = 0;
1014 * @var string left Used to provide support for RTL languages
1016 this.left = $('html').attr('dir') == 'ltr' ? 'left' : 'right';
1018 * Adjusts the width of the navigation panel to the specified value
1020 * @param int pos Navigation width in pixels
1024 this.setWidth = function (pos) {
1025 var $resizer = $('#pma_navigation_resizer');
1026 var resizer_width = $resizer.width();
1027 var $collapser = $('#pma_navigation_collapser');
1028 $('#pma_navigation').width(pos);
1029 $('body').css('margin-' + this.left, pos + 'px');
1030 $("#floating_menubar, #pma_console")
1031 .css('margin-' + this.left, (pos + resizer_width) + 'px');
1032 $resizer.css(this.left, pos + 'px');
1035 .css(this.left, pos + resizer_width)
1036 .html(this.getSymbol(pos))
1037 .prop('title', PMA_messages.strShowPanel);
1040 .css(this.left, pos)
1041 .html(this.getSymbol(pos))
1042 .prop('title', PMA_messages.strHidePanel);
1044 setTimeout(function () {
1045 $(window).trigger('resize');
1049 * Returns the horizontal position of the mouse,
1050 * relative to the outer side of the navigation panel
1052 * @param int pos Navigation width in pixels
1056 this.getPos = function (event) {
1057 var pos = event.pageX;
1058 var windowWidth = $(window).width();
1059 var windowScroll = $(window).scrollLeft();
1060 pos = pos - windowScroll;
1061 if (this.left != 'left') {
1062 pos = windowWidth - event.pageX;
1066 } else if (pos + 100 >= windowWidth) {
1067 pos = windowWidth - 100;
1069 this.panel_width = 0;
1074 * Returns the HTML code for the arrow symbol used in the collapser
1076 * @param int width The width of the panel
1080 this.getSymbol = function (width) {
1081 if (this.left == 'left') {
1096 * Event handler for initiating a resize of the panel
1098 * @param object e Event data (contains a reference to resizeHandler)
1102 this.mousedown = function (event) {
1103 event.preventDefault();
1105 .bind('mousemove', {'resize_handler': event.data.resize_handler},
1106 $.throttle(event.data.resize_handler.mousemove, 4))
1107 .bind('mouseup', {'resize_handler': event.data.resize_handler},
1108 event.data.resize_handler.mouseup);
1109 $('body').css('cursor', 'col-resize');
1112 * Event handler for terminating a resize of the panel
1114 * @param object e Event data (contains a reference to resizeHandler)
1118 this.mouseup = function (event) {
1119 $('body').css('cursor', '');
1120 $.cookie('pma_navi_width', event.data.resize_handler.getPos(event));
1121 $('#topmenu').menuResizer('resize');
1123 .unbind('mousemove')
1127 * Event handler for updating the panel during a resize operation
1129 * @param object e Event data (contains a reference to resizeHandler)
1133 this.mousemove = function (event) {
1134 event.preventDefault();
1135 var pos = event.data.resize_handler.getPos(event);
1136 event.data.resize_handler.setWidth(pos);
1137 if ($('.sticky_columns').length !== 0) {
1138 handleAllStickyColumns();
1142 * Event handler for collapsing the panel
1144 * @param object e Event data (contains a reference to resizeHandler)
1148 this.collapse = function (event) {
1149 event.preventDefault();
1150 var panel_width = event.data.resize_handler.panel_width;
1151 var width = $('#pma_navigation').width();
1152 if (width === 0 && panel_width === 0) {
1155 event.data.resize_handler.setWidth(panel_width);
1156 event.data.resize_handler.panel_width = width;
1159 * Event handler for resizing the navigation tree height on window resize
1163 this.treeResize = function (event) {
1164 var $nav = $("#pma_navigation"),
1165 $nav_tree = $("#pma_navigation_tree"),
1166 $nav_header = $("#pma_navigation_header"),
1167 $nav_tree_content = $("#pma_navigation_tree_content");
1168 $nav_tree.height($nav.height() - $nav_header.height());
1169 if ($nav_tree_content.length > 0) {
1170 $nav_tree_content.height($nav_tree.height() - $nav_tree_content.position().top);
1172 //TODO: in fast filter search response there is no #pma_navigation_tree_content, needs to be added in php
1174 'overflow-y': 'auto'
1177 // Set content bottom space beacuse of console
1178 $('body').css('margin-bottom', $('#pma_console').height() + 'px');
1180 /* Initialisation section begins here */
1181 if ($.cookie('pma_navi_width')) {
1182 // If we have a cookie, set the width of the panel to its value
1183 var pos = Math.abs(parseInt($.cookie('pma_navi_width'), 10) || 0);
1185 $('#topmenu').menuResizer('resize');
1187 // Register the events for the resizer and the collapser
1188 $(document).on('mousedown', '#pma_navigation_resizer', {'resize_handler': this}, this.mousedown);
1189 $(document).on('click', '#pma_navigation_collapser', {'resize_handler': this}, this.collapse);
1191 // Add the correct arrow symbol to the collapser
1192 $('#pma_navigation_collapser').html(this.getSymbol($('#pma_navigation').width()));
1193 // Fix navigation tree height
1194 $(window).on('resize', this.treeResize);
1195 // need to call this now and then, browser might decide
1196 // to show/hide horizontal scrollbars depending on page content width
1197 setInterval(this.treeResize, 2000);
1199 }; // End of ResizeHandler
1202 * @var object PMA_fastFilter Handles the functionality that allows filtering
1203 * of the items in a branch of the navigation tree
1205 var PMA_fastFilter = {
1207 * Construct for the asynchronous fast filter functionality
1209 * @param object $this A jQuery object pointing to the list container
1210 * which is the nearest parent of the fast filter
1211 * @param string searchClause The query string for the filter
1213 * @return new PMA_fastFilter.filter object
1215 filter: function ($this, searchClause) {
1217 * @var object $this A jQuery object pointing to the list container
1218 * which is the nearest parent of the fast filter
1222 * @var bool searchClause The query string for the filter
1224 this.searchClause = searchClause;
1226 * @var object $clone A clone of the original contents
1227 * of the navigation branch before
1228 * the fast filter was applied
1230 this.$clone = $this.clone();
1232 * @var object xhr A reference to the ajax request that is currently running
1236 * @var int timeout Used to delay the request for asynchronous search
1238 this.timeout = null;
1240 var $filterInput = $this.find('li.fast_filter input.searchClause');
1241 if ($filterInput.length !== 0 &&
1242 $filterInput.val() !== '' &&
1243 $filterInput.val() != $filterInput[0].defaultValue
1249 * Gets the query string from the database fast filter form
1253 getSearchClause: function () {
1255 var $input = $('#pma_navigation_tree')
1256 .find('li.fast_filter.db_fast_filter input.searchClause');
1257 if ($input.length && $input.val() != $input[0].defaultValue) {
1258 retval = $input.val();
1263 * Gets the query string from a second level item's fast filter form
1264 * The retrieval is done by trasversing the navigation tree backwards
1268 getSearchClause2: function ($this) {
1269 var $filterContainer = $this.closest('div.list_container');
1270 var $filterInput = $([]);
1271 if ($filterContainer
1272 .find('li.fast_filter:not(.db_fast_filter) input.searchClause')
1274 $filterInput = $filterContainer
1275 .find('li.fast_filter:not(.db_fast_filter) input.searchClause');
1277 var searchClause2 = '';
1278 if ($filterInput.length !== 0 &&
1279 $filterInput.first().val() != $filterInput[0].defaultValue
1281 searchClause2 = $filterInput.val();
1283 return searchClause2;
1286 * @var hash events A list of functions that are bound to DOM events
1287 * at the top of this file
1290 focus: function (event) {
1291 var $obj = $(this).closest('div.list_container');
1292 if (! $obj.data('fastFilter')) {
1295 new PMA_fastFilter.filter($obj, $(this).val())
1298 if ($(this).val() == this.defaultValue) {
1304 blur: function (event) {
1305 if ($(this).val() === '') {
1306 $(this).val(this.defaultValue);
1308 var $obj = $(this).closest('div.list_container');
1309 if ($(this).val() == this.defaultValue && $obj.data('fastFilter')) {
1310 $obj.data('fastFilter').restore();
1313 mouseover: function (event) {
1315 if ($(this).closest('li.fast_filter').is('.db_fast_filter')) {
1316 message = PMA_messages.strHoverDbFastFilter;
1318 var node_type = $(this).siblings("input[name='pos2_name']").val();
1319 var node_name = PMA_messages.strTables;
1320 if (node_type == 'views') {
1321 node_name = PMA_messages.strViews;
1322 } else if (node_type == 'procedures') {
1323 node_name = PMA_messages.strProcedures;
1324 } else if (node_type == 'functions') {
1325 node_name = PMA_messages.strFunctions;
1326 } else if (node_type == 'events') {
1327 node_name = PMA_messages.strEvents;
1329 message = PMA_sprintf(PMA_messages.strHoverFastFilter, node_name);
1331 PMA_tooltip($(this), 'input', message);
1333 keyup: function (event) {
1334 var $obj = $(this).closest('div.list_container');
1336 if ($(this).val() != this.defaultValue && $(this).val() !== '') {
1337 $obj.find('div.pageselector').hide();
1338 str = $(this).val();
1342 * FIXME at the server level a value match is done while on
1343 * the client side it is a regex match. These two should be aligned
1346 // regex used for filtering.
1349 regex = new RegExp(str, 'i');
1354 // this is the div that houses the items to be filtered by this filter.
1356 if ($(this).closest('li.fast_filter').is('.db_fast_filter')) {
1357 outerContainer = $('#pma_navigation_tree_content');
1359 outerContainer = $obj;
1362 // filters items that are directly under the div as well as grouped in
1363 // groups. Does not filter child items (i.e. a database search does
1364 // not filter tables)
1365 var item_filter = function($curr) {
1366 $curr.children('ul').children('li.navGroup').each(function() {
1367 $(this).children('div.list_container').each(function() {
1368 item_filter($(this)); // recursive
1371 $curr.children('ul').children('li').children('a').not('.container').each(function() {
1372 if (regex.test($(this).text())) {
1373 $(this).parent().show().removeClass('hidden');
1375 $(this).parent().hide().addClass('hidden');
1379 item_filter(outerContainer);
1381 // hides containers that does not have any visible children
1382 var container_filter = function ($curr) {
1383 $curr.children('ul').children('li.navGroup').each(function() {
1384 var $group = $(this);
1385 $group.children('div.list_container').each(function() {
1386 container_filter($(this)); // recursive
1388 $group.show().removeClass('hidden');
1389 if ($group.children('div.list_container').children('ul')
1390 .children('li').not('.hidden').length === 0) {
1391 $group.hide().addClass('hidden');
1395 container_filter(outerContainer);
1397 if ($(this).val() != this.defaultValue && $(this).val() !== '') {
1398 if (! $obj.data('fastFilter')) {
1401 new PMA_fastFilter.filter($obj, $(this).val())
1404 if (event.keyCode == 13) {
1405 $obj.data('fastFilter').update($(this).val());
1408 } else if ($obj.data('fastFilter')) {
1409 $obj.data('fastFilter').restore(true);
1412 clear: function (event) {
1413 event.stopPropagation();
1414 // Clear the input and apply the fast filter with empty input
1415 var filter = $(this).closest('div.list_container').data('fastFilter');
1419 var value = $(this).prev()[0].defaultValue;
1420 $(this).prev().val(value).trigger('keyup');
1425 * Handles a change in the search clause
1427 * @param string searchClause The query string for the filter
1431 PMA_fastFilter.filter.prototype.update = function (searchClause) {
1432 if (this.searchClause != searchClause) {
1433 this.searchClause = searchClause;
1438 * After a delay of 250mS, initiates a request to retrieve search results
1439 * Multiple calls to this function will always abort the previous request
1443 PMA_fastFilter.filter.prototype.request = function () {
1445 if (self.$this.find('li.fast_filter').find('img.throbber').length === 0) {
1446 self.$this.find('li.fast_filter').append(
1447 $('<div class="throbber"></div>').append(
1448 $('#pma_navigation_content')
1449 .find('img.throbber')
1451 .css({visibility: 'visible', display: 'block'})
1458 var url = $('#pma_navigation').find('a.navigation_url').attr('href');
1459 var params = self.$this.find('> ul > li > form.fast_filter').first().serialize();
1460 if (self.$this.find('> ul > li > form.fast_filter:first input[name=searchClause]').length === 0) {
1461 var $input = $('#pma_navigation_tree').find('li.fast_filter.db_fast_filter input.searchClause');
1462 if ($input.length && $input.val() != $input[0].defaultValue) {
1463 params += '&searchClause=' + encodeURIComponent($input.val());
1471 complete: function (jqXHR, status) {
1472 if (status != 'abort') {
1473 var data = $.parseJSON(jqXHR.responseText);
1474 self.$this.find('li.fast_filter').find('div.throbber').remove();
1475 if (data && data.results) {
1476 self.swap.apply(self, [data.message]);
1483 * Replaces the contents of the navigation branch with the search results
1485 * @param string list The search results
1489 PMA_fastFilter.filter.prototype.swap = function (list) {
1491 .html($(list).html())
1495 .find('li.fast_filter input.searchClause')
1496 .val(this.searchClause);
1497 this.$this.data('fastFilter', this);
1500 * Restores the navigation to the original state after the fast filter is cleared
1502 * @param bool focus Whether to also focus the input box of the fast filter
1506 PMA_fastFilter.filter.prototype.restore = function (focus) {
1507 if(this.$this.children('ul').first().hasClass('search_results')) {
1508 this.$this.html(this.$clone.html()).children().show();
1509 this.$this.data('fastFilter', this);
1511 this.$this.find('li.fast_filter input.searchClause').focus();
1514 this.searchClause = '';
1515 this.$this.find('div.pageselector').show();
1516 this.$this.find('div.throbber').remove();
1520 * Show full name when cursor hover and name not shown completely
1522 * @param object $containerELem Container element
1526 function PMA_showFullName($containerELem) {
1528 $containerELem.find('.hover_show_full').mouseenter(function() {
1530 var $this = $(this);
1531 var thisOffset = $this.offset();
1532 if($this.text() === '') {
1535 var $parent = $this.parent();
1536 if( ($parent.offset().left + $parent.outerWidth())
1537 < (thisOffset.left + $this.outerWidth()))
1539 var $fullNameLayer = $('#full_name_layer');
1540 if($fullNameLayer.length === 0)
1542 $('body').append('<div id="full_name_layer" class="hide"></div>');
1543 $('#full_name_layer').mouseleave(function() {
1545 $(this).addClass('hide')
1546 .removeClass('hovering');
1547 }).mouseenter(function() {
1549 $(this).addClass('hovering');
1551 $fullNameLayer = $('#full_name_layer');
1553 $fullNameLayer.removeClass('hide');
1554 $fullNameLayer.css({left: thisOffset.left, top: thisOffset.top});
1555 $fullNameLayer.html($this.clone());
1556 setTimeout(function() {
1557 if(! $fullNameLayer.hasClass('hovering'))
1559 $fullNameLayer.trigger('mouseleave');