1/* vim: set expandtab sw=4 ts=4 sts=4: */ 2/** 3 * function used in or for navigation panel 4 * 5 * @package phpMyAdmin-Navigation 6 */ 7 8/* global isStorageSupported, setupConfigTabs, setupRestoreField, setupValidation */ // js/config.js 9/* global RTE */ // js/rte.js 10 11var Navigation = {}; 12 13/** 14 * updates the tree state in sessionStorage 15 * 16 * @returns void 17 */ 18Navigation.treeStateUpdate = function () { 19 // update if session storage is supported 20 if (isStorageSupported('sessionStorage')) { 21 var storage = window.sessionStorage; 22 // try catch necessary here to detect whether 23 // content to be stored exceeds storage capacity 24 try { 25 storage.setItem('navTreePaths', JSON.stringify(Navigation.traverseForPaths())); 26 storage.setItem('server', CommonParams.get('server')); 27 storage.setItem('token', CommonParams.get('token')); 28 } catch (error) { 29 // storage capacity exceeded & old navigation tree 30 // state is no more valid, so remove it 31 storage.removeItem('navTreePaths'); 32 storage.removeItem('server'); 33 storage.removeItem('token'); 34 } 35 } 36}; 37 38/** 39 * updates the filter state in sessionStorage 40 * 41 * @returns void 42 */ 43Navigation.filterStateUpdate = function (filterName, filterValue) { 44 if (isStorageSupported('sessionStorage')) { 45 var storage = window.sessionStorage; 46 try { 47 var currentFilter = $.extend({}, JSON.parse(storage.getItem('navTreeSearchFilters'))); 48 var filter = {}; 49 filter[filterName] = filterValue; 50 currentFilter = $.extend(currentFilter, filter); 51 storage.setItem('navTreeSearchFilters', JSON.stringify(currentFilter)); 52 } catch (error) { 53 storage.removeItem('navTreeSearchFilters'); 54 } 55 } 56}; 57 58/** 59 * restores the filter state on navigation reload 60 * 61 * @returns void 62 */ 63Navigation.filterStateRestore = function () { 64 if (isStorageSupported('sessionStorage') 65 && typeof window.sessionStorage.navTreeSearchFilters !== 'undefined' 66 ) { 67 var searchClauses = JSON.parse(window.sessionStorage.navTreeSearchFilters); 68 if (Object.keys(searchClauses).length < 1) { 69 return; 70 } 71 // restore database filter if present and not empty 72 if (searchClauses.hasOwnProperty('dbFilter') 73 && searchClauses.dbFilter.length 74 ) { 75 var $obj = $('#pma_navigation_tree'); 76 if (! $obj.data('fastFilter')) { 77 $obj.data( 78 'fastFilter', 79 new Navigation.FastFilter.Filter($obj, '') 80 ); 81 } 82 $obj.find('li.fast_filter.db_fast_filter input.searchClause') 83 .val(searchClauses.dbFilter) 84 .trigger('keyup'); 85 } 86 // find all table filters present in the tree 87 var $tableFilters = $('#pma_navigation_tree li.database') 88 .children('div.list_container') 89 .find('li.fast_filter input.searchClause'); 90 // restore table filters 91 $tableFilters.each(function () { 92 $obj = $(this).closest('div.list_container'); 93 // aPath associated with this filter 94 var filterName = $(this).siblings('input[name=aPath]').val(); 95 // if this table's filter has a state stored in storage 96 if (searchClauses.hasOwnProperty(filterName) 97 && searchClauses[filterName].length 98 ) { 99 // clear state if item is not visible, 100 // happens when table filter becomes invisible 101 // as db filter has already been applied 102 if (! $obj.is(':visible')) { 103 Navigation.filterStateUpdate(filterName, ''); 104 return true; 105 } 106 if (! $obj.data('fastFilter')) { 107 $obj.data( 108 'fastFilter', 109 new Navigation.FastFilter.Filter($obj, '') 110 ); 111 } 112 $(this).val(searchClauses[filterName]) 113 .trigger('keyup'); 114 } 115 }); 116 } 117}; 118 119/** 120 * Loads child items of a node and executes a given callback 121 * 122 * @param isNode 123 * @param $expandElem expander 124 * @param callback callback function 125 * 126 * @returns void 127 */ 128Navigation.loadChildNodes = function (isNode, $expandElem, callback) { 129 var $destination = null; 130 var params = null; 131 132 if (isNode) { 133 if (!$expandElem.hasClass('expander')) { 134 return; 135 } 136 $destination = $expandElem.closest('li'); 137 params = { 138 'aPath': $expandElem.find('span.aPath').text(), 139 'vPath': $expandElem.find('span.vPath').text(), 140 'pos': $expandElem.find('span.pos').text(), 141 'pos2_name': $expandElem.find('span.pos2_name').text(), 142 'pos2_value': $expandElem.find('span.pos2_value').text(), 143 'searchClause': '', 144 'searchClause2': '' 145 }; 146 if ($expandElem.closest('ul').hasClass('search_results')) { 147 params.searchClause = Navigation.FastFilter.getSearchClause(); 148 params.searchClause2 = Navigation.FastFilter.getSearchClause2($expandElem); 149 } 150 } else { 151 $destination = $('#pma_navigation_tree_content'); 152 params = { 153 'aPath': $expandElem.attr('aPath'),
154 'vPath': $expandElem.attr('vPath'), 155 'pos': $expandElem.attr('pos'), 156 'pos2_name': '', 157 'pos2_value': '', 158 'searchClause': '', 159 'searchClause2': '' 160 }; 161 } 162 163 var url = $('#pma_navigation').find('a.navigation_url').attr('href'); 164 $.get(url, params, function (data) { 165 if (typeof data !== 'undefined' && data.success === true) { 166 $destination.find('div.list_container').remove(); // FIXME: Hack, there shouldn't be a list container there 167 if (isNode) { 168 $destination.append(data.message); 169 $expandElem.addClass('loaded'); 170 } else { 171 $destination.html(data.message); 172 $destination.children() 173 .first() 174 .css({ 175 border: '0px', 176 margin: '0em', 177 padding : '0em' 178 }) 179 .slideDown('slow'); 180 } 181 if (data.errors) { 182 var $errors = $(data.errors); 183 if ($errors.children().length > 0) { 184 $('#pma_errors').replaceWith(data.errors); 185 } 186 } 187 if (callback && typeof callback === 'function') { 188 callback(data); 189 } 190 } else if (data.redirect_flag === '1') { 191 if (window.location.href.indexOf('?') === -1) { 192 window.location.href += '?session_expired=1'; 193 } else { 194 window.location.href += CommonParams.get('arg_separator') + 'session_expired=1'; 195 } 196 window.location.reload(); 197 } else { 198 var $throbber = $expandElem.find('img.throbber'); 199 $throbber.hide(); 200 var $icon = $expandElem.find('img.ic_b_plus'); 201 $icon.show(); 202 Functions.ajaxShowMessage(data.error, false); 203 } 204 }); 205}; 206 207/** 208 * Collapses a node in navigation tree. 209 * 210 * @param $expandElem expander 211 * 212 * @returns void 213 */ 214Navigation.collapseTreeNode = function ($expandElem) { 215 var $children = $expandElem.closest('li').children('div.list_container'); 216 var $icon = $expandElem.find('img'); 217 if ($expandElem.hasClass('loaded')) { 218 if ($icon.is('.ic_b_minus')) { 219 $icon.removeClass('ic_b_minus').addClass('ic_b_plus'); 220 $children.slideUp('fast'); 221 } 222 } 223 $expandElem.trigger('blur'); 224 $children.promise().done(Navigation.treeStateUpdate); 225}; 226 227/** 228 * Traverse the navigation tree backwards to generate all the actual 229 * and virtual paths, as well as the positions in the pagination at 230 * various levels, if necessary. 231 * 232 * @return Object 233 */ 234Navigation.traverseForPaths = function () { 235 var params = { 236 pos: $('#pma_navigation_tree').find('div.dbselector select').val() 237 }; 238 if ($('#navi_db_select').length) { 239 return params; 240 } 241 var count = 0; 242 $('#pma_navigation_tree').find('a.expander:visible').each(function () { 243 if ($(this).find('img').is('.ic_b_minus') && 244 $(this).closest('li').find('div.list_container .ic_b_minus').length === 0 245 ) { 246 params['n' + count + '_aPath'] = $(this).find('span.aPath').text(); 247 params['n' + count + '_vPath'] = $(this).find('span.vPath').text(); 248 249 var pos2Name = $(this).find('span.pos2_name').text(); 250 if (! pos2Name) { 251 pos2Name = $(this) 252 .parent() 253 .parent() 254 .find('span.pos2_name:last') 255 .text(); 256 } 257 var pos2Value = $(this).find('span.pos2_value').text(); 258 if (! pos2Value) { 259 pos2Value = $(this) 260 .parent() 261 .parent() 262 .find('span.pos2_value:last') 263 .text(); 264 } 265 266 params['n' + count + '_pos2_name'] = pos2Name; 267 params['n' + count + '_pos2_value'] = pos2Value; 268 269 params['n' + count + '_pos3_name'] = $(this).find('span.pos3_name').text(); 270 params['n' + count + '_pos3_value'] = $(this).find('span.pos3_value').text(); 271 count++; 272 } 273 }); 274 return params; 275}; 276 277/** 278 * Executed on page load 279 */ 280$(function () { 281 if (! $('#pma_navigation').length) { 282 // Don't bother running any code if the navigation is not even on the page 283 return; 284 } 285
286 // Do not let the page reload on submitting the fast filter 287 $(document).on('submit', '.fast_filter', function (event) { 288 event.preventDefault(); 289 }); 290 291 // Fire up the resize handlers 292 new Navigation.ResizeHandler(); 293 294 /** 295 * opens/closes (hides/shows) tree elements 296 * loads data via ajax 297 */ 298 $(document).on('click', '#pma_navigation_tree a.expander', function (event) { 299 event.preventDefault(); 300 event.stopImmediatePropagation(); 301 var $icon = $(this).find('img'); 302 if ($icon.is('.ic_b_plus')) { 303 Navigation.expandTreeNode($(this)); 304 } else { 305 Navigation.collapseTreeNode($(this)); 306 } 307 }); 308 309 /** 310 * Register event handler for click on the reload 311 * navigation icon at the top of the panel 312 */ 313 $(document).on('click', '#pma_navigation_reload', function (event) { 314 event.preventDefault(); 315 316 // Find the loading symbol and show it 317 var $iconThrobberSrc = $('#pma_navigation').find('.throbber'); 318 $iconThrobberSrc.show(); 319 // TODO Why is a loading symbol both hidden, and invisible? 320 $iconThrobberSrc.css('visibility', ''); 321 322 // Callback to be used to hide the loading symbol when done reloading 323 function hideNav () { 324 $iconThrobberSrc.hide(); 325 } 326 327 // Reload the navigation 328 Navigation.reload(hideNav); 329 }); 330 331 $(document).on('change', '#navi_db_select', function () { 332 if (! $(this).val()) { 333 CommonParams.set('db', ''); 334 Navigation.reload(); 335 } 336 $(this).closest('form').trigger('submit'); 337 }); 338 339 /** 340 * Register event handler for click on the collapse all 341 * navigation icon at the top of the navigation tree 342 */ 343 $(document).on('click', '#pma_navigation_collapse', function (event) { 344 event.preventDefault(); 345 $('#pma_navigation_tree').find('a.expander').each(function () { 346 var $icon = $(this).find('img'); 347 if ($icon.is('.ic_b_minus')) { 348 $(this).trigger('click'); 349 } 350 }); 351 }); 352 353 /** 354 * Register event handler to toggle 355 * the 'link with main panel' icon on mouseenter. 356 */ 357 $(document).on('mouseenter', '#pma_navigation_sync', function (event) { 358 event.preventDefault(); 359 var synced = $('#pma_navigation_tree').hasClass('synced'); 360 var $img = $('#pma_navigation_sync').children('img'); 361 if (synced) { 362 $img.removeClass('ic_s_link').addClass('ic_s_unlink'); 363 } else { 364 $img.removeClass('ic_s_unlink').addClass('ic_s_link'); 365 } 366 }); 367 368 /** 369 * Register event handler to toggle 370 * the 'link with main panel' icon on mouseout. 371 */ 372 $(document).on('mouseout', '#pma_navigation_sync', function (event) { 373 event.preventDefault(); 374 var synced = $('#pma_navigation_tree').hasClass('synced'); 375 var $img = $('#pma_navigation_sync').children('img'); 376 if (synced) { 377 $img.removeClass('ic_s_unlink').addClass('ic_s_link'); 378 } else { 379 $img.removeClass('ic_s_link').addClass('ic_s_unlink'); 380 } 381 }); 382 383 /** 384 * Register event handler to toggle 385 * the linking with main panel behavior 386 */ 387 $(document).on('click', '#pma_navigation_sync', function (event) { 388 event.preventDefault(); 389 var synced = $('#pma_navigation_tree').hasClass('synced'); 390 var $img = $('#pma_navigation_sync').children('img'); 391 if (synced) { 392 $img 393 .removeClass('ic_s_unlink') 394 .addClass('ic_s_link') 395 .attr('alt', Messages.linkWithMain) 396 .attr('title', Messages.linkWithMain); 397 $('#pma_navigation_tree') 398 .removeClass('synced') 399 .find('li.selected') 400 .removeClass('selected'); 401 } else { 402 $img 403 .removeClass('ic_s_link') 404 .addClass('ic_s_unlink') 405 .attr('alt', Messages.unlinkWithMain) 406 .attr('title', Messages.unlinkWithMain); 407 $('#pma_navigation_tree').addClass('synced'); 408 Navigation.showCurrent(); 409 } 410 }); 411 412 /** 413 * Bind all "fast filter" events 414 */ 415 $(document).on('click', '#pma_navigation_tree li.fast_filter span', Navigation.FastFilter.events.clear); 416 $(document).on('focus', '#pma_navigation_tree li.fast_filter input.searchClause', Navigation.FastFilter.events.focus); 417 $(document).on('blur', '#pma_navigation_tree li.fast_filter input.searchClause', Navigation.FastFilter.events.blur); 418 $(document).on('keyup', '#pma_navigation_tree li.fast_filter input.searchClause', Navigation.FastFilter.events.keyup); 419 420 /** 421 * Ajax handler for pagination 422 */ 423 $(document).on('click', '#pma_navigation_tree div.pageselector a.ajax', function (event) { 424 event.preventDefault(); 425 Navigation.treePagination($(this)); 426 }); 427 428 /** 429 * Node highlighting 430 */ 431 $(document).on( 432 'mouseover', 433 '#pma_navigation_tree.highlight li:not(.fast_filter)', 434 function () { 435 if ($('li:visible', this).length === 0) { 436 $(this).addClass('activePointer'); 437 } 438 } 439 ); 440 $(document).on( 441 'mouseout', 442 '#pma_navigation_tree.highlight li:not(.fast_filter)', 443 function () { 444 $(this).removeClass('activePointer'); 445 } 446 ); 447 448 /** Create a Routine, Trigger or Event */ 449 $(document).on('click', 'li.new_procedure a.ajax, li.new_function a.ajax', function (event) { 450 event.preventDefault(); 451 var dialog = new RTE.Object('routine'); 452 dialog.editorDialog(1, $(this)); 453 }); 454 $(document).on('click', 'li.new_trigger a.ajax', function (event) { 455 event.preventDefault(); 456 var dialog = new RTE.Object('trigger'); 457 dialog.editorDialog(1, $(this)); 458 }); 459 $(document).on('click', 'li.new_event a.ajax', function (event) { 460 event.preventDefault(); 461 var dialog = new RTE.Object('event'); 462 dialog.editorDialog(1, $(this)); 463 }); 464 465 /** Edit Routines, Triggers or Events */ 466 $(document).on('click', 'li.procedure > a.ajax, li.function > a.ajax', function (event) { 467 event.preventDefault(); 468 var dialog = new RTE.Object('routine'); 469 dialog.editorDialog(0, $(this)); 470 }); 471 $(document).on('click', 'li.trigger > a.ajax', function (event) { 472 event.preventDefault(); 473 var dialog = new RTE.Object('trigger'); 474 dialog.editorDialog(0, $(this)); 475 }); 476 $(document).on('click', 'li.event > a.ajax', function (event) { 477 event.preventDefault(); 478 var dialog = new RTE.Object('event'); 479 dialog.editorDialog(0, $(this)); 480 }); 481 482 /** Execute Routines */ 483 $(document).on('click', 'li.procedure div a.ajax img,' + 484 ' li.function div a.ajax img', function (event) { 485 event.preventDefault(); 486 var dialog = new RTE.Object('routine'); 487 dialog.executeDialog($(this).parent()); 488 }); 489 /** Export Triggers and Events */ 490 $(document).on('click', 'li.trigger div:eq(1) a.ajax img,' + 491 ' li.event div:eq(1) a.ajax img', function (event) { 492 event.preventDefault(); 493 var dialog = new RTE.Object(); 494 dialog.exportDialog($(this).parent()); 495 }); 496 497 /** New index */ 498 $(document).on('click', '#pma_navigation_tree li.new_index a.ajax', function (event) { 499 event.preventDefault(); 500 var url = $(this).attr('href').substr( 501 $(this).attr('href').indexOf('?') + 1 502 ) + CommonParams.get('arg_separator') + 'ajax_request=true'; 503 var title = Messages.strAddIndex; 504 Functions.indexEditorDialog(url, title); 505 }); 506 507 /** Edit index */ 508 $(document).on('click', 'li.index a.ajax', function (event) { 509 event.preventDefault(); 510 var url = $(this).attr('href').substr( 511 $(this).attr('href').indexOf('?') + 1 512 ) + CommonParams.get('arg_separator') + 'ajax_request=true'; 513 var title = Messages.strEditIndex; 514 Functions.indexEditorDialog(url, title); 515 }); 516 517 /** New view */ 518 $(document).on('click', 'li.new_view a.ajax', function (event) { 519 event.preventDefault(); 520 Functions.createViewDialog($(this)); 521 }); 522 523 /** Hide navigation tree item */ 524 $(document).on('click', 'a.hideNavItem.ajax', function (event) { 525 event.preventDefault(); 526 var argSep = CommonParams.get('arg_separator'); 527 var params = $(this).getPostData(); 528 params += argSep + 'ajax_request=true' + argSep + 'server=' + CommonParams.get('server'); 529 $.ajax({ 530 type: 'POST', 531 data: params, 532 url: $(this).attr('href'), 533 success: function (data) { 534 if (typeof data !== 'undefined' && data.success === true) { 535 Navigation.reload(); 536 } else { 537 Functions.ajaxShowMessage(data.error); 538 } 539 } 540 }); 541 }); 542
543 /** Display a dialog to choose hidden navigation items to show */ 544 $(document).on('click', 'a.showUnhide.ajax', function (event) { 545 event.preventDefault(); 546 var $msg = Functions.ajaxShowMessage(); 547 var argSep = CommonParams.get('arg_separator'); 548 var params = $(this).getPostData(); 549 params += argSep + 'ajax_request=true'; 550 $.post($(this).attr('href'), params, function (data) { 551 if (typeof data !== 'undefined' && data.success === true) { 552 Functions.ajaxRemoveMessage($msg); 553 var buttonOptions = {}; 554 buttonOptions[Messages.strClose] = function () { 555 $(this).dialog('close'); 556 }; 557 $('<div></div>') 558 .attr('id', 'unhideNavItemDialog') 559 .append(data.message) 560 .dialog({ 561 width: 400, 562 minWidth: 200, 563 modal: true, 564 buttons: buttonOptions, 565 title: Messages.strUnhideNavItem, 566 close: function () { 567 $(this).remove(); 568 } 569 }); 570 } else { 571 Functions.ajaxShowMessage(data.error); 572 } 573 }); 574 }); 575 576 /** Show a hidden navigation tree item */ 577 $(document).on('click', 'a.unhideNavItem.ajax', function (event) { 578 event.preventDefault(); 579 var $tr = $(this).parents('tr'); 580 var $hiddenTableCount = $tr.parents('tbody').children().length; 581 var $hideDialogBox = $tr.closest('div.ui-dialog'); 582 var $msg = Functions.ajaxShowMessage(); 583 var argSep = CommonParams.get('arg_separator'); 584 var params = $(this).getPostData(); 585 params += argSep + 'ajax_request=true' + argSep + 'server=' + CommonParams.get('server'); 586 $.ajax({ 587 type: 'POST', 588 data: params, 589 url: $(this).attr('href'), 590 success: function (data) { 591 Functions.ajaxRemoveMessage($msg); 592 if (typeof data !== 'undefined' && data.success === true) { 593 $tr.remove(); 594 if ($hiddenTableCount === 1) { 595 $hideDialogBox.remove(); 596 } 597 Navigation.reload(); 598 } else { 599 Functions.ajaxShowMessage(data.error); 600 } 601 } 602 }); 603 }); 604 605 // Add/Remove favorite table using Ajax. 606 $(document).on('click', '.favorite_table_anchor', function (event) { 607 event.preventDefault(); 608 var $self = $(this); 609 var anchorId = $self.attr('id'); 610 if ($self.data('favtargetn') !== null) { 611 if ($('a[data-favtargets="' + $self.data('favtargetn') + '"]').length > 0) { 612 $('a[data-favtargets="' + $self.data('favtargetn') + '"]').trigger('click'); 613 return; 614 } 615 } 616 617 $.ajax({ 618 url: $self.attr('href'), 619 cache: false, 620 type: 'POST', 621 data: { 622 'favoriteTables': (isStorageSupported('localStorage') && typeof window.localStorage.favoriteTables !== 'undefined') 623 ? window.localStorage.favoriteTables 624 : '', 625 'server': CommonParams.get('server'), 626 }, 627 success: function (data) { 628 if (data.changes) { 629 $('#pma_favorite_list').html(data.list); 630 $('#' + anchorId).parent().html(data.anchor); 631 Functions.tooltip( 632 $('#' + anchorId), 633 'a', 634 $('#' + anchorId).attr('title') 635 ); 636 // Update localStorage. 637 if (isStorageSupported('localStorage')) { 638 window.localStorage.favoriteTables = data.favoriteTables; 639 } 640 } else { 641 Functions.ajaxShowMessage(data.message); 642 } 643 } 644 }); 645 }); 646 // Check if session storage is supported 647 if (isStorageSupported('sessionStorage')) { 648 var storage = window.sessionStorage;
649 // remove tree from storage if Navi_panel config form is submitted 650 $(document).on('submit', 'form.config-form', function () { 651 storage.removeItem('navTreePaths'); 652 }); 653 // Initialize if no previous state is defined 654 if ($('#pma_navigation_tree_content').length && 655 typeof storage.navTreePaths === 'undefined' 656 ) { 657 Navigation.reload(); 658 } else if (CommonParams.get('server') === storage.server && 659 CommonParams.get('token') === storage.token 660 ) { 661 // Reload the tree to the state before page refresh 662 Navigation.reload(Navigation.filterStateRestore, JSON.parse(storage.navTreePaths)); 663 } else { 664 // If the user is different 665 Navigation.treeStateUpdate(); 666 Navigation.reload(); 667 } 668 } 669}); 670 671/** 672 * Expands a node in navigation tree. 673 * 674 * @param $expandElem expander 675 * @param callback callback function 676 * 677 * @returns void 678 */ 679Navigation.expandTreeNode = function ($expandElem, callback) { 680 var $children = $expandElem.closest('li').children('div.list_container'); 681 var $icon = $expandElem.find('img'); 682 if ($expandElem.hasClass('loaded')) { 683 if ($icon.is('.ic_b_plus')) { 684 $icon.removeClass('ic_b_plus').addClass('ic_b_minus'); 685 $children.slideDown('fast'); 686 } 687 if (callback && typeof callback === 'function') { 688 callback.call(); 689 } 690 $children.promise().done(Navigation.treeStateUpdate); 691 } else { 692 var $throbber = $('#pma_navigation').find('.throbber') 693 .first() 694 .clone() 695 .css({ visibility: 'visible', display: 'block' }) 696 .on('click', false); 697 $icon.hide(); 698 $throbber.insertBefore($icon); 699 700 Navigation.loadChildNodes(true, $expandElem, function (data) { 701 if (typeof data !== 'undefined' && data.success === true) { 702 var $destination = $expandElem.closest('li'); 703 $icon.removeClass('ic_b_plus').addClass('ic_b_minus'); 704 $children = $destination.children('div.list_container'); 705 $children.slideDown('fast'); 706 if ($destination.find('ul > li').length === 1) { 707 $destination.find('ul > li') 708 .find('a.expander.container') 709 .trigger('click'); 710 } 711 if (callback && typeof callback === 'function') { 712 callback.call(); 713 } 714 Navigation.showFullName($destination); 715 } else { 716 Functions.ajaxShowMessage(data.error, false); 717 } 718 $icon.show(); 719 $throbber.remove(); 720 $children.promise().done(Navigation.treeStateUpdate); 721 }); 722 } 723 $expandElem.trigger('blur'); 724}; 725 726/** 727 * Auto-scrolls the newly chosen database 728 * 729 * @param object $element The element to set to view 730 * @param boolean $forceToTop Whether to force scroll to top 731 * 732 */ 733Navigation.scrollToView = function ($element, $forceToTop) { 734 Navigation.filterStateRestore(); 735 var $container = $('#pma_navigation_tree_content'); 736 var elemTop = $element.offset().top - $container.offset().top; 737 var textHeight = 20; 738 var scrollPadding = 20; // extra padding from top of bottom when scrolling to view 739 if (elemTop < 0 || $forceToTop) { 740 $container.stop().animate({ 741 scrollTop: elemTop + $container.scrollTop() - scrollPadding 742 }); 743 } else if (elemTop + textHeight > $container.height()) { 744 $container.stop().animate({ 745 scrollTop: elemTop + textHeight - $container.height() + $container.scrollTop() + scrollPadding 746 }); 747 } 748}; 749 750/** 751 * Expand the navigation and highlight the current database or table/view 752 * 753 * @returns void 754 */ 755Navigation.showCurrent = function () { 756 var db = CommonParams.get('db'); 757 var table = CommonParams.get('table'); 758 759 var autoexpand = $('#pma_navigation_tree').hasClass('autoexpand'); 760 761 $('#pma_navigation_tree') 762 .find('li.selected') 763 .removeClass('selected'); 764 var $dbItem; 765 if (db) { 766 $dbItem = findLoadedItem( 767 $('#pma_navigation_tree').find('> div'), db, 'database', !table 768 ); 769 if ($('#navi_db_select').length && 770 $('option:selected', $('#navi_db_select')).length 771 ) { 772 if (! Navigation.selectCurrentDatabase()) { 773 return; 774 } 775 // If loaded database in navigation is not same as current one 776 if ($('#pma_navigation_tree_content').find('span.loaded_db:first').text() 777 !== $('#navi_db_select').val() 778 ) { 779 Navigation.loadChildNodes(false, $('option:selected', $('#navi_db_select')), function () { 780 handleTableOrDb(table, $('#pma_navigation_tree_content')); 781 var $children = $('#pma_navigation_tree_content').children('div.list_container'); 782 $children.promise().done(Navigation.treeStateUpdate); 783 }); 784 } else { 785 handleTableOrDb(table, $('#pma_navigation_tree_content')); 786 } 787 } else if ($dbItem) { 788 fullExpand(table, $dbItem); 789 } 790 } else if ($('#navi_db_select').length && $('#navi_db_select').val()) { 791 $('#navi_db_select').val('').hide().trigger('change'); 792 } else if (autoexpand && $('#pma_navigation_tree_content > ul > li.database').length === 1) { 793 // automatically expand the list if there is only single database 794 795 // find the name of the database 796 var dbItemName = ''; 797 798 $('#pma_navigation_tree_content > ul > li.database').children('a').each(function () { 799 var name = $(this).text(); 800 if (!dbItemName && name.trim()) { // if the name is not empty, it is the desired element 801 dbItemName = name; 802 } 803 }); 804 805 $dbItem = findLoadedItem( 806 $('#pma_navigation_tree').find('> div'), dbItemName, 'database', !table 807 ); 808 809 fullExpand(table, $dbItem); 810 } 811 Navigation.showFullName($('#pma_navigation_tree')); 812 813 function fullExpand (table, $dbItem) { 814 var $expander = $dbItem.children('div:first').children('a.expander'); 815 // if not loaded or loaded but collapsed 816 if (! $expander.hasClass('loaded') || 817 $expander.find('img').is('.ic_b_plus') 818 ) { 819 Navigation.expandTreeNode($expander, function () { 820 handleTableOrDb(table, $dbItem); 821 }); 822 } else { 823 handleTableOrDb(table, $dbItem); 824 } 825 } 826 827 function handleTableOrDb (table, $dbItem) { 828 if (table) { 829 loadAndHighlightTableOrView($dbItem, table); 830 } else { 831 var $container = $dbItem.children('div.list_container'); 832 var $tableContainer = $container.children('ul').children('li.tableContainer'); 833 if ($tableContainer.length > 0) { 834 var $expander = $tableContainer.children('div:first').children('a.expander'); 835 $tableContainer.addClass('selected'); 836 Navigation.expandTreeNode($expander, function () { 837 Navigation.scrollToView($dbItem, true); 838 }); 839 } else { 840 Navigation.scrollToView($dbItem, true); 841 } 842 } 843 } 844 845 function findLoadedItem ($container, name, clazz, doSelect) { 846 var ret = false;
847 $container.children('ul').children('li').each(function () { 848 var $li = $(this); 849 // this is a navigation group, recurse 850 if ($li.is('.navGroup')) { 851 var $container = $li.children('div.list_container'); 852 var $childRet = findLoadedItem( 853 $container, name, clazz, doSelect 854 ); 855 if ($childRet) { 856 ret = $childRet; 857 return false; 858 } 859 } else { // this is a real navigation item 860 // name and class matches 861 if (((clazz && $li.is('.' + clazz)) || ! clazz) && 862 $li.children('a').text() === name) { 863 if (doSelect) { 864 $li.addClass('selected'); 865 } 866 // taverse up and expand and parent navigation groups 867 $li.parents('.navGroup').each(function () { 868 var $cont = $(this).children('div.list_container'); 869 if (! $cont.is(':visible')) { 870 $(this) 871 .children('div:first') 872 .children('a.expander') 873 .trigger('click'); 874 } 875 }); 876 ret = $li; 877 return false; 878 } 879 } 880 }); 881 return ret; 882 } 883 884 function loadAndHighlightTableOrView ($dbItem, itemName) { 885 var $container = $dbItem.children('div.list_container'); 886 var $expander; 887 var $whichItem = isItemInContainer($container, itemName, 'li.table, li.view'); 888 // If item already there in some container 889 if ($whichItem) { 890 // get the relevant container while may also be a subcontainer 891 var $relatedContainer = $whichItem.closest('li.subContainer').length 892 ? $whichItem.closest('li.subContainer') 893 : $dbItem; 894 $whichItem = findLoadedItem( 895 $relatedContainer.children('div.list_container'), 896 itemName, null, true 897 ); 898 // Show directly 899 showTableOrView($whichItem, $relatedContainer.children('div:first').children('a.expander')); 900 // else if item not there, try loading once 901 } else { 902 var $subContainers = $dbItem.find('.subContainer'); 903 // If there are subContainers i.e. tableContainer or viewContainer 904 if ($subContainers.length > 0) { 905 var $containers = []; 906 $subContainers.each(function (index) { 907 $containers[index] = $(this); 908 $expander = $containers[index] 909 .children('div:first') 910 .children('a.expander'); 911 if (! $expander.hasClass('loaded')) { 912 loadAndShowTableOrView($expander, $containers[index], itemName); 913 } 914 }); 915 // else if no subContainers 916 } else { 917 $expander = $dbItem 918 .children('div:first') 919 .children('a.expander'); 920 if (! $expander.hasClass('loaded')) { 921 loadAndShowTableOrView($expander, $dbItem, itemName); 922 } 923 } 924 } 925 } 926 927 function loadAndShowTableOrView ($expander, $relatedContainer, itemName) { 928 Navigation.loadChildNodes(true, $expander, function () { 929 var $whichItem = findLoadedItem( 930 $relatedContainer.children('div.list_container'), 931 itemName, null, true 932 ); 933 if ($whichItem) { 934 showTableOrView($whichItem, $expander); 935 } 936 }); 937 } 938 939 function showTableOrView ($whichItem, $expander) { 940 Navigation.expandTreeNode($expander, function () { 941 if ($whichItem) { 942 Navigation.scrollToView($whichItem, false); 943 } 944 }); 945 } 946 947 function isItemInContainer ($container, name, clazz) { 948 var $whichItem = null; 949 var $items = $container.find(clazz); 950 $items.each(function () { 951 if ($(this).children('a').text() === name) { 952 $whichItem = $(this); 953 return false;
954 } 955 }); 956 return $whichItem; 957 } 958}; 959 960/** 961 * Disable navigation panel settings 962 * 963 * @return void 964 */ 965Navigation.disableSettings = function () { 966 $('#pma_navigation_settings_icon').addClass('hide'); 967 $('#pma_navigation_settings').remove(); 968}; 969 970/** 971 * Ensure that navigation panel settings is properly setup. 972 * If not, set it up 973 * 974 * @return void 975 */ 976Navigation.ensureSettings = function (selflink) { 977 $('#pma_navigation_settings_icon').removeClass('hide'); 978 979 if (!$('#pma_navigation_settings').length) { 980 var params = { 981 getNaviSettings: true, 982 server: CommonParams.get('server'), 983 }; 984 var url = $('#pma_navigation').find('a.navigation_url').attr('href'); 985 $.post(url, params, function (data) { 986 if (typeof data !== 'undefined' && data.success) { 987 $('#pma_navi_settings_container').html(data.message); 988 setupRestoreField(); 989 setupValidation(); 990 setupConfigTabs(); 991 $('#pma_navigation_settings').find('form').attr('action', selflink); 992 } else { 993 Functions.ajaxShowMessage(data.error); 994 } 995 }); 996 } else { 997 $('#pma_navigation_settings').find('form').attr('action', selflink); 998 } 999}; 1000 1001/** 1002 * Reloads the whole navigation tree while preserving its state 1003 * 1004 * @param function the callback function 1005 * @param Object stored navigation paths 1006 * 1007 * @return void 1008 */ 1009Navigation.reload = function (callback, paths) { 1010 var params = { 1011 'reload': true, 1012 'no_debug': true, 1013 'server': CommonParams.get('server'), 1014 }; 1015 var pathsLocal = paths || Navigation.traverseForPaths(); 1016 $.extend(params, pathsLocal); 1017 if ($('#navi_db_select').length) { 1018 params.db = CommonParams.get('db'); 1019 requestNaviReload(params); 1020 return; 1021 } 1022 requestNaviReload(params); 1023 1024 function requestNaviReload (params) { 1025 var url = $('#pma_navigation').find('a.navigation_url').attr('href'); 1026 $.post(url, params, function (data) { 1027 if (typeof data !== 'undefined' && data.success) { 1028 $('#pma_navigation_tree').html(data.message).children('div').show(); 1029 if ($('#pma_navigation_tree').hasClass('synced')) { 1030 Navigation.selectCurrentDatabase(); 1031 Navigation.showCurrent(); 1032 } 1033 // Fire the callback, if any 1034 if (typeof callback === 'function') { 1035 callback.call(); 1036 } 1037 Navigation.treeStateUpdate(); 1038 } else { 1039 Functions.ajaxShowMessage(data.error); 1040 } 1041 }); 1042 } 1043}; 1044 1045Navigation.selectCurrentDatabase = function () { 1046 var $naviDbSelect = $('#navi_db_select'); 1047 1048 if (!$naviDbSelect.length) { 1049 return false; 1050 } 1051 1052 if (CommonParams.get('db')) { // db selected 1053 $naviDbSelect.show(); 1054 } 1055 1056 $naviDbSelect.val(CommonParams.get('db')); 1057 return $naviDbSelect.val() === CommonParams.get('db'); 1058}; 1059 1060/** 1061 * Handles any requests to change the page in a branch of a tree 1062 * 1063 * This can be called from link click or select change event handlers 1064 * 1065 * @param object $this A jQuery object that points to the element that 1066 * initiated the action of changing the page 1067 * 1068 * @return void 1069 */ 1070Navigation.treePagination = function ($this) { 1071 var $msgbox = Functions.ajaxShowMessage(); 1072 var isDbSelector = $this.closest('div.pageselector').is('.dbselector'); 1073 var url; 1074 var params; 1075 if ($this[0].tagName === 'A') { 1076 url = $this.attr('href'); 1077 params = 'ajax_request=true'; 1078 } else { // tagName === 'SELECT' 1079 url = 'navigation.php'; 1080 params = $this.closest('form').serialize() + CommonParams.get('arg_separator') + 'ajax_request=true'; 1081 } 1082 var searchClause = Navigation.FastFilter.getSearchClause(); 1083 if (searchClause) { 1084 params += CommonParams.get('arg_separator') + 'searchClause=' + encodeURIComponent(searchClause); 1085 } 1086 if (isDbSelector) { 1087 params += CommonParams.get('arg_separator') + 'full=true'; 1088 } else { 1089 var searchClause2 = Navigation.FastFilter.getSearchClause2($this); 1090 if (searchClause2) { 1091 params += CommonParams.get('arg_separator') + 'searchClause2=' + encodeURIComponent(searchClause2); 1092 } 1093 } 1094 $.post(url, params, function (data) { 1095 if (typeof data !== 'undefined' && data.success) { 1096 Functions.ajaxRemoveMessage($msgbox); 1097 var val; 1098 if (isDbSelector) { 1099 val = Navigation.FastFilter.getSearchClause(); 1100 $('#pma_navigation_tree') 1101 .html(data.message) 1102 .children('div') 1103 .show(); 1104 if (val) { 1105 $('#pma_navigation_tree') 1106 .find('li.fast_filter input.searchClause') 1107 .val(val); 1108 } 1109 } else { 1110 var $parent = $this.closest('div.list_container').parent(); 1111 val = Navigation.FastFilter.getSearchClause2($this); 1112 $this.closest('div.list_container').html( 1113 $(data.message).children().show() 1114 ); 1115 if (val) { 1116 $parent.find('li.fast_filter input.searchClause').val(val); 1117 } 1118 $parent.find('span.pos2_value:first').text( 1119 $parent.find('span.pos2_value:last').text() 1120 ); 1121 $parent.find('span.pos3_value:first').text( 1122 $parent.find('span.pos3_value:last').text() 1123 ); 1124 } 1125 } else { 1126 Functions.ajaxShowMessage(data.error); 1127 Functions.handleRedirectAndReload(data); 1128 } 1129 Navigation.treeStateUpdate(); 1130 }); 1131}; 1132 1133/** 1134 * @var ResizeHandler Custom object that manages the resizing of the navigation 1135 * 1136 * XXX: Must only be ever instanciated once 1137 * XXX: Inside event handlers the 'this' object is accessed as 'event.data.resize_handler' 1138 */ 1139Navigation.ResizeHandler = function () { 1140 /** 1141 * @var int panelWidth Used by the collapser to know where to go 1142 * back to when uncollapsing the panel 1143 */ 1144 this.panelWidth = 0; 1145 /** 1146 * @var string left Used to provide support for RTL languages 1147 */ 1148 this.left = $('html').attr('dir') === 'ltr' ? 'left' : 'right'; 1149 /** 1150 * Adjusts the width of the navigation panel to the specified value 1151 * 1152 * @param {int} position Navigation width in pixels 1153 * 1154 * @return void 1155 */ 1156 this.setWidth = function (position) { 1157 var pos = position; 1158 if (typeof pos !== 'number') { 1159 pos = 240; 1160 } 1161 var $resizer = $('#pma_navigation_resizer'); 1162 var resizerWidth = $resizer.width(); 1163 var $collapser = $('#pma_navigation_collapser'); 1164 var windowWidth = $(window).width(); 1165 $('#pma_navigation').width(pos); 1166 $('body').css('margin-' + this.left, pos + 'px'); 1167 // Issue #15127 : Adding fixed positioning to menubar 1168 // Issue #15570 : Panels on homescreen go underneath of floating menubar 1169 $('#floating_menubar') 1170 .css('margin-' + this.left, $('#pma_navigation').width() + $('#pma_navigation_resizer').width()) 1171 .css(this.left, 0) 1172 .css({ 1173 'position': 'fixed', 1174 'top': 0, 1175 'width': '100%', 1176 'z-index': 99 1177 }) 1178 .append($('#serverinfo')) 1179 .append($('#topmenucontainer')); 1180 // Allow the DOM to render, then adjust the padding on the body 1181 setTimeout(function () { 1182 $('body').css( 1183 'padding-top', 1184 $('#floating_menubar').outerHeight(true) 1185 ); 1186 }, 2); 1187 $('#pma_console') 1188 .css('margin-' + this.left, (pos + resizerWidth) + 'px'); 1189 $resizer.css(this.left, pos + 'px'); 1190 if (pos === 0) { 1191 $collapser 1192 .css(this.left, pos + resizerWidth) 1193 .html(this.getSymbol(pos)) 1194 .prop('title', Messages.strShowPanel); 1195 } else if (windowWidth > 768) { 1196 $collapser 1197 .css(this.left, pos) 1198 .html(this.getSymbol(pos)) 1199 .prop('title', Messages.strHidePanel); 1200 $('#pma_navigation_resizer').css({ 'width': '3px' }); 1201 } else { 1202 $collapser 1203 .css(this.left, windowWidth - 22) 1204 .html(this.getSymbol(100)) 1205 .prop('title', Messages.strHidePanel); 1206 $('#pma_navigation').width(windowWidth); 1207 $('body').css('margin-' + this.left, '0px'); 1208 $('#pma_navigation_resizer').css({ 'width': '0px' }); 1209 } 1210 setTimeout(function () { 1211 $(window).trigger('resize'); 1212 }, 4); 1213 }; 1214 /** 1215 * Returns the horizontal position of the mouse, 1216 * relative to the outer side of the navigation panel 1217 * 1218 * @param int pos Navigation width in pixels 1219 * 1220 * @return void 1221 */ 1222 this.getPos = function (event) { 1223 var pos = event.pageX; 1224 var windowWidth = $(window).width(); 1225 var windowScroll = $(window).scrollLeft(); 1226 pos = pos - windowScroll; 1227 if (this.left !== 'left') { 1228 pos = windowWidth - event.pageX; 1229 } 1230 if (pos < 0) { 1231 pos = 0; 1232 } else if (pos + 100 >= windowWidth) { 1233 pos = windowWidth - 100; 1234 } else { 1235 this.panelWidth = 0; 1236 } 1237 return pos; 1238 }; 1239 /** 1240 * Returns the HTML code for the arrow symbol used in the collapser 1241 * 1242 * @param int width The width of the panel 1243 * 1244 * @return string 1245 */
1246 this.getSymbol = function (width) { 1247 if (this.left === 'left') { 1248 if (width === 0) { 1249 return '→'; 1250 } else { 1251 return '←'; 1252 } 1253 } else { 1254 if (width === 0) { 1255 return '←'; 1256 } else { 1257 return '→'; 1258 } 1259 } 1260 }; 1261 /** 1262 * Event handler for initiating a resize of the panel 1263 * 1264 * @param object e Event data (contains a reference to Navigation.ResizeHandler) 1265 * 1266 * @return void 1267 */ 1268 this.mousedown = function (event) { 1269 event.preventDefault(); 1270 $(document) 1271 .on('mousemove', { 'resize_handler': event.data.resize_handler }, 1272 $.throttle(event.data.resize_handler.mousemove, 4)) 1273 .on('mouseup', { 'resize_handler': event.data.resize_handler }, 1274 event.data.resize_handler.mouseup); 1275 $('body').css('cursor', 'col-resize'); 1276 }; 1277 /** 1278 * Event handler for terminating a resize of the panel 1279 * 1280 * @param object e Event data (contains a reference to Navigation.ResizeHandler) 1281 * 1282 * @return void 1283 */ 1284 this.mouseup = function (event) { 1285 $('body').css('cursor', ''); 1286 Functions.configSet('NavigationWidth', event.data.resize_handler.getPos(event)); 1287 $('#topmenu').menuResizer('resize'); 1288 $(document) 1289 .off('mousemove') 1290 .off('mouseup'); 1291 }; 1292 /** 1293 * Event handler for updating the panel during a resize operation 1294 * 1295 * @param object e Event data (contains a reference to Navigation.ResizeHandler) 1296 * 1297 * @return void 1298 */ 1299 this.mousemove = function (event) { 1300 event.preventDefault(); 1301 var pos = event.data.resize_handler.getPos(event); 1302 event.data.resize_handler.setWidth(pos); 1303 if ($('.sticky_columns').length !== 0) { 1304 Sql.handleAllStickyColumns(); 1305 } 1306 }; 1307 /** 1308 * Event handler for collapsing the panel 1309 * 1310 * @param object e Event data (contains a reference to Navigation.ResizeHandler) 1311 * 1312 * @return void 1313 */ 1314 this.collapse = function (event) { 1315 event.preventDefault(); 1316 var panelWidth = event.data.resize_handler.panelWidth; 1317 var width = $('#pma_navigation').width(); 1318 if (width === 0 && panelWidth === 0) { 1319 panelWidth = 240; 1320 } 1321 Functions.configSet('NavigationWidth', panelWidth); 1322 event.data.resize_handler.setWidth(panelWidth); 1323 event.data.resize_handler.panelWidth = width; 1324 }; 1325 /** 1326 * Event handler for resizing the navigation tree height on window resize 1327 * 1328 * @return void 1329 */ 1330 this.treeResize = function () { 1331 var $nav = $('#pma_navigation'); 1332 var $navTree = $('#pma_navigation_tree'); 1333 var $navHeader = $('#pma_navigation_header'); 1334 var $navTreeContent = $('#pma_navigation_tree_content'); 1335 var height = ($nav.height() - $navHeader.height()); 1336 1337 height = height > 50 ? height : 800; // keep min. height 1338 $navTree.height(height); 1339 if ($navTreeContent.length > 0) { 1340 $navTreeContent.height(height - $navTreeContent.position().top); 1341 } else { 1342 // TODO: in fast filter search response there is no #pma_navigation_tree_content, needs to be added in php 1343 $navTree.css({ 1344 'overflow-y': 'auto' 1345 }); 1346 } 1347 // Set content bottom space beacuse of console 1348 $('body').css('margin-bottom', $('#pma_console').height() + 'px'); 1349 }; 1350 // Hide the pma_navigation initially when loaded on mobile 1351 if ($(window).width() < 768) { 1352 this.setWidth(0); 1353 } else { 1354 this.setWidth(Functions.configGet('NavigationWidth', false)); 1355 $('#topmenu').menuResizer('resize'); 1356 } 1357 // Register the events for the resizer and the collapser 1358 $(document).on('mousedown', '#pma_navigation_resizer', { 'resize_handler': this }, this.mousedown); 1359 $(document).on('click', '#pma_navigation_collapser', { 'resize_handler': this }, this.collapse); 1360 1361 // Add the correct arrow symbol to the collapser 1362 $('#pma_navigation_collapser').html(this.getSymbol($('#pma_navigation').width())); 1363 // Fix navigation tree height 1364 $(window).on('resize', this.treeResize); 1365 // need to call this now and then, browser might decide 1366 // to show/hide horizontal scrollbars depending on page content width 1367 setInterval(this.treeResize, 2000); 1368 this.treeResize(); 1369}; 1370 1371/** 1372 * @var object FastFilter Handles the functionality that allows filtering 1373 * of the items in a branch of the navigation tree 1374 */ 1375Navigation.FastFilter = { 1376 /** 1377 * Construct for the asynchronous fast filter functionality 1378 * 1379 * @param object $this A jQuery object pointing to the list container 1380 * which is the nearest parent of the fast filter 1381 * @param string searchClause The query string for the filter 1382 * 1383 * @return new Navigation.FastFilter.Filter object 1384 */ 1385 Filter: function ($this, searchClause) { 1386 /** 1387 * @var object $this A jQuery object pointing to the list container 1388 * which is the nearest parent of the fast filter 1389 */
1390 this.$this = $this; 1391 /** 1392 * @var bool searchClause The query string for the filter 1393 */ 1394 this.searchClause = searchClause; 1395 /** 1396 * @var object $clone A clone of the original contents 1397 * of the navigation branch before 1398 * the fast filter was applied 1399 */ 1400 this.$clone = $this.clone(); 1401 /** 1402 * @var object xhr A reference to the ajax request that is currently running 1403 */ 1404 this.xhr = null; 1405 /** 1406 * @var int timeout Used to delay the request for asynchronous search 1407 */ 1408 this.timeout = null; 1409 1410 var $filterInput = $this.find('li.fast_filter input.searchClause'); 1411 if ($filterInput.length !== 0 && 1412 $filterInput.val() !== '' && 1413 $filterInput.val() !== $filterInput[0].defaultValue 1414 ) { 1415 this.request(); 1416 } 1417 }, 1418 /** 1419 * Gets the query string from the database fast filter form 1420 * 1421 * @return string 1422 */ 1423 getSearchClause: function () { 1424 var retval = ''; 1425 var $input = $('#pma_navigation_tree') 1426 .find('li.fast_filter.db_fast_filter input.searchClause'); 1427 if ($input.length && $input.val() !== $input[0].defaultValue) { 1428 retval = $input.val(); 1429 } 1430 return retval; 1431 }, 1432 /** 1433 * Gets the query string from a second level item's fast filter form 1434 * The retrieval is done by trasversing the navigation tree backwards 1435 * 1436 * @return string 1437 */ 1438 getSearchClause2: function ($this) { 1439 var $filterContainer = $this.closest('div.list_container'); 1440 var $filterInput = $([]); 1441 if ($filterContainer 1442 .find('li.fast_filter:not(.db_fast_filter) input.searchClause') 1443 .length !== 0) { 1444 $filterInput = $filterContainer 1445 .find('li.fast_filter:not(.db_fast_filter) input.searchClause'); 1446 } 1447 var searchClause2 = ''; 1448 if ($filterInput.length !== 0 && 1449 $filterInput.first().val() !== $filterInput[0].defaultValue 1450 ) { 1451 searchClause2 = $filterInput.val(); 1452 } 1453 return searchClause2; 1454 }, 1455 /** 1456 * @var hash events A list of functions that are bound to DOM events 1457 * at the top of this file 1458 */ 1459 events: { 1460 focus: function () { 1461 var $obj = $(this).closest('div.list_container'); 1462 if (! $obj.data('fastFilter')) { 1463 $obj.data( 1464 'fastFilter', 1465 new Navigation.FastFilter.Filter($obj, $(this).val()) 1466 ); 1467 } 1468 if ($(this).val() === this.defaultValue) { 1469 $(this).val(''); 1470 } else { 1471 $(this).trigger('select'); 1472 } 1473 }, 1474 blur: function () { 1475 if ($(this).val() === '') { 1476 $(this).val(this.defaultValue); 1477 } 1478 var $obj = $(this).closest('div.list_container'); 1479 if ($(this).val() === this.defaultValue && $obj.data('fastFilter')) { 1480 $obj.data('fastFilter').restore(); 1481 } 1482 }, 1483 keyup: function (event) { 1484 var $obj = $(this).closest('div.list_container'); 1485 var str = ''; 1486 if ($(this).val() !== this.defaultValue && $(this).val() !== '') { 1487 $obj.find('div.pageselector').hide(); 1488 str = $(this).val(); 1489 } 1490 1491 /** 1492 * FIXME at the server level a value match is done while on 1493 * the client side it is a regex match. These two should be aligned 1494 */ 1495 1496 // regex used for filtering. 1497 var regex; 1498 try { 1499 regex = new RegExp(str, 'i'); 1500 } catch (err) { 1501 return; 1502 } 1503 1504 // this is the div that houses the items to be filtered by this filter. 1505 var outerContainer; 1506 if ($(this).closest('li.fast_filter').is('.db_fast_filter')) { 1507 outerContainer = $('#pma_navigation_tree_content'); 1508 } else { 1509 outerContainer = $obj; 1510 } 1511 1512 // filters items that are directly under the div as well as grouped in 1513 // groups. Does not filter child items (i.e. a database search does 1514 // not filter tables) 1515 var itemFilter = function ($curr) { 1516 $curr.children('ul').children('li.navGroup').each(function () { 1517 $(this).children('div.list_container').each(function () { 1518 itemFilter($(this)); // recursive 1519 }); 1520 }); 1521 $curr.children('ul').children('li').children('a').not('.container').each(function () { 1522 if (regex.test($(this).text())) { 1523 $(this).parent().show().removeClass('hidden'); 1524 } else { 1525 $(this).parent().hide().addClass('hidden'); 1526 } 1527 }); 1528 }; 1529 itemFilter(outerContainer); 1530 1531 // hides containers that does not have any visible children 1532 var containerFilter = function ($curr) { 1533 $curr.children('ul').children('li.navGroup').each(function () { 1534 var $group = $(this); 1535 $group.children('div.list_container').each(function () { 1536 containerFilter($(this)); // recursive 1537 }); 1538 $group.show().removeClass('hidden'); 1539 if ($group.children('div.list_container').children('ul') 1540 .children('li').not('.hidden').length === 0) { 1541 $group.hide().addClass('hidden'); 1542 } 1543 }); 1544 }; 1545 containerFilter(outerContainer); 1546 1547 if ($(this).val() !== this.defaultValue && $(this).val() !== '') { 1548 if (! $obj.data('fastFilter')) { 1549 $obj.data( 1550 'fastFilter', 1551 new Navigation.FastFilter.Filter($obj, $(this).val()) 1552 ); 1553 } else { 1554 if (event.keyCode === 13) { 1555 $obj.data('fastFilter').update($(this).val()); 1556 } 1557 } 1558 } else if ($obj.data('fastFilter')) { 1559 $obj.data('fastFilter').restore(true); 1560 } 1561 // update filter state 1562 var filterName; 1563 if ($(this).attr('name') === 'searchClause2') { 1564 filterName = $(this).siblings('input[name=aPath]').val(); 1565 } else { 1566 filterName = 'dbFilter'; 1567 } 1568 Navigation.filterStateUpdate(filterName, $(this).val()); 1569 }, 1570 clear: function (event) { 1571 event.stopPropagation(); 1572 // Clear the input and apply the fast filter with empty input 1573 var filter = $(this).closest('div.list_container').data('fastFilter'); 1574 if (filter) {
1575 filter.restore(); 1576 } 1577 var value = $(this).prev()[0].defaultValue; 1578 $(this).prev().val(value).trigger('keyup'); 1579 } 1580 } 1581}; 1582/** 1583 * Handles a change in the search clause 1584 * 1585 * @param string searchClause The query string for the filter 1586 * 1587 * @return void 1588 */ 1589Navigation.FastFilter.Filter.prototype.update = function (searchClause) { 1590 if (this.searchClause !== searchClause) { 1591 this.searchClause = searchClause; 1592 this.request(); 1593 } 1594}; 1595/** 1596 * After a delay of 250mS, initiates a request to retrieve search results 1597 * Multiple calls to this function will always abort the previous request 1598 * 1599 * @return void 1600 */ 1601Navigation.FastFilter.Filter.prototype.request = function () { 1602 var self = this; 1603 if (self.$this.find('li.fast_filter').find('img.throbber').length === 0) { 1604 self.$this.find('li.fast_filter').append( 1605 $('<div class="throbber"></div>').append( 1606 $('#pma_navigation_content') 1607 .find('img.throbber') 1608 .clone() 1609 .css({ visibility: 'visible', display: 'block' }) 1610 ) 1611 ); 1612 } 1613 if (self.xhr) { 1614 self.xhr.abort(); 1615 } 1616 var url = $('#pma_navigation').find('a.navigation_url').attr('href'); 1617 var params = self.$this.find('> ul > li > form.fast_filter').first().serialize(); 1618 1619 if (self.$this.find('> ul > li > form.fast_filter:first input[name=searchClause]').length === 0) { 1620 var $input = $('#pma_navigation_tree').find('li.fast_filter.db_fast_filter input.searchClause'); 1621 if ($input.length && $input.val() !== $input[0].defaultValue) { 1622 params += CommonParams.get('arg_separator') + 'searchClause=' + encodeURIComponent($input.val()); 1623 } 1624 } 1625 self.xhr = $.ajax({ 1626 url: url, 1627 type: 'post', 1628 dataType: 'json', 1629 data: params, 1630 complete: function (jqXHR, status) { 1631 if (status !== 'abort') { 1632 var data = JSON.parse(jqXHR.responseText); 1633 self.$this.find('li.fast_filter').find('div.throbber').remove(); 1634 if (data && data.results) { 1635 self.swap.apply(self, [data.message]); 1636 } 1637 } 1638 } 1639 }); 1640}; 1641/** 1642 * Replaces the contents of the navigation branch with the search results 1643 * 1644 * @param string list The search results 1645 * 1646 * @return void 1647 */ 1648Navigation.FastFilter.Filter.prototype.swap = function (list) { 1649 this.$this 1650 .html($(list).html()) 1651 .children() 1652 .show() 1653 .end() 1654 .find('li.fast_filter input.searchClause') 1655 .val(this.searchClause); 1656 this.$this.data('fastFilter', this); 1657}; 1658/** 1659 * Restores the navigation to the original state after the fast filter is cleared 1660 * 1661 * @param bool focus Whether to also focus the input box of the fast filter 1662 * 1663 * @return void 1664 */ 1665Navigation.FastFilter.Filter.prototype.restore = function (focus) { 1666 if (this.$this.children('ul').first().hasClass('search_results')) { 1667 this.$this.html(this.$clone.html()).children().show(); 1668 this.$this.data('fastFilter', this); 1669 if (focus) { 1670 this.$this.find('li.fast_filter input.searchClause').trigger('focus'); 1671 } 1672 } 1673 this.searchClause = ''; 1674 this.$this.find('div.pageselector').show(); 1675 this.$this.find('div.throbber').remove(); 1676}; 1677 1678/** 1679 * Show full name when cursor hover and name not shown completely 1680 * 1681 * @param object $containerELem Container element 1682 * 1683 * @return void 1684 */ 1685Navigation.showFullName = function ($containerELem) { 1686 $containerELem.find('.hover_show_full').on('mouseenter', function () { 1687 /** mouseenter */ 1688 var $this = $(this); 1689 var thisOffset = $this.offset(); 1690 if ($this.text() === '') { 1691 return; 1692 } 1693 var $parent = $this.parent(); 1694 if (($parent.offset().left + $parent.outerWidth()) 1695 < (thisOffset.left + $this.outerWidth())) { 1696 var $fullNameLayer = $('#full_name_layer'); 1697 if ($fullNameLayer.length === 0) { 1698 $('body').append('<div id="full_name_layer" class="hide"></div>'); 1699 $('#full_name_layer').mouseleave(function () { 1700 /** mouseleave */ 1701 $(this).addClass('hide') 1702 .removeClass('hovering'); 1703 }).on('mouseenter', function () { 1704 /** mouseenter */ 1705 $(this).addClass('hovering'); 1706 }); 1707 $fullNameLayer = $('#full_name_layer'); 1708 } 1709 $fullNameLayer.removeClass('hide'); 1710 $fullNameLayer.css({ left: thisOffset.left, top: thisOffset.top }); 1711 $fullNameLayer.html($this.clone()); 1712 setTimeout(function () { 1713 if (! $fullNameLayer.hasClass('hovering')) { 1714 $fullNameLayer.trigger('mouseleave'); 1715 } 1716 }, 200); 1717 } 1718 }); 1719};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.