zul/src/main/resources/web/js/zul/tab/Tabbox.ts
/* Tabbox.ts
{{IS_NOTE
Purpose:
Description:
History:
Fri Jan 23 10:32:34 TST 2009, Created by Flyworld
}}IS_NOTE
Copyright (C) 2008 Potix Corporation. All Rights Reserved.
{{IS_RIGHT
}}IS_RIGHT
*/
/** The tabbox related widgets, such as tabbox and tabpanel.
*/
//zk.$package('zul.tab');
// TabboxSkipper is only used in this file. No need to export TabboxSkipper.
class TabboxSkipper extends zk.Skipper {
skipper?: zk.Skipper;
constructor(skipper?: zk.Skipper) {
super();
this.skipper = skipper;
}
override skipped(wgt: Tabbox, child: zk.Widget): boolean {
return (wgt.toolbar === child && wgt.getMold() !== 'default') || !!this.skipper?.skipped(wgt, child);
}
}
/**
* A tabbox.
*
* <p>
* Event:
* <ol>
* <li>onSelect is sent when user changes the tab.</li>
* </ol>
*
* <p>
* Mold:
* <dl>
* <dt>default</dt>
* <dd>The default tabbox.</dd>
* <dt>accordion</dt>
* <dd>The accordion tabbox.</dd>
* </dl>
*
* <p>{@link Toolbar} only works in the horizontal default mold and
* the {@link isTabscroll} to be true.
*
* @defaultValue {@link getZclass}: z-tabbox.
* @import zul.wgt.Toolbar
*/
@zk.WrapClass('zul.tab.Tabbox')
export class Tabbox extends zul.Widget {
/** @internal */
_orient = 'top';
/** @internal */
_tabscroll = true;
/** @internal */
_maximalHeight = false;
/* ZK-1441
* Reference: doClick_() in Tab.js, _sel() in Tabpanel.js
*/
/** @internal */
_animating?: boolean = false;
/** @internal */
_nativebar = true;
/** @internal */
_panelSpacing?: string;
tabs?: zul.tab.Tabs;
tabpanels?: zul.tab.Tabpanels;
toolbar?: zul.wgt.Toolbar;
/** @internal */
_selTab?: zul.tab.Tab;
/** @internal */
_scrolling?: boolean;
/** @internal */
_toolbarWidth?: number;
/** @internal */
_shallSize?: boolean;
/**
* @returns whether the tab scrolling is enabled.
* @defaultValue `true`.
*/
isTabscroll(): boolean {
return this._tabscroll;
}
/**
* Sets whether to eable the tab scrolling
*/
setTabscroll(tabscroll: boolean, opts?: Record<string, boolean>): this {
const o = this._tabscroll;
this._tabscroll = tabscroll;
if (o !== tabscroll || opts?.force) {
this.rerender();
}
return this;
}
/**
* @returns the orient.
* @defaultValue `"top"`.
* <p>
* Note: only the default mold supports it (not supported if accordion).
*/
getOrient(): string {
return this._orient;
}
/**
* Sets the orient.
*
* @param orient - either "top", "left", "bottom or "right".
* @since 7.0.0 "horizontal" is renamed to "top" and "vertical" is renamed to "left".
*/
setOrient(orient: string, opts?: Record<string, boolean>): this {
const o = this._orient;
this._orient = orient;
if (o !== orient || opts?.force) {
if (orient == 'horizontal')
this._orient = 'top';
else if (orient == 'vertical')
this._orient = 'left';
this.rerender();
}
return this;
}
/**
* @returns whether to use maximum height of all tabpanel in initial phase or not.
* @defaultValue `false`.
* @since 7.0.0
*/
isMaximalHeight(): boolean {
return this._maximalHeight;
}
/**
* Sets whether to use maximum height of all tabpanel in initial phase or not.
* <p>
* The Client ROD feature will be disabled if it is set to true.
* @since 7.0.0
*/
setMaximalHeight(maximalHeight: boolean, opts?: Record<string, boolean>): this {
const o = this._maximalHeight;
this._maximalHeight = maximalHeight;
if (o !== maximalHeight || opts?.force) {
this.rerender();
}
return this;
}
/**
* @returns the spacing between {@link Tabpanel}. This is used by certain
* molds, such as accordion.
* @defaultValue `null` (no spacing).
*/
getPanelSpacing(): string | undefined {
return this._panelSpacing;
}
/**
* Sets the spacing between {@link Tabpanel}. This is used by certain molds,
* such as accordion.
*/
setPanelSpacing(panelSpacing: string, opts?: Record<string, boolean>): this {
const o = this._panelSpacing;
this._panelSpacing = panelSpacing;
if (o !== panelSpacing || opts?.force) {
this.rerender();
}
return this;
}
/**
* @returns the tabs that this tabbox owns.
*/
getTabs(): zul.tab.Tabs | undefined {
return this.tabs;
}
/**
* @returns the tabpanels that this tabbox owns.
*/
getTabpanels(): zul.tab.Tabpanels | undefined {
return this.tabpanels;
}
/**
* @returns the auxiliary toolbar that this tabbox owns.
*/
getToolbar(): zul.wgt.Toolbar | undefined {
return this.toolbar;
}
/** @internal */
override domClass_(no?: zk.DomClassOptions): string {
let scHTML = super.domClass_(no);
if (!no || !no.zclass) {
var cls = this.inAccordionMold() ?
this.$s('accordion') : this.$s(this.getOrient());
scHTML += ' ' + /*safe*/ cls;
}
return scHTML;
}
/**
* @returns whether it is a horizontal tabbox.
*/
isHorizontal(): boolean {
var orient = this.getOrient();
return 'horizontal' == orient || 'top' == orient || 'bottom' == orient;
}
/**
* @returns whether it is the top orientation.
*/
isTop(): boolean {
var orient = this.getOrient();
return 'horizontal' == orient || 'top' == orient;
}
/**
* @returns whether it is the bottom orientation.
*/
isBottom(): boolean {
return 'bottom' == this.getOrient();
}
/**
* @returns whether it is a vertical tabbox.
*/
isVertical(): boolean {
var orient = this.getOrient();
return 'vertical' == orient || 'left' == orient || 'right' == orient;
}
/**
* @returns whether it is the right orientation.
*/
isRight(): boolean {
return 'right' == this.getOrient();
}
/**
* @returns whether it is the left orientation.
*/
isLeft(): boolean {
var orient = this.getOrient();
return 'vertical' == orient || 'left' == orient;
}
/**
* @returns whether it is in the accordion mold.
*/
inAccordionMold(): boolean {
return this.getMold().includes('accordion');
}
/**
* @returns the selected index.
*/
getSelectedIndex(): number {
return this._selTab ? this._selTab.getIndex() : -1;
}
/**
* Sets the selected index.
*/
setSelectedIndex(selectedIndex: number): this {
if (this.tabs)
this.setSelectedTab(this.tabs.getChildAt<zul.tab.Tab>(selectedIndex));
return this;
}
/**
* @returns the selected tab panel.
*/
getSelectedPanel(): zul.tab.Tabpanel | undefined {
return this._selTab ? this._selTab.getLinkedPanel() : undefined;
}
/**
* Sets the selected tab panel.
*/
setSelectedPanel(selectedPanel: zul.tab.Tabpanel | undefined): this {
if (selectedPanel && selectedPanel.getTabbox() != this)
return this;
var tab = selectedPanel?.getLinkedTab();
if (tab)
this.setSelectedTab(tab);
return this;
}
/**
* @returns the selected tab.
*/
getSelectedTab(): zul.tab.Tab | undefined {
return this._selTab;
}
/**
* Sets the selected tab.
*/
setSelectedTab(selectedTab: zul.tab.Tab | undefined, fromServer?: boolean): this {
if (this._selTab != selectedTab) {
this._setSel(selectedTab, !fromServer);
this._selTab = selectedTab;
}
return this;
}
/** @internal */
override bind_(desktop?: zk.Desktop, skipper?: zk.Skipper, after?: CallableFunction[]): void {
super.bind_(desktop, new TabboxSkipper(skipper), after); // F61-ZK-970.zul
// used in Tabs.js
this._scrolling = false;
var toolbar = this.getToolbar();
if (this.inAccordionMold())
zWatch.listen({onResponse: this});
else if (toolbar && this.getTabs()) {
zWatch.listen({onResponse: this});
this._toolbarWidth = jq(toolbar.$n_()).width();
}
for (var key = ['right', 'left', 'down', 'up'], le = key.length; le--;) {
const btn = this.$n(key[le]);
if (btn)
this.domListen_(btn, 'onClick', '_doClick', key[le]);
}
this._fixMaxHeight();
zk.afterMount(() => {
const tabs = this.tabs,
seltab = this._selTab;
if (seltab && tabs) {
if (this.isVertical())
tabs._scrollcheck('vsel', seltab);
else if (!this.inAccordionMold())
tabs._scrollcheck('sel', seltab);
}
});
}
/** @internal */
override unbind_(skipper?: zk.Skipper, after?: CallableFunction[], keepRod?: boolean): void {
zWatch.unlisten({onResponse: this});
for (var key = ['right', 'left', 'down', 'up'], le = key.length; le--;) {
const btn = this.$n(key[le]);
if (btn)
this.domUnlisten_(btn, 'onClick', '_doClick', key[le]);
}
this._toolbarWidth = undefined;
super.unbind_(skipper, after, keepRod);
}
/** @internal */
_doClick(evt: zk.Event, direction: string): void {
if (!this.tabs || !this.tabs.nChildren) return; // nothing to do
var cave = this.tabs.$n_('cave'),
allTab = jq(cave).children(),
move = 0,
head = this.tabs.$n_(),
isVert = this.isVertical(),
scrollLength = isVert ? this.tabs._tabsScrollTop : this.tabs._tabsScrollLeft,
offsetLength = isVert ? head.offsetHeight : head.offsetWidth,
plus = scrollLength + offsetLength;
//Scroll to next right tab
switch (direction) {
case 'right':
for (var i = 0, count = allTab.length; i < count; i++) {
if (allTab[i].offsetLeft + allTab[i].offsetWidth > plus) {
move = allTab[i].offsetLeft + allTab[i].offsetWidth - scrollLength - offsetLength;
if (!move || isNaN(move))
return;
this.tabs._doScroll('right', move);
return;
}
}
break;
case 'left':
for (var i = 0, count = allTab.length; i < count; i++) {
if (allTab[i].offsetLeft >= scrollLength) {
//if no Sibling tab no scroll
var tabli = jq(allTab[i]).prev('li')[0];
if (!tabli) return;
move = scrollLength - tabli.offsetLeft;
if (isNaN(move)) return;
this.tabs._doScroll('left', move);
return;
}
}
move = scrollLength - allTab[allTab.length - 1].offsetLeft;
if (isNaN(move)) return;
this.tabs._doScroll('left', move);
break;
case 'up':
for (var i = 0, count = allTab.length; i < count; i++) {
if (allTab[i].offsetTop >= scrollLength) {
var preli = jq(allTab[i]).prev('li')[0];
if (!preli) return;
move = scrollLength - preli.offsetTop;
this.tabs._doScroll('up', move);
return;
}
}
var preli = allTab[allTab.length - 1];
if (!preli) return;
move = scrollLength - preli.offsetTop;
this.tabs._doScroll('up', move);
break;
case 'down':
for (var i = 0, count = allTab.length; i < count; i++) {
if (allTab[i].offsetTop + allTab[i].offsetHeight > plus) {
move = allTab[i].offsetTop + allTab[i].offsetHeight - scrollLength - offsetLength;
if (!move || isNaN(move)) return;
this.tabs._doScroll('down', move);
return;
}
}
break;
}
}
/**
* Synchronizes the size immediately.
* This method is called automatically if the widget is created
* at the server (i.e., {@link inServer} is true).
* You have to invoke this method only if you create this widget
* at client and add or remove children from this widget.
* @since 5.0.8
*/
syncSize(): void {
this._shallSize = false;
if (this.desktop)
zUtl.fireSized(this, -1); //no beforeSize
}
onResponse(): void {
if (this.inAccordionMold()) {
if (this._shallSize)
this.syncSize();
} else if (this._toolbarWidth) { // accordion mold not support toolbar
var toolbarWidth = jq(this.getToolbar()!.$n_()).width();
if (toolbarWidth != this._toolbarWidth) { // toolbar width changed
this._toolbarWidth = toolbarWidth;
this.getTabs()!.onSize();
}
}
}
/** @internal */
_syncSize(): void {
if (this.desktop)
this._shallSize = true;
}
/** @internal */
override beforeChildReplaced_(oldTabs: zul.tab.Tabs, newTabs: zul.tab.Tabs): void {
// NOTE: At this point, `this.tabs === oldTabs`. Thus, `this.setSelectedIndex()` will set for `oldTabs` not `newTabs`.
newTabs.getChildAt<zul.tab.Tab>(this.getSelectedIndex())!.setSelected(true);
}
/** @internal */
override onChildAdded_(child: zk.Widget): void {
super.onChildAdded_(child);
if (child instanceof zul.wgt.Toolbar)
this.toolbar = child;
else if (child instanceof zul.tab.Tabs)
this.tabs = child;
else if (child instanceof zul.tab.Tabpanels) {
this.tabpanels = child;
}
this.rerender();
}
/** @internal */
override onChildRemoved_(child: zk.Widget): void {
super.onChildRemoved_(child);
if (child == this.toolbar)
this.toolbar = undefined;
else if (child == this.tabs)
this.tabs = undefined;
else if (child == this.tabpanels)
this.tabpanels = undefined;
if (!this.childReplacing_)
this.rerender();
}
override setWidth(width?: string): this {
super.setWidth(width);
if (this.desktop)
zUtl.fireSized(this, -1); //no beforeSize
return this;
}
override setHeight(height?: string): this {
super.setHeight(height);
if (this.desktop)
zUtl.fireSized(this, -1); //no beforeSize
return this;
}
/** @internal */
_fixMaxHeight(): void {
if (this._maximalHeight) {
var max = 0,
pnls = this.getTabpanels()!,
fc = pnls.firstChild;
for (var c = fc; c; c = c.nextSibling) {
var panel = c ? c.getCaveNode() : undefined;
if (!panel)
return;
else {
var hgh = jq(panel).outerHeight()!;
if (hgh > max)
max = hgh;
}
}
for (var c = fc; c; c = c.nextSibling) {
var panel = c.getCaveNode();
if (panel)
panel.style.height = jq.px0(max);
}
}
}
/** @internal */
_setSel(newtab: zul.tab.Tab | undefined, notify: boolean): void {
if (newtab) {
var oldtab = this._selTab;
if (oldtab != newtab) {
if (oldtab && this.inAccordionMold()) {
var p = newtab.getLinkedPanel();
if (p) p._changeSel(oldtab.getLinkedPanel());
}
if (oldtab && oldtab != newtab)
oldtab._sel(false, false);
newtab._sel(true, notify);
}
}
}
}