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