Viewing File: /usr/local/cpanel/base/frontend/jupiter/_assets/breadcrumb.js

// Copyright 2023 cPanel, L.L.C. - All rights reserved.
// copyright@cpanel.net
// https://cpanel.net
// This code is subject to the cPanel license. Unauthorized copying is prohibited

/**
 * @example
 * Dispatch an event named "breadcrumbSetCrumbs" with the payload
 *
 * dispatchEvent(new CustomEvent('breadcrumbSetCrumbs', {
 *     bubbles: true,
 *     detail: {
 *       separator: ">",
 *       crumbs: [
 *	        {
 *              displayName: "",
 *              longName: "",
 *              link: ""
 *	        }
 *      ]
 *      }
 *   })
 *);
 *
 * Listen for event "breadcrumbNavigate" to pick up on user clicks of a breadcrumb element. Payload (e["detail"]):
 * {
 *      link: ""
 * }
 */

(function() {
    "use strict";

    /**
     * Breadcrumb class that handles all of logic for creating a breadcrumb DOM element
    */
    function Breadcrumb() {
        this.crumbs = [];
        this.crumbId = -1;
    }

    Breadcrumb.prototype = {

        /**
         * Adds an id to the array of breadcrumbs that was in the action
         * @param {Object[]} arrOfCrumbs Array of breadcrumbs to have an id added
        */
        setBreadcrumbId: function(arrOfCrumbs) {
            this.crumbs = [];
            this.crumbs = arrOfCrumbs.map(function(crumb, i) {
                this.crumbId++;
                return {
                    id: "breadcrumbItem_" + i,
                    displayName: crumb.displayName,
                    longName: crumb.longName,
                    link: crumb.link,
                    isRealHref: crumb.isRealHref,
                };
            }, this);
        },

        /**
         * Creates the breadcrumb DOM fragment.
         * @param {Object[]} arrOfCrumbs Array of breadcrumbs to have an id added
         * @param {string} separator A string of characters to be used for separating each breadcrumb
         * @throws Will throw an error when the container element is not present.
        */
        buildBreadcrumbFragment: function(arrOfCrumbs, separator) {
            var breadcrumbContainer = document.getElementById("cpanel-breadcrumbs");
            if (!breadcrumbContainer) {
                throw "Missing parent container for breadcrumbs";
            }

            // Note: Temporary solution to make breadcrumbs work with bootstrap5.
            // TODO: Create a breadcrumb component that works in all technologies
            var isJupiterStyle = breadcrumbContainer.getAttribute("data-jupiter-style");

            separator = separator || "/";
            this.setBreadcrumbId(arrOfCrumbs);
            var breadcrumbDOMFragment = document.createDocumentFragment();
            breadcrumbContainer.appendChild(breadcrumbDOMFragment);

            for (var i = 0; i < this.crumbs.length; i++) {
                var crumbsLength = this.crumbs.length;
                var crumb = this.crumbs[i];
                var element = this.createAnElement(crumb, i, crumbsLength);

                breadcrumbDOMFragment.appendChild(element);

                // eslint-disable-next-line eqeqeq
                if (isJupiterStyle == null) {
                    var separatorElement = this.createSeparatorElement(separator);

                    // Check that there are more than 1 breadcrumb and it's not on the last element before adding a separator element
                    if (
                        crumbsLength > 1 && i < crumbsLength - 1
                    ) {
                        breadcrumbDOMFragment.appendChild(separatorElement);
                    }
                }

            }

            breadcrumbContainer.textContent = "";
            breadcrumbContainer.appendChild(breadcrumbDOMFragment);

            // Display the element in case it was hidden previously
            breadcrumbContainer.style = "display: flex";
        },

        /**
         * The logic for deciding what type of element to create for the breadcrumb fragment.
         * @param {Object} crumb A specific breadcrumb JS object
         * @param {number} position The position of the breadcrumb JS object in the breadcrumb array
         * @param {number} length The length of the breadcrumb array
        */
        createAnElement: function(crumb, position, length) {

            // One item make it only a span with no link
            if (length === 1) {
                return this.createParentSpanElement(crumb);
            }

            // Make an anchor element that shows on mobile for the 2nd to last element in list
            if (position === (length - 2)) {
                return this.createParentAnchorElement(crumb);
            }

            // Make the last element a span without an anchor tag. Different from above because it hides on mobile
            if (position === (length - 1)) {
                return this.createLastSpanElement(crumb);
            }
            return this.createMiddleAnchorElement(crumb);
        },

        /**
         * Creates the seperator element for the breadcrumb fragment being created.
         * @param {string} separatorString String of characters used to separate each breadcrumb element.
        */
        createSeparatorElement: function(separatorString) {
            var separatorElement = document.createElement("span");
            separatorElement.setAttribute("class", "hidden-xs breadcrumb-separator");
            separatorElement.textContent = separatorString;
            return separatorElement;
        },

        /**
         * Creates a span element without an anchor tag.
         * @param {Object} crumb A specific breadcrumb JS object
        */
        createParentSpanElement: function(crumb) {
            var span = document.createElement("span");
            span.setAttribute("id", crumb.id);
            span.setAttribute("aria-current", "page");
            span.setAttribute("class", "breadcrumb-item");
            span.textContent = crumb.displayName;
            return span;
        },

        /**
         * Creates a span element without an anchor tag that is hidden on a mobile view.
         * @param {Object} crumb A specific breadcrumb JS object
        */
        createLastSpanElement: function(crumb) {
            var element = this.createParentSpanElement(crumb);
            element.setAttribute("class", "breadcrumb-item hidden-xs d-none d-sm-block");
            return element;
        },

        /**
         * Creates an anchor element that does not navigate and dispatches an event when clicked.
         * @param {Object} crumb A specific breadcrumb JS object
        */
        createParentAnchorElement: function(crumb) {
            var anchor = document.createElement("a");
            anchor.textContent = crumb.displayName;

            // This stops the anchor tag from doing anything and allows angular to handle routing
            var linkHref = "javascript:;";
            var isRealHref = Object.hasOwn(crumb, "isRealHref") && crumb.isRealHref;
            if ( isRealHref ) {
                linkHref = crumb.link;
            }
            anchor.setAttribute("href", linkHref);
            anchor.setAttribute("id", crumb.id);
            anchor.setAttribute("class", "breadcrumb-item");
            if ( Object.hasOwn( crumb, "longName" ) && crumb.longName ) {
                anchor.setAttribute("title", crumb.longName);
            }
            anchor.addEventListener("click", function(e) {
                anchor.dispatchEvent(new CustomEvent("breadcrumbNavigate", {
                    bubbles: true,
                    detail: {
                        link: crumb.link,
                    },
                }));
            });
            return anchor;
        },

        /**
         * Creates an anchor element that also hides on mobile.
         * @param {Object} crumb A specific breadcrumb JS object
        */
        createMiddleAnchorElement: function(crumb) {
            var element = this.createParentAnchorElement(crumb);
            element.setAttribute("class", "hidden-xs breadcrumb-item d-none d-sm-block");
            return element;
        },
    };

    function appendHelpLink(helpLink) {
        var breadcrumbContainer = document.getElementById("cpanel-breadcrumbs");
        var helpElem = document.createElement("a");
        helpElem.setAttribute( "href", helpLink );
        helpElem.setAttribute( "target", "_blank" );
        helpElem.setAttribute( "style", "text-decoration:none;" );
        var helpIcon = document.createElement("span");
        helpIcon.setAttribute( "title", "Documentation" ); // XXX TODO: Localize?
        helpIcon.setAttribute( "class", "ri-question-line" );
        helpIcon.setAttribute( "style", "margin-left:.25rem;width:1rem;height:1rem;color:black;");
        helpElem.appendChild(helpIcon);
        breadcrumbContainer.appendChild(helpElem);
    }

    var breadcrumb = new Breadcrumb();

    /**
     * Adds event listener to global window for setting breadcrumbs
    */
    document.addEventListener("breadcrumbSetCrumbs", function(e) {
        breadcrumb.buildBreadcrumbFragment(e.detail.crumbs, e.detail.separator);

        // Add help link if provided
        if ( Object.hasOwn(e.detail, "help") && e.detail.help ) {
            appendHelpLink(e.detail.help);
        }
    });
})();
Back to Directory File Manager