mar10/fancytree

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

Summary

Maintainability
C
1 day
Test Coverage
/*!
 * jquery.fancytree.wide.js
 * Support for 100% wide selection bars.
 * (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
 */

(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 ($) {
    "use strict";

    var reNumUnit = /^([+-]?(?:\d+|\d*\.\d+))([a-z]*|%)$/; // split "1.5em" to ["1.5", "em"]

    /*******************************************************************************
     * Private functions and variables
     */
    // var _assert = $.ui.fancytree.assert;

    /* Calculate inner width without scrollbar */
    // function realInnerWidth($el) {
    //     // http://blog.jquery.com/2012/08/16/jquery-1-8-box-sizing-width-csswidth-and-outerwidth/
    // //    inst.contWidth = parseFloat(this.$container.css("width"), 10);
    //     // 'Client width without scrollbar' - 'padding'
    //     return $el[0].clientWidth - ($el.innerWidth() -  parseFloat($el.css("width"), 10));
    // }

    /* Create a global embedded CSS style for the tree. */
    function defineHeadStyleElement(id, cssText) {
        id = "fancytree-style-" + id;
        var $headStyle = $("#" + id);

        if (!cssText) {
            $headStyle.remove();
            return null;
        }
        if (!$headStyle.length) {
            $headStyle = $("<style />")
                .attr("id", id)
                .addClass("fancytree-style")
                .prop("type", "text/css")
                .appendTo("head");
        }
        try {
            $headStyle.html(cssText);
        } catch (e) {
            // fix for IE 6-8
            $headStyle[0].styleSheet.cssText = cssText;
        }
        return $headStyle;
    }

    /* Calculate the CSS rules that indent title spans. */
    function renderLevelCss(
        containerId,
        depth,
        levelOfs,
        lineOfs,
        labelOfs,
        measureUnit
    ) {
        var i,
            prefix = "#" + containerId + " span.fancytree-level-",
            rules = [];

        for (i = 0; i < depth; i++) {
            rules.push(
                prefix +
                    (i + 1) +
                    " span.fancytree-title { padding-left: " +
                    (i * levelOfs + lineOfs) +
                    measureUnit +
                    "; }"
            );
        }
        // Some UI animations wrap the UL inside a DIV and set position:relative on both.
        // This breaks the left:0 and padding-left:nn settings of the title
        rules.push(
            "#" +
                containerId +
                " div.ui-effects-wrapper ul li span.fancytree-title, " +
                "#" +
                containerId +
                " li.fancytree-animating span.fancytree-title " + // #716
                "{ padding-left: " +
                labelOfs +
                measureUnit +
                "; position: static; width: auto; }"
        );
        return rules.join("\n");
    }

    // /**
    //  * [ext-wide] Recalculate the width of the selection bar after the tree container
    //  * was resized.<br>
    //  * May be called explicitly on container resize, since there is no resize event
    //  * for DIV tags.
    //  *
    //  * @alias Fancytree#wideUpdate
    //  * @requires jquery.fancytree.wide.js
    //  */
    // $.ui.fancytree._FancytreeClass.prototype.wideUpdate = function(){
    //     var inst = this.ext.wide,
    //         prevCw = inst.contWidth,
    //         prevLo = inst.lineOfs;

    //     inst.contWidth = realInnerWidth(this.$container);
    //     // Each title is precceeded by 2 or 3 icons (16px + 3 margin)
    //     //     + 1px title border and 3px title padding
    //     // TODO: use code from treeInit() below
    //     inst.lineOfs = (this.options.checkbox ? 3 : 2) * 19;
    //     if( prevCw !== inst.contWidth || prevLo !== inst.lineOfs ) {
    //         this.debug("wideUpdate: " + inst.contWidth);
    //         this.visit(function(node){
    //             node.tree._callHook("nodeRenderTitle", node);
    //         });
    //     }
    // };

    /*******************************************************************************
     * Extension code
     */
    $.ui.fancytree.registerExtension({
        name: "wide",
        version: "@VERSION",
        // Default options for this extension.
        options: {
            iconWidth: null, // Adjust this if @fancy-icon-width != "16px"
            iconSpacing: null, // Adjust this if @fancy-icon-spacing != "3px"
            labelSpacing: null, // Adjust this if padding between icon and label != "3px"
            levelOfs: null, // Adjust this if ul padding != "16px"
        },

        treeCreate: function (ctx) {
            this._superApply(arguments);
            this.$container.addClass("fancytree-ext-wide");

            var containerId,
                cssText,
                iconSpacingUnit,
                labelSpacingUnit,
                iconWidthUnit,
                levelOfsUnit,
                instOpts = ctx.options.wide,
                // css sniffing
                $dummyLI = $(
                    "<li id='fancytreeTemp'><span class='fancytree-node'><span class='fancytree-icon' /><span class='fancytree-title' /></span><ul />"
                ).appendTo(ctx.tree.$container),
                $dummyIcon = $dummyLI.find(".fancytree-icon"),
                $dummyUL = $dummyLI.find("ul"),
                // $dummyTitle = $dummyLI.find(".fancytree-title"),
                iconSpacing =
                    instOpts.iconSpacing || $dummyIcon.css("margin-left"),
                iconWidth = instOpts.iconWidth || $dummyIcon.css("width"),
                labelSpacing = instOpts.labelSpacing || "3px",
                levelOfs = instOpts.levelOfs || $dummyUL.css("padding-left");

            $dummyLI.remove();

            iconSpacingUnit = iconSpacing.match(reNumUnit)[2];
            iconSpacing = parseFloat(iconSpacing, 10);
            labelSpacingUnit = labelSpacing.match(reNumUnit)[2];
            labelSpacing = parseFloat(labelSpacing, 10);
            iconWidthUnit = iconWidth.match(reNumUnit)[2];
            iconWidth = parseFloat(iconWidth, 10);
            levelOfsUnit = levelOfs.match(reNumUnit)[2];
            if (
                iconSpacingUnit !== iconWidthUnit ||
                levelOfsUnit !== iconWidthUnit ||
                labelSpacingUnit !== iconWidthUnit
            ) {
                $.error(
                    "iconWidth, iconSpacing, and levelOfs must have the same css measure unit"
                );
            }
            this._local.measureUnit = iconWidthUnit;
            this._local.levelOfs = parseFloat(levelOfs);
            this._local.lineOfs =
                (1 +
                    (ctx.options.checkbox ? 1 : 0) +
                    (ctx.options.icon === false ? 0 : 1)) *
                    (iconWidth + iconSpacing) +
                iconSpacing;
            this._local.labelOfs = labelSpacing;
            this._local.maxDepth = 10;

            // Get/Set a unique Id on the container (if not already exists)
            containerId = this.$container.uniqueId().attr("id");
            // Generated css rules for some levels (extended on demand)
            cssText = renderLevelCss(
                containerId,
                this._local.maxDepth,
                this._local.levelOfs,
                this._local.lineOfs,
                this._local.labelOfs,
                this._local.measureUnit
            );
            defineHeadStyleElement(containerId, cssText);
        },
        treeDestroy: function (ctx) {
            // Remove generated css rules
            defineHeadStyleElement(this.$container.attr("id"), null);
            return this._superApply(arguments);
        },
        nodeRenderStatus: function (ctx) {
            var containerId,
                cssText,
                res,
                node = ctx.node,
                level = node.getLevel();

            res = this._super(ctx);
            // Generate some more level-n rules if required
            if (level > this._local.maxDepth) {
                containerId = this.$container.attr("id");
                this._local.maxDepth *= 2;
                node.debug(
                    "Define global ext-wide css up to level " +
                        this._local.maxDepth
                );
                cssText = renderLevelCss(
                    containerId,
                    this._local.maxDepth,
                    this._local.levelOfs,
                    this._local.lineOfs,
                    this._local.labelSpacing,
                    this._local.measureUnit
                );
                defineHeadStyleElement(containerId, cssText);
            }
            // Add level-n class to apply indentation padding.
            // (Setting element style would not work, since it cannot easily be
            // overriden while animations run)
            $(node.span).addClass("fancytree-level-" + level);
            return res;
        },
    });
    // Value returned by `require('jquery.fancytree..')`
    return $.ui.fancytree;
}); // End of closure