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.
 
 
 
 
 

1015 lines
26 KiB

  1. /*!
  2. * jquery.fancytree.grid.js
  3. *
  4. * Render tree as table (aka 'tree grid', 'table tree').
  5. * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
  6. *
  7. * Copyright (c) 2008-2023, Martin Wendt (http://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. (function (factory) {
  16. if (typeof define === "function" && define.amd) {
  17. // AMD. Register as an anonymous module.
  18. define(["jquery", "./jquery.fancytree"], factory);
  19. } else if (typeof module === "object" && module.exports) {
  20. // Node/CommonJS
  21. require("./jquery.fancytree");
  22. module.exports = factory(require("jquery"));
  23. } else {
  24. // Browser globals
  25. factory(jQuery);
  26. }
  27. })(function ($) {
  28. "use strict";
  29. /******************************************************************************
  30. * Private functions and variables
  31. */
  32. var FT = $.ui.fancytree,
  33. _assert = FT.assert,
  34. SCROLL_MODE = "wheel"; // 'wheel' | 'scroll'
  35. // EPS = 1.0;
  36. /*
  37. * [ext-grid] ...
  38. *
  39. * @alias Fancytree#_addScrollbar
  40. * @requires jquery.fancytree.grid.js
  41. */
  42. function _addScrollbar(table) {
  43. var sbWidth = 10,
  44. $table = $(table),
  45. position = $table.position(),
  46. // top = $table.find("tbody").position().top,
  47. $sb = $("<div>", {
  48. class: "fancytree-scrollbar",
  49. css: {
  50. border: "1px solid gray",
  51. position: "absolute",
  52. top: position.top,
  53. left: position.left + $table.width(),
  54. width: sbWidth,
  55. height: $table.find("tbody").height(),
  56. },
  57. });
  58. $table
  59. .css({
  60. "margin-right": sbWidth,
  61. })
  62. .after($sb);
  63. return $sb;
  64. }
  65. /*
  66. * [ext-grid] Invalidate renumber status, i.e. trigger renumber next time.
  67. *
  68. * @alias Fancytree#_renumberReset
  69. * @requires jquery.fancytree.grid.js
  70. */
  71. $.ui.fancytree._FancytreeClass.prototype._renumberReset = function () {
  72. // this.debug("_renumberReset()");
  73. this.visibleNodeList = null;
  74. };
  75. /*
  76. * [ext-grid] Adjust the start value if the content would be outside otherwise.
  77. *
  78. * @alias Fancytree#_fixStart
  79. * @requires jquery.fancytree.grid.js
  80. */
  81. $.ui.fancytree._FancytreeClass.prototype._fixStart = function (
  82. start,
  83. apply
  84. ) {
  85. var vp = this.viewport,
  86. nodeList = this.visibleNodeList;
  87. start = start == null ? vp.start : start;
  88. // this.debug("_fixStart(" + start + ", " + !!apply + ")");
  89. var orgStart = start;
  90. // Don't scroll down below bottom node
  91. if (nodeList) {
  92. start = Math.min(start, this.visibleNodeList.length - vp.count);
  93. start = Math.max(start, 0, start);
  94. if (start !== orgStart) {
  95. this.debug("Adjust start " + orgStart + " => " + start);
  96. if (apply) {
  97. vp.start = start;
  98. }
  99. }
  100. }
  101. return start;
  102. };
  103. /*
  104. * [ext-grid] ...
  105. *
  106. * @alias Fancytree#_shiftViewport
  107. * @requires jquery.fancytree.grid.js
  108. */
  109. $.ui.fancytree._FancytreeClass.prototype._shiftViewport = function (
  110. mode,
  111. ofs
  112. ) {
  113. this.debug("_shiftViewport", mode, ofs);
  114. switch (mode) {
  115. case "vscroll":
  116. if (ofs) {
  117. this.setViewport({
  118. start: this.viewport.start + (ofs > 0 ? 1 : -1),
  119. });
  120. }
  121. break;
  122. default:
  123. throw Error("Invalid mode: " + mode);
  124. }
  125. };
  126. /**
  127. * [ext-grid] Return true if viewport cannot be scrolled down any further.
  128. *
  129. * @alias Fancytree#isViewportBottom
  130. * @requires jquery.fancytree.grid.js
  131. */
  132. $.ui.fancytree._FancytreeClass.prototype.isViewportBottom = function () {
  133. return (
  134. this.viewport.start + this.viewport.count >=
  135. this.visibleNodeList.length
  136. );
  137. };
  138. /**
  139. * [ext-grid] Define a subset of rows/columns to display and redraw.
  140. *
  141. * @param {object | boolean} options viewport boundaries and status.
  142. *
  143. * @alias Fancytree#setViewport
  144. * @requires jquery.fancytree.grid.js
  145. */
  146. $.ui.fancytree._FancytreeClass.prototype.setViewport = function (opts) {
  147. if (typeof opts === "boolean") {
  148. this.debug("setViewport( " + opts + ")");
  149. return this.setViewport({ enabled: opts });
  150. }
  151. opts = opts || {};
  152. var i,
  153. count,
  154. start,
  155. newRow,
  156. redrawReason = "",
  157. vp = this.viewport,
  158. diffVp = { start: 0, count: 0, enabled: null, force: null },
  159. newVp = $.extend({}, vp),
  160. trList = this.tbody.children,
  161. trCount = trList.length;
  162. // Sanitize viewport settings and check if we need to redraw
  163. this.debug("setViewport(" + opts.start + ", +" + opts.count + ")");
  164. if (opts.force) {
  165. redrawReason += "force";
  166. diffVp.force = true;
  167. }
  168. opts.enabled = opts.enabled !== false; // default to true
  169. if (vp.enabled !== opts.enabled) {
  170. redrawReason += "enable";
  171. newVp.enabled = diffVp.enabled = opts.enabled;
  172. }
  173. start = opts.start == null ? vp.start : Math.max(0, +opts.start);
  174. // Adjust start value to assure the current content is inside vp
  175. start = this._fixStart(start, false);
  176. if (vp.start !== +start) {
  177. redrawReason += "start";
  178. newVp.start = start;
  179. diffVp.start = start - vp.start;
  180. }
  181. count = opts.count == null ? vp.count : Math.max(1, +opts.count);
  182. if (vp.count !== +count) {
  183. redrawReason += "count";
  184. newVp.count = count;
  185. diffVp.count = count - vp.count;
  186. }
  187. // if (vp.left !== +opts.left) {
  188. // diffVp.left = left - vp.left;
  189. // newVp.left = opts.left;
  190. // redrawReason += "left";
  191. // }
  192. // if (vp.right !== +opts.right) {
  193. // diffVp.right = right - vp.right;
  194. // newVp.right = opts.right;
  195. // redrawReason += "right";
  196. // }
  197. if (!redrawReason) {
  198. return false;
  199. }
  200. // Let user cancel or modify the update
  201. var info = {
  202. next: newVp,
  203. diff: diffVp,
  204. reason: redrawReason,
  205. scrollOnly: redrawReason === "start",
  206. };
  207. if (
  208. !opts.noEvents &&
  209. this._triggerTreeEvent("beforeUpdateViewport", null, info) === false
  210. ) {
  211. return false;
  212. }
  213. info.prev = $.extend({}, vp);
  214. delete info.next;
  215. // vp.enabled = newVp.enabled;
  216. vp.start = newVp.start;
  217. vp.count = newVp.count;
  218. // Make sure we have the correct count of TRs
  219. var prevPhase = this.isVpUpdating;
  220. if (trCount > count) {
  221. for (i = 0; i < trCount - count; i++) {
  222. delete this.tbody.lastChild.ftnode;
  223. this.tbody.removeChild(this.tbody.lastChild);
  224. }
  225. } else if (trCount < count) {
  226. for (i = 0; i < count - trCount; i++) {
  227. newRow = this.rowFragment.firstChild.cloneNode(true);
  228. this.tbody.appendChild(newRow);
  229. }
  230. }
  231. trCount = trList.length;
  232. // Update visible node cache if needed
  233. var force = opts.force;
  234. this.redrawViewport(force);
  235. if (!opts.noEvents) {
  236. this._triggerTreeEvent("updateViewport", null, info);
  237. }
  238. this.isVpUpdating = prevPhase;
  239. return true;
  240. };
  241. /**
  242. * [ext-grid] Calculate the viewport count from current scroll wrapper height.
  243. *
  244. * @alias Fancytree#adjustViewportSize
  245. * @requires jquery.fancytree.grid.js
  246. */
  247. $.ui.fancytree._FancytreeClass.prototype.adjustViewportSize = function () {
  248. _assert(
  249. this.scrollWrapper,
  250. "No parent div.fancytree-grid-container found."
  251. );
  252. if (this.isVpUpdating) {
  253. this.debug("Ignoring adjustViewportSize() during VP update.");
  254. return;
  255. }
  256. // Calculate how many rows fit into current container height
  257. var $table = this.$container,
  258. wrapper = this.scrollWrapper,
  259. trHeight = $table.find(">tbody>tr").first().height() || 0,
  260. tableHeight = $table.height(),
  261. headHeight = tableHeight - this.viewport.count * trHeight,
  262. wrapperHeight = wrapper.offsetHeight,
  263. free = wrapperHeight - headHeight,
  264. newCount = trHeight ? Math.floor(free / trHeight) : 0;
  265. // console.info(
  266. // "set container height",
  267. // $(this)
  268. // .parent(".fancytree-grid-container")
  269. // .height()
  270. // );
  271. this.setViewport({ count: newCount });
  272. // if (SCROLL_MODE === "scroll") {
  273. // // Add bottom margin to the table, to make sure the wrapper becomes
  274. // // scrollable
  275. // var mb = wrapperHeight - $table.height() - 2.0 * EPS;
  276. // this.debug("margin-bottom=" + mb);
  277. // $table.css("margin-bottom", mb);
  278. // }
  279. };
  280. /*
  281. * [ext-grid] Calculate the scroll container dimension from the current tree table.
  282. *
  283. * @alias Fancytree#initViewportWrapper
  284. * @requires jquery.fancytree.grid.js
  285. */
  286. $.ui.fancytree._FancytreeClass.prototype._initViewportWrapper =
  287. function () {
  288. var // wrapper = this.scrollWrapper,
  289. // $wrapper = $(wrapper),
  290. tree = this;
  291. // if (SCROLL_MODE === "scroll") {
  292. // $wrapper.on("scroll", function(e) {
  293. // var viewport = tree.viewport,
  294. // curTop = wrapper.scrollTop,
  295. // homeTop = viewport.start === 0 ? 0 : EPS,
  296. // dy = viewport.start === 0 ? 1 : curTop - EPS; //homeTop;
  297. // tree.debug(
  298. // "Got 'scroll' event: scrollTop=" +
  299. // curTop +
  300. // ", homeTop=" +
  301. // homeTop +
  302. // ", start=" +
  303. // viewport.start +
  304. // ", dy=" +
  305. // dy
  306. // );
  307. // if (tree.isVpUpdating) {
  308. // tree.debug("Ignoring scroll during VP update.");
  309. // return;
  310. // } else if (curTop === homeTop) {
  311. // tree.debug("Ignoring scroll to neutral " + homeTop + ".");
  312. // return;
  313. // }
  314. // tree._shiftViewport("vscroll", dy);
  315. // homeTop = viewport.start === 0 ? 0 : EPS;
  316. // setTimeout(function() {
  317. // tree.debug(
  318. // "scrollTop(" +
  319. // wrapper.scrollTop +
  320. // " -> " +
  321. // homeTop +
  322. // ")..."
  323. // );
  324. // wrapper.scrollTop = homeTop;
  325. // }, 0);
  326. // });
  327. // }
  328. if (SCROLL_MODE === "wheel") {
  329. this.$container.on("wheel", function (e) {
  330. var orgEvent = e.originalEvent,
  331. viewport = tree.viewport,
  332. dy = orgEvent.deltaY; // * orgEvent.wheelDeltaY;
  333. if (
  334. !dy ||
  335. e.altKey ||
  336. e.ctrlKey ||
  337. e.metaKey ||
  338. e.shiftKey
  339. ) {
  340. return true;
  341. }
  342. if (dy < 0 && viewport.start === 0) {
  343. return true;
  344. }
  345. if (dy > 0 && tree.isViewportBottom()) {
  346. return true;
  347. }
  348. tree.debug(
  349. "Got 'wheel' event: dy=" +
  350. dy +
  351. ", mode=" +
  352. orgEvent.deltaMode
  353. );
  354. tree._shiftViewport("vscroll", dy);
  355. return false;
  356. });
  357. }
  358. };
  359. /*
  360. * [ext-grid] Renumber and collect all visible rows.
  361. *
  362. * @param {bool} [force=false]
  363. * @param {FancytreeNode | int} [startIdx=0]
  364. * @alias Fancytree#_renumberVisibleNodes
  365. * @requires jquery.fancytree.grid.js
  366. */
  367. $.ui.fancytree._FancytreeClass.prototype._renumberVisibleNodes = function (
  368. force,
  369. startIdx
  370. ) {
  371. if (
  372. (!this.options.viewport.enabled || this.visibleNodeList != null) &&
  373. force !== true
  374. ) {
  375. // this.debug("_renumberVisibleNodes() ignored.");
  376. return false;
  377. }
  378. this.debugTime("_renumberVisibleNodes()");
  379. var i = 0,
  380. prevLength = this.visibleNodeList ? this.visibleNodeList.length : 0,
  381. visibleNodeList = (this.visibleNodeList = []);
  382. // Reset previous data
  383. this.visit(function (node) {
  384. node._rowIdx = null;
  385. // node.span = null;
  386. // if (node.tr) {
  387. // delete node.tr.ftnode;
  388. // node.tr = null;
  389. // }
  390. });
  391. // Iterate over all *visible* nodes
  392. this.visitRows(function (node) {
  393. node._rowIdx = i++;
  394. visibleNodeList.push(node);
  395. });
  396. this.debugTimeEnd("_renumberVisibleNodes()");
  397. if (i !== prevLength) {
  398. this._triggerTreeEvent("updateViewport", null, {
  399. reason: "renumber",
  400. diff: { start: 0, count: 0, enabled: null, force: null },
  401. next: $.extend({}, this.viewport),
  402. // visibleCount: prevLength,
  403. // cur: i,
  404. });
  405. }
  406. };
  407. /**
  408. * [ext-grid] Render all visible nodes into the viweport.
  409. *
  410. * @param {bool} [force=false]
  411. * @alias Fancytree#redrawViewport
  412. * @requires jquery.fancytree.grid.js
  413. */
  414. $.ui.fancytree._FancytreeClass.prototype.redrawViewport = function (force) {
  415. if (this._enableUpdate === false) {
  416. // tree.debug("no render", tree._enableUpdate);
  417. return;
  418. }
  419. this.debugTime("redrawViewport()");
  420. this._renumberVisibleNodes(force);
  421. // Adjust vp.start value to assure the current content is inside:
  422. this._fixStart(null, true);
  423. var i = 0,
  424. vp = this.viewport,
  425. visibleNodeList = this.visibleNodeList,
  426. start = vp.start,
  427. bottom = start + vp.count,
  428. tr,
  429. _renderCount = 0,
  430. trIdx = 0,
  431. trList = this.tbody.children,
  432. prevPhase = this.isVpUpdating;
  433. // Reset previous data
  434. this.visit(function (node) {
  435. // node.debug("redrawViewport(): _rowIdx=" + node._rowIdx);
  436. node.span = null;
  437. if (node.tr) {
  438. delete node.tr.ftnode;
  439. node.tr = null;
  440. }
  441. });
  442. // Redraw the whole tree, erasing all node markup before and after
  443. // the viewport
  444. for (i = start; i < bottom; i++) {
  445. var node = visibleNodeList[i];
  446. tr = trList[trIdx];
  447. if (!node) {
  448. // TODO: make trailing empty rows configurable (custom template or remove TRs)
  449. var newRow = this.rowFragment.firstChild.cloneNode(true);
  450. this.tbody.replaceChild(newRow, tr);
  451. trIdx++;
  452. continue;
  453. }
  454. if (tr !== node.tr) {
  455. node.tr = tr;
  456. node.render();
  457. _renderCount++;
  458. // TODO:
  459. // Implement scrolling by re-using existing markup
  460. // e.g. shifting TRs or TR child elements instead of
  461. // re-creating all the time
  462. }
  463. trIdx++;
  464. }
  465. this.isVpUpdating = prevPhase;
  466. this.debugTimeEnd("redrawViewport()");
  467. };
  468. $.ui.fancytree.registerExtension({
  469. name: "grid",
  470. version: "2.38.3",
  471. // Default options for this extension.
  472. options: {
  473. checkboxColumnIdx: null, // render the checkboxes into the this column index (default: nodeColumnIdx)
  474. indentation: 16, // indent every node level by 16px
  475. mergeStatusColumns: true, // display 'nodata', 'loading', 'error' centered in a single, merged TR
  476. nodeColumnIdx: 0, // render node expander, icon, and title to this column (default: #0)
  477. },
  478. // Overide virtual methods for this extension.
  479. // `this` : is this extension object
  480. // `this._super`: the virtual function that was overriden (member of prev. extension or Fancytree)
  481. treeInit: function (ctx) {
  482. var i,
  483. columnCount,
  484. n,
  485. $row,
  486. $tbody,
  487. tree = ctx.tree,
  488. opts = ctx.options,
  489. tableOpts = opts.table,
  490. $table = tree.widget.element,
  491. $scrollWrapper = $table.parent(".fancytree-grid-container");
  492. if ($.inArray("table", opts.extensions) >= 0) {
  493. $.error("ext-grid and ext-table are mutually exclusive.");
  494. }
  495. if (opts.renderStatusColumns === true) {
  496. opts.renderStatusColumns = opts.renderColumns;
  497. }
  498. // Note: we also re-use CSS rules from ext-table
  499. $table.addClass(
  500. "fancytree-container fancytree-ext-grid fancytree-ext-table"
  501. );
  502. $tbody = $table.find(">tbody");
  503. if (!$tbody.length) {
  504. // TODO: not sure if we can rely on browsers to insert missing <tbody> before <tr>s:
  505. if ($table.find(">tr").length) {
  506. $.error(
  507. "Expected table > tbody > tr. If you see this, please open an issue."
  508. );
  509. }
  510. $tbody = $("<tbody>").appendTo($table);
  511. }
  512. tree.tbody = $tbody[0];
  513. // Prepare row templates:
  514. // Determine column count from table header if any
  515. columnCount = $("thead >tr", $table).last().find(">th").length;
  516. // Read TR templates from tbody if any
  517. $row = $tbody.children("tr").first();
  518. if ($row.length) {
  519. n = $row.children("td").length;
  520. if (columnCount && n !== columnCount) {
  521. tree.warn(
  522. "Column count mismatch between thead (" +
  523. columnCount +
  524. ") and tbody (" +
  525. n +
  526. "): using tbody."
  527. );
  528. columnCount = n;
  529. }
  530. $row = $row.clone();
  531. } else {
  532. // Only thead is defined: create default row markup
  533. _assert(
  534. columnCount >= 1,
  535. "Need either <thead> or <tbody> with <td> elements to determine column count."
  536. );
  537. $row = $("<tr />");
  538. for (i = 0; i < columnCount; i++) {
  539. $row.append("<td />");
  540. }
  541. }
  542. $row.find(">td")
  543. .eq(tableOpts.nodeColumnIdx)
  544. .html("<span class='fancytree-node' />");
  545. if (opts.aria) {
  546. $row.attr("role", "row");
  547. $row.find("td").attr("role", "gridcell");
  548. }
  549. tree.rowFragment = document.createDocumentFragment();
  550. tree.rowFragment.appendChild($row.get(0));
  551. $tbody.empty();
  552. // Make sure that status classes are set on the node's <tr> elements
  553. tree.statusClassPropName = "tr";
  554. tree.ariaPropName = "tr";
  555. this.nodeContainerAttrName = "tr";
  556. // #489: make sure $container is set to <table>, even if ext-dnd is listed before ext-grid
  557. tree.$container = $table;
  558. if ($scrollWrapper.length) {
  559. tree.scrollWrapper = $scrollWrapper[0];
  560. this._initViewportWrapper();
  561. } else {
  562. tree.scrollWrapper = null;
  563. }
  564. // Scrolling is implemented completely differently here
  565. $.ui.fancytree.overrideMethod(
  566. $.ui.fancytree._FancytreeNodeClass.prototype,
  567. "scrollIntoView",
  568. function (effects, options) {
  569. var node = this,
  570. tree = node.tree,
  571. topNode = options && options.topNode,
  572. vp = tree.viewport,
  573. start = vp ? vp.start : null;
  574. if (!tree.viewport) {
  575. return node._super.apply(this, arguments);
  576. }
  577. if (node._rowIdx < vp.start) {
  578. start = node._rowIdx;
  579. } else if (node._rowIdx >= vp.start + vp.count) {
  580. start = node._rowIdx - vp.count + 1;
  581. }
  582. if (topNode && topNode._rowIdx < start) {
  583. start = topNode._rowIdx;
  584. }
  585. tree.setViewport({ start: start });
  586. // Return a resolved promise
  587. return $.Deferred(function () {
  588. this.resolveWith(node);
  589. }).promise();
  590. }
  591. );
  592. tree.visibleNodeList = null; // Set by _renumberVisibleNodes()
  593. tree.viewport = {
  594. enabled: true,
  595. start: 0,
  596. count: 10,
  597. left: 0,
  598. right: 0,
  599. };
  600. this.setViewport(
  601. $.extend(
  602. {
  603. // enabled: true,
  604. autoSize: true,
  605. start: 0,
  606. count: 10,
  607. left: 0,
  608. right: 0,
  609. keepEmptyRows: true,
  610. noEvents: true,
  611. },
  612. opts.viewport
  613. )
  614. );
  615. // tree.$scrollbar = _addScrollbar($table);
  616. this._superApply(arguments);
  617. // standard Fancytree created a root UL
  618. $(tree.rootNode.ul).remove();
  619. tree.rootNode.ul = null;
  620. // Add container to the TAB chain
  621. // #577: Allow to set tabindex to "0", "-1" and ""
  622. this.$container.attr("tabindex", opts.tabindex);
  623. // this.$container.attr("tabindex", opts.tabbable ? "0" : "-1");
  624. if (opts.aria) {
  625. tree.$container
  626. .attr("role", "treegrid")
  627. .attr("aria-readonly", true);
  628. }
  629. },
  630. nodeKeydown: function (ctx) {
  631. var nextNode = null,
  632. nextIdx = null,
  633. tree = ctx.tree,
  634. node = ctx.node,
  635. nodeList = tree.visibleNodeList,
  636. // treeOpts = ctx.options,
  637. viewport = tree.viewport,
  638. event = ctx.originalEvent,
  639. eventString = FT.eventToString(event);
  640. tree.debug("nodeKeydown(" + eventString + ")");
  641. switch (eventString) {
  642. case "home":
  643. case "meta+up":
  644. nextIdx = 0;
  645. break;
  646. case "end":
  647. case "meta+down":
  648. nextIdx = nodeList.length - 1;
  649. break;
  650. case "pageup":
  651. nextIdx = node._rowIdx - viewport.count;
  652. break;
  653. case "pagedown":
  654. nextIdx = node._rowIdx + viewport.count;
  655. break;
  656. }
  657. if (nextIdx != null) {
  658. nextIdx = Math.min(Math.max(0, nextIdx), nodeList.length - 1);
  659. nextNode = nodeList[nextIdx];
  660. nextNode.makeVisible();
  661. nextNode.setActive();
  662. return false;
  663. }
  664. return this._superApply(arguments);
  665. },
  666. nodeRemoveChildMarkup: function (ctx) {
  667. var node = ctx.node;
  668. node.visit(function (n) {
  669. if (n.tr) {
  670. delete n.tr.ftnode;
  671. n.tr = null;
  672. n.span = null;
  673. }
  674. });
  675. },
  676. nodeRemoveMarkup: function (ctx) {
  677. var node = ctx.node;
  678. if (node.tr) {
  679. delete node.tr.ftnode;
  680. node.tr = null;
  681. node.span = null;
  682. }
  683. this.nodeRemoveChildMarkup(ctx);
  684. },
  685. /* Override standard render. */
  686. nodeRender: function (ctx, force, deep, collapsed, _recursive) {
  687. var children,
  688. i,
  689. l,
  690. outsideViewport,
  691. subCtx,
  692. tree = ctx.tree,
  693. node = ctx.node;
  694. if (tree._enableUpdate === false) {
  695. node.debug("nodeRender(): _enableUpdate: false");
  696. return;
  697. }
  698. var opts = ctx.options,
  699. viewport = tree.viewport.enabled ? tree.viewport : null,
  700. start = viewport && viewport.start > 0 ? +viewport.start : 0,
  701. bottom = viewport ? start + viewport.count - 1 : 0,
  702. isRootNode = !node.parent;
  703. _assert(viewport);
  704. // node.debug("nodeRender(): " + node + ", isRoot=" + isRootNode, "tr=" + node.tr, "hcp=" + ctx.hasCollapsedParents, "parent.tr=" + (node.parent && node.parent.tr));
  705. if (!_recursive) {
  706. // node.debug("nodeRender(): start top node");
  707. if (isRootNode && viewport) {
  708. node.debug("nodeRender(): redrawViewport() instead");
  709. return ctx.tree.redrawViewport();
  710. }
  711. ctx.hasCollapsedParents = node.parent && !node.parent.expanded;
  712. // Make sure visible row indices are up-to-date
  713. if (viewport) {
  714. tree._renumberVisibleNodes();
  715. }
  716. }
  717. if (!isRootNode) {
  718. outsideViewport =
  719. viewport &&
  720. (node._rowIdx < start ||
  721. node._rowIdx >= start + viewport.count);
  722. // node.debug(
  723. // "nodeRender(): idx=" +
  724. // node._rowIdx +
  725. // ", outside=" +
  726. // outsideViewport +
  727. // ", TR count=" +
  728. // tree.tbody.rows.length
  729. // );
  730. if (outsideViewport) {
  731. // node.debug("nodeRender(): outsideViewport: ignored");
  732. return;
  733. }
  734. if (!node.tr) {
  735. if (node._rowIdx == null) {
  736. // node.warn("nodeRender(): ignoring hidden");
  737. return;
  738. }
  739. node.debug("nodeRender(): creating new TR.");
  740. node.tr = tree.tbody.rows[node._rowIdx - start];
  741. }
  742. // _assert(
  743. // node.tr,
  744. // "nodeRender() called for node.tr == null: " + node
  745. // );
  746. node.tr.ftnode = node;
  747. if (node.key && opts.generateIds) {
  748. node.tr.id = opts.idPrefix + node.key;
  749. }
  750. node.span = $("span.fancytree-node", node.tr).get(0);
  751. // Set icon, link, and title (normally this is only required on initial render)
  752. // var ctx = this._makeHookContext(node);
  753. this.nodeRenderTitle(ctx); // triggers renderColumns()
  754. // Allow tweaking, binding, after node was created for the first time
  755. if (opts.createNode) {
  756. opts.createNode.call(this, { type: "createNode" }, ctx);
  757. }
  758. }
  759. // Allow tweaking after node state was rendered
  760. if (opts.renderNode) {
  761. opts.renderNode.call(tree, { type: "renderNode" }, ctx);
  762. }
  763. // Visit child nodes
  764. // Add child markup
  765. children = node.children;
  766. _assert(!deep, "deep is not supported");
  767. if (children && (isRootNode || deep || node.expanded)) {
  768. for (i = 0, l = children.length; i < l; i++) {
  769. var child = children[i];
  770. if (viewport && child._rowIdx > bottom) {
  771. children[i].debug("BREAK render children loop");
  772. return false;
  773. }
  774. subCtx = $.extend({}, ctx, { node: child });
  775. subCtx.hasCollapsedParents =
  776. subCtx.hasCollapsedParents || !node.expanded;
  777. this.nodeRender(subCtx, force, deep, collapsed, true);
  778. }
  779. }
  780. },
  781. nodeRenderTitle: function (ctx, title) {
  782. var $cb,
  783. res,
  784. tree = ctx.tree,
  785. node = ctx.node,
  786. opts = ctx.options,
  787. isStatusNode = node.isStatusNode();
  788. res = this._super(ctx, title);
  789. if (node.isRootNode()) {
  790. return res;
  791. }
  792. // Move checkbox to custom column
  793. if (
  794. opts.checkbox &&
  795. !isStatusNode &&
  796. opts.table.checkboxColumnIdx != null
  797. ) {
  798. $cb = $("span.fancytree-checkbox", node.span); //.detach();
  799. $(node.tr)
  800. .find("td")
  801. .eq(+opts.table.checkboxColumnIdx)
  802. .html($cb);
  803. }
  804. // Update element classes according to node state
  805. this.nodeRenderStatus(ctx);
  806. if (isStatusNode) {
  807. if (opts.renderStatusColumns) {
  808. // Let user code write column content
  809. opts.renderStatusColumns.call(
  810. tree,
  811. { type: "renderStatusColumns" },
  812. ctx
  813. );
  814. } else if (opts.grid.mergeStatusColumns && node.isTopLevel()) {
  815. node.warn("mergeStatusColumns is not yet implemented.");
  816. // This approach would not work, since the roe may be re-used:
  817. // $(node.tr)
  818. // .find(">td")
  819. // .eq(0)
  820. // .prop("colspan", tree.columnCount)
  821. // .text(node.title)
  822. // .addClass("fancytree-status-merged")
  823. // .nextAll()
  824. // .remove();
  825. } // else: default rendering for status node: leave other cells empty
  826. } else if (opts.renderColumns) {
  827. opts.renderColumns.call(tree, { type: "renderColumns" }, ctx);
  828. }
  829. return res;
  830. },
  831. nodeRenderStatus: function (ctx) {
  832. var indent,
  833. node = ctx.node,
  834. opts = ctx.options;
  835. this._super(ctx);
  836. $(node.tr).removeClass("fancytree-node");
  837. // indent
  838. indent = (node.getLevel() - 1) * opts.table.indentation;
  839. if (opts.rtl) {
  840. $(node.span).css({ paddingRight: indent + "px" });
  841. } else {
  842. $(node.span).css({ paddingLeft: indent + "px" });
  843. }
  844. },
  845. /* Expand node, return Deferred.promise. */
  846. nodeSetExpanded: function (ctx, flag, callOpts) {
  847. var node = ctx.node,
  848. tree = ctx.tree;
  849. // flag defaults to true
  850. flag = flag !== false;
  851. if ((node.expanded && flag) || (!node.expanded && !flag)) {
  852. // Expanded state isn't changed - just call base implementation
  853. return this._superApply(arguments);
  854. }
  855. var dfd = new $.Deferred(),
  856. subOpts = $.extend({}, callOpts, {
  857. noEvents: true,
  858. noAnimation: true,
  859. });
  860. callOpts = callOpts || {};
  861. function _afterExpand(ok) {
  862. tree.redrawViewport(true);
  863. if (ok) {
  864. if (
  865. flag &&
  866. ctx.options.autoScroll &&
  867. !callOpts.noAnimation &&
  868. node.hasChildren()
  869. ) {
  870. // Scroll down to last child, but keep current node visible
  871. node.getLastChild()
  872. .scrollIntoView(true, { topNode: node })
  873. .always(function () {
  874. if (!callOpts.noEvents) {
  875. tree._triggerNodeEvent(
  876. flag ? "expand" : "collapse",
  877. ctx
  878. );
  879. }
  880. dfd.resolveWith(node);
  881. });
  882. } else {
  883. if (!callOpts.noEvents) {
  884. tree._triggerNodeEvent(
  885. flag ? "expand" : "collapse",
  886. ctx
  887. );
  888. }
  889. dfd.resolveWith(node);
  890. }
  891. } else {
  892. if (!callOpts.noEvents) {
  893. tree._triggerNodeEvent(
  894. flag ? "expand" : "collapse",
  895. ctx
  896. );
  897. }
  898. dfd.rejectWith(node);
  899. }
  900. }
  901. // Call base-expand with disabled events and animation
  902. this._super(ctx, flag, subOpts)
  903. .done(function () {
  904. _afterExpand(true);
  905. })
  906. .fail(function () {
  907. _afterExpand(false);
  908. });
  909. return dfd.promise();
  910. },
  911. treeClear: function (ctx) {
  912. // this.nodeRemoveChildMarkup(this._makeHookContext(this.rootNode));
  913. // this._renumberReset(); // Invalidate visible row cache
  914. return this._superApply(arguments);
  915. },
  916. treeDestroy: function (ctx) {
  917. this.$container.find("tbody").empty();
  918. this.$container.off("wheel");
  919. if (this.$source) {
  920. this.$source.removeClass("fancytree-helper-hidden");
  921. }
  922. this._renumberReset(); // Invalidate visible row cache
  923. return this._superApply(arguments);
  924. },
  925. treeStructureChanged: function (ctx, type) {
  926. // debugger;
  927. if (type !== "addNode" || ctx.tree.visibleNodeList) {
  928. // this.debug("treeStructureChanged(" + type + ")");
  929. this._renumberReset(); // Invalidate visible row cache
  930. }
  931. },
  932. });
  933. // Value returned by `require('jquery.fancytree..')`
  934. return $.ui.fancytree;
  935. }); // End of closure