zul/src/main/resources/web/js/zul/tab/Tab.ts

Summary

Maintainability
A
0 mins
Test Coverage
/* Tab.ts

{{IS_NOTE
    Purpose:

    Description:

    History:
        Fri Jan 23 10:32:51 TST 2009, Created by Flyworld
}}IS_NOTE

Copyright (C) 2008 Potix Corporation. All Rights Reserved.

{{IS_RIGHT
}}IS_RIGHT
*/
// ZK-886, called by unbind_ and rerender
// this._oldId used in tab.js
// this.$n() will be cleared during rerender
// but LinkedPanel.firstChild will not,
// the condition LinkedPanel.firstChild != this.$n()
// will get the wrong result
// delete it later for the invalidate() case
function _logId(wgt: zul.tab.Tab): void {
    if (!wgt._oldId) {
        wgt._oldId = wgt.uuid;
        setTimeout(function () {
            delete wgt._oldId;
        }, 0);
    }
}
/**
 * A tab.
 * @defaultValue {@link getZclass}: z-tab.
 */
@zk.WrapClass('zul.tab.Tab')
export class Tab extends zul.LabelImageWidget implements zul.LabelImageWidgetWithDisable {
    override parent!: zul.tab.Tabs | undefined;

    /** @internal */
    _oldId?: string;
    /** @internal */
    _closable?: boolean;
    /** @internal */
    _disabled?: boolean;

    constructor() {
        super(); // FIXME: params?
        this.listen({ onClose: this }, -1000);
    }

    /**
     * @returns whether this tab is closable. If closable, a button is displayed
     * and the onClose event is sent if an user clicks the button.
     * @defaultValue `false`.
     */
    isClosable(): boolean {
        return !!this._closable;
    }

    /**
     * Sets whether this tab is closable. If closable, a button is displayed and
     * the onClose event is sent if an user clicks the button.
     * @defaultValue `false`.
     */
    setClosable(closable: boolean, opts?: Record<string, boolean>): this {
        const o = this._closable;
        this._closable = closable;

        if (o !== closable || opts?.force) {
            this.rerender();
        }

        return this;
    }

    override getImage(): string | undefined {
        return this._image;
    }

    override setImage(image: string, opts?: Record<string, boolean>): this {
        const o = this._image;
        this._image = image;

        if (o !== image || opts?.force) {
            if (image && this._preloadImage) zUtl.loadImage(image);
            this.rerender();
        }

        return this;
    }

    /**
     * @returns whether this tab is disabled.
     * @defaultValue `false`.
     */
    isDisabled(): boolean {
        return !!this._disabled;
    }

    /**
     * Sets whether this tab is disabled. If a tab is disabled, then it cann't
     * be selected or closed by user, but it still can be controlled by server
     * side program.
     */
    setDisabled(disabled: boolean, opts?: Record<string, boolean>): this {
        const o = this._disabled;
        this._disabled = disabled;

        if (o !== disabled || opts?.force) {
            this.rerender();
        }

        return this;
    }

    /**
     * @returns whether this tab is selected.
     */
    isSelected(): boolean {
        const tabbox = this.getTabbox();
        return tabbox?.getSelectedTab() == this;
    }

    /**
     * Sets whether this tab is selected.
     */
    setSelected(selected: boolean, fromServer?: boolean): this {
        const tabbox = this.getTabbox();
        if (tabbox && selected) {
            tabbox.setSelectedTab(this, fromServer);
        }
        return this;
    }

    /**
     * @returns the tabbox owns this component.
     */
    getTabbox(): zul.tab.Tabbox | undefined {
        return this.parent ? this.parent.parent : undefined;
    }

    /**
     * @returns the index of this panel, or -1 if it doesn't belong to any tabs.
     */
    getIndex(): number {
        return this.getChildIndex();
    }

    /**
     * @returns the panel associated with this tab.
     */
    getLinkedPanel(): zul.tab.Tabpanel | undefined {
        return this.getTabbox()?.getTabpanels()?.getChildAt<zul.tab.Tabpanel>(this.getIndex());
    }

    /** @internal */
    _doCloseClick(evt: zk.Event): void {
        if (!this._disabled) {
            this.fire('onClose');
            evt.stop();
        }
    }

    /** @internal */
    _sel(toSel: boolean, notify?: boolean): void {
        const tabbox = this.getTabbox();

        /* ZK-1441
         * If tabbox is animating (end-user click different tabs quickly), ignore this action.
         */
        if (!tabbox || tabbox._animating) return;

        const panel = this.getLinkedPanel(),
            inAccordion = tabbox.inAccordionMold();

        if (toSel) {
            const ps = tabbox.tabpanels;
            if (ps) {
                if (ps._selPnl && ps._selPnl != panel) ps._selPnl._sel(false, inAccordion);
                ps._selPnl = panel; //stored in tabpanels
            }
            tabbox._selTab = this;
        }

        if (!this.desktop) return;

        if (toSel)
            jq(this).addClass(this.$s('selected'));
        else
            jq(this).removeClass(this.$s('selected'));

        if (panel?.isVisible()) //Bug ZK-1618: not show tabpanel if visible is false
            panel._sel(toSel, true);

        if (!inAccordion) {
            this.parent?._fixWidth(toSel); //ZK-2810: don't set height to tabbox when deselect
        }

        if (toSel) {
            if (tabbox.isVertical())
                this.parent?._scrollcheck('vsel', this);
            else if (!tabbox.inAccordionMold())
                this.parent?._scrollcheck('sel', this);
        }

        if (notify)
            this.fire('onSelect', { items: [this], reference: this });
    }

    override setHeight(height: string | undefined): this {
        super.setHeight(height);
        if (this.desktop) {
            this._calcHgh();
            zUtl.fireSized(this.parent!);
        }
        return this;
    }

    override setWidth(width: string | undefined): this {
        super.setWidth(width);
        if (this.desktop)
            zUtl.fireSized(this.parent!);
        return this;
    }

    /** @internal */
    _calcHgh(): void {
        const tabbox = this.getTabbox()!;

        if (!tabbox.isVertical()) {
            const r = tabbox.$n('right'),
                l = tabbox.$n('left'),
                tb = tabbox.toolbar?.$n(),
                tabs = tabbox.tabs!.$n(),
                hgh = jq.px0(tabs ? tabs.offsetHeight : 0);

            if (r && l) {
                r.style.height = l.style.height = hgh;
            }
            if (tb) {
                tb.style.height = hgh;
            }
        }
    }

    //protected
    /** @internal */
    override doClick_(evt: zk.Event, popupOnly?: boolean): void {
        if (this._disabled) return;
        /* ZK-1441
         * If tabbox is animating (end-user click different tabs quickly), ignore this action.
         */
        if (this.getTabbox()?._animating) return;
        this.setSelected(true);
        super.doClick_(evt, popupOnly);
    }

    /** @internal */
    override domClass_(no?: zk.DomClassOptions): string {
        let sclsHTML = super.domClass_(no);
        if (!no || !no.zclass) {
            if (this.isDisabled()) sclsHTML += ' ' + this.$s('disabled');
            if (this.isSelected()) sclsHTML += ' ' + this.$s('selected');
        }
        return sclsHTML;
    }

    /** @internal */
    override domContent_(): string {
        let label = zUtl.encodeXML(this.getLabel()),
            img = this.getImage();
        const /*safe*/ iconSclass = this.domIcon_();

        if (!label) label = '&nbsp;';
        if (!img && !iconSclass) return label;
        if (!img) {
            img = iconSclass;
        } else
            /*safe*/ img = '<img src="' + /*safe*/ img + '" class="' + this.$s('image') + '" alt="" aria-hidden="true"/>'
                + (iconSclass ? ' ' + /*safe*/ iconSclass : '');
        return DOMPurify.sanitize(label ? img + ' ' + label : img);
    }

    //bug #3014664
    override setVflex(vflex: boolean | string | undefined): this { //vflex ignored for Tab
        if (vflex != 'min') vflex = false;
        return super.setVflex(vflex);
    }

    //bug #3014664
    override setHflex(hflex: boolean | string | undefined): this { //hflex ignored for Tab
        if (hflex != 'min') hflex = false;
        return super.setHflex(hflex);
    }

    /** @internal */
    override bind_(desktop?: zk.Desktop, skipper?: zk.Skipper, after?: CallableFunction[]): void {
        super.bind_(desktop, skipper, after);
        const closebtn = this.isClosable() ? this.$n('cls') : undefined;
        if (closebtn) {
            this.domListen_(closebtn, 'onClick', '_doCloseClick');
        }
        if (this.getHeight())
            this._calcHgh();

        //ZK-3016 make sure parent always do scrollCheck on child bind
        this.parent!._shallCheck = true;
    }

    /** @internal */
    override unbind_(skipper?: zk.Skipper, after?: CallableFunction[], keepRod?: boolean): void {
        const closebtn = this.$n('cls');
        // ZK-886
        _logId(this);
        if (closebtn) {
            this.domUnlisten_(closebtn, 'onClick', '_doCloseClick');
        }
        super.unbind_(skipper, after, keepRod);
    }

    //event handler//
    onClose(): void {
        if (this.getTabbox()!.inAccordionMold()) {
            this.getTabbox()!._syncSize();
        }
    }

    /** @internal */
    override deferRedrawHTML_(out: string[]): void {
        const tag = this.getTabbox()!.inAccordionMold() ? 'div' : 'li';
        out.push(`<${tag} ${this.domAttrs_({ domClass: true })} class="z-renderdefer"></${tag}>`);
    }

    override rerender(skipper?: number | zk.Skipper): this {
        // ZK-886
        if (this.desktop)
            _logId(this);
        super.rerender(skipper);
        return this;
    }

    /** @internal */
    contentRenderer_(out: string[]): void {
        out.push(`<span id="${this.uuid}-cnt" class="${this.$s('text')}">`, this.domContent_(), '</span>');
    }
}
/** @class zul.tab.TabRenderer
 * The renderer used to render a Tab.
 * It is designed to be overriden
 * @since 5.0.5
 */
export var TabRenderer = {
    /**
     * Check the Tab whether to render the frame
     */
    isFrameRequired(): boolean {
        return false;
    }
};
zul.tab.TabRenderer = TabRenderer;