mar10/fancytree

View on GitHub
src/jquery.fancytree.childcounter.js

Summary

Maintainability
A
3 hrs
Test Coverage
// 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