navigation.js 53 KB

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