Source: ui/sidebar/SidebarElement.js

import { UiElement } from "../UiElement.js";
import "./SidebarElement.css";

export { SidebarElement };

/**
 * Extension of UiElement to be used as a baseclass
 * for Elements inside a sidebar. A title and a 
 * collapse feature is added to the element.
 * 
 * @author rhess <robin.hess@awi.de>
 * @memberof vef.ui.sidebar
 */
class SidebarElement extends UiElement {

    class = "sidebar-element";
    html = `
        <div class="sidebar-element-header">
            <div class="sidebar-element-header-inner">
                <span class="sidebar-element-title-span">
                    <span class="sidebar-element-collapse fas fa-chevron-down"></span>
                    <span class="sidebar-element-title"></span>
                </span>
                <span class="notification-counter"></span>
                <span class="sidebar-element-tools"></span>
            </div>
        </div>
        <div class="sidebar-element-content"></div>
    `;

    /** 
     * @param {string/HTMLElement} target id or HTMLElement (optional)
     * @param {Object} events events for the EventObject (optional)
     */
    constructor(target, events) {
        super(target, Object.assign({
            "collapse": [],
            "expand": []
        }, events || {}));

        this.setClass(this.class);
        this.setHtml(this.html);

        this.content_ = this.query(".sidebar-element-content");
        this.title_ = this.query(".sidebar-element-title");
        this.collapseButton_ = this.query(".sidebar-element-collapse");
        this.toolContainer_ = this.query(".sidebar-element-tools");
        this.notificationCounter_ = this.query(".notification-counter");
        this.timeout_ = null;
        this.currentHeight_ = null;

        this.initEvents_();
    }

    /**
     * init the element base structure
     * 
     * @private
     */
    initEvents_() {
        this.collapseButton_.addEventListener("click", () => {
            (this.content_.classList.contains("hidden"))
                ? this.expand()
                : this.collapse();
        }, false);
    }

    /** 
     * return the content DOM-element
     */
    getContentContainer() {
        return this.content_;
    }

    /**
     * set the content of the element
     */
    setContent(content) {
        if (typeof content === "string") {
            this.content_.innerHTML = content;
        } else if (content instanceof HTMLElement) {
            this.content_.innerHTML = "";
            this.content_.appendChild(content);
        }
    }

    /** 
     * change the title displayed in the
     * header of the element
     */
    setTitle(title) {
        if(title == undefined) return
        this.title_.innerText = title;
        this.title_.title = title;
    }

    /** 
     * collapse the content of the element
     */
    collapse() {
        if (this.timeout_) clearTimeout(this.timeout_);

        this.content_.classList.add("hidden");
        this.content_.style.overflow = "hidden";

        // set max height to current height for animation
        const rect = this.content_.getBoundingClientRect();
        this.currentHeight_ = rect.height + "px";
        this.content_.style.maxHeight = this.currentHeight_;
        // reflows DOM element before playing animation;
        this.content_.getClientRects();
        this.content_.style.maxHeight = "0px";

        // set "collapsed" state when animation is finished
        this.timeout_ = setTimeout(() => {
            this.content_.classList.add("collapsed");
            this.timeout_ = null;
        }, 333);

        this.collapseButton_.classList.remove("fa-chevron-down");
        this.collapseButton_.classList.add("fa-chevron-right");

        this.fire("collapse", this);
    }

    /** 
     * expand the content of the element
     */
    expand() {
        if (this.timeout_) clearTimeout(this.timeout_);

        this.content_.style.maxHeight = "0px";
        this.content_.classList.remove("hidden", "collapsed");
        // reflows DOM element before playing animation;
        this.content_.getClientRects();

        this.content_.style.maxHeight = this.currentHeight_ || (window.innerHeight + "px");

        // remove max-height after the animation is finished
        this.timeout_ = setTimeout(() => {
            this.content_.style.maxHeight = "unset";
            this.content_.style.overflow = "unset";
            this.timeout_ = null;
        }, 333);

        this.collapseButton_.classList.remove("fa-chevron-right");
        this.collapseButton_.classList.add("fa-chevron-down");

        this.fire("expand", this);
    }

    /** 
     * add a tool to the header as html or string.
     * If a customElement is defined, the other arguments have no effect
     * 
     * @param {string} icon icon css classes
     * @param {string} onClick callback function for click event. boolean payload defines enabled-state
     * @param {string} tooltip (optional) tooltip when hovering the mouse
     * @param {boolean} toggleMode switching between two states on each click
     * @param {HTMLElement} customElement (optional) custom element
     */
    addTool(icon, onClick, tooltip, toggleMode, customElement) {
        if (customElement instanceof HTMLElement) {
            this.toolContainer_.insertAdjacentElement("afterbegin", customElement);
        } else {
            const tool = document.createElement("div");
            tool.setAttribute("tabindex", "0");
            tool.classList.add("sidebar-element-tool", ...icon.split(" "));

            if (typeof tooltip == "string") {
                tool.title = tooltip.trim();
            }

            tool.addEventListener("click", e => {
                if (toggleMode) {
                    if (tool.classList.contains("enabled")) {
                        tool.classList.remove("enabled");
                        onClick(false, e);
                    } else {
                        tool.classList.add("enabled");
                        onClick(true, e);
                    }
                } else {
                    onClick(true, e);
                }
            });
            tool.addEventListener("keydown", e => {
                if(e.key != "Enter") return;
                if (toggleMode) {
                    if (tool.classList.contains("enabled")) {
                        tool.classList.remove("enabled");
                        onClick(false, e);
                    } else {
                        tool.classList.add("enabled");
                        onClick(true, e);
                    }
                } else {
                    onClick(true, e);
                }
            });

            this.toolContainer_.insertAdjacentElement("afterbegin", tool);
        }
    }

    /**
     * Set value for the notification bubble in the elements header
     * @param {number} cnt 
     */
    setNotificationCount(cnt) {
        if (Number.isFinite(cnt) && (cnt > 0)) {
            this.notificationCounter_.innerText = cnt;
        } else {
            this.notificationCounter_.innerText = "";
        }
    }

    /**
     * disable pointer events and make content transparent
     */
    disable() {
        this.setClass("disabled");
    }

    /**
     * enable pointer events and remove transparency of the content
     */
    enable() {
        this.removeClass("disabled");
    }
}