PageSourceSearch

https://acclaim.edu.au/js/fancy-tree/dist/modules/jquery.fancytree.table.js

js acclaim.edu.au collected 2026-10-05 06:48:29 UTC 15,966 bytes, 546 lines download raw bytes

vendor: 15,966 bytes, lines 1-546
1/*!
2 * jquery.fancytree.table.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-2019, Martin Wendt (https://wwWendt.de)
8 *
9 * Released under the MIT license
10 * https://github.com/mar10/fancytree/wiki/LicenseInfo
11 *
12 * @version 2.33.0
13 * @date 2019-10-29T08:00:07Z
14 */
15
16(function(factory) {
17	if (typeof define === "function" && define.amd) {
18		// AMD. Register as an anonymous module.
19		define(["jquery", "./jquery.fancytree"], factory);
20	} else if (typeof module === "object" && module.exports) {
21		// Node/CommonJS
22		require("./jquery.fancytree");
23		module.exports = factory(require("jquery"));
24	} else {
25		// Browser globals
26		factory(jQuery);
27	}
28})(function($) {
29	"use strict";
30
31	/******************************************************************************
32	 * Private functions and variables
33	 */
34	function _assert(cond, msg) {
35		msg = msg || "";
36		if (!cond) {
37			$.error("Assertion failed " + msg);
38		}
39	}
40
41	function insertFirstChild(referenceNode, newNode) {
42		referenceNode.insertBefore(newNode, referenceNode.firstChild);
43	}
44
45	function insertSiblingAfter(referenceNode, newNode) {
46		referenceNode.parentNode.insertBefore(
47			newNode,
48			referenceNode.nextSibling
49		);
50	}
51
52	/* Show/hide all rows that are structural descendants of `parent`. */
53	function setChildRowVisibility(parent, flag) {
54		parent.visit(function(node) {
55			var tr = node.tr;
56			// currentFlag = node.hide ? false : flag; // fix for ext-filter
57			if (tr) {
58				tr.style.display = node.hide || !flag ? "none" : "";
59			}
60			if (!node.expanded) {
61				return "skip";
62			}
63		});
64	}
65
66	/* Find node that is rendered in previous row. */
67	function findPrevRowNode(node) {
68		var i,
69			last,
70			prev,
71			parent = node.parent,
72			siblings = parent ? parent.children : null;
73
74		if (siblings && siblings.length > 1 && siblings[0] !== node) {
75			// use the lowest descendant of the preceeding sibling
76			i = $.inArray(node, siblings);
77			prev = siblings[i - 1];
78			_assert(prev.tr);
79			// descend to lowest child (with a <tr> tag)
80			while (prev.children && prev.children.length) {
81				last = prev.children[prev.children.length - 1];
82				if (!last.tr) {
83					break;
84				}
85				prev = last;
86			}
87		} else {
88			// if there is no preceding sibling, use the direct parent
89			prev = parent;
90		}
91		return prev;
92	}
93
94	$.ui.fancytree.registerExtension({
95		name: "table",
96		version: "2.33.0",
97		// Default options for this extension.
98		options: {
99			checkboxColumnIdx: null, // render the checkboxes into the this column index (default: nodeColumnIdx)
100			indentation: 16, // indent every node level by 16px
101			mergeStatusColumns: true, // display 'nodata', 'loading', 'error' centered in a single, merged TR
102			nodeColumnIdx: 0, // render node expander, icon, and title to this column (default: #0)
103		},
104		// Overide virtual methods for this extension.
105		// `this`       : is this extension object
106		// `this._super`: the virtual function that was overriden (member of prev. extension or Fancytree)
107		treeInit: function(ctx) {
108			var i,
109				n,
110				$row,
111				$tbody,
112				tree = ctx.tree,
113				opts = ctx.options,
114				tableOpts = opts.table,
115				$table = tree.widget.element;
116
117			if (tableOpts.customStatus != null) {
118				if (opts.renderStatusColumns == null) {
119					tree.warn(
120						"The 'customStatus' option is deprecated since v2.15.0. Use 'renderStatusColumns' instead."
121					);
122					opts.renderStatusColumns = tableOpts.customStatus;
123				} else {
124					$.error(
125						"The 'customStatus' option is deprecated since v2.15.0. Use 'renderStatusColumns' only instead."
126					);
127				}
128			}
129			if (opts.renderStatusColumns) {
130				if (opts.renderStatusColumns === true) {
131					opts.renderStatusColumns = opts.renderColumns;
132					// } else if( opts.renderStatusColumns === "wide" ) {
133					// 	opts.renderStatusColumns = _renderStatusNodeWide;
134				}
135			}
136
137			$table.addClass("fancytree-container fancytree-ext-table");
138			$tbody = $table.find(">tbody");
139			if (!$tbody.length) {
140				// TODO: not sure if we can rely on browsers to insert missing <tbody> before <tr>s:
141				if ($table.find(">tr").length) {
142					$.error(
143						"Expected table > tbody > tr. If you see this please open an issue."
144					);
145				}
146				$tbody = $("<tbody>").appendTo($table);
147			}
148
149			tree.tbody = $tbody[0];
150
151			// Prepare row templates:
152			// Determine column count from table header if any
153			tree.columnCount = $("thead >tr", $table)
154				.last()
155				.find(">th", $table).length;
156			// Read TR templates from tbody if any
157			$row = $tbody.children("tr").first();
158			if ($row.length) {
159				n = $row.children("td").length;
160				if (tree.columnCount && n !== tree.columnCount) {
161					tree.warn(
162						"Column count mismatch between thead (" +
163							tree.columnCount +
164							") and tbody (" +
165							n +
166							"): using tbody."
167					);
168					tree.columnCount = n;
169				}
170				$row = $row.clone();
171			} else {
172				// Only thead is defined: create default row markup
173				_assert(
174					tree.columnCount >= 1,
175					"Need either <thead> or <tbody> with <td> elements to determine column count."
176				);
177				$row = $("<tr />");
178				for (i = 0; i < tree.columnCount; i++) {
179					$row.append("<td />");
180				}
181			}
182			$row.find(">td")
183				.eq(tableOpts.nodeColumnIdx)
184				.html("<span class='fancytree-node' />");
185			if (opts.aria) {
186				$row.attr("role", "row");
187				$row.find("td").attr("role", "gridcell");
188			}
189			tree.rowFragment = document.createDocumentFragment();
190			tree.rowFragment.appendChild($row.get(0));
191
192			// // If tbody contains a second row, use this as status node template
193			// $row = $tbody.children("tr").eq(1);
194			// if( $row.length === 0 ) {
195			// 	tree.statusRowFragment = tree.rowFragment;
196			// } else {
197			// 	$row = $row.clone();
198			// 	tree.statusRowFragment = document.createDocumentFragment();
199			// 	tree.statusRowFragment.appendChild($row.get(0));
200			// }
201			//
202			$tbody.empty();
203
204			// Make sure that status classes are set on the node's <tr> elements
205			tree.statusClassPropName = "tr";
206			tree.ariaPropName = "tr";
207			this.nodeContainerAttrName = "tr";
208
209			// #489: make sure $container is set to <table>, even if ext-dnd is listed before ext-table
210			tree.$container = $table;
211
212			this._superApply(arguments);
213
214			// standard Fancytree created a root UL
215			$(tree.rootNode.ul).remove();
216			tree.rootNode.ul = null;
217
218			// Add container to the TAB chain
219			// #577: Allow to set tabindex to "0", "-1" and ""
220			this.$container.attr("tabindex", opts.tabindex);
221			// this.$container.attr("tabindex", opts.tabbable ? "0" : "-1");
222			if (opts.aria) {
223				tree.$container
224					.attr("role", "treegrid")
225					.attr("aria-readonly", true);
226			}
227		},
228		nodeRemoveChildMarkup: function(ctx) {
229			var node = ctx.node;
230			//		node.debug("nodeRemoveChildMarkup()");
231			node.visit(function(n) {
232				if (n.tr) {
233					$(n.tr).remove();
234					n.tr = null;
235				}
236			});
237		},
238		nodeRemoveMarkup: function(ctx) {
239			var node = ctx.node;
240			//		node.debug("nodeRemoveMarkup()");
241			if (node.tr) {
242				$(node.tr).remove();
243				node.tr = null;
244			}
245			this.nodeRemoveChildMarkup(ctx);
246		},
247		/* Override standard render. */
248		nodeRender: function(ctx, force, deep, collapsed, _recursive) {
249			var children,
250				firstTr,
251				i,
252				l,
253				newRow,
254				prevNode,
255				prevTr,
256				subCtx,
257				tree = ctx.tree,
258				node = ctx.node,
259				opts = ctx.options,
260				isRootNode = !node.parent;
261
262			if (tree._enableUpdate === false) {
263				// $.ui.fancytree.debug("*** nodeRender _enableUpdate: false");
264				return;
265			}
266			if (!_recursive) {
267				ctx.hasCollapsedParents = node.parent && !node.parent.expanded;
268			}
269			// $.ui.fancytree.debug("*** nodeRender " + node + ", isRoot=" + isRootNode, "tr=" + node.tr, "hcp=" + ctx.hasCollapsedParents, "parent.tr=" + (node.parent && node.parent.tr));
270			if (!isRootNode) {
271				if (node.tr && force) {
272					this.nodeRemoveMarkup(ctx);
273				}
274				if (node.tr) {
275					if (force) {
276						// Set icon, link, and title (normally this is only required on initial render)
277						this.nodeRenderTitle(ctx); // triggers renderColumns()
278					} else {
279						// Update element classes according to node state
280						this.nodeRenderStatus(ctx);
281					}
282				} else {
283					if (ctx.hasCollapsedParents && !deep) {
284						// #166: we assume that the parent will be (recursively) rendered
285						// later anyway.
286						// node.debug("nodeRender ignored due to unrendered parent");
287						return;
288					}
289					// Create new <tr> after previous row
290					// if( node.isStatusNode() ) {
291					// 	newRow = tree.statusRowFragment.firstChild.cloneNode(true);
292					// } else {
293					newRow = tree.rowFragment.firstChild.cloneNode(true);
294					// }
295					prevNode = findPrevRowNode(node);
296					// $.ui.fancytree.debug("*** nodeRender " + node + ": prev: " + prevNode.key);
297					_assert(prevNode);
298					if (collapsed === true && _recursive) {
299						// hide all child rows, so we can use an animation to show it later
300						newRow.style.display = "none";
301					} else if (deep && ctx.hasCollapsedParents) {
302						// also hide this row if deep === true but any parent is collapsed
303						newRow.style.display = "none";
304						//					newRow.style.color = "red";
305					}
306					if (prevNode.tr) {
307						insertSiblingAfter(prevNode.tr, newRow);
308					} else {
309						_assert(
310							!prevNode.parent,
311							"prev. row must have a tr, or be system root"
312						);
313						// tree.tbody.appendChild(newRow);
314						insertFirstChild(tree.tbody, newRow); // #675
315					}
316					node.tr = newRow;
317					if (node.key && opts.generateIds) {
318						node.tr.id = opts.idPrefix + node.key;
319					}
320					node.tr.ftnode = node;
321					// if(opts.aria){
322					// 	$(node.tr).attr("aria-labelledby", "ftal_" + opts.idPrefix + node.key);
323					// }
324					node.span = $("span.fancytree-node", node.tr).get(0);
325					// Set icon, link, and title (normally this is only required on initial render)
326					this.nodeRenderTitle(ctx);
327					// Allow tweaking, binding, after node was created for the first time
328					//				tree._triggerNodeEvent("createNode", ctx);
329					if (opts.createNode) {
330						opts.createNode.call(tree, { type: "createNode" }, ctx);
331					}
332				}
333			}
334			// Allow tweaking after node state was rendered
335			//		tree._triggerNodeEvent("renderNode", ctx);
336			if (opts.renderNode) {
337				opts.renderNode.call(tree, { type: "renderNode" }, ctx);
338			}
339			// Visit child nodes
340			// Add child markup
341			children = node.children;
342			if (children && (isRootNode || deep || node.expanded)) {
343				for (i = 0, l = children.length; i < l; i++) {
344					subCtx = $.extend({}, ctx, { node: children[i] });
345					subCtx.hasCollapsedParents =
346						subCtx.hasCollapsedParents || !node.expanded;
347					this.nodeRender(subCtx, force, deep, collapsed, true);
348				}
349			}
350			// Make sure, that <tr> order matches node.children order.
351			if (children && !_recursive) {
352				// we only have to do it once, for the root branch
353				prevTr = node.tr || null;
354				firstTr = tree.tbody.firstChild;
355				// Iterate over all descendants
356				node.visit(function(n) {
357					if (n.tr) {
358						if (
359							!n.parent.expanded &&
360							n.tr.style.display !== "none"
361						) {
362							// fix after a node was dropped over a collapsed
363							n.tr.style.display = "none";
364							setChildRowVisibility(n, false);
365						}
366						if (n.tr.previousSibling !== prevTr) {
367							node.debug("_fixOrder: mismatch at node: " + n);
368							var nextTr = prevTr ? prevTr.nextSibling : firstTr;
369							tree.tbody.insertBefore(n.tr, nextTr);
370						}
371						prevTr = n.tr;
372					}
373				});
374			}
375			// Update element classes according to node state
376			// if(!isRootNode){
377			// 	this.nodeRenderStatus(ctx);
378			// }
379		},
380		nodeRenderTitle: function(ctx, title) {
381			var $cb,
382				res,
383				tree = ctx.tree,
384				node = ctx.node,
385				opts = ctx.options,
386				isStatusNode = node.isStatusNode();
387
388			res = this._super(ctx, title);
389
390			if (node.isRootNode()) {
391				return res;
392			}
393			// Move checkbox to custom column
394			if (
395				opts.checkbox &&
396				!isStatusNode &&
397				opts.table.checkboxColumnIdx != null
398			) {
399				$cb = $("span.fancytree-checkbox", node.span); //.detach();
400				$(node.tr)
401					.find("td")
402					.eq(+opts.table.checkboxColumnIdx)
403					.html($cb);
404			}
405			// Update element classes according to node state
406			this.nodeRenderStatus(ctx);
407
408			if (isStatusNode) {
409				if (opts.renderStatusColumns) {
410					// Let user code write column content
411					opts.renderStatusColumns.call(
412						tree,
413						{ type: "renderStatusColumns" },
414						ctx
415					);
416				} else if (opts.table.mergeStatusColumns && node.isTopLevel()) {
417					$(node.tr)
418						.find(">td")
419						.eq(0)
420						.prop("colspan", tree.columnCount)
421						.text(node.title)
422						.addClass("fancytree-status-merged")
423						.nextAll()
424						.remove();
425				} // else: default rendering for status node: leave other cells empty
426			} else if (opts.renderColumns) {
427				opts.renderColumns.call(tree, { type: "renderColumns" }, ctx);
428			}
429			return res;
430		},
431		nodeRenderStatus: function(ctx) {
432			var indent,
433				node = ctx.node,
434				opts = ctx.options;
435
436			this._super(ctx);
437
438			$(node.tr).removeClass("fancytree-node");
439			// indent
440			indent = (node.getLevel() - 1) * opts.table.indentation;
441			if (opts.rtl) {
442				$(node.span).css({ paddingRight: indent + "px" });
443			} else {
444				$(node.span).css({ paddingLeft: indent + "px" });
445			}
446		},
447		/* Expand node, return Deferred.promise. */
448		nodeSetExpanded: function(ctx, flag, callOpts) {
449			// flag defaults to true
450			flag = flag !== false;
451
452			if ((ctx.node.expanded && flag) || (!ctx.node.expanded && !flag)) {
453				// Expanded state isn't changed - just call base implementation
454				return this._superApply(arguments);
455			}
456
457			var dfd = new $.Deferred(),
458				subOpts = $.extend({}, callOpts, {
459					noEvents: true,
460					noAnimation: true,
461				});
462
463			callOpts = callOpts || {};
464
465			function _afterExpand(ok) {
466				setChildRowVisibility(ctx.node, flag);
467				if (ok) {
468					if (
469						flag &&
470						ctx.options.autoScroll &&
471						!callOpts.noAnimation &&
472						ctx.node.hasChildren()
473					) {
474						// Scroll down to last child, but keep current node visible
475						ctx.node
476							.getLastChild()
477							.scrollIntoView(true, { topNode: ctx.node })
478							.always(function() {
479								if (!callOpts.noEvents) {
480									ctx.tree._triggerNodeEvent(
481										flag ? "expand" : "collapse",
482										ctx
483									);
484								}
485								dfd.resolveWith(ctx.node);
486							});
487					} else {
488						if (!callOpts.noEvents) {
489							ctx.tree._triggerNodeEvent(
490								flag ? "expand" : "collapse",
491								ctx
492							);
493						}
494						dfd.resolveWith(ctx.node);
495					}
496				} else {
497					if (!callOpts.noEvents) {
498						ctx.tree._triggerNodeEvent(
499							flag ? "expand" : "collapse",
500							ctx
501						);
502					}
503					dfd.rejectWith(ctx.node);
504				}
505			}
506			// Call base-expand with disabled events and animation
507			this._super(ctx, flag, subOpts)
508				.done(function() {
509					_afterExpand(true);
510				})
511				.fail(function() {
512					_afterExpand(false);
513				});
514			return dfd.promise();
515		},
516		nodeSetStatus: function(ctx, status, message, details) {
517			if (status === "ok") {
518				var node = ctx.node,
519					firstChild = node.children ? node.children[0] : null;
520				if (firstChild && firstChild.isStatusNode()) {
521					$(firstChild.tr).remove();
522				}
523			}
524			return this._superApply(arguments);
525		},
526		treeClear: function(ctx) {
527			this.nodeRemoveChildMarkup(this._makeHookContext(this.rootNode));
528			return this._superApply(arguments);
529		},
530		treeDestroy: function(ctx) {
531			this.$container.find("tbody").empty();
532			if (this.$source) {
533				this.$source.removeClass("fancytree-helper-hidden");
534			}
535			return this._superApply(arguments);
536		},
537		/*,
538	treeSetFocus: function(ctx, flag) {
539//	        alert("treeSetFocus" + ctx.tree.$container);
540		ctx.tree.$container.focus();
541		$.ui.fancytree.focusTree = ctx.tree;
542	}*/
543	});
544	// Value returned by `require('jquery.fancytree..')`
545	return $.ui.fancytree;
546}); // End of closure

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.