navigation.js 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286
  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. * Executed on page load
  9. */
  10. $(function () {
  11. if (! $('#pma_navigation').length) {
  12. // Don't bother running any code if the navigation is not even on the page
  13. return;
  14. }
  15. // Do not let the page reload on submitting the fast filter
  16. $(document).on('submit', '.fast_filter', function (event) {
  17. event.preventDefault();
  18. });
  19. // Fire up the resize handlers
  20. new ResizeHandler();
  21. /**
  22. * opens/closes (hides/shows) tree elements
  23. * loads data via ajax
  24. */
  25. $('#pma_navigation_tree a.expander').live('click', function (event) {
  26. event.preventDefault();
  27. event.stopImmediatePropagation();
  28. var $icon = $(this).find('img');
  29. if ($icon.is('.ic_b_plus')) {
  30. expandTreeNode($(this));
  31. } else {
  32. collapseTreeNode($(this));
  33. }
  34. });
  35. /**
  36. * Register event handler for click on the reload
  37. * navigation icon at the top of the panel
  38. */
  39. $('#pma_navigation_reload').live('click', function (event) {
  40. event.preventDefault();
  41. // reload icon object
  42. var $icon = $(this).find('img');
  43. // source of the hidden throbber icon
  44. var icon_throbber_src = $('#pma_navigation .throbber').attr('src');
  45. // source of the reload icon
  46. var icon_reload_src = $icon.attr('src');
  47. // replace the source of the reload icon with the one for throbber
  48. $icon.attr('src', icon_throbber_src);
  49. PMA_reloadNavigation();
  50. // after one second, put back the reload icon
  51. setTimeout(function () {
  52. $icon.attr('src', icon_reload_src);
  53. }, 1000);
  54. });
  55. /**
  56. * Bind all "fast filter" events
  57. */
  58. $('#pma_navigation_tree li.fast_filter span')
  59. .live('click', PMA_fastFilter.events.clear);
  60. $('#pma_navigation_tree li.fast_filter input.searchClause')
  61. .live('focus', PMA_fastFilter.events.focus)
  62. .live('blur', PMA_fastFilter.events.blur)
  63. .live('keyup', PMA_fastFilter.events.keyup);
  64. /**
  65. * Ajax handler for pagination
  66. */
  67. $('#pma_navigation_tree div.pageselector a.ajax').live('click', function (event) {
  68. event.preventDefault();
  69. PMA_navigationTreePagination($(this));
  70. });
  71. /**
  72. * Node highlighting
  73. */
  74. $('#pma_navigation_tree.highlight li:not(.fast_filter)').live(
  75. 'mouseover',
  76. function () {
  77. if ($('li:visible', this).length === 0) {
  78. $(this).addClass('activePointer');
  79. }
  80. }
  81. );
  82. $('#pma_navigation_tree.highlight li:not(.fast_filter)').live(
  83. 'mouseout',
  84. function () {
  85. $(this).removeClass('activePointer');
  86. }
  87. );
  88. /** Create a Routine, Trigger or Event */
  89. $('li.new_procedure a.ajax, li.new_function a.ajax').live('click', function (event) {
  90. event.preventDefault();
  91. var dialog = new RTE.object('routine');
  92. dialog.editorDialog(1, $(this));
  93. });
  94. $('li.new_trigger a.ajax').live('click', function (event) {
  95. event.preventDefault();
  96. var dialog = new RTE.object('trigger');
  97. dialog.editorDialog(1, $(this));
  98. });
  99. $('li.new_event a.ajax').live('click', function (event) {
  100. event.preventDefault();
  101. var dialog = new RTE.object('event');
  102. dialog.editorDialog(1, $(this));
  103. });
  104. /** Execute Routines */
  105. $('li.procedure > a.ajax, li.function > a.ajax').live('click', function (event) {
  106. event.preventDefault();
  107. var dialog = new RTE.object('routine');
  108. dialog.executeDialog($(this));
  109. });
  110. /** Edit Triggers and Events */
  111. $('li.trigger > a.ajax').live('click', function (event) {
  112. event.preventDefault();
  113. var dialog = new RTE.object('trigger');
  114. dialog.editorDialog(0, $(this));
  115. });
  116. $('li.event > a.ajax').live('click', function (event) {
  117. event.preventDefault();
  118. var dialog = new RTE.object('event');
  119. dialog.editorDialog(0, $(this));
  120. });
  121. /** Edit Routines */
  122. $('li.procedure div a.ajax img,' +
  123. ' li.function div a.ajax img').live('click', function (event) {
  124. event.preventDefault();
  125. var dialog = new RTE.object('routine');
  126. dialog.editorDialog(0, $(this).parent());
  127. });
  128. /** Export Triggers and Events */
  129. $('li.trigger div:eq(1) a.ajax img,' +
  130. ' li.event div:eq(1) a.ajax img'
  131. ).live('click', function (event) {
  132. event.preventDefault();
  133. var dialog = new RTE.object();
  134. dialog.exportDialog($(this).parent());
  135. });
  136. /** New index */
  137. $('#pma_navigation_tree li.new_index a.ajax').live('click', function (event) {
  138. event.preventDefault();
  139. var url = $(this).attr('href').substr(
  140. $(this).attr('href').indexOf('?') + 1
  141. ) + '&ajax_request=true';
  142. var title = PMA_messages.strAddIndex;
  143. indexEditorDialog(url, title);
  144. });
  145. /** Edit index */
  146. $('li.index a.ajax').live('click', function (event) {
  147. event.preventDefault();
  148. var url = $(this).attr('href').substr(
  149. $(this).attr('href').indexOf('?') + 1
  150. ) + '&ajax_request=true';
  151. var title = PMA_messages.strEditIndex;
  152. indexEditorDialog(url, title);
  153. });
  154. /** New view */
  155. $('li.new_view a.ajax').live('click', function (event) {
  156. event.preventDefault();
  157. PMA_createViewDialog($(this));
  158. });
  159. /** Hide navigation tree item */
  160. $('a.hideNavItem.ajax').live('click', function (event) {
  161. event.preventDefault();
  162. $.ajax({
  163. url: $(this).attr('href') + '&ajax_request=true',
  164. success: function (data) {
  165. if (data.success === true) {
  166. PMA_reloadNavigation();
  167. } else {
  168. PMA_ajaxShowMessage(data.error);
  169. }
  170. }
  171. });
  172. });
  173. /** Display a dialog to choose hidden navigation items to show */
  174. $('a.showUnhide.ajax').live('click', function (event) {
  175. event.preventDefault();
  176. var $msg = PMA_ajaxShowMessage();
  177. $.get($(this).attr('href') + '&ajax_request=1', function (data) {
  178. if (data.success === true) {
  179. PMA_ajaxRemoveMessage($msg);
  180. var buttonOptions = {};
  181. buttonOptions[PMA_messages.strClose] = function () {
  182. $(this).dialog("close");
  183. };
  184. $('<div/>')
  185. .attr('id', 'unhideNavItemDialog')
  186. .append(data.message)
  187. .dialog({
  188. width: 400,
  189. minWidth: 200,
  190. modal: true,
  191. buttons: buttonOptions,
  192. title: PMA_messages.strUnhideNavItem,
  193. close: function () {
  194. $(this).remove();
  195. }
  196. });
  197. } else {
  198. PMA_ajaxShowMessage(data.error);
  199. }
  200. });
  201. });
  202. /** Show a hidden navigation tree item */
  203. $('a.unhideNavItem.ajax').live('click', function (event) {
  204. event.preventDefault();
  205. var $tr = $(this).parents('tr');
  206. var $msg = PMA_ajaxShowMessage();
  207. $.ajax({
  208. url: $(this).attr('href') + '&ajax_request=true',
  209. success: function (data) {
  210. PMA_ajaxRemoveMessage($msg);
  211. if (data.success === true) {
  212. $tr.remove();
  213. PMA_reloadNavigation();
  214. } else {
  215. PMA_ajaxShowMessage(data.error);
  216. }
  217. }
  218. });
  219. });
  220. // Add/Remove favorite table using Ajax.
  221. $(".favorite_table_anchor").live("click", function (event) {
  222. event.preventDefault();
  223. $self = $(this);
  224. var anchor_id = $self.attr("id");
  225. if($self.data("favtargetn") != null)
  226. if($('a[data-favtargets="' + $self.data("favtargetn") + '"]').length > 0)
  227. {
  228. $('a[data-favtargets="' + $self.data("favtargetn") + '"]').trigger('click');
  229. return;
  230. }
  231. $.ajax({
  232. url: $self.attr('href'),
  233. cache: false,
  234. type: 'POST',
  235. data: {
  236. favorite_tables: (window.localStorage['favorite_tables']
  237. !== undefined)
  238. ? window.localStorage['favorite_tables']
  239. : ''
  240. },
  241. success: function (data) {
  242. if (data.changes) {
  243. $('#pma_favorite_list').html(data.list);
  244. $('#' + anchor_id).parent().html(data.anchor);
  245. PMA_tooltip(
  246. $('#' + anchor_id),
  247. 'a',
  248. $('#' + anchor_id).attr("title")
  249. );
  250. // Update localStorage.
  251. if (window.localStorage !== undefined) {
  252. window.localStorage['favorite_tables']
  253. = data.favorite_tables;
  254. }
  255. } else {
  256. PMA_ajaxShowMessage(data.message);
  257. }
  258. }
  259. });
  260. });
  261. PMA_showCurrentNavigation();
  262. });
  263. /**
  264. * Expands a node in navigation tree.
  265. *
  266. * @param $expandElem expander
  267. * @param callback callback function
  268. *
  269. * @returns void
  270. */
  271. function expandTreeNode($expandElem, callback) {
  272. var $children = $expandElem.closest('li').children('div.list_container');
  273. var $icon = $expandElem.find('img');
  274. if ($expandElem.hasClass('loaded')) {
  275. if ($icon.is('.ic_b_plus')) {
  276. $icon.removeClass('ic_b_plus').addClass('ic_b_minus');
  277. $children.slideDown('fast');
  278. }
  279. if (callback && typeof callback == 'function') {
  280. callback.call();
  281. }
  282. } else {
  283. var $throbber = $('#pma_navigation .throbber')
  284. .first()
  285. .clone()
  286. .css({visibility: 'visible', display: 'block'})
  287. .click(false);
  288. $icon.hide();
  289. $throbber.insertBefore($icon);
  290. loadChildNodes($expandElem, function (data) {
  291. if (data.success === true) {
  292. var $destination = $expandElem.closest('li');
  293. $icon.removeClass('ic_b_plus').addClass('ic_b_minus');
  294. $destination
  295. .children('div.list_container')
  296. .slideDown('fast');
  297. if ($destination.find('ul > li').length == 1) {
  298. $destination.find('ul > li')
  299. .find('a.expander.container')
  300. .click();
  301. }
  302. if (callback && typeof callback == 'function') {
  303. callback.call();
  304. }
  305. PMA_showFullName($destination);
  306. } else {
  307. PMA_ajaxShowMessage(data.error, false);
  308. }
  309. $icon.show();
  310. $throbber.remove();
  311. });
  312. }
  313. $expandElem.blur();
  314. }
  315. /**
  316. * Auto-scrolls the newly chosen database
  317. *
  318. * @param object $element The element to set to view
  319. * @param boolean $forceToTop Whether to force scroll to top
  320. *
  321. */
  322. function scrollToView($element, $forceToTop) {
  323. var $container = $('#pma_navigation_tree_content');
  324. var elemTop = $element.offset().top - $container.offset().top;
  325. var textHeight = 20;
  326. var scrollPadding = 20; // extra padding from top of bottom when scrolling to view
  327. if (elemTop < 0 || $forceToTop) {
  328. $container.stop().animate({
  329. scrollTop: elemTop + $container.scrollTop() - scrollPadding
  330. });
  331. } else if (elemTop + textHeight > $container.height()) {
  332. $container.stop().animate({
  333. scrollTop: elemTop + textHeight - $container.height() + $container.scrollTop() + scrollPadding
  334. });
  335. }
  336. }
  337. /**
  338. * Collapses a node in navigation tree.
  339. *
  340. * @param $expandElem expander
  341. *
  342. * @returns void
  343. */
  344. function collapseTreeNode($expandElem) {
  345. var $children = $expandElem.closest('li').children('div.list_container');
  346. var $icon = $expandElem.find('img');
  347. if ($expandElem.hasClass('loaded')) {
  348. if ($icon.is('.ic_b_minus')) {
  349. $icon.removeClass('ic_b_minus').addClass('ic_b_plus');
  350. $children.slideUp('fast');
  351. }
  352. }
  353. $expandElem.blur();
  354. }
  355. /**
  356. * Loads child items of a node and executes a given callback
  357. *
  358. * @param $expandElem expander
  359. * @param callback callback function
  360. *
  361. * @returns void
  362. */
  363. function loadChildNodes($expandElem, callback) {
  364. if (!$expandElem.hasClass('expander')) {
  365. return;
  366. }
  367. var $destination = $expandElem.closest('li');
  368. var searchClause = PMA_fastFilter.getSearchClause();
  369. var searchClause2 = PMA_fastFilter.getSearchClause2($expandElem);
  370. var params = {
  371. aPath: $expandElem.find('span.aPath').text(),
  372. vPath: $expandElem.find('span.vPath').text(),
  373. pos: $expandElem.find('span.pos').text(),
  374. pos2_name: $expandElem.find('span.pos2_name').text(),
  375. pos2_value: $expandElem.find('span.pos2_value').text(),
  376. searchClause: searchClause,
  377. searchClause2: searchClause2
  378. };
  379. var url = $('#pma_navigation').find('a.navigation_url').attr('href');
  380. $.get(url, params, function (data) {
  381. if (data.success === true) {
  382. $expandElem.addClass('loaded');
  383. $destination.find('div.list_container').remove(); // FIXME: Hack, there shouldn't be a list container there
  384. $destination.append(data.message);
  385. if (callback && typeof callback == 'function') {
  386. callback(data);
  387. }
  388. } else if(data.redirect_flag == "1") {
  389. window.location.href += '&session_expired=1';
  390. window.location.reload();
  391. } else {
  392. var $throbber = $expandElem.find('img.throbber');
  393. $throbber.hide();
  394. $icon = $expandElem.find('img.ic_b_plus');
  395. $icon.show();
  396. PMA_ajaxShowMessage(data.error, false);
  397. }
  398. });
  399. }
  400. /**
  401. * Expand the navigation and highlight the current database or table/view
  402. *
  403. * @returns void
  404. */
  405. function PMA_showCurrentNavigation() {
  406. var db = PMA_commonParams.get('db');
  407. var table = PMA_commonParams.get('table');
  408. $('#pma_navigation_tree')
  409. .find('li.selected')
  410. .removeClass('selected');
  411. if (db) {
  412. var $dbItem = findLoadedItem(
  413. $('#pma_navigation_tree > div'), db, 'database', !table
  414. );
  415. if ($dbItem) {
  416. var $expander = $dbItem.children('div:first').children('a.expander');
  417. // if not loaded or loaded but collapsed
  418. if (! $expander.hasClass('loaded') ||
  419. $expander.find('img').is('.ic_b_plus')
  420. ) {
  421. expandTreeNode($expander, function () {
  422. handleTableOrDb(table, $dbItem);
  423. });
  424. } else {
  425. handleTableOrDb(table, $dbItem);
  426. }
  427. }
  428. }
  429. PMA_showFullName($('#pma_navigation_tree'));
  430. function handleTableOrDb(table, $dbItem) {
  431. if (table) {
  432. loadAndHighlightTableOrView($dbItem, table);
  433. } else {
  434. var $container = $dbItem.children('div.list_container');
  435. var $tableContainer = $container.children('ul').children('li.tableContainer');
  436. if ($tableContainer.length > 0) {
  437. var $expander = $tableContainer.children('div:first').children('a.expander');
  438. expandTreeNode($expander, function () {
  439. scrollToView($dbItem, true);
  440. });
  441. } else {
  442. scrollToView($dbItem, true);
  443. }
  444. }
  445. }
  446. function findLoadedItem($container, name, clazz, doSelect) {
  447. var ret = false;
  448. $container.children('ul').children('li').each(function () {
  449. var $li = $(this);
  450. // this is a navigation group, recurse
  451. if ($li.is('.navGroup')) {
  452. var $container = $li.children('div.list_container');
  453. var $childRet = findLoadedItem(
  454. $container, name, clazz, doSelect
  455. );
  456. if ($childRet) {
  457. ret = $childRet;
  458. return false;
  459. }
  460. } else { // this is a real navigation item
  461. // name and class matches
  462. if (((clazz && $li.is('.' + clazz)) || ! clazz) &&
  463. $li.children('a').text() == name) {
  464. if (doSelect) {
  465. $li.addClass('selected');
  466. }
  467. // taverse up and expand and parent navigation groups
  468. $li.parents('.navGroup').each(function () {
  469. $cont = $(this).children('div.list_container');
  470. if (! $cont.is(':visible')) {
  471. $(this)
  472. .children('div:first')
  473. .children('a.expander')
  474. .click();
  475. }
  476. });
  477. ret = $li;
  478. return false;
  479. }
  480. }
  481. });
  482. return ret;
  483. }
  484. function loadAndHighlightTableOrView($dbItem, itemName) {
  485. var $container = $dbItem.children('div.list_container');
  486. var $expander;
  487. var $whichItem = isItemInContainer($container, itemName, 'li.table, li.view');
  488. //If item already there in some container
  489. if ($whichItem) {
  490. //get the relevant container while may also be a subcontainer
  491. var $relatedContainer = $whichItem.closest('li.subContainer').length
  492. ? $whichItem.closest('li.subContainer')
  493. : $dbItem;
  494. $whichItem = findLoadedItem(
  495. $relatedContainer.children('div.list_container'),
  496. itemName, null, true
  497. );
  498. //Show directly
  499. showTableOrView($whichItem, $relatedContainer.children('div:first').children('a.expander'));
  500. //else if item not there, try loading once
  501. } else {
  502. var $sub_containers = $dbItem.find('.subContainer');
  503. //If there are subContainers i.e. tableContainer or viewContainer
  504. if($sub_containers.length > 0) {
  505. var $containers = new Array();
  506. $sub_containers.each(function (index) {
  507. $containers[index] = $(this);
  508. $expander = $containers[index]
  509. .children('div:first')
  510. .children('a.expander');
  511. collapseTreeNode($expander);
  512. loadAndShowTableOrView($expander, $containers[index], itemName);
  513. });
  514. // else if no subContainers
  515. } else {
  516. $expander = $dbItem
  517. .children('div:first')
  518. .children('a.expander');
  519. collapseTreeNode($expander);
  520. loadAndShowTableOrView($expander, $dbItem, itemName);
  521. }
  522. }
  523. }
  524. function loadAndShowTableOrView($expander, $relatedContainer, itemName) {
  525. loadChildNodes($expander, function (data) {
  526. var $whichItem = findLoadedItem(
  527. $relatedContainer.children('div.list_container'),
  528. itemName, null, true
  529. );
  530. if ($whichItem) {
  531. showTableOrView($whichItem, $expander);
  532. }
  533. });
  534. }
  535. function showTableOrView($whichItem, $expander) {
  536. expandTreeNode($expander, function (data) {
  537. if ($whichItem) {
  538. scrollToView($whichItem, false);
  539. }
  540. });
  541. }
  542. function isItemInContainer($container, name, clazz)
  543. {
  544. var $whichItem = null;
  545. $items = $container.find(clazz);
  546. var found = false;
  547. $items.each(function () {
  548. if ($(this).children('a').text() == name) {
  549. $whichItem = $(this);
  550. return false;
  551. }
  552. });
  553. return $whichItem;
  554. }
  555. }
  556. /**
  557. * Reloads the whole navigation tree while preserving its state
  558. *
  559. * @param function the callback function
  560. * @return void
  561. */
  562. function PMA_reloadNavigation(callback) {
  563. var params = {
  564. reload: true,
  565. pos: $('#pma_navigation_tree').find('a.expander:first > span.pos').text()
  566. };
  567. // Traverse the navigation tree backwards to generate all the actual
  568. // and virtual paths, as well as the positions in the pagination at
  569. // various levels, if necessary.
  570. var count = 0;
  571. $('#pma_navigation_tree').find('a.expander:visible').each(function () {
  572. if ($(this).find('img').is('.ic_b_minus') &&
  573. $(this).closest('li').find('div.list_container .ic_b_minus').length === 0
  574. ) {
  575. params['n' + count + '_aPath'] = $(this).find('span.aPath').text();
  576. params['n' + count + '_vPath'] = $(this).find('span.vPath').text();
  577. var pos2_name = $(this).find('span.pos2_name').text();
  578. if (! pos2_name) {
  579. pos2_name = $(this)
  580. .parent()
  581. .parent()
  582. .find('span.pos2_name:last')
  583. .text();
  584. }
  585. var pos2_value = $(this).find('span.pos2_value').text();
  586. if (! pos2_value) {
  587. pos2_value = $(this)
  588. .parent()
  589. .parent()
  590. .find('span.pos2_value:last')
  591. .text();
  592. }
  593. params['n' + count + '_pos2_name'] = pos2_name;
  594. params['n' + count + '_pos2_value'] = pos2_value;
  595. params['n' + count + '_pos3_name'] = $(this).find('span.pos3_name').text();
  596. params['n' + count + '_pos3_value'] = $(this).find('span.pos3_value').text();
  597. count++;
  598. }
  599. });
  600. var url = $('#pma_navigation').find('a.navigation_url').attr('href');
  601. $.post(url, params, function (data) {
  602. if (data.success) {
  603. $('#pma_navigation_tree').html(data.message).children('div').show();
  604. PMA_showCurrentNavigation();
  605. // Fire the callback, if any
  606. if (typeof callback === 'function') {
  607. callback.call();
  608. }
  609. } else {
  610. PMA_ajaxShowMessage(data.error);
  611. }
  612. });
  613. }
  614. /**
  615. * Handles any requests to change the page in a branch of a tree
  616. *
  617. * This can be called from link click or select change event handlers
  618. *
  619. * @param object $this A jQuery object that points to the element that
  620. * initiated the action of changing the page
  621. *
  622. * @return void
  623. */
  624. function PMA_navigationTreePagination($this) {
  625. var $msgbox = PMA_ajaxShowMessage();
  626. var isDbSelector = $this.closest('div.pageselector').is('.dbselector');
  627. var url, params;
  628. if ($this[0].tagName == 'A') {
  629. url = $this.attr('href');
  630. params = 'ajax_request=true';
  631. } else { // tagName == 'SELECT'
  632. url = 'navigation.php';
  633. params = $this.closest("form").serialize() + '&ajax_request=true';
  634. }
  635. var searchClause = PMA_fastFilter.getSearchClause();
  636. if (searchClause) {
  637. params += '&searchClause=' + encodeURIComponent(searchClause);
  638. }
  639. if (isDbSelector) {
  640. params += '&full=true';
  641. } else {
  642. var searchClause2 = PMA_fastFilter.getSearchClause2($this);
  643. if (searchClause2) {
  644. params += '&searchClause2=' + encodeURIComponent(searchClause2);
  645. }
  646. }
  647. $.post(url, params, function (data) {
  648. PMA_ajaxRemoveMessage($msgbox);
  649. if (data.success) {
  650. if (isDbSelector) {
  651. var val = PMA_fastFilter.getSearchClause();
  652. $('#pma_navigation_tree')
  653. .html(data.message)
  654. .children('div')
  655. .show();
  656. if (val) {
  657. $('#pma_navigation_tree')
  658. .find('li.fast_filter input.searchClause')
  659. .val(val);
  660. }
  661. } else {
  662. var $parent = $this.closest('div.list_container').parent();
  663. var val = PMA_fastFilter.getSearchClause2($this);
  664. $this.closest('div.list_container').html(
  665. $(data.message).children().show()
  666. );
  667. if (val) {
  668. $parent.find('li.fast_filter input.searchClause').val(val);
  669. }
  670. $parent.find('span.pos2_value:first').text(
  671. $parent.find('span.pos2_value:last').text()
  672. );
  673. $parent.find('span.pos3_value:first').text(
  674. $parent.find('span.pos3_value:last').text()
  675. );
  676. }
  677. } else {
  678. PMA_ajaxShowMessage(data.error);
  679. }
  680. });
  681. }
  682. /**
  683. * @var ResizeHandler Custom object that manages the resizing of the navigation
  684. *
  685. * XXX: Must only be ever instanciated once
  686. * XXX: Inside event handlers the 'this' object is accessed as 'event.data.resize_handler'
  687. */
  688. var ResizeHandler = function () {
  689. /**
  690. * Whether the user has initiated a resize operation
  691. */
  692. this.active = false;
  693. /**
  694. * @var int panel_width Used by the collapser to know where to go
  695. * back to when uncollapsing the panel
  696. */
  697. this.panel_width = 0;
  698. /**
  699. * @var string left Used to provide support for RTL languages
  700. */
  701. this.left = $('html').attr('dir') == 'ltr' ? 'left' : 'right';
  702. /**
  703. * Adjusts the width of the navigation panel to the specified value
  704. *
  705. * @param int pos Navigation width in pixels
  706. *
  707. * @return void
  708. */
  709. this.setWidth = function (pos) {
  710. var $resizer = $('#pma_navigation_resizer');
  711. var resizer_width = $resizer.width();
  712. var $collapser = $('#pma_navigation_collapser');
  713. $('#pma_navigation').width(pos);
  714. $('body').css('margin-' + this.left, pos + 'px');
  715. $("#floating_menubar")
  716. .css('margin-' + this.left, (pos + resizer_width) + 'px');
  717. $resizer.css(this.left, pos + 'px');
  718. if (pos === 0) {
  719. $collapser
  720. .css(this.left, pos + resizer_width)
  721. .html(this.getSymbol(pos))
  722. .prop('title', PMA_messages.strShowPanel);
  723. } else {
  724. $collapser
  725. .css(this.left, pos)
  726. .html(this.getSymbol(pos))
  727. .prop('title', PMA_messages.strHidePanel);
  728. }
  729. setTimeout(function () {
  730. $(window).trigger('resize');
  731. }, 4);
  732. };
  733. /**
  734. * Returns the horizontal position of the mouse,
  735. * relative to the outer side of the navigation panel
  736. *
  737. * @param int pos Navigation width in pixels
  738. *
  739. * @return void
  740. */
  741. this.getPos = function (event) {
  742. var pos = event.pageX;
  743. var windowWidth = $(window).width();
  744. var windowScroll = $(window).scrollLeft();
  745. pos = pos - windowScroll;
  746. if (this.left != 'left') {
  747. pos = windowWidth - event.pageX;
  748. }
  749. if (pos < 0) {
  750. pos = 0;
  751. } else if (pos + 100 >= windowWidth) {
  752. pos = windowWidth - 100;
  753. } else {
  754. this.panel_width = 0;
  755. }
  756. return pos;
  757. };
  758. /**
  759. * Returns the HTML code for the arrow symbol used in the collapser
  760. *
  761. * @param int width The width of the panel
  762. *
  763. * @return string
  764. */
  765. this.getSymbol = function (width) {
  766. if (this.left == 'left') {
  767. if (width === 0) {
  768. return '&rarr;';
  769. } else {
  770. return '&larr;';
  771. }
  772. } else {
  773. if (width === 0) {
  774. return '&larr;';
  775. } else {
  776. return '&rarr;';
  777. }
  778. }
  779. };
  780. /**
  781. * Event handler for initiating a resize of the panel
  782. *
  783. * @param object e Event data (contains a reference to resizeHandler)
  784. *
  785. * @return void
  786. */
  787. this.mousedown = function (event) {
  788. event.preventDefault();
  789. event.data.resize_handler.active = true;
  790. $('body').css('cursor', 'col-resize');
  791. };
  792. /**
  793. * Event handler for terminating a resize of the panel
  794. *
  795. * @param object e Event data (contains a reference to resizeHandler)
  796. *
  797. * @return void
  798. */
  799. this.mouseup = function (event) {
  800. if (event.data.resize_handler.active) {
  801. event.data.resize_handler.active = false;
  802. $('body').css('cursor', '');
  803. $.cookie('pma_navi_width', event.data.resize_handler.getPos(event));
  804. $('#topmenu').menuResizer('resize');
  805. }
  806. };
  807. /**
  808. * Event handler for updating the panel during a resize operation
  809. *
  810. * @param object e Event data (contains a reference to resizeHandler)
  811. *
  812. * @return void
  813. */
  814. this.mousemove = function (event) {
  815. if (event.data && event.data.resize_handler && event.data.resize_handler.active) {
  816. event.preventDefault();
  817. var pos = event.data.resize_handler.getPos(event);
  818. event.data.resize_handler.setWidth(pos);
  819. }
  820. if($('#sticky_columns').length !== 0) {
  821. handleStickyColumns();
  822. }
  823. };
  824. /**
  825. * Event handler for collapsing the panel
  826. *
  827. * @param object e Event data (contains a reference to resizeHandler)
  828. *
  829. * @return void
  830. */
  831. this.collapse = function (event) {
  832. event.preventDefault();
  833. event.data.active = false;
  834. var panel_width = event.data.resize_handler.panel_width;
  835. var width = $('#pma_navigation').width();
  836. if (width === 0 && panel_width === 0) {
  837. panel_width = 240;
  838. }
  839. event.data.resize_handler.setWidth(panel_width);
  840. event.data.resize_handler.panel_width = width;
  841. };
  842. /**
  843. * Event handler for resizing the navigation tree height on window resize
  844. *
  845. * @return void
  846. */
  847. this.treeResize = function (event) {
  848. var $nav = $("#pma_navigation"),
  849. $nav_tree = $("#pma_navigation_tree"),
  850. $nav_header = $("#pma_navigation_header"),
  851. $nav_tree_content = $("#pma_navigation_tree_content");
  852. $nav_tree.height($nav.height() - $nav_header.height());
  853. if ($nav_tree_content.length > 0) {
  854. $nav_tree_content.height($nav_tree.height() - $nav_tree_content.position().top);
  855. } else {
  856. //TODO: in fast filter search response there is no #pma_navigation_tree_content, needs to be added in php
  857. $nav_tree.css({
  858. 'overflow-y': 'auto'
  859. });
  860. }
  861. };
  862. /* Initialisation section begins here */
  863. if ($.cookie('pma_navi_width')) {
  864. // If we have a cookie, set the width of the panel to its value
  865. var pos = Math.abs(parseInt($.cookie('pma_navi_width'), 10) || 0);
  866. this.setWidth(pos);
  867. $('#topmenu').menuResizer('resize');
  868. }
  869. // Register the events for the resizer and the collapser
  870. $('#pma_navigation_resizer')
  871. .live('mousedown', {'resize_handler': this}, this.mousedown);
  872. $(document)
  873. .bind('mouseup', {'resize_handler': this}, this.mouseup)
  874. .bind('mousemove', {'resize_handler': this}, $.throttle(this.mousemove, 4));
  875. var $collapser = $('#pma_navigation_collapser');
  876. $collapser.live('click', {'resize_handler': this}, this.collapse);
  877. // Add the correct arrow symbol to the collapser
  878. $collapser.html(this.getSymbol($('#pma_navigation').width()));
  879. // Fix navigation tree height
  880. $(window).on('resize', this.treeResize);
  881. // need to call this now and then, browser might decide
  882. // to show/hide horizontal scrollbars depending on page content width
  883. setInterval(this.treeResize, 2000);
  884. this.treeResize();
  885. }; // End of ResizeHandler
  886. /**
  887. * @var object PMA_fastFilter Handles the functionality that allows filtering
  888. * of the items in a branch of the navigation tree
  889. */
  890. var PMA_fastFilter = {
  891. /**
  892. * Construct for the asynchronous fast filter functionality
  893. *
  894. * @param object $this A jQuery object pointing to the list container
  895. * which is the nearest parent of the fast filter
  896. * @param string searchClause The query string for the filter
  897. *
  898. * @return new PMA_fastFilter.filter object
  899. */
  900. filter: function ($this, searchClause) {
  901. /**
  902. * @var object $this A jQuery object pointing to the list container
  903. * which is the nearest parent of the fast filter
  904. */
  905. this.$this = $this;
  906. /**
  907. * @var bool searchClause The query string for the filter
  908. */
  909. this.searchClause = searchClause;
  910. /**
  911. * @var object $clone A clone of the original contents
  912. * of the navigation branch before
  913. * the fast filter was applied
  914. */
  915. this.$clone = $this.clone();
  916. /**
  917. * @var bool swapped Whether the user clicked on the "N other results" link
  918. */
  919. this.swapped = false;
  920. /**
  921. * @var object xhr A reference to the ajax request that is currently running
  922. */
  923. this.xhr = null;
  924. /**
  925. * @var int timeout Used to delay the request for asynchronous search
  926. */
  927. this.timeout = null;
  928. var $filterInput = $this.find('li.fast_filter input.searchClause');
  929. if ($filterInput.length !== 0 &&
  930. $filterInput.val() !== '' &&
  931. $filterInput.val() != $filterInput[0].defaultValue
  932. ) {
  933. this.request();
  934. }
  935. },
  936. /**
  937. * Gets the query string from the database fast filter form
  938. *
  939. * @return string
  940. */
  941. getSearchClause: function () {
  942. var retval = '';
  943. var $input = $('#pma_navigation_tree')
  944. .find('li.fast_filter.db_fast_filter input.searchClause');
  945. if ($input.length && $input.val() != $input[0].defaultValue) {
  946. retval = $input.val();
  947. }
  948. return retval;
  949. },
  950. /**
  951. * Gets the query string from a second level item's fast filter form
  952. * The retrieval is done by trasversing the navigation tree backwards
  953. *
  954. * @return string
  955. */
  956. getSearchClause2: function ($this) {
  957. var $filterContainer = $this.closest('div.list_container');
  958. var $filterInput = $([]);
  959. if ($filterContainer
  960. .children('li.fast_filter:not(.db_fast_filter) input.searchClause')
  961. .length !== 0) {
  962. $filterInput = $filterContainer
  963. .children('li.fast_filter:not(.db_fast_filter) input.searchClause');
  964. }
  965. var searchClause2 = '';
  966. if ($filterInput.length !== 0 &&
  967. $filterInput.first().val() != $filterInput[0].defaultValue
  968. ) {
  969. searchClause2 = $filterInput.val();
  970. }
  971. return searchClause2;
  972. },
  973. /**
  974. * @var hash events A list of functions that are bound to DOM events
  975. * at the top of this file
  976. */
  977. events: {
  978. focus: function (event) {
  979. var $obj = $(this).closest('div.list_container');
  980. if (! $obj.data('fastFilter')) {
  981. $obj.data(
  982. 'fastFilter',
  983. new PMA_fastFilter.filter($obj, $(this).val())
  984. );
  985. }
  986. if ($(this).val() == this.defaultValue) {
  987. $(this).val('');
  988. } else {
  989. $(this).select();
  990. }
  991. },
  992. blur: function (event) {
  993. if ($(this).val() === '') {
  994. $(this).val(this.defaultValue);
  995. }
  996. var $obj = $(this).closest('div.list_container');
  997. if ($(this).val() == this.defaultValue && $obj.data('fastFilter')) {
  998. $obj.data('fastFilter').restore();
  999. }
  1000. },
  1001. keyup: function (event) {
  1002. var $obj = $(this).closest('div.list_container');
  1003. var str = '';
  1004. if ($(this).val() != this.defaultValue && $(this).val() !== '') {
  1005. $obj.find('div.pageselector').hide();
  1006. str = $(this).val();
  1007. }
  1008. /**
  1009. * FIXME at the server level a value match is done while on
  1010. * the client side it is a regex match. These two should be aligned
  1011. */
  1012. // regex used for filtering.
  1013. var regex;
  1014. try {
  1015. regex = new RegExp(str, 'i');
  1016. } catch (err) {
  1017. return;
  1018. }
  1019. // this is the div that houses the items to be filtered by this filter.
  1020. var outerContainer;
  1021. if ($(this).closest('li.fast_filter').is('.db_fast_filter')) {
  1022. outerContainer = $('#pma_navigation_tree_content');
  1023. } else {
  1024. outerContainer = $obj;
  1025. }
  1026. // filters items that are directly under the div as well as grouped in
  1027. // groups. Does not filter child items (i.e. a database search does
  1028. // not filter tables)
  1029. var item_filter = function($curr) {
  1030. $curr.children('ul').children('li.navGroup').each(function() {
  1031. $(this).children('div.list_container').each(function() {
  1032. item_filter($(this)); // recursive
  1033. });
  1034. });
  1035. $curr.children('ul').children('li').children('a').not('.container').each(function() {
  1036. if (regex.test($(this).text())) {
  1037. $(this).parent().show().removeClass('hidden');
  1038. } else {
  1039. $(this).parent().hide().addClass('hidden');
  1040. }
  1041. });
  1042. };
  1043. item_filter(outerContainer);
  1044. // hides containers that does not have any visible children
  1045. var container_filter = function ($curr) {
  1046. $curr.children('ul').children('li.navGroup').each(function() {
  1047. var $group = $(this);
  1048. $group.children('div.list_container').each(function() {
  1049. container_filter($(this)); // recursive
  1050. });
  1051. $group.show().removeClass('hidden');
  1052. if ($group.children('div.list_container').children('ul')
  1053. .children('li').not('.hidden').length === 0) {
  1054. $group.hide().addClass('hidden');
  1055. }
  1056. });
  1057. };
  1058. container_filter(outerContainer);
  1059. if ($(this).val() != this.defaultValue && $(this).val() !== '') {
  1060. if (! $obj.data('fastFilter')) {
  1061. $obj.data(
  1062. 'fastFilter',
  1063. new PMA_fastFilter.filter($obj, $(this).val())
  1064. );
  1065. } else {
  1066. $obj.data('fastFilter').update($(this).val());
  1067. }
  1068. } else if ($obj.data('fastFilter')) {
  1069. $obj.data('fastFilter').restore(true);
  1070. }
  1071. },
  1072. clear: function (event) {
  1073. event.stopPropagation();
  1074. // Clear the input and apply the fast filter with empty input
  1075. var filter = $(this).closest('div.list_container').data('fastFilter');
  1076. if (filter) {
  1077. filter.restore();
  1078. }
  1079. var value = $(this).prev()[0].defaultValue;
  1080. $(this).prev().val(value).trigger('keyup');
  1081. }
  1082. }
  1083. };
  1084. /**
  1085. * Handles a change in the search clause
  1086. *
  1087. * @param string searchClause The query string for the filter
  1088. *
  1089. * @return void
  1090. */
  1091. PMA_fastFilter.filter.prototype.update = function (searchClause) {
  1092. if (this.searchClause != searchClause) {
  1093. this.searchClause = searchClause;
  1094. this.$this.find('.moreResults').remove();
  1095. this.request();
  1096. }
  1097. };
  1098. /**
  1099. * After a delay of 250mS, initiates a request to retrieve search results
  1100. * Multiple calls to this function will always abort the previous request
  1101. *
  1102. * @return void
  1103. */
  1104. PMA_fastFilter.filter.prototype.request = function () {
  1105. var self = this;
  1106. clearTimeout(self.timeout);
  1107. if (self.$this.find('li.fast_filter').find('img.throbber').length === 0) {
  1108. self.$this.find('li.fast_filter').append(
  1109. $('<div class="throbber"></div>').append(
  1110. $('#pma_navigation_content')
  1111. .find('img.throbber')
  1112. .clone()
  1113. .css({visibility: 'visible', display: 'block'})
  1114. )
  1115. );
  1116. }
  1117. self.timeout = setTimeout(function () {
  1118. if (self.xhr) {
  1119. self.xhr.abort();
  1120. }
  1121. var url = $('#pma_navigation').find('a.navigation_url').attr('href');
  1122. var results = self.$this.find('li:not(.hidden):not(.fast_filter):not(.navGroup)').not('[class^=new]').not('[class^=warp_link]').length;
  1123. var params = self.$this.find('> ul > li > form.fast_filter').first().serialize() + "&results=" + results;
  1124. if (self.$this.find('> ul > li > form.fast_filter:first input[name=searchClause]').length === 0) {
  1125. var $input = $('#pma_navigation_tree').find('li.fast_filter.db_fast_filter input.searchClause');
  1126. if ($input.length && $input.val() != $input[0].defaultValue) {
  1127. params += '&searchClause=' + encodeURIComponent($input.val());
  1128. }
  1129. }
  1130. self.xhr = $.ajax({
  1131. url: url,
  1132. type: 'post',
  1133. dataType: 'json',
  1134. data: params,
  1135. complete: function (jqXHR) {
  1136. var data = $.parseJSON(jqXHR.responseText);
  1137. self.$this.find('li.fast_filter').find('div.throbber').remove();
  1138. if (data && data.results) {
  1139. var $listItem = $('<li />', {'class': 'moreResults'})
  1140. .appendTo(self.$this.find('li.fast_filter'));
  1141. $('<a />', {href: '#'})
  1142. .text(data.results)
  1143. .appendTo($listItem)
  1144. .click(function (event) {
  1145. event.preventDefault();
  1146. self.swap.apply(self, [data.message]);
  1147. });
  1148. }
  1149. }
  1150. });
  1151. }, 250);
  1152. };
  1153. /**
  1154. * Replaces the contents of the navigation branch with the search results
  1155. *
  1156. * @param string list The search results
  1157. *
  1158. * @return void
  1159. */
  1160. PMA_fastFilter.filter.prototype.swap = function (list) {
  1161. this.swapped = true;
  1162. this.$this
  1163. .html($(list).html())
  1164. .children()
  1165. .show()
  1166. .end()
  1167. .find('li.fast_filter input.searchClause')
  1168. .val(this.searchClause);
  1169. this.$this.data('fastFilter', this);
  1170. };
  1171. /**
  1172. * Restores the navigation to the original state after the fast filter is cleared
  1173. *
  1174. * @param bool focus Whether to also focus the input box of the fast filter
  1175. *
  1176. * @return void
  1177. */
  1178. PMA_fastFilter.filter.prototype.restore = function (focus) {
  1179. if (this.swapped) {
  1180. this.swapped = false;
  1181. this.$this.html(this.$clone.html()).children().show();
  1182. this.$this.data('fastFilter', this);
  1183. if (focus) {
  1184. this.$this.find('li.fast_filter input.searchClause').focus();
  1185. }
  1186. }
  1187. this.searchClause = '';
  1188. this.$this.find('.moreResults').remove();
  1189. this.$this.find('div.pageselector').show();
  1190. this.$this.find('div.throbber').remove();
  1191. };
  1192. /**
  1193. * Show full name when cursor hover and name not shown completely
  1194. *
  1195. * @param object $containerELem Container element
  1196. *
  1197. * @return void
  1198. */
  1199. function PMA_showFullName($containerELem) {
  1200. $containerELem.find('.hover_show_full').mouseenter(function() {
  1201. /** mouseenter */
  1202. var $this = $(this);
  1203. var thisOffset = $this.offset();
  1204. if($this.text() == '')
  1205. return;
  1206. var $parent = $this.parent();
  1207. if( ($parent.offset().left + $parent.outerWidth())
  1208. < (thisOffset.left + $this.outerWidth()))
  1209. {
  1210. var $fullNameLayer = $('#full_name_layer');
  1211. if($fullNameLayer.length == 0)
  1212. {
  1213. $('body').append('<div id="full_name_layer" class="hide"></div>');
  1214. $('#full_name_layer').mouseleave(function() {
  1215. /** mouseleave */
  1216. $(this).addClass('hide')
  1217. .removeClass('hovering');
  1218. }).mouseenter(function() {
  1219. /** mouseenter */
  1220. $(this).addClass('hovering');
  1221. });
  1222. $fullNameLayer = $('#full_name_layer');
  1223. }
  1224. $fullNameLayer.removeClass('hide');
  1225. $fullNameLayer.css({left: thisOffset.left, top: thisOffset.top});
  1226. $fullNameLayer.html($this.clone());
  1227. setTimeout(function() {
  1228. if(! $fullNameLayer.hasClass('hovering'))
  1229. {
  1230. $fullNameLayer.trigger('mouseleave');
  1231. }
  1232. }, 200);
  1233. }
  1234. });
  1235. }