zul/src/main/resources/web/js/zul/tab/Tab.ts
/* 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 = ' ';
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;