/*! * jquery.fancytree.fixed.js * * Add fixed colums and headers to ext.table. * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/) * * Copyright (c) 2008-2021, Martin Wendt (https://wwWendt.de) * * Released under the MIT license * https://github.com/mar10/fancytree/wiki/LicenseInfo * * @version 2.38.0 * @date 2021-02-09T20:03:49Z */ // Allow to use multiple var statements inside a function (function(factory) { if (typeof define === "function" && define.amd) { // AMD. Register as an anonymous module. define([ "jquery", "./jquery.fancytree", "./jquery.fancytree.table", ], factory); } else if (typeof module === "object" && module.exports) { // Node/CommonJS require("./jquery.fancytree.table"); // core + table module.exports = factory(require("jquery")); } else { // Browser globals factory(jQuery); } })(function($) { "use strict"; /****************************************************************************** * Private functions and variables */ $.ui.fancytree.registerExtension({ name: "fixed", version: "0.0.1", // Default options for this extension. options: { fixCol: 1, fixColWidths: null, fixRows: true, scrollSpeed: 50, resizable: true, classNames: { table: "fancytree-ext-fixed", wrapper: "fancytree-ext-fixed-wrapper", topLeft: "fancytree-ext-fixed-wrapper-tl", topRight: "fancytree-ext-fixed-wrapper-tr", bottomLeft: "fancytree-ext-fixed-wrapper-bl", bottomRight: "fancytree-ext-fixed-wrapper-br", hidden: "fancytree-ext-fixed-hidden", counterpart: "fancytree-ext-fixed-node-counterpart", scrollBorderBottom: "fancytree-ext-fixed-scroll-border-bottom", scrollBorderRight: "fancytree-ext-fixed-scroll-border-right", hover: "fancytree-ext-fixed-hover", }, }, // Overide virtual methods for this extension. // `this` : is this extension object // `this._super`: the virtual function that was overriden (member of prev. extension or Fancytree) treeInit: function(ctx) { this._requireExtension("table", true, true); // 'fixed' requires the table extension to be loaded before itself var res = this._superApply(arguments), tree = ctx.tree, options = this.options.fixed, fcn = this.options.fixed.classNames, $table = tree.widget.element, fixedColCount = options.fixCols, fixedRowCount = options.fixRows, $tableWrapper = $table.parent(), $topLeftWrapper = $("
").addClass(fcn.topLeft), $topRightWrapper = $("
").addClass(fcn.topRight), $bottomLeftWrapper = $("
").addClass(fcn.bottomLeft), $bottomRightWrapper = $("
").addClass(fcn.bottomRight), tableStyle = $table.attr("style"), tableClass = $table.attr("class"), $topLeftTable = $("") .attr("style", tableStyle) .attr("class", tableClass), $topRightTable = $("
") .attr("style", tableStyle) .attr("class", tableClass), $bottomLeftTable = $table, $bottomRightTable = $("
") .attr("style", tableStyle) .attr("class", tableClass), $head = $table.find("thead"), $colgroup = $table.find("colgroup"), headRowCount = $head.find("tr").length; this.$fixedWrapper = $tableWrapper; $table.addClass(fcn.table); $tableWrapper.addClass(fcn.wrapper); $bottomRightTable.append($("")); if ($colgroup.length) { $colgroup.remove(); } if (typeof fixedRowCount === "boolean") { fixedRowCount = fixedRowCount ? headRowCount : 0; } else { fixedRowCount = Math.max( 0, Math.min(fixedRowCount, headRowCount) ); } if (fixedRowCount) { $topLeftTable.append($head.clone(true)); $topRightTable.append($head.clone(true)); $head.remove(); } $topLeftTable.find("tr").each(function(idx) { $(this) .find("th") .slice(fixedColCount) .remove(); }); $topRightTable.find("tr").each(function(idx) { $(this) .find("th") .slice(0, fixedColCount) .remove(); }); this.$fixedWrapper = $tableWrapper; $tableWrapper.append( $topLeftWrapper.append($topLeftTable), $topRightWrapper.append($topRightTable), $bottomLeftWrapper.append($bottomLeftTable), $bottomRightWrapper.append($bottomRightTable) ); $bottomRightTable.on("keydown", function(evt) { var node = tree.focusNode, ctx = tree._makeHookContext(node || tree, evt), res = tree._callHook("nodeKeydown", ctx); return res; }); $bottomRightTable.on("click dblclick", "tr", function(evt) { var $trLeft = $(this), $trRight = $trLeft.data(fcn.counterpart), node = $.ui.fancytree.getNode($trRight), ctx = tree._makeHookContext(node, evt), et = $.ui.fancytree.getEventTarget(evt), prevPhase = tree.phase; try { tree.phase = "userEvent"; switch (evt.type) { case "click": ctx.targetType = et.type; if (node.isPagingNode()) { return ( tree._triggerNodeEvent( "clickPaging", ctx, evt ) === true ); } return tree._triggerNodeEvent("click", ctx, evt) === false ? false : tree._callHook("nodeClick", ctx); case "dblclick": ctx.targetType = et.type; return tree._triggerNodeEvent( "dblclick", ctx, evt ) === false ? false : tree._callHook("nodeDblclick", ctx); } } finally { tree.phase = prevPhase; } }); $tableWrapper .on( "mouseenter", "." + fcn.bottomRight + " table tr, ." + fcn.bottomLeft + " table tr", function(evt) { var $tr = $(this), $trOther = $tr.data(fcn.counterpart); $tr.addClass(fcn.hover); $trOther.addClass(fcn.hover); } ) .on( "mouseleave", "." + fcn.bottomRight + " table tr, ." + fcn.bottomLeft + " table tr", function(evt) { var $tr = $(this), $trOther = $tr.data(fcn.counterpart); $tr.removeClass(fcn.hover); $trOther.removeClass(fcn.hover); } ); $bottomLeftWrapper.on("mousewheel DOMMouseScroll", function(event) { var $this = $(this), newScroll = $this.scrollTop(), scrollUp = event.originalEvent.wheelDelta > 0 || event.originalEvent.detail < 0; newScroll += scrollUp ? -options.scrollSpeed : options.scrollSpeed; $this.scrollTop(newScroll); $bottomRightWrapper.scrollTop(newScroll); event.preventDefault(); }); $bottomRightWrapper.scroll(function() { var $this = $(this), scrollLeft = $this.scrollLeft(), scrollTop = $this.scrollTop(); $topLeftWrapper .toggleClass(fcn.scrollBorderBottom, scrollTop > 0) .toggleClass(fcn.scrollBorderRight, scrollLeft > 0); $topRightWrapper .toggleClass(fcn.scrollBorderBottom, scrollTop > 0) .scrollLeft(scrollLeft); $bottomLeftWrapper .toggleClass(fcn.scrollBorderRight, scrollLeft > 0) .scrollTop(scrollTop); }); $.ui.fancytree.overrideMethod( $.ui.fancytree._FancytreeNodeClass.prototype, "scrollIntoView", function(effects, options) { var $prevContainer = tree.$container; tree.$container = $bottomRightWrapper; return this._super .apply(this, arguments) .always(function() { tree.$container = $prevContainer; }); } ); return res; }, treeLoad: function(ctx) { var self = this, res = this._superApply(arguments); res.done(function() { self.ext.fixed._adjustLayout.call(self); if (self.options.fixed.resizable) { self.ext.fixed._makeTableResizable(); } }); return res; }, _makeTableResizable: function() { var $wrapper = this.$fixedWrapper, fcn = this.options.fixed.classNames, $topLeftWrapper = $wrapper.find("div." + fcn.topLeft), $topRightWrapper = $wrapper.find("div." + fcn.topRight), $bottomLeftWrapper = $wrapper.find("div." + fcn.bottomLeft), $bottomRightWrapper = $wrapper.find("div." + fcn.bottomRight); function _makeResizable($table) { $table.resizable({ handles: "e", resize: function(evt, ui) { var width = Math.max($table.width(), ui.size.width); $bottomLeftWrapper.css("width", width); $topLeftWrapper.css("width", width); $bottomRightWrapper.css("left", width); $topRightWrapper.css("left", width); }, stop: function() { $table.css("width", "100%"); }, }); } _makeResizable($topLeftWrapper.find("table")); _makeResizable($bottomLeftWrapper.find("table")); }, /* Called by nodeRender to sync node order with tag order.*/ // nodeFixOrder: function(ctx) { // }, nodeLoadChildren: function(ctx, source) { return this._superApply(arguments); }, nodeRemoveChildMarkup: function(ctx) { var node = ctx.node; function _removeChild(elem) { var i, child, children = elem.children; if (children) { for (i = 0; i < children.length; i++) { child = children[i]; if (child.trRight) { $(child.trRight).remove(); } _removeChild(child); } } } _removeChild(node); return this._superApply(arguments); }, nodeRemoveMarkup: function(ctx) { var node = ctx.node; if (node.trRight) { $(node.trRight).remove(); } return this._superApply(arguments); }, nodeSetActive: function(ctx, flag, callOpts) { var node = ctx.node, cn = this.options._classNames; if (node.trRight) { $(node.trRight) .toggleClass(cn.active, flag) .toggleClass(cn.focused, flag); } return this._superApply(arguments); }, nodeKeydown: function(ctx) { return this._superApply(arguments); }, nodeSetFocus: function(ctx, flag) { var node = ctx.node, cn = this.options._classNames; if (node.trRight) { $(node.trRight).toggleClass(cn.focused, flag); } return this._superApply(arguments); }, nodeRender: function(ctx, force, deep, collapsed, _recursive) { var res = this._superApply(arguments), node = ctx.node, isRootNode = !node.parent; if (!isRootNode) { var $trLeft = $(node.tr), fcn = this.options.fixed.classNames, $trRight = $trLeft.data(fcn.counterpart); if (!$trRight && $trLeft.length) { var idx = $trLeft.index(), fixedColCount = this.options.fixed.fixCols, $blTableBody = $( "div." + fcn.bottomLeft + " table tbody" ), $brTableBody = $( "div." + fcn.bottomRight + " table tbody" ), $prevLeftNode = $blTableBody .find("tr") .eq(Math.max(idx + 1, 0)), prevRightNode = $prevLeftNode.data(fcn.counterpart); $trRight = $trLeft.clone(true); var trRight = $trRight.get(0); if (prevRightNode) { $(prevRightNode).before($trRight); } else { $brTableBody.append($trRight); } $trRight.show(); trRight.ftnode = node; node.trRight = trRight; $trLeft .find("td") .slice(fixedColCount) .remove(); $trRight .find("td") .slice(0, fixedColCount) .remove(); $trLeft.data(fcn.counterpart, $trRight); $trRight.data(fcn.counterpart, $trLeft); } } return res; }, nodeRenderTitle: function(ctx, title) { return this._superApply(arguments); }, nodeRenderStatus: function(ctx) { var res = this._superApply(arguments), node = ctx.node; if (node.trRight) { var $trRight = $(node.trRight), $trLeft = $(node.tr), fcn = this.options.fixed.classNames, hovering = $trRight.hasClass(fcn.hover), trClasses = $trLeft.attr("class"); $trRight.attr("class", trClasses); if (hovering) { $trRight.addClass(fcn.hover); $trLeft.addClass(fcn.hover); } } return res; }, nodeSetExpanded: function(ctx, flag, callOpts) { var res, self = this, node = ctx.node, $leftTr = $(node.tr), fcn = this.options.fixed.classNames, cn = this.options._classNames, $rightTr = $leftTr.data(fcn.counterpart); flag = typeof flag === "undefined" ? true : flag; if (!$rightTr) { return this._superApply(arguments); } $rightTr.toggleClass(cn.expanded, !!flag); if (flag && !node.isExpanded()) { res = this._superApply(arguments); res.done(function() { node.visit(function(child) { var $trLeft = $(child.tr), $trRight = $trLeft.data(fcn.counterpart); self.ext.fixed._adjustRowHeight($trLeft, $trRight); if (!child.expanded) { return "skip"; } }); self.ext.fixed._adjustColWidths(); self.ext.fixed._adjustWrapperLayout(); }); } else if (!flag && node.isExpanded()) { node.visit(function(child) { var $trLeft = $(child.tr), $trRight = $trLeft.data(fcn.counterpart); if ($trRight) { if (!child.expanded) { return "skip"; } } }); self.ext.fixed._adjustColWidths(); self.ext.fixed._adjustWrapperLayout(); res = this._superApply(arguments); } else { res = this._superApply(arguments); } return res; }, nodeSetStatus: function(ctx, status, message, details) { return this._superApply(arguments); }, treeClear: function(ctx) { var tree = ctx.tree, $table = tree.widget.element, $wrapper = this.$fixedWrapper, fcn = this.options.fixed.classNames; $table .find("tr, td, th, thead") .removeClass(fcn.hidden) .css({ "min-width": "auto", height: "auto", }); $wrapper.empty().append($table); return this._superApply(arguments); }, treeRegisterNode: function(ctx, add, node) { return this._superApply(arguments); }, treeDestroy: function(ctx) { var tree = ctx.tree, $table = tree.widget.element, $wrapper = this.$fixedWrapper, fcn = this.options.fixed.classNames; $table .find("tr, td, th, thead") .removeClass(fcn.hidden) .css({ "min-width": "auto", height: "auto", }); $wrapper.empty().append($table); return this._superApply(arguments); }, _adjustColWidths: function() { if (this.options.fixed.adjustColWidths) { this.options.fixed.adjustColWidths.call(this); return; } var $wrapper = this.$fixedWrapper, fcn = this.options.fixed.classNames, $tlWrapper = $wrapper.find("div." + fcn.topLeft), $blWrapper = $wrapper.find("div." + fcn.bottomLeft), $trWrapper = $wrapper.find("div." + fcn.topRight), $brWrapper = $wrapper.find("div." + fcn.bottomRight); function _adjust($topWrapper, $bottomWrapper) { var $trTop = $topWrapper.find("thead tr").first(), $trBottom = $bottomWrapper.find("tbody tr").first(); $trTop.find("th").each(function(idx) { var $thTop = $(this), $tdBottom = $trBottom.find("td").eq(idx), thTopWidth = $thTop.width(), thTopOuterWidth = $thTop.outerWidth(), tdBottomWidth = $tdBottom.width(), tdBottomOuterWidth = $tdBottom.outerWidth(), newWidth = Math.max( thTopOuterWidth, tdBottomOuterWidth ); $thTop.css( "min-width", newWidth - (thTopOuterWidth - thTopWidth) ); $tdBottom.css( "min-width", newWidth - (tdBottomOuterWidth - tdBottomWidth) ); }); } _adjust($tlWrapper, $blWrapper); _adjust($trWrapper, $brWrapper); }, _adjustRowHeight: function($tr1, $tr2) { var fcn = this.options.fixed.classNames; if (!$tr2) { $tr2 = $tr1.data(fcn.counterpart); } $tr1.css("height", "auto"); $tr2.css("height", "auto"); var row1Height = $tr1.outerHeight(), row2Height = $tr2.outerHeight(), newHeight = Math.max(row1Height, row2Height); $tr1.css("height", newHeight + 1); $tr2.css("height", newHeight + 1); }, _adjustWrapperLayout: function() { var $wrapper = this.$fixedWrapper, fcn = this.options.fixed.classNames, $topLeftWrapper = $wrapper.find("div." + fcn.topLeft), $topRightWrapper = $wrapper.find("div." + fcn.topRight), $bottomLeftWrapper = $wrapper.find("div." + fcn.bottomLeft), $bottomRightWrapper = $wrapper.find("div." + fcn.bottomRight), $topLeftTable = $topLeftWrapper.find("table"), $topRightTable = $topRightWrapper.find("table"), // $bottomLeftTable = $bottomLeftWrapper.find("table"), wrapperWidth = $wrapper.width(), wrapperHeight = $wrapper.height(), fixedWidth = Math.min(wrapperWidth, $topLeftTable.width()), fixedHeight = Math.min( wrapperHeight, Math.max($topLeftTable.height(), $topRightTable.height()) ); // vScrollbar = $bottomRightWrapper.get(0).scrollHeight > (wrapperHeight - fixedHeight), // hScrollbar = $bottomRightWrapper.get(0).scrollWidth > (wrapperWidth - fixedWidth); $topLeftWrapper.css({ width: fixedWidth, height: fixedHeight, }); $topRightWrapper.css({ // width: wrapperWidth - fixedWidth - (vScrollbar ? 17 : 0), // width: "calc(100% - " + (fixedWidth + (vScrollbar ? 17 : 0)) + "px)", width: "calc(100% - " + (fixedWidth + 17) + "px)", height: fixedHeight, left: fixedWidth, }); $bottomLeftWrapper.css({ width: fixedWidth, // height: vScrollbar ? wrapperHeight - fixedHeight - (hScrollbar ? 17 : 0) : "auto", // height: vScrollbar ? ("calc(100% - " + (fixedHeight + (hScrollbar ? 17 : 0)) + "px)") : "auto", // height: vScrollbar ? ("calc(100% - " + (fixedHeight + 17) + "px)") : "auto", height: "calc(100% - " + (fixedHeight + 17) + "px)", top: fixedHeight, }); $bottomRightWrapper.css({ // width: wrapperWidth - fixedWidth, // height: vScrollbar ? wrapperHeight - fixedHeight : "auto", width: "calc(100% - " + fixedWidth + "px)", // height: vScrollbar ? ("calc(100% - " + fixedHeight + "px)") : "auto", height: "calc(100% - " + fixedHeight + "px)", top: fixedHeight, left: fixedWidth, }); }, _adjustLayout: function() { var self = this, $wrapper = this.$fixedWrapper, fcn = this.options.fixed.classNames, $topLeftWrapper = $wrapper.find("div." + fcn.topLeft), $topRightWrapper = $wrapper.find("div." + fcn.topRight), $bottomLeftWrapper = $wrapper.find("div." + fcn.bottomLeft); // $bottomRightWrapper = $wrapper.find("div." + fcn.bottomRight) $topLeftWrapper.find("table tr").each(function(idx) { var $trRight = $topRightWrapper.find("tr").eq(idx); self.ext.fixed._adjustRowHeight($(this), $trRight); }); $bottomLeftWrapper .find("table tbody") .find("tr") .each(function(idx) { // var $trRight = $bottomRightWrapper.find("tbody").find("tr").eq(idx); self.ext.fixed._adjustRowHeight($(this)); }); self.ext.fixed._adjustColWidths.call(this); self.ext.fixed._adjustWrapperLayout.call(this); }, // treeSetFocus: function(ctx, flag) { //// alert("treeSetFocus" + ctx.tree.$container); // ctx.tree.$container.focus(); // $.ui.fancytree.focusTree = ctx.tree; // } }); // Value returned by `require('jquery.fancytree..')` return $.ui.fancytree; }); // End of closure