Source: jquery.fancytree.childcounter.js

// Extending Fancytree
// ===================
//
// See also the [live demo](https://wwWendt.de/tech/fancytree/demo/sample-ext-childcounter.html) of this code.
//
// Every extension should have a comment header containing some information
// about the author, copyright and licensing. Also a pointer to the latest
// source code.
// Prefix with `/*!` so the comment is not removed by the minifier.

/*!
 * jquery.fancytree.childcounter.js
 *
 * Add a child counter bubble to tree nodes.
 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
 *
 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
 *
 * Released under the MIT license
 * https://github.com/mar10/fancytree/wiki/LicenseInfo
 *
 * @version @VERSION
 * @date @DATE
 */

// To keep the global namespace clean, we wrap everything in a closure.
// The UMD wrapper pattern defines the dependencies on jQuery and the
// Fancytree core module, and makes sure that we can use the `require()`
// syntax with package loaders.

(function (factory) {
	if (typeof define === "function" && define.amd) {
		// AMD. Register as an anonymous module.
		define(["jquery", "./jquery.fancytree"], factory);
	} else if (typeof module === "object" && module.exports) {
		// Node/CommonJS
		require("./jquery.fancytree");
		module.exports = factory(require("jquery"));
	} else {
		// Browser globals
		factory(jQuery);
	}
})(function ($) {
	// Consider to use [strict mode](http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/)
	"use strict";

	// The [coding guidelines](http://contribute.jquery.org/style-guide/js/)
	// require jshint /eslint compliance.
	// But for this sample, we want to allow unused variables for demonstration purpose.

	/*eslint-disable no-unused-vars */

	// Adding methods
	// --------------

	// New member functions can be added to the `Fancytree` class.
	// This function will be available for every tree instance:
	//
	//     var tree = $.ui.fancytree.getTree("#tree");
	//     tree.countSelected(false);

	$.ui.fancytree._FancytreeClass.prototype.countSelected = function (
		topOnly
	) {
		var tree = this,
			treeOptions = tree.options;

		return tree.getSelectedNodes(topOnly).length;
	};

	// The `FancytreeNode` class can also be easily extended. This would be called
	// like
	//     node.updateCounters();
	//
	// It is also good practice to add a docstring comment.
	/**
	 * [ext-childcounter] Update counter badges for `node` and its parents.
	 * May be called in the `loadChildren` event, to update parents of lazy loaded
	 * nodes.
	 * @alias FancytreeNode#updateCounters
	 * @requires jquery.fancytree.childcounters.js
	 */
	$.ui.fancytree._FancytreeNodeClass.prototype.updateCounters = function () {
		var node = this,
			$badge = $("span.fancytree-childcounter", node.span),
			extOpts = node.tree.options.childcounter,
			count = node.countChildren(extOpts.deep);

		node.data.childCounter = count;
		if (
			(count || !extOpts.hideZeros) &&
			(!node.isExpanded() || !extOpts.hideExpanded)
		) {
			if (!$badge.length) {
				$badge = $("<span class='fancytree-childcounter'/>").appendTo(
					$(
						"span.fancytree-icon,span.fancytree-custom-icon",
						node.span
					)
				);
			}
			$badge.text(count);
		} else {
			$badge.remove();
		}
		if (extOpts.deep && !node.isTopLevel() && !node.isRootNode()) {
			node.parent.updateCounters();
		}
	};

	// Finally, we can extend the widget API and create functions that are called
	// like so:
	//
	//     $("#tree").fancytree("widgetMethod1", "abc");

	$.ui.fancytree.prototype.widgetMethod1 = function (arg1) {
		var tree = this.tree;
		return arg1;
	};

	// Register a Fancytree extension
	// ------------------------------
	// A full blown extension, extension is available for all trees and can be
	// enabled like so (see also the [live demo](https://wwWendt.de/tech/fancytree/demo/sample-ext-childcounter.html)):
	//
	//    <script src="../src/jquery.fancytree.js"></script>
	//    <script src="../src/jquery.fancytree.childcounter.js"></script>
	//    ...
	//
	//     $("#tree").fancytree({
	//         extensions: ["childcounter"],
	//         childcounter: {
	//             hideExpanded: true
	//         },
	//         ...
	//     });
	//

	/* 'childcounter' extension */
	$.ui.fancytree.registerExtension({
		// Every extension must be registered by a unique name.
		name: "childcounter",
		// Version information should be compliant with [semver](http://semver.org)
		version: "@VERSION",

		// Extension specific options and their defaults.
		// This options will be available as `tree.options.childcounter.hideExpanded`

		options: {
			deep: true,
			hideZeros: true,
			hideExpanded: false,
		},

		// Attributes other than `options` (or functions) can be defined here, and
		// will be added to the tree.ext.EXTNAME namespace, in this case `tree.ext.childcounter.foo`.
		// They can also be accessed as `this._local.foo` from within the extension
		// methods.
		foo: 42,

		// Local functions are prefixed with an underscore '_'.
		// Callable as `this._local._appendCounter()`.

		_appendCounter: function (bar) {
			var tree = this;
		},

		// **Override virtual methods for this extension.**
		//
		// Fancytree implements a number of 'hook methods', prefixed by 'node...' or 'tree...'.
		// with a `ctx` argument (see [EventData](https://wwWendt.de/tech/fancytree/doc/jsdoc/global.html#EventData)
		// for details) and an extended calling context:<br>
		// `this`       : the Fancytree instance<br>
		// `this._local`: the namespace that contains extension attributes and private methods (same as this.ext.EXTNAME)<br>
		// `this._super`: the virtual function that was overridden (member of previous extension or Fancytree)
		//
		// See also the [complete list of available hook functions](https://wwWendt.de/tech/fancytree/doc/jsdoc/Fancytree_Hooks.html).

		/* Init */
		// `treeInit` is triggered when a tree is initalized. We can set up classes or
		// bind event handlers here...
		treeInit: function (ctx) {
			var tree = this, // same as ctx.tree,
				opts = ctx.options,
				extOpts = ctx.options.childcounter;
			// Optionally check for dependencies with other extensions
			/* this._requireExtension("glyph", false, false); */
			// Call the base implementation
			this._superApply(arguments);
			// Add a class to the tree container
			this.$container.addClass("fancytree-ext-childcounter");
		},

		// Destroy this tree instance (we only call the default implementation, so
		// this method could as well be omitted).

		treeDestroy: function (ctx) {
			this._superApply(arguments);
		},

		// Overload the `renderTitle` hook, to append a counter badge
		nodeRenderTitle: function (ctx, title) {
			var node = ctx.node,
				extOpts = ctx.options.childcounter,
				count =
					node.data.childCounter == null
						? node.countChildren(extOpts.deep)
						: +node.data.childCounter;
			// Let the base implementation render the title
			// We use `_super()` instead of `_superApply()` here, since it is a little bit
			// more performant when called often
			this._super(ctx, title);
			// Append a counter badge
			if (
				(count || !extOpts.hideZeros) &&
				(!node.isExpanded() || !extOpts.hideExpanded)
			) {
				$(
					"span.fancytree-icon,span.fancytree-custom-icon",
					node.span
				).append(
					$("<span class='fancytree-childcounter'/>").text(count)
				);
			}
		},
		// Overload the `setExpanded` hook, so the counters are updated
		nodeSetExpanded: function (ctx, flag, callOpts) {
			var tree = ctx.tree,
				node = ctx.node;
			// Let the base implementation expand/collapse the node, then redraw the title
			// after the animation has finished
			return this._superApply(arguments).always(function () {
				tree.nodeRenderTitle(ctx);
			});
		},

		// End of extension definition
	});
	// Value returned by `require('jquery.fancytree..')`
	return $.ui.fancytree;
}); // End of closure

Fork me on GitHub