You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

675 lines
19 KiB

  1. /*!
  2. * jquery.fancytree.fixed.js
  3. *
  4. * Add fixed colums and headers to ext.table.
  5. * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
  6. *
  7. * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
  8. *
  9. * Released under the MIT license
  10. * https://github.com/mar10/fancytree/wiki/LicenseInfo
  11. *
  12. * @version 2.38.3
  13. * @date 2023-02-01T20:52:50Z
  14. */
  15. // Allow to use multiple var statements inside a function
  16. (function (factory) {
  17. if (typeof define === "function" && define.amd) {
  18. // AMD. Register as an anonymous module.
  19. define([
  20. "jquery",
  21. "./jquery.fancytree",
  22. "./jquery.fancytree.table",
  23. ], factory);
  24. } else if (typeof module === "object" && module.exports) {
  25. // Node/CommonJS
  26. require("./jquery.fancytree.table"); // core + table
  27. module.exports = factory(require("jquery"));
  28. } else {
  29. // Browser globals
  30. factory(jQuery);
  31. }
  32. })(function ($) {
  33. "use strict";
  34. /******************************************************************************
  35. * Private functions and variables
  36. */
  37. $.ui.fancytree.registerExtension({
  38. name: "fixed",
  39. version: "0.0.1",
  40. // Default options for this extension.
  41. options: {
  42. fixCol: 1,
  43. fixColWidths: null,
  44. fixRows: true,
  45. scrollSpeed: 50,
  46. resizable: true,
  47. classNames: {
  48. table: "fancytree-ext-fixed",
  49. wrapper: "fancytree-ext-fixed-wrapper",
  50. topLeft: "fancytree-ext-fixed-wrapper-tl",
  51. topRight: "fancytree-ext-fixed-wrapper-tr",
  52. bottomLeft: "fancytree-ext-fixed-wrapper-bl",
  53. bottomRight: "fancytree-ext-fixed-wrapper-br",
  54. hidden: "fancytree-ext-fixed-hidden",
  55. counterpart: "fancytree-ext-fixed-node-counterpart",
  56. scrollBorderBottom: "fancytree-ext-fixed-scroll-border-bottom",
  57. scrollBorderRight: "fancytree-ext-fixed-scroll-border-right",
  58. hover: "fancytree-ext-fixed-hover",
  59. },
  60. },
  61. // Overide virtual methods for this extension.
  62. // `this` : is this extension object
  63. // `this._super`: the virtual function that was overriden (member of prev. extension or Fancytree)
  64. treeInit: function (ctx) {
  65. this._requireExtension("table", true, true);
  66. // 'fixed' requires the table extension to be loaded before itself
  67. var res = this._superApply(arguments),
  68. tree = ctx.tree,
  69. options = this.options.fixed,
  70. fcn = this.options.fixed.classNames,
  71. $table = tree.widget.element,
  72. fixedColCount = options.fixCols,
  73. fixedRowCount = options.fixRows,
  74. $tableWrapper = $table.parent(),
  75. $topLeftWrapper = $("<div>").addClass(fcn.topLeft),
  76. $topRightWrapper = $("<div>").addClass(fcn.topRight),
  77. $bottomLeftWrapper = $("<div>").addClass(fcn.bottomLeft),
  78. $bottomRightWrapper = $("<div>").addClass(fcn.bottomRight),
  79. tableStyle = $table.attr("style"),
  80. tableClass = $table.attr("class"),
  81. $topLeftTable = $("<table>")
  82. .attr("style", tableStyle)
  83. .attr("class", tableClass),
  84. $topRightTable = $("<table>")
  85. .attr("style", tableStyle)
  86. .attr("class", tableClass),
  87. $bottomLeftTable = $table,
  88. $bottomRightTable = $("<table>")
  89. .attr("style", tableStyle)
  90. .attr("class", tableClass),
  91. $head = $table.find("thead"),
  92. $colgroup = $table.find("colgroup"),
  93. headRowCount = $head.find("tr").length;
  94. this.$fixedWrapper = $tableWrapper;
  95. $table.addClass(fcn.table);
  96. $tableWrapper.addClass(fcn.wrapper);
  97. $bottomRightTable.append($("<tbody>"));
  98. if ($colgroup.length) {
  99. $colgroup.remove();
  100. }
  101. if (typeof fixedRowCount === "boolean") {
  102. fixedRowCount = fixedRowCount ? headRowCount : 0;
  103. } else {
  104. fixedRowCount = Math.max(
  105. 0,
  106. Math.min(fixedRowCount, headRowCount)
  107. );
  108. }
  109. if (fixedRowCount) {
  110. $topLeftTable.append($head.clone(true));
  111. $topRightTable.append($head.clone(true));
  112. $head.remove();
  113. }
  114. $topLeftTable.find("tr").each(function (idx) {
  115. $(this).find("th").slice(fixedColCount).remove();
  116. });
  117. $topRightTable.find("tr").each(function (idx) {
  118. $(this).find("th").slice(0, fixedColCount).remove();
  119. });
  120. this.$fixedWrapper = $tableWrapper;
  121. $tableWrapper.append(
  122. $topLeftWrapper.append($topLeftTable),
  123. $topRightWrapper.append($topRightTable),
  124. $bottomLeftWrapper.append($bottomLeftTable),
  125. $bottomRightWrapper.append($bottomRightTable)
  126. );
  127. $bottomRightTable.on("keydown", function (evt) {
  128. var node = tree.focusNode,
  129. ctx = tree._makeHookContext(node || tree, evt),
  130. res = tree._callHook("nodeKeydown", ctx);
  131. return res;
  132. });
  133. $bottomRightTable.on("click dblclick", "tr", function (evt) {
  134. var $trLeft = $(this),
  135. $trRight = $trLeft.data(fcn.counterpart),
  136. node = $.ui.fancytree.getNode($trRight),
  137. ctx = tree._makeHookContext(node, evt),
  138. et = $.ui.fancytree.getEventTarget(evt),
  139. prevPhase = tree.phase;
  140. try {
  141. tree.phase = "userEvent";
  142. switch (evt.type) {
  143. case "click":
  144. ctx.targetType = et.type;
  145. if (node.isPagingNode()) {
  146. return (
  147. tree._triggerNodeEvent(
  148. "clickPaging",
  149. ctx,
  150. evt
  151. ) === true
  152. );
  153. }
  154. return tree._triggerNodeEvent("click", ctx, evt) ===
  155. false
  156. ? false
  157. : tree._callHook("nodeClick", ctx);
  158. case "dblclick":
  159. ctx.targetType = et.type;
  160. return tree._triggerNodeEvent(
  161. "dblclick",
  162. ctx,
  163. evt
  164. ) === false
  165. ? false
  166. : tree._callHook("nodeDblclick", ctx);
  167. }
  168. } finally {
  169. tree.phase = prevPhase;
  170. }
  171. });
  172. $tableWrapper
  173. .on(
  174. "mouseenter",
  175. "." +
  176. fcn.bottomRight +
  177. " table tr, ." +
  178. fcn.bottomLeft +
  179. " table tr",
  180. function (evt) {
  181. var $tr = $(this),
  182. $trOther = $tr.data(fcn.counterpart);
  183. $tr.addClass(fcn.hover);
  184. $trOther.addClass(fcn.hover);
  185. }
  186. )
  187. .on(
  188. "mouseleave",
  189. "." +
  190. fcn.bottomRight +
  191. " table tr, ." +
  192. fcn.bottomLeft +
  193. " table tr",
  194. function (evt) {
  195. var $tr = $(this),
  196. $trOther = $tr.data(fcn.counterpart);
  197. $tr.removeClass(fcn.hover);
  198. $trOther.removeClass(fcn.hover);
  199. }
  200. );
  201. $bottomLeftWrapper.on(
  202. "mousewheel DOMMouseScroll",
  203. function (event) {
  204. var $this = $(this),
  205. newScroll = $this.scrollTop(),
  206. scrollUp =
  207. event.originalEvent.wheelDelta > 0 ||
  208. event.originalEvent.detail < 0;
  209. newScroll += scrollUp
  210. ? -options.scrollSpeed
  211. : options.scrollSpeed;
  212. $this.scrollTop(newScroll);
  213. $bottomRightWrapper.scrollTop(newScroll);
  214. event.preventDefault();
  215. }
  216. );
  217. $bottomRightWrapper.scroll(function () {
  218. var $this = $(this),
  219. scrollLeft = $this.scrollLeft(),
  220. scrollTop = $this.scrollTop();
  221. $topLeftWrapper
  222. .toggleClass(fcn.scrollBorderBottom, scrollTop > 0)
  223. .toggleClass(fcn.scrollBorderRight, scrollLeft > 0);
  224. $topRightWrapper
  225. .toggleClass(fcn.scrollBorderBottom, scrollTop > 0)
  226. .scrollLeft(scrollLeft);
  227. $bottomLeftWrapper
  228. .toggleClass(fcn.scrollBorderRight, scrollLeft > 0)
  229. .scrollTop(scrollTop);
  230. });
  231. $.ui.fancytree.overrideMethod(
  232. $.ui.fancytree._FancytreeNodeClass.prototype,
  233. "scrollIntoView",
  234. function (effects, options) {
  235. var $prevContainer = tree.$container;
  236. tree.$container = $bottomRightWrapper;
  237. return this._super
  238. .apply(this, arguments)
  239. .always(function () {
  240. tree.$container = $prevContainer;
  241. });
  242. }
  243. );
  244. return res;
  245. },
  246. treeLoad: function (ctx) {
  247. var self = this,
  248. res = this._superApply(arguments);
  249. res.done(function () {
  250. self.ext.fixed._adjustLayout.call(self);
  251. if (self.options.fixed.resizable) {
  252. self.ext.fixed._makeTableResizable();
  253. }
  254. });
  255. return res;
  256. },
  257. _makeTableResizable: function () {
  258. var $wrapper = this.$fixedWrapper,
  259. fcn = this.options.fixed.classNames,
  260. $topLeftWrapper = $wrapper.find("div." + fcn.topLeft),
  261. $topRightWrapper = $wrapper.find("div." + fcn.topRight),
  262. $bottomLeftWrapper = $wrapper.find("div." + fcn.bottomLeft),
  263. $bottomRightWrapper = $wrapper.find("div." + fcn.bottomRight);
  264. function _makeResizable($table) {
  265. $table.resizable({
  266. handles: "e",
  267. resize: function (evt, ui) {
  268. var width = Math.max($table.width(), ui.size.width);
  269. $bottomLeftWrapper.css("width", width);
  270. $topLeftWrapper.css("width", width);
  271. $bottomRightWrapper.css("left", width);
  272. $topRightWrapper.css("left", width);
  273. },
  274. stop: function () {
  275. $table.css("width", "100%");
  276. },
  277. });
  278. }
  279. _makeResizable($topLeftWrapper.find("table"));
  280. _makeResizable($bottomLeftWrapper.find("table"));
  281. },
  282. /* Called by nodeRender to sync node order with tag order.*/
  283. // nodeFixOrder: function(ctx) {
  284. // },
  285. nodeLoadChildren: function (ctx, source) {
  286. return this._superApply(arguments);
  287. },
  288. nodeRemoveChildMarkup: function (ctx) {
  289. var node = ctx.node;
  290. function _removeChild(elem) {
  291. var i,
  292. child,
  293. children = elem.children;
  294. if (children) {
  295. for (i = 0; i < children.length; i++) {
  296. child = children[i];
  297. if (child.trRight) {
  298. $(child.trRight).remove();
  299. }
  300. _removeChild(child);
  301. }
  302. }
  303. }
  304. _removeChild(node);
  305. return this._superApply(arguments);
  306. },
  307. nodeRemoveMarkup: function (ctx) {
  308. var node = ctx.node;
  309. if (node.trRight) {
  310. $(node.trRight).remove();
  311. }
  312. return this._superApply(arguments);
  313. },
  314. nodeSetActive: function (ctx, flag, callOpts) {
  315. var node = ctx.node,
  316. cn = this.options._classNames;
  317. if (node.trRight) {
  318. $(node.trRight)
  319. .toggleClass(cn.active, flag)
  320. .toggleClass(cn.focused, flag);
  321. }
  322. return this._superApply(arguments);
  323. },
  324. nodeKeydown: function (ctx) {
  325. return this._superApply(arguments);
  326. },
  327. nodeSetFocus: function (ctx, flag) {
  328. var node = ctx.node,
  329. cn = this.options._classNames;
  330. if (node.trRight) {
  331. $(node.trRight).toggleClass(cn.focused, flag);
  332. }
  333. return this._superApply(arguments);
  334. },
  335. nodeRender: function (ctx, force, deep, collapsed, _recursive) {
  336. var res = this._superApply(arguments),
  337. node = ctx.node,
  338. isRootNode = !node.parent;
  339. if (!isRootNode && this.$fixedWrapper) {
  340. var $trLeft = $(node.tr),
  341. fcn = this.options.fixed.classNames,
  342. $trRight = $trLeft.data(fcn.counterpart);
  343. if (!$trRight && $trLeft.length) {
  344. var idx = $trLeft.index(),
  345. fixedColCount = this.options.fixed.fixCols,
  346. $blTableBody = this.$fixedWrapper.find(
  347. "div." + fcn.bottomLeft + " table tbody"
  348. ),
  349. $brTableBody = this.$fixedWrapper.find(
  350. "div." + fcn.bottomRight + " table tbody"
  351. ),
  352. $prevLeftNode = $blTableBody
  353. .find("tr")
  354. .eq(Math.max(idx + 1, 0)),
  355. prevRightNode = $prevLeftNode.data(fcn.counterpart);
  356. $trRight = $trLeft.clone(true);
  357. var trRight = $trRight.get(0);
  358. if (prevRightNode) {
  359. $(prevRightNode).before($trRight);
  360. } else {
  361. $brTableBody.append($trRight);
  362. }
  363. $trRight.show();
  364. trRight.ftnode = node;
  365. node.trRight = trRight;
  366. $trLeft.find("td").slice(fixedColCount).remove();
  367. $trRight.find("td").slice(0, fixedColCount).remove();
  368. $trLeft.data(fcn.counterpart, $trRight);
  369. $trRight.data(fcn.counterpart, $trLeft);
  370. }
  371. }
  372. return res;
  373. },
  374. nodeRenderTitle: function (ctx, title) {
  375. return this._superApply(arguments);
  376. },
  377. nodeRenderStatus: function (ctx) {
  378. var res = this._superApply(arguments),
  379. node = ctx.node;
  380. if (node.trRight) {
  381. var $trRight = $(node.trRight),
  382. $trLeft = $(node.tr),
  383. fcn = this.options.fixed.classNames,
  384. hovering = $trRight.hasClass(fcn.hover),
  385. trClasses = $trLeft.attr("class");
  386. $trRight.attr("class", trClasses);
  387. if (hovering) {
  388. $trRight.addClass(fcn.hover);
  389. $trLeft.addClass(fcn.hover);
  390. }
  391. }
  392. return res;
  393. },
  394. nodeSetExpanded: function (ctx, flag, callOpts) {
  395. var res,
  396. self = this,
  397. node = ctx.node,
  398. $leftTr = $(node.tr),
  399. fcn = this.options.fixed.classNames,
  400. cn = this.options._classNames,
  401. $rightTr = $leftTr.data(fcn.counterpart);
  402. flag = typeof flag === "undefined" ? true : flag;
  403. if (!$rightTr) {
  404. return this._superApply(arguments);
  405. }
  406. $rightTr.toggleClass(cn.expanded, !!flag);
  407. if (flag && !node.isExpanded()) {
  408. res = this._superApply(arguments);
  409. res.done(function () {
  410. node.visit(function (child) {
  411. var $trLeft = $(child.tr),
  412. $trRight = $trLeft.data(fcn.counterpart);
  413. self.ext.fixed._adjustRowHeight($trLeft, $trRight);
  414. if (!child.expanded) {
  415. return "skip";
  416. }
  417. });
  418. self.ext.fixed._adjustColWidths();
  419. self.ext.fixed._adjustWrapperLayout();
  420. });
  421. } else if (!flag && node.isExpanded()) {
  422. node.visit(function (child) {
  423. var $trLeft = $(child.tr),
  424. $trRight = $trLeft.data(fcn.counterpart);
  425. if ($trRight) {
  426. if (!child.expanded) {
  427. return "skip";
  428. }
  429. }
  430. });
  431. self.ext.fixed._adjustColWidths();
  432. self.ext.fixed._adjustWrapperLayout();
  433. res = this._superApply(arguments);
  434. } else {
  435. res = this._superApply(arguments);
  436. }
  437. return res;
  438. },
  439. nodeSetStatus: function (ctx, status, message, details) {
  440. return this._superApply(arguments);
  441. },
  442. treeClear: function (ctx) {
  443. var tree = ctx.tree,
  444. $table = tree.widget.element,
  445. $wrapper = this.$fixedWrapper,
  446. fcn = this.options.fixed.classNames;
  447. $table.find("tr, td, th, thead").removeClass(fcn.hidden).css({
  448. "min-width": "auto",
  449. height: "auto",
  450. });
  451. $wrapper.empty().append($table);
  452. return this._superApply(arguments);
  453. },
  454. treeRegisterNode: function (ctx, add, node) {
  455. return this._superApply(arguments);
  456. },
  457. treeDestroy: function (ctx) {
  458. var tree = ctx.tree,
  459. $table = tree.widget.element,
  460. $wrapper = this.$fixedWrapper,
  461. fcn = this.options.fixed.classNames;
  462. $table.find("tr, td, th, thead").removeClass(fcn.hidden).css({
  463. "min-width": "auto",
  464. height: "auto",
  465. });
  466. $wrapper.empty().append($table);
  467. return this._superApply(arguments);
  468. },
  469. _adjustColWidths: function () {
  470. if (this.options.fixed.adjustColWidths) {
  471. this.options.fixed.adjustColWidths.call(this);
  472. return;
  473. }
  474. var $wrapper = this.$fixedWrapper,
  475. fcn = this.options.fixed.classNames,
  476. $tlWrapper = $wrapper.find("div." + fcn.topLeft),
  477. $blWrapper = $wrapper.find("div." + fcn.bottomLeft),
  478. $trWrapper = $wrapper.find("div." + fcn.topRight),
  479. $brWrapper = $wrapper.find("div." + fcn.bottomRight);
  480. function _adjust($topWrapper, $bottomWrapper) {
  481. var $trTop = $topWrapper.find("thead tr").first(),
  482. $trBottom = $bottomWrapper.find("tbody tr").first();
  483. $trTop.find("th").each(function (idx) {
  484. var $thTop = $(this),
  485. $tdBottom = $trBottom.find("td").eq(idx),
  486. thTopWidth = $thTop.width(),
  487. thTopOuterWidth = $thTop.outerWidth(),
  488. tdBottomWidth = $tdBottom.width(),
  489. tdBottomOuterWidth = $tdBottom.outerWidth(),
  490. newWidth = Math.max(
  491. thTopOuterWidth,
  492. tdBottomOuterWidth
  493. );
  494. $thTop.css(
  495. "min-width",
  496. newWidth - (thTopOuterWidth - thTopWidth)
  497. );
  498. $tdBottom.css(
  499. "min-width",
  500. newWidth - (tdBottomOuterWidth - tdBottomWidth)
  501. );
  502. });
  503. }
  504. _adjust($tlWrapper, $blWrapper);
  505. _adjust($trWrapper, $brWrapper);
  506. },
  507. _adjustRowHeight: function ($tr1, $tr2) {
  508. var fcn = this.options.fixed.classNames;
  509. if (!$tr2) {
  510. $tr2 = $tr1.data(fcn.counterpart);
  511. }
  512. $tr1.css("height", "auto");
  513. $tr2.css("height", "auto");
  514. var row1Height = $tr1.outerHeight(),
  515. row2Height = $tr2.outerHeight(),
  516. newHeight = Math.max(row1Height, row2Height);
  517. $tr1.css("height", newHeight + 1);
  518. $tr2.css("height", newHeight + 1);
  519. },
  520. _adjustWrapperLayout: function () {
  521. var $wrapper = this.$fixedWrapper,
  522. fcn = this.options.fixed.classNames,
  523. $topLeftWrapper = $wrapper.find("div." + fcn.topLeft),
  524. $topRightWrapper = $wrapper.find("div." + fcn.topRight),
  525. $bottomLeftWrapper = $wrapper.find("div." + fcn.bottomLeft),
  526. $bottomRightWrapper = $wrapper.find("div." + fcn.bottomRight),
  527. $topLeftTable = $topLeftWrapper.find("table"),
  528. $topRightTable = $topRightWrapper.find("table"),
  529. // $bottomLeftTable = $bottomLeftWrapper.find("table"),
  530. wrapperWidth = $wrapper.width(),
  531. wrapperHeight = $wrapper.height(),
  532. fixedWidth = Math.min(wrapperWidth, $topLeftTable.width()),
  533. fixedHeight = Math.min(
  534. wrapperHeight,
  535. Math.max($topLeftTable.height(), $topRightTable.height())
  536. );
  537. // vScrollbar = $bottomRightWrapper.get(0).scrollHeight > (wrapperHeight - fixedHeight),
  538. // hScrollbar = $bottomRightWrapper.get(0).scrollWidth > (wrapperWidth - fixedWidth);
  539. $topLeftWrapper.css({
  540. width: fixedWidth,
  541. height: fixedHeight,
  542. });
  543. $topRightWrapper.css({
  544. // width: wrapperWidth - fixedWidth - (vScrollbar ? 17 : 0),
  545. // width: "calc(100% - " + (fixedWidth + (vScrollbar ? 17 : 0)) + "px)",
  546. width: "calc(100% - " + (fixedWidth + 17) + "px)",
  547. height: fixedHeight,
  548. left: fixedWidth,
  549. });
  550. $bottomLeftWrapper.css({
  551. width: fixedWidth,
  552. // height: vScrollbar ? wrapperHeight - fixedHeight - (hScrollbar ? 17 : 0) : "auto",
  553. // height: vScrollbar ? ("calc(100% - " + (fixedHeight + (hScrollbar ? 17 : 0)) + "px)") : "auto",
  554. // height: vScrollbar ? ("calc(100% - " + (fixedHeight + 17) + "px)") : "auto",
  555. height: "calc(100% - " + (fixedHeight + 17) + "px)",
  556. top: fixedHeight,
  557. });
  558. $bottomRightWrapper.css({
  559. // width: wrapperWidth - fixedWidth,
  560. // height: vScrollbar ? wrapperHeight - fixedHeight : "auto",
  561. width: "calc(100% - " + fixedWidth + "px)",
  562. // height: vScrollbar ? ("calc(100% - " + fixedHeight + "px)") : "auto",
  563. height: "calc(100% - " + fixedHeight + "px)",
  564. top: fixedHeight,
  565. left: fixedWidth,
  566. });
  567. },
  568. _adjustLayout: function () {
  569. var self = this,
  570. $wrapper = this.$fixedWrapper,
  571. fcn = this.options.fixed.classNames,
  572. $topLeftWrapper = $wrapper.find("div." + fcn.topLeft),
  573. $topRightWrapper = $wrapper.find("div." + fcn.topRight),
  574. $bottomLeftWrapper = $wrapper.find("div." + fcn.bottomLeft);
  575. // $bottomRightWrapper = $wrapper.find("div." + fcn.bottomRight)
  576. $topLeftWrapper.find("table tr").each(function (idx) {
  577. var $trRight = $topRightWrapper.find("tr").eq(idx);
  578. self.ext.fixed._adjustRowHeight($(this), $trRight);
  579. });
  580. $bottomLeftWrapper
  581. .find("table tbody")
  582. .find("tr")
  583. .each(function (idx) {
  584. // var $trRight = $bottomRightWrapper.find("tbody").find("tr").eq(idx);
  585. self.ext.fixed._adjustRowHeight($(this));
  586. });
  587. self.ext.fixed._adjustColWidths.call(this);
  588. self.ext.fixed._adjustWrapperLayout.call(this);
  589. },
  590. // treeSetFocus: function(ctx, flag) {
  591. //// alert("treeSetFocus" + ctx.tree.$container);
  592. // ctx.tree.$container.focus();
  593. // $.ui.fancytree.focusTree = ctx.tree;
  594. // }
  595. });
  596. // Value returned by `require('jquery.fancytree..')`
  597. return $.ui.fancytree;
  598. }); // End of closure