1// Extending Fancytree
2// ===================
3//
4// See also the [live demo](https://wwWendt.de/tech/fancytree/demo/sample-ext-childcounter.html) of this code.
5//
6// Every extension should have a comment header containing some information
7// about the author, copyright and licensing. Also a pointer to the latest
8// source code.
9// Prefix with `/*!` so the comment is not removed by the minifier.
10
11/*!
12 * jquery.fancytree.childcounter.js
13 *
14 * Add a child counter bubble to tree nodes.
15 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
16 *
17 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
18 *
19 * Released under the MIT license
20 * https://github.com/mar10/fancytree/wiki/LicenseInfo
21 *
22 * @version 2.38.3
23 * @date 2023-02-01T20:52:50Z
24 */
25
26// To keep the global namespace clean, we wrap everything in a closure.
27// The UMD wrapper pattern defines the dependencies on jQuery and the
28// Fancytree core module, and makes sure that we can use the `require()`
29// syntax with package loaders.
30
31(function (factory) {
32	if (typeof define === "function" && define.amd) {
33		// AMD. Register as an anonymous module.
34		define(["jquery", "./jquery.fancytree"], factory);
35	} else if (typeof module === "object" && module.exports) {
36		// Node/CommonJS
37		require("./jquery.fancytree");
38		module.exports = factory(require("jquery"));
39	} else {
40		// Browser globals
41		factory(jQuery);
42	}
43})(function ($) {
44	// Consider to use [strict mode](http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/)
45	"use strict";
46
47	// The [coding guidelines](http://contribute.jquery.org/style-guide/js/)
48	// require jshint /eslint compliance.
49	// But for this sample, we want to allow unused variables for demonstration purpose.
50
51	/*eslint-disable no-unused-vars */
52
53	// Adding methods
54	// --------------
55
56	// New member functions can be added to the `Fancytree` class.
57	// This function will be available for every tree instance:
58	//
59	//     var tree = $.ui.fancytree.getTree("#tree");
60	//     tree.countSelected(false);
61
62	$.ui.fancytree._FancytreeClass.prototype.countSelected = function (
63		topOnly
64	) {
65		var tree = this,
66			treeOptions = tree.options;
67
68		return tree.getSelectedNodes(topOnly).length;
69	};
70
71	// The `FancytreeNode` class can also be easily extended. This would be called
72	// like
73	//     node.updateCounters();
74	//
75	// It is also good practice to add a docstring comment.
76	/**
77	 * [ext-childcounter] Update counter badges for `node` and its parents.
78	 * May be called in the `loadChildren` event, to update parents of lazy loaded
79	 * nodes.
80	 * @alias FancytreeNode#updateCounters
81	 * @requires jquery.fancytree.childcounters.js
82	 */
83	$.ui.fancytree._FancytreeNodeClass.prototype.updateCounters = function () {
84		var node = this,
85			$badge = $("span.fancytree-childcounter", node.span),
86			extOpts = node.tree.options.childcounter,
87			count = node.countChildren(extOpts.deep);
88
89		node.data.childCounter = count;
90		if (
91			(count || !extOpts.hideZeros) &&
92			(!node.isExpanded() || !extOpts.hideExpanded)
93		) {
94			if (!$badge.length) {
95				$badge = $("<span class='fancytree-childcounter'/>").appendTo(
96					$(
97						"span.fancytree-icon,span.fancytree-custom-icon",
98						node.span
99					)
100				);
101			}
102			$badge.text(count);
103		} else {
104			$badge.remove();
105		}
106		if (extOpts.deep && !node.isTopLevel() && !node.isRootNode()) {
107			node.parent.updateCounters();
108		}
109	};
110
111	// Finally, we can extend the widget API and create functions that are called
112	// like so:
113	//
114	//     $("#tree").fancytree("widgetMethod1", "abc");
115
116	$.ui.fancytree.prototype.widgetMethod1 = function (arg1) {
117		var tree = this.tree;
118		return arg1;
119	};
120
121	// Register a Fancytree extension
122	// ------------------------------
123	// A full blown extension, extension is available for all trees and can be
124	// enabled like so (see also the [live demo](https://wwWendt.de/tech/fancytree/demo/sample-ext-childcounter.html)):
125	//
126	//    <script src="../src/jquery.fancytree.js"></script>
127	//    <script src="../src/jquery.fancytree.childcounter.js"></script>
128	//    ...
129	//
130	//     $("#tree").fancytree({
131	//         extensions: ["childcounter"],
132	//         childcounter: {
133	//             hideExpanded: true
134	//         },
135	//         ...
136	//     });
137	//
138
139	/* 'childcounter' extension */
140	$.ui.fancytree.registerExtension({
141		// Every extension must be registered by a unique name.
142		name: "childcounter",
143		// Version information should be compliant with [semver](http://semver.org)
144		version: "2.38.3",
145
146		// Extension specific options and their defaults.
147		// This options will be available as `tree.options.childcounter.hideExpanded`
148
149		options: {
150			deep: true,
151			hideZeros: true,
152			hideExpanded: false,
153		},
154
155		// Attributes other than `options` (or functions) can be defined here, and
156		// will be added to the tree.ext.EXTNAME namespace, in this case `tree.ext.childcounter.foo`.
157		// They can also be accessed as `this._local.foo` from within the extension
158		// methods.
159		foo: 42,
160
161		// Local functions are prefixed with an underscore '_'.
162		// Callable as `this._local._appendCounter()`.
163
164		_appendCounter: function (bar) {
165			var tree = this;
166		},
167
168		// **Override virtual methods for this extension.**
169		//
170		// Fancytree implements a number of 'hook methods', prefixed by 'node...' or 'tree...'.
171		// with a `ctx` argument (see [EventData](https://wwWendt.de/tech/fancytree/doc/jsdoc/global.html#EventData)
172		// for details) and an extended calling context:<br>
173		// `this`       : the Fancytree instance<br>
174		// `this._local`: the namespace that contains extension attributes and private methods (same as this.ext.EXTNAME)<br>
175		// `this._super`: the virtual function that was overridden (member of previous extension or Fancytree)
176		//
177		// See also the [complete list of available hook functions](https://wwWendt.de/tech/fancytree/doc/jsdoc/Fancytree_Hooks.html).
178
179		/* Init */
180		// `treeInit` is triggered when a tree is initalized. We can set up classes or
181		// bind event handlers here...
182		treeInit: function (ctx) {
183			var tree = this, // same as ctx.tree,
184				opts = ctx.options,
185				extOpts = ctx.options.childcounter;
186			// Optionally check for dependencies with other extensions
187			/* this._requireExtension("glyph", false, false); */
188			// Call the base implementation
189			this._superApply(arguments);
190			// Add a class to the tree container
191			this.$container.addClass("fancytree-ext-childcounter");
192		},
193
194		// Destroy this tree instance (we only call the default implementation, so
195		// this method could as well be omitted).
196
197		treeDestroy: function (ctx) {
198			this._superApply(arguments);
199		},
200
201		// Overload the `renderTitle` hook, to append a counter badge
202		nodeRenderTitle: function (ctx, title) {
203			var node = ctx.node,
204				extOpts = ctx.options.childcounter,
205				count =
206					node.data.childCounter == null
207						? node.countChildren(extOpts.deep)
208						: +node.data.childCounter;
209			// Let the base implementation render the title
210			// We use `_super()` instead of `_superApply()` here, since it is a little bit
211			// more performant when called often
212			this._super(ctx, title);
213			// Append a counter badge
214			if (
215				(count || !extOpts.hideZeros) &&
216				(!node.isExpanded() || !extOpts.hideExpanded)
217			) {
218				$(
219					"span.fancytree-icon,span.fancytree-custom-icon",
220					node.span
221				).append(
222					$("<span class='fancytree-childcounter'/>").text(count)
223				);
224			}
225		},
226		// Overload the `setExpanded` hook, so the counters are updated
227		nodeSetExpanded: function (ctx, flag, callOpts) {
228			var tree = ctx.tree,
229				node = ctx.node;
230			// Let the base implementation expand/collapse the node, then redraw the title
231			// after the animation has finished
232			return this._superApply(arguments).always(function () {
233				tree.nodeRenderTitle(ctx);
234			});
235		},
236
237		// End of extension definition
238	});
239	// Value returned by `require('jquery.fancytree..')`
240	return $.ui.fancytree;
241}); // End of closure
242