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