zul/src/main/resources/web/js/zul/mesh/MeshWidget.ts
/* MeshWidget.ts
Purpose:
Description:
History:
Sat May 2 09:36:31 2009, Created by tomyeh
Copyright (C) 2009 Potix Corporation. All Rights Reserved.
This program is distributed under LGPL Version 2.1 in the hope that
it will be useful, but WITHOUT ANY WARRANTY.
*/
/** The two-dimensional mesh related widgets.
* A mesh widget is a two-dimensional widgets, such as grid, listbox and tree.
* Classes in this package is the skeletal implementation that can be used
* to simplify the implementation of grid, listbox and tree.
*/
//zk.$package('zul.mesh');
export interface MeshWidth {
width: number;
wds: number[];
}
var _shallFocusBack: boolean;
function _calcMinWd(wgt: MeshWidget): MeshWidth {
var wgtn = wgt.$n(),
ws = wgtn ? wgtn.style.whiteSpace : ''; //bug#3106514: sizedByContent with not visible columns
if (wgtn) {
wgtn.style.whiteSpace = 'nowrap'; // B50-3316030, B50-3346235: pre cause extra space
}
var eheadtblw: string | undefined,
efoottblw: string | undefined,
ebodytblw: string | undefined,
eheadtblfix: string | undefined,
efoottblfix: string | undefined,
ebodytblfix: string | undefined,
hdfaker = wgt.ehdfaker,
bdfaker = wgt.ebdfaker,
ftfaker = wgt.eftfaker,
head = wgt.head,
headn = head ? head.$n() : undefined,
hdfakerws: string[] = [],
bdfakerws: string[] = [],
ftfakerws: string[] = [],
hdws: string[] = [],
hdcavews: string[] = [];
if (wgt.eheadtbl && headn) {//clear and backup headers widths
eheadtblw = wgt.getInnerWidth() || '';
wgt.eheadtbl.width = '';
wgt.eheadtbl.style.width = '';
eheadtblfix = wgt.eheadtbl.style.tableLayout;
wgt.eheadtbl.style.tableLayout = '';
if (zk.chrome)
wgt.ebodytbl!.style.display = 'block';
var headcol = hdfaker!.firstChild,
headcell = headn.firstChild;
for (var i = 0; headcol; headcol = headcol.nextSibling, i++) {
var headcave = headcell ? headcell.firstChild : undefined;
if (!headcave)
continue;
hdfakerws[i] = (headcol as HTMLElement).style.width;
(headcol as HTMLElement).style.width = '';
hdws[i] = (headcell as HTMLElement).style.width;
(headcell as HTMLElement).style.width = '';
hdcavews[i] = (headcave as HTMLElement).style.width;
(headcave as HTMLElement).style.width = '';
headcell = headcell!.nextSibling;
}
}
if (headn)
headn.style.width = '';
if (wgt.efoottbl) {//clear and backup footers widths
efoottblw = eheadtblw || wgt.efoottbl.width;
wgt.efoottbl.width = '';
wgt.efoottbl.style.width = '';
efoottblfix = wgt.efoottbl.style.tableLayout;
wgt.efoottbl.style.tableLayout = '';
if (zk.chrome)
wgt.ebodytbl!.style.display = 'block';
if (ftfaker) {
var footcol = ftfaker.firstChild,
ftrows = wgt.efootrows,
ftcells = ftrows ? ftrows.rows[0].cells : undefined;
for (var i = 0; footcol; footcol = footcol.nextSibling) {
if (ftcells?.[i])
ftcells[i].style.width = ''; // reset it
ftfakerws[i++] = (footcol as HTMLElement).style.width;
(footcol as HTMLElement).style.width = '';
}
}
}
if (wgt.ebodytbl) {//clear and backup body faker widths
ebodytblw = eheadtblw || wgt.ebodytbl.width;
wgt.ebodytbl.width = '';
wgt.ebodytbl.style.width = '';
ebodytblfix = wgt.ebodytbl.style.tableLayout;
wgt.ebodytbl.style.tableLayout = '';
if (zk.chrome)
wgt.ebodytbl.style.display = 'block';
if (bdfaker) {
var bodycol = bdfaker.firstChild;
for (var i = 0; bodycol; bodycol = bodycol.nextSibling) {
bdfakerws[i++] = (bodycol as HTMLElement).style.width;
(bodycol as HTMLElement).style.width = '';
}
}
}
//calculate widths
var wds: number[] = [],
width = 0,
len = head ? head.nChildren : 0,
w = head ? head.firstChild : undefined,
headWgt = wgt.getHeadWidget();
if (bdfaker && w) {
var bodycells = wgt._getFirstRowCells(wgt.ebodyrows),
footcells = ftfaker ? wgt._getFirstRowCells(wgt.efootrows) : undefined;
for (var i = 0; i < len; i++) {
var wd = bodycells?.[i] ? zk(bodycells[i]).offsetWidthCeil() : 0,
ftwd = footcells?.[i] && zk(footcells[i]).isVisible() ? footcells[i].offsetWidth : 0,
hdwd = w?.isVisible() ? w.getContentWidth_() : 0,
header: zk.Widget | undefined;
if ((header = headWgt!.getChildAt(i)) && header.getWidth())
hdwd = Math.max(hdwd, ftwd);
if (hdwd > wd)
wd = hdwd;
if (ftwd > wd)
wd = ftwd;
wds[i] = wd;
// Bug ZK-2772 don't plus one when frozen exists.
if (!wgt.frozen && ((zk.ff as number) > 4 || zk.safari)) // firefox4 & IE9, 10, 11 & safari still cause break line in case B50-3147926 column 1
++wds[i];
width += wds[i]; // using wds[i] instead of wd for B50-3183172.zul
if (w)
w = w.nextSibling;
}
wgt._deleteFakeRow(wgt.ebodyrows);
if (ftfaker)
wgt._deleteFakeRow(wgt.efootrows);
} else {
var tr = _getSigRow(wgt);
if (tr) {
for (var cells = tr.cells, i = cells.length; i--;) {
var wd = cells[i].offsetWidth;
wds[i] = wd;
// Bug ZK-2772 don't plus one when frozen exists.
if (!wgt.frozen && ((zk.ff as number) > 4)) // firefox4 & IE9, 10, 11 still cause break line in case B50-3147926 column 1
++wds[i];
width += wds[i]; // using wds[i] instead of wd for B50-3183172.zul
}
}
}
// ZK-3313: The width of empty message counts
var empty = wgt.$n('empty');
if (empty && empty.style.display != 'none') {
var emptyWidth = empty.offsetWidth;
if (emptyWidth > width)
width = emptyWidth;
}
if (wgt.eheadtbl && headn) {//restore headers widths
if (!eheadtblw!.includes('%')) { // once sized and consider faker bar
var $hdfakerbar = jq(wgt.head!.$n_('hdfaker')).find('[id*=hdfaker-bar]'),
hdfakerbar = $hdfakerbar[0];
if (hdfakerbar) eheadtblw = jq.px0(parseInt(eheadtblw!) + parseInt(hdfakerbar.style.width));
wgt.eheadtbl.style.width = eheadtblw!;
} else
wgt.eheadtbl.width = eheadtblw ?? '';
wgt.eheadtbl.style.tableLayout = eheadtblfix ?? '';
if (zk.chrome)
wgt.eheadtbl.style.display = '';
var headcol = hdfaker!.firstChild,
headcell = headn.firstChild;
for (var i = 0; headcol; headcol = headcol.nextSibling, i++) {
var headcave = headcell ? headcell.firstChild : undefined;
if (!headcave)
continue;
(headcol as HTMLElement).style.width = hdfakerws[i];
(headcell as HTMLElement).style.width = hdws[i];
(headcave as HTMLElement).style.width = hdcavews[i];
headcell = headcell!.nextSibling;
}
}
if (wgt.efoottbl) {//restore footers widths
wgt.efoottbl.width = efoottblw ?? '';
wgt.efoottbl.style.tableLayout = efoottblfix ?? '';
if (zk.chrome)
wgt.efoottbl.style.display = '';
if (ftfaker) {
var footcol = ftfaker.firstChild;
for (var i = 0; footcol; footcol = footcol.nextSibling)
(footcol as HTMLElement).style.width = ftfakerws[i++];
}
}
if (wgt.ebodytbl) {//restore body fakers widths
wgt.ebodytbl.width = ebodytblw ?? '';
wgt.ebodytbl.style.tableLayout = ebodytblfix ?? '';
if (zk.chrome)
wgt.ebodytbl.style.display = '';
if (bdfaker) {
var bodycol = bdfaker.firstChild;
for (var i = 0; bodycol; bodycol = bodycol.nextSibling)
(bodycol as HTMLElement).style.width = bdfakerws[i++];
}
}
if (wgtn)
wgtn.style.whiteSpace = ws;
return { width: width, wds: wds };
}
function _fixBodyMinWd(wgt: MeshWidget, fixMesh?: boolean): void {
// effective only when there is no header
var sbc = wgt.isSizedByContent(),
meshmin = wgt._hflex == 'min';
if (!wgt.head && (meshmin || sbc)) {
var bdw = zk(wgt.$n()).padBorderWidth(),
wd = _getMinWd(wgt) + bdw, // has to call _getMinWd first so wgt._minWd will be available
// eslint-disable-next-line zk/noNull
tr: ChildNode | null = wgt.ebodytbl!,
wds = wgt._minWd!.wds,
wlen = wds.length;
if (fixMesh && meshmin)
wgt.setFlexSize_({ width: wd }, true);
if (!(tr.firstChild) || !(tr = tr.firstChild.firstChild))
return; // no first tr
// eslint-disable-next-line zk/noNull
for (var c: ChildNode | null = tr.firstChild, i = 0; c && (i < wlen); c = c.nextSibling)
(c as HTMLElement).style.width = jq.px(wds[i++]);
if (sbc && !meshmin) {
// add flex <td> if absent
var bdfx = tr.lastChild,
bdfxid = wgt.uuid + '-bdflex';
if (!bdfx || (bdfx as HTMLElement).id != bdfxid) {
jq(tr).append(/*safe*/ '<td id="' + bdfxid + '"></td>');
bdfx = tr.lastChild;
}
}
}
}
function _fixPageSize(wgt: MeshWidget, rows: HTMLCollectionOf<HTMLTableRowElement>): boolean {
var ebody = wgt.ebody;
if (!ebody)
return false; //not ready yet
var max = ebody.offsetHeight;
if (zk(ebody).hasHScroll()) //with horizontal scrollbar
max -= jq.scrollbarWidth();
if (max == wgt._prehgh) return false; //same height, avoid fixing page size
wgt._prehgh = max;
var ebodytbl = wgt.ebodytbl!,
etbparent = ebodytbl.offsetParent,
etbtop = ebodytbl.offsetTop,
hgh = 0,
row: HTMLTableRowElement | undefined,
j = 0;
for (var it = wgt.getBodyWidgetIterator({ skipHidden: true }),
len = rows.length; it.next() && j < len; j++) {
row = rows[j];
var top: number | undefined = row.offsetTop - (row.offsetParent == etbparent ? etbtop : 0);
if (top > max) {
--j;
break;
}
hgh = top;
}
if (row) { //there is row
var pgib = wgt.$n('pgib'),
pgibHgh = 0;
if (pgib)
pgibHgh = pgib.offsetHeight;
var withPgibMax = max + pgibHgh;
if (top! <= withPgibMax) { //row not exceeds the height, estimate
var paging = (wgt.paging) ? wgt.paging : wgt.getPaginal()!,
totalsz = paging.getTotalSize(),
rowsHgh = hgh + row.offsetHeight,
j1 = Math.floor(j * max / rowsHgh),
j2 = Math.floor(j * withPgibMax / rowsHgh);
if (totalsz > j2 || !paging.isAutohide())
j = j1;
else
j = j2;
}
//enforce pageSize change
if (j == 0) j = 1; //at least one per page
if (j != wgt.getPageSize()) {
wgt.fire('onPageSize', { size: j });
return true;
}
}
return false;
}
function _adjMinWd(wgt: MeshWidget): void {
if (wgt._hflex == 'min') {
var w = _getMinWd(wgt),
n = wgt.$n_();
wgt._hflexsz = w + zk(n).padBorderWidth(); //override
n.style.width = jq.px0(wgt._hflexsz);
}
}
function _getMinWd(wgt: MeshWidget): number {
wgt._calcMinWds();
var bdfaker = wgt.ebdfaker,
wd: number,
wds: number[] = [],
width: number,
_minwds = wgt._minWd!.wds;
if (wgt.head && bdfaker) {
width = 0;
var w = wgt.head.firstChild,
bdcol = bdfaker.firstChild;
for (var i = 0; w; w = w.nextSibling) {
if (w._hflex == 'min')
wd = wds[i] = _minwds[i] + zk(w.$n()).padBorderWidth();
else {
if (w._width && w._width.indexOf('px') > 0)
wd = wds[i] = zk.parseInt(w._width);
else
wd = wds[i] = zk.parseInt((bdcol as HTMLElement).style.width);
}
// ZK-2130: should save the head width
w._origWd = jq.px0(wd);
width += wd;
++i;
bdcol = bdcol!.nextSibling;
}
} else
width = wgt._minWd!.width; // no header
return width + (zk(wgt.ebody).hasVScroll() ? jq.scrollbarWidth() : 0);
}
function _getSigRow(wgt: MeshWidget): HTMLTableRowElement | undefined {
// scan for tr with largest number of td children
var rw = wgt.getBodyWidgetIterator().next(),
// eslint-disable-next-line zk/noNull
tr = rw ? rw.$n() : null;
if (!tr)
return;
// eslint-disable-next-line zk/noNull
for (var maxtr = tr, len: number, max = maxtr.cells.length; tr; tr = tr.nextSibling as HTMLTableRowElement | null)
if ((len = tr.cells.length) > max) {
maxtr = tr;
max = len;
}
return maxtr;
}
function _cpCellWd(wgt: MeshWidget): void {
var dst = wgt.efootrows!.rows[0],
srcrows = wgt.ebodyrows!.rows;
if (!dst || !srcrows || !srcrows.length || !dst.cells.length)
return;
var ncols = dst.cells.length,
// eslint-disable-next-line zk/noNull
src: HTMLTableRowElement | null | undefined,
maxnc = 0;
for (var j = 0, it = wgt.getBodyWidgetIterator({ skipHidden: true }), w: zul.mesh.Item | undefined; (w = it.next());) {
if (!w._loaded || w.z_rod)
continue;
var row = srcrows[j++], $row = zk(row),
cells = row.cells, nc = $row.ncols(),
valid = cells.length == nc && $row.isVisible();
//skip with colspan and invisible
if (valid && nc >= ncols) {
maxnc = ncols;
src = row;
break;
}
if (nc > maxnc) {
src = valid ? row : undefined;
maxnc = nc;
} else if (nc == maxnc && !src && valid) {
src = row;
}
}
if (!maxnc) return;
var fakeRow = !src;
if (fakeRow) { //the longest row containing colspan
src = document.createElement('TR') as HTMLTableRowElement;
src.style.height = '0px';
//Note: we cannot use display="none" (offsetWidth won't be right)
for (var j = 0; j < maxnc; ++j)
src.appendChild(document.createElement('TD'));
srcrows[0].parentNode!.appendChild(src);
}
//we have to clean up first, since, in FF, if dst contains %
//the copy might not be correct
for (var j = maxnc; j--;)
dst.cells[j].style.width = '';
var sum = 0;
for (var j = maxnc; j--;) {
var d = dst.cells[j], s = src!.cells[j];
if (zk.opera) {
sum += s.offsetWidth;
d.style.width = zk(s).contentWidth() as unknown as string;
} else {
d.style.width = `${s.offsetWidth}px`;
if (maxnc > 1) { //don't handle single cell case (bug 1729739)
var v = s.offsetWidth - d.offsetWidth;
if (v != 0) {
v += s.offsetWidth;
if (v < 0) v = 0;
d.style.width = `${v}px`;
}
}
}
}
if (zk.opera && wgt.isSizedByContent())
(dst.parentNode!.parentNode as HTMLElement).style.width = `${sum}px`;
if (fakeRow)
src!.parentNode!.removeChild(src!);
}
function listenOnFitSize(wgt: MeshWidget): void {
if (wgt._rows && !wgt._rowsOnFitSize) {
zWatch.listen({ onFitSize: wgt });
wgt._rowsOnFitSize = true;
}
}
function unlistenOnFitSize(wgt: MeshWidget): void {
if (wgt._rowsOnFitSize) {
zWatch.unlisten({ onFitSize: wgt });
delete wgt._rowsOnFitSize;
}
}
export interface Item extends zul.Widget<HTMLTableRowElement> {
/** @internal */
_loaded?: boolean;
/** @internal */
_index?: number;
}
export interface ItemIterator<TItem extends zul.mesh.Item = zul.mesh.Item> {
hasNext(): boolean;
next(): TItem | undefined;
length?: number;
}
/**
* A skeletal implementation for a mesh widget.
* @see zul.grid.Grid
* @see zul.sel.Tree
* @see zul.sel.Listbox
*/
@zk.WrapClass('zul.mesh.MeshWidget')
export abstract class MeshWidget extends zul.Widget {
/** @internal */
_rows = 0;
/** @internal */
_pagingPosition = 'bottom';
/** @internal */
_prehgh = -1;
/** @internal */
_minWd?: MeshWidth; //minimum width for each column
/** @internal */
_sizedByContent?: boolean;
/** @internal */
_span?: string | boolean;
/** @internal */
_nspan?: number;
/** @internal */
_autopaging?: boolean;
/** @internal */
_model?: boolean | string;
/** @internal */
_paginal?: zul.mesh.Paging;
/** @internal */
_pendOnRender?: boolean;
/** @internal */
_ebodyScrollPos?: { l: number; t: number };
// Types established from inspecting https://www.zkoss.org/zkdemo/grid/header_and_footer
ehead?: HTMLDivElement;
eheadtbl?: HTMLTableElement;
ehdfaker?: HTMLTableColElement;
eheadrows?: HTMLTableSectionElement;
// Types established from inspecting https://www.zkoss.org/zkdemo/grid/header_and_footer
ebody?: HTMLDivElement;
ebodytbl?: HTMLTableElement;
ebdfaker?: HTMLTableColElement;
ebodyrows?: HTMLTableSectionElement;
// Types established from inspecting https://www.zkoss.org/zkdemo/grid/header_and_footer
efoot?: HTMLDivElement;
efoottbl?: HTMLTableElement;
eftfaker?: HTMLTableColElement;
efootrows?: HTMLTableSectionElement;
efrozen?: HTMLElement;
frozen?: zul.mesh.Frozen;
paging?: zul.mesh.Paging;
heads: zul.mesh.HeadWidget[];
head?: zul.mesh.HeadWidget;
foot?: zul.grid.Foot;
/** @internal */
_visiRows?: number;
/** @internal */
_wsbak?: string;
/** @internal */
_targetIndex?: number;
/** @internal */
_keepScroll?: boolean;
/** @internal */
_bottomBoundary?: number;
/** @internal */
_topBoundary?: number;
/** @internal */
_rowsOnFitSize?: boolean;
/** @internal */
_scrollbar?: zul.Scrollbar;
/** @internal */
_cachehgh?: number;
/** @internal */
_lastDevicePixelRatio?: number;
/** @internal */
_adjustScrollTopLater?: boolean;
/** @internal */
_shallSize?: boolean;
/** @internal */
_syncingbodyrows?: boolean;
/** @internal */
_shallClearTableWidth?: boolean;
/** @internal */
_shallShowScrollbar?: boolean;
/** @internal */
_syncEmpty(): void {
// Empty on purpose. To be inherited.
}
constructor() {
super(); // FIXME: arguments?
this.heads = [];
}
// `zul.mesh.prototype.HeadWidget.onColSize` could assign `_innerWidth` a number
/** @internal */
_innerWidth = '100%';
/** @internal */
_currentTop = 0;
/** @internal */
_currentLeft = 0;
/** @internal */
_nativebar = true;
abstract getHeadWidgetClass(): typeof zul.mesh.HeadWidget;
abstract getBodyWidgetIterator(opts?: Record<string, unknown>): ItemIterator;
abstract itemIterator(opts?: Record<string, unknown>): ItemIterator;
/**
* @returns (int) the rows. Zero means no limitation.
* @defaultValue `0`.
*/
getRows(): number {
return this._rows;
}
/**
* Sets the rows.
* <p>
* Note: if both {@link setHeight} is specified with non-empty,
* {@link setRows} is ignored
*/
setRows(rows: number, opts?: Record<string, boolean>): this {
const o = this._rows;
this._rows = rows;
if (o !== rows || opts?.force) {
listenOnFitSize(this);
var n = this.$n();
if (n) {
n._lastsz = undefined;
this.onSize();
}
}
return this;
}
/**
* @returns how to position the paging of the widget at the client screen.
* It is meaningless if the mold is not in "paging".
*/
getPagingPosition(): string {
return this._pagingPosition;
}
/**
* Sets how to position the paging of the widget at the client screen.
* It is meaningless if the mold is not in "paging".
* @param pagingPosition - how to position. It can only be "bottom" (the default), or
* "top", or "both".
*/
setPagingPosition(pagingPosition: string, opts?: Record<string, boolean>): this {
const o = this._pagingPosition;
this._pagingPosition = pagingPosition;
if (o !== pagingPosition || opts?.force) {
this.rerender();
}
return this;
}
/**
* Returns whether the paging component of this component is disabled
* @since 10.0.0
*/
isPagingDisabled(): boolean {
return !!this.getPagingChild()?.isDisabled();
}
/**
* Sets whether to disable the Paging component of this component
* @since 10.0.0
*/
setPagingDisabled(pagingDisabled: boolean): this {
this.getPagingChild()?.setDisabled(pagingDisabled);
return this;
}
/**
* @returns whether sizing the widget column width by its content. Default is false.
* <p>Note: if the "sized-by-content" attribute of component is specified,
* it's prior to the original value.
* @see {@link setSizedByContent}
*/
isSizedByContent(): boolean {
return !!this._sizedByContent;
}
/**
* Sets whether sizing the widget column width by its content. Default is false, i.e.
* the outline of the widget is dependent on browser. It means, we don't
* calculate the width of each cell. If set to true, the outline will count on
* the content of body. In other words, the outline of the widget will be like
* ZK version 2.4.1 that the header's width is only for reference.
*
* <p> You can also specify the "sized-by-content" attribute of component in
* lang-addon.xml directly, it will then take higher priority.
*/
setSizedByContent(sizedByContent: boolean, opts?: Record<string, boolean>): this {
const o = this._sizedByContent;
this._sizedByContent = sizedByContent;
if (o !== sizedByContent || opts?.force) {
this.rerender();
}
return this;
}
/**
* @returns column span hint of this widget.
* @defaultValue `null`
* @since 5.0.6
* @see {@link setSpan}
*/
getSpan(): string | boolean | undefined {
return this._span;
}
isSpan(): string | boolean | undefined {
return this._span;
}
/**
* Sets column span hint of this mesh widget.
* <p>The parameter span is a number in String type indicating how this
* component distributes remaining empty space to the
* specified column(0-based). "0" means distribute remaining empty space to the 1st column; "1" means
* distribute remaining empty space to the 2nd column, etc.. The spanning column will grow to
* fit the extra remaining space.</p>
* <p>Special span hint with "true" means span ALL columns proportionally per their
* original widths while null or "false" means NOT spanning any column.</p>
* @defaultValue `null`. That is, NOT span any column.</p>
* <p>Note span is meaningful only if there is remaining empty space for columns.</p>
*
* @param span - the column span hint.
* @since 5.0.6
* @see {@link getSpan}
*/
setSpan(span: string | boolean, opts?: Record<string, boolean>): this {
const o = this._span;
this._span = span;
if (o !== span || opts?.force) {
//ZK-2776: if span="false", !isSpan() will return false, because "false" is string not boolean
var isTrue = true === span || 'true' == span,
isFalse = false === span || 'false' == span;
this._span = isTrue ? true : isFalse ? false : span;
var x = isTrue ? -65500 : isFalse ? 0 : (zk.parseInt(span) + 1);
this._nspan = x < 0 && x != -65500 ? 0 : x;
this.rerender();
}
return this;
}
/**
* @returns whether turn on auto-paging facility when mold is
* "paging". If it is set to true, the {@link setPageSize} is ignored;
* rather, the page size(number of item count) is automatically determined by the
* height of this widget dynamically.
* @see {@link setAutopaging}
*/
isAutopaging(): boolean {
return !!this._autopaging;
}
/**
* Sets whether turn on auto-paging facility when mold is
* "paging". If it is set to true, the {@link setPageSize} is ignored;
* rather, the page size(number of item count) is automatically determined by the
* height of this widget dynamically.
*/
setAutopaging(autopaging: boolean, opts?: Record<string, boolean>): this {
const o = this._autopaging;
this._autopaging = autopaging;
if (o !== autopaging || opts?.force) {
this.rerender();
}
return this;
}
/**
* @returns whether the widget is in model mode or not.
*/
isModel(): boolean {
return !!this._model;
}
// TODO: Used by grid. Should prefer the use of isModel
getModel(): boolean | string | undefined {
return this._model; // it could be "group" for groups model
}
/**
* Sets whether the widget is in model mode.
*/
setModel(model: boolean): this {
this._model = model;
return this;
}
/**
* @returns the inner width of this component.
* The inner width is the width of the inner table.
* @defaultValue `"100%"`
* @see {@link setInnerWidth}
*/
getInnerWidth(): string {
return this._innerWidth;
}
/**
* Sets the inner width of this component.
* The inner width is the width of the inner table.
* By default, it is 100%. That is, it is the same as the width
* of this component. However, it is changed when the user
* is sizing the column's width.
*
* <p>Application developers rarely call this method, unless
* they want to preserve the widths of sizable columns
* changed by the user.
* To preserve the widths, the developer have to store the widths of
* all columns and the inner width ({@link getInnerWidth}),
* and then restore them when re-creating this component.
*
* @param innerWidth - the inner width. If null, "100%" is assumed.
*/
setInnerWidth(innerWidth: string, opts?: Record<string, boolean>): this {
const o = this._innerWidth;
this._innerWidth = innerWidth;
if (o !== innerWidth || opts?.force) {
if (innerWidth == null) this._innerWidth = innerWidth = '100%';
if (this.eheadtbl) this.eheadtbl.style.width = innerWidth;
if (this.ebodytbl) this.ebodytbl.style.width = innerWidth;
if (this.efoottbl) this.efoottbl.style.width = innerWidth;
}
return this;
}
/**
* @returns the external Paging widget, if any.
*/
getPaginal(): zul.mesh.Paging | undefined {
return this._paginal;
}
/**
* Sets the external Paging widget.
*/
setPaginal(paginal?: zul.mesh.Paging): this {
if (this._paginal != paginal) {
this._paginal?.setMeshWidget();
this._paginal = paginal;
this._paginal?.setMeshWidget(this);
}
return this;
}
/**
* @returns the page size, aka., the number rows per page.
* @see {@link zul.mesh.Paging.getPageSize}
*/
getPageSize(): number {
return this.getPagingChild()?.getPageSize() ?? 0;
}
/**
* Sets the page size, aka., the number rows per page.
* @see {@link zul.mesh.Paging.setPageSize}
*/
setPageSize(pageSize: number): this {
this.getPagingChild()?.setPageSize(pageSize);
return this;
}
/**
* @returns the number of pages.
* Note: there is at least one page even no item at all.
* @see {@link zul.mesh.Paging.getPageCount}
*/
getPageCount(): number {
return this.getPagingChild()?.getPageCount() ?? 1;
}
/**
* @returns the active page (starting from 0).
* @see {@link zul.mesh.Paging.getActivePage}
*/
getActivePage(): number {
return this.getPagingChild()?.getActivePage() ?? 0;
}
/**
* Sets the active page (starting from 0).
* @see {@link zul.mesh.Paging.setActivePage}
*/
setActivePage(activePage: number): this {
this.getPagingChild()?.setActivePage(activePage);
return this;
}
/**
* @returns whether the widget is in paging mold.
*/
inPagingMold(): boolean {
return 'paging' == this.getMold();
}
getPagingChild(): zul.mesh.Paging | undefined {
return this.paging ?? this.getPaginal();
}
override setHeight(height?: string): this {
super.setHeight(height);
if (this.desktop) {
this._setHgh(height);
this.onSize();
}
return this;
}
override setWidth(width?: string): this {
super.setWidth(width);
if (this.eheadtbl) this.eheadtbl.style.width = '';
if (this.efoottbl) this.efoottbl.style.width = '';
if (this.desktop)
this.onSize();
return this;
}
override setStyle(style: string): this {
if (this._style != style) {
super.setStyle(style);
if (this.desktop)
this.onSize();
}
return this;
}
/**
* @returns the self's head widget.
* @since 5.0.4
*/
getHeadWidget(): zul.mesh.HeadWidget | undefined {
return this.head;
}
/**
* @param el - the element got focus.
* @returns the focused cell element.
* @since 5.0.7
*/
getFocusCell(el: HTMLElement): HTMLTableCellElement | undefined {
var td: HTMLTableCellElement | undefined;
// Note: `jq.each` is in general not the same as `jq().each` although it is in this case.
// But, only `jq.each` matches the definition in `jQuery.d.ts`.
// See https://api.jquery.com/jquery.each/#:~:text=%24.each()%20function-,is%20not%20the%20same%20as,-%24(selector).each()
jq.each([this.ebodytbl, this.eheadtbl, this.efoottbl], function (i) {
if (this && jq.isAncestor(this, el)) {
var tds = jq(el).parents(i == 1 ? 'th' : 'td'); // headtable uses TH, others uses TD
for (var i = 0, j = tds.length; i < j; i++) {
td = tds[i];
if (jq(td).parents('table')[0] == this) {
return false; // break the loop;
}
}
}
});
return td;
}
/** @internal */
_moveToHidingFocusCell(index: number, ignoreWidth?: boolean, notFocusBack?: boolean): void { //used in Row/Listcell and a11y frozen
//B50-3178977 navigating the input in hiddin column.
var td = this.ehdfaker ? this.ehdfaker.childNodes[index] as HTMLTableCellElement | undefined : undefined,
frozen = this.frozen;
if (td && frozen && (ignoreWidth || zk.parseInt(td.style.width) == 0)
&& (index = index - frozen.getColumns()!) >= 0) {
const bar = this._scrollbar;
if (this._nativebar) {
frozen.setStart(index);
} else if (bar) {
frozen._doScrollNow(index);
bar.setBarPosition(index);
}
_shallFocusBack = !notFocusBack;
}
}
/** @internal */
_restoreFocus(): void { //used in Frozen
if (_shallFocusBack && zk.currentFocus) {
_shallFocusBack = false;
zk.currentFocus.focus();
}
}
/** @internal */
disableAutoSizing_(): void {
this._span = false;
this._nspan = 0;
this._sizedByContent = false;
}
/** @internal */
override bind_(desktop?: zk.Desktop, skipper?: zk.Skipper, after?: CallableFunction[]): void {
super.bind_(desktop, skipper, after);
this._bindDomNode();
if (this.frozen)
this._cssflex = false; //force to use old flex
if (this._cssflex && this.isChildrenFlex()) {//css flex
this.ehdfaker!.style.display = 'none';
} else if (this._hflex != 'min')
this._fixHeaders();
if (this.ehead) //sync scroll for input tab key scroll
this.domListen_(this.ehead, 'onScroll', '_doSyncScroll');
var ebody = this.ebody;
if (this._nativebar && ebody) {
this.domListen_(ebody, 'onScroll', '_doScroll');
ebody.style.overflow = 'auto';
if (this.efrozen && !zk.mobile)
jq(ebody).css('overflow-x', 'hidden'); // keep non line break
}
zWatch.listen({ onSize: this, onResponse: this });
}
/** @internal */
override unbind_(skipper?: zk.Skipper, after?: CallableFunction[], keepRod?: boolean): void {
unlistenOnFitSize(this);
zWatch.unlisten({ onSize: this, onResponse: this });
if (this.ehead) //sync scroll for input tab key scroll
this.domUnlisten_(this.ehead, 'onScroll', '_doSyncScroll');
var ebody = this.ebody;
if (this._nativebar && ebody) {
this.domUnlisten_(ebody, 'onScroll', '_doScroll');
if (this.efrozen)
jq(ebody).css('overflow-x', 'auto');
}
super.unbind_(skipper, after, keepRod);
}
override clearCache(): void {
super.clearCache();
this.ebody = this.ehead = this.efoot = this.efrozen = this.ebodytbl
= this.eheadtbl = this.efoottbl = this.ebodyrows
= this.ehdfaker = this.ebdfaker = undefined;
}
/**
* 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 {
if (this.desktop) {
this.clearCachedSize_();
if (this._hflex == 'min') {
zFlex.onFitSize.call(this);
} else {
this._fixHeaders();
}
zWatch.fireDown('onSize', this);
}
}
// These paramters are not used. They exist for the sake of inheritance.
onResponse(ctl?: zk.ZWatchController, opts?: Record<string, unknown>): void {
if (this._shallSize) {
if (this._shallClearTableWidth) {
this._clearTableWidth();
this._shallClearTableWidth = false;
}
this.syncSize();
this._shallSize = false; // just in case
}
}
/** @internal */
_syncSize(shallClearTableWidth?: boolean): void {
// fixed for F50-3025422.zul on ZTL
if (this.desktop) {
this._shallSize = true;
this._shallClearTableWidth = !!shallClearTableWidth;
}
}
/** @internal */
_clearTableWidth(): void {
if (this.desktop) {
// clear table width
var hdtbl = this.eheadtbl,
bdtbl = this.ebodytbl,
fttbl = this.efoottbl;
if (hdtbl)
hdtbl.style.width = '';
if (bdtbl)
bdtbl.style.width = '';
if (fttbl)
fttbl.style.width = '';
}
}
/** @internal */
_fixHeaders(force?: boolean): boolean { //used in HeadWidget
if (this.head && this.ehead) {
if (this._cssflex && this.isChildrenFlex()) return false;
var empty = true,
flex = false,
shouldFix = false,
shouldCheckEmptyWidth = !this.frozen && this.eheadtbl && this.eheadtbl.style.width,
hdsmin = (this._hflex == 'min') || this.isSizedByContent();
for (var i = this.heads.length; i-- > 0;) {
var header = this.heads[i],
emptyHeader = true,
column = 0;
for (var w = header.firstChild; w; w = w.nextSibling) {
//B70-ZK-2559: when dynamic adding auxhead, there has already
//been auxhead widget while dom element hasn't attached yet
var childNode = this.ehdfaker!.childNodes[column++];
if (hdsmin && (childNode && !(childNode as HTMLElement).style.width) && !w._nhflex) {
// B50-3357475: assume header hflex min if width/hflex unspecified
w._hflex = 'min';
w._nhflex = -65500; // min
w._nhflexbak = true;
}
if (!flex && w._nhflex)
flex = true;
if (w.getLabel() || w.getImage() || w.nChildren) {
emptyHeader = false;
empty = false;
}
if (shouldCheckEmptyWidth && !w.getWidth()) {
shouldFix = true;
}
}
if (header._visible) {
//B70-ZK-2559: description as mentioned
var n = header.$n();
if (n)
n.style.display = emptyHeader ? 'none' : ''; // Bug ZK-2348
}
}
var old = this.ehead.style.display,
tofix = force && flex && this.isRealVisible(); //Bug ZK-1647: no need to consider empty header for flex calculation
this.ehead.style.display = empty ? 'none' : '';
//onSize is not fired to empty header when loading page, so we have to simulate it here
for (var w = this.head.firstChild; w; w = w.nextSibling) {
if (tofix && w._nhflex)
w.fixFlex_();
if (w._nhflexbak) {
delete w._hflex;
delete w._nhflex;
delete w._nhflexbak;
}
}
return shouldFix || old != this.ehead.style.display;
}
return false;
}
/** @internal */
_adjFlexWd(): void { //used by HeadWidget
var head = this.head;
if (head) {
var hdfaker = this.ehdfaker!,
bdfaker = this.ebdfaker!,
hdcol = hdfaker.firstChild,
bdcol = bdfaker.firstChild,
ftfaker = this.eftfaker,
// eslint-disable-next-line zk/noNull
ftcol: ChildNode | null = null;
if (ftfaker)
ftcol = ftfaker.firstChild;
//B70-ZK-2130: clean table width to prevent incorrect width
this.eheadtbl!.style.width = '';
this.ebodytbl!.style.width = '';
//B70-ZK-2394: store total bdcol width
var tblWidth = 0,
cachedOffsetWidths: Record<string, number> = {};
// ZK-2098: should skip if bdcol doesn't exist
for (var w = head.firstChild; w && bdcol; w = w.nextSibling) {
// ZK-2130: should save the header width
// ZK-2071: use offsetWidth instead of style.width
cachedOffsetWidths[w.uuid] = w.$n_().offsetWidth;
bdcol = bdcol.nextSibling;
}
bdcol = bdfaker.firstChild;
// ZK-4320: Do not adjust widget's width if smooth is false and frozen is scrolled
var frozen = this.frozen;
if (!(frozen && !frozen._smooth && frozen.getStart())) {
for (var w = head.firstChild, wd: number | undefined; w && bdcol; w = w.nextSibling) {
var wwd = cachedOffsetWidths[w.uuid];
if (w.isVisible() && wwd > 0.1)
w._origWd = jq.px0(wwd);
// B70-ZK-2036: Do not adjust widget's width if it is not visible.
if (w.isVisible() && (wd = w._hflexWidth) !== undefined) {
var revisedWidth = zk(bdcol).revisedWidth(Math.round(wd)),
revisedWidthPx = jq.px0(revisedWidth);
//B70-ZK-2509: w.$n().offsetWidth is small when there are many columns at beginning, so save revised width if any
w._origWd = revisedWidthPx;
(bdcol as HTMLElement).style.width = revisedWidthPx;
(hdcol as HTMLElement).style.width = revisedWidthPx;
if (ftcol)
(ftcol as HTMLElement).style.width = revisedWidthPx;
//B70-ZK-2394: store total bdcol width
tblWidth += revisedWidth;
}
bdcol = bdcol.nextSibling;
hdcol = hdcol!.nextSibling;
if (ftcol)
ftcol = ftcol.nextSibling;
}
}
//B70-ZK-2394: sync width from colgroup to hdtbl, bdtbl, fttbl
var allWidths = this._isAllWidths();
if (allWidths) {
var hdtbl = this.eheadtbl,
bdtbl = this.ebodytbl,
fttbl = this.efoottbl;
if (hdtbl) {
var tblWidthPx = jq.px0(tblWidth);
hdtbl.style.width = tblWidthPx;
if (bdtbl)
bdtbl.style.width = tblWidthPx;
if (fttbl)
fttbl.style.width = tblWidthPx;
}
}
_adjMinWd(this);
this._afterCalcSize();
}
}
/** @internal */
_bindDomNode(): void {
this.ehead = this.$n('head');
this.eheadtbl = this.$n('headtbl');
this.ebody = this.$n('body');
this.ebodytbl = this.$n('cave');
this.efoot = this.$n('foot');
this.efoottbl = this.$n('foottbl');
this.efrozen = this.$n('frozen');
// Grid will bind ebodyrows in Rows widget
var erows = this.$n<HTMLTableSectionElement>('rows');
if (this.ebody && erows)
this.ebodyrows = erows;
if (this.ehead) {
this.eheadrows = this.$n('headrows');
this.ehdfaker = this.head!.$n('hdfaker');
this.ebdfaker = this.head!.$n('bdfaker');
if (this.efoot)
this.eftfaker = this.head!.$n('ftfaker');
}
if (this.efoot)
this.efootrows = this.$n('footrows');
}
override replaceHTML(n: HTMLElement | string, desktop?: zk.Desktop, skipper?: zk.Skipper, _trim_?: boolean, _callback_?: CallableFunction[]): void { // tree outer
var old = this._syncingbodyrows;
this._syncingbodyrows = true;
try {
//bug #2995434
//20100503, Henri: cannot use $supers('replaceHTML') since it
//will recursive back to this function via fire('onSize'). However,
//ZK's $supers() is simulated and when we call $supers() again
//here, the system thought it is calling from its super class rather
//than this class and it will be wrong. Therefore, we are forced to
//call super class's replaceHTML directly instead.
//Therefore, we have to specify MeshWidget as follows
super.replaceHTML(n, desktop, skipper, _trim_, _callback_);
} finally {
this._syncingbodyrows = old;
}
}
/** @internal */
override replaceChildHTML_(child: zk.Widget, n: HTMLElement | string, desktop?: zk.Desktop, skipper?: zk.Skipper, _trim_?: boolean): void { // rows outer
var old = this._syncingbodyrows;
this._syncingbodyrows = true;
try {
super.replaceChildHTML_(child, n, desktop, skipper, _trim_);
} finally {
this._syncingbodyrows = old;
}
}
fireOnRender(timeout: number): void {
if (!this._pendOnRender) {
this._pendOnRender = true;
setTimeout(this.proxy(this._onRender), timeout ? timeout : 100);
}
}
/** @internal */
_doScroll(): void { //called zkmax, overriden in Listbox
var t = zul.mesh.Scrollbar.getScrollPosV(this),
l = zul.mesh.Scrollbar.getScrollPosH(this),
hScrolled = l != this._currentLeft,
scrolled = (t != this._currentTop || hScrolled),
ebody = this.ebody!,
ehead = this.ehead,
efoot = this.efoot;
// ZK-2069: fire onScroll if has scrollable property
if (jq(this).data('scrollable')) {
zWatch.fireDown('onScroll', this);
zWatch.fire('_onSyncScroll', this); // ZK-4408: for Popup only
}
//B70-ZK-2070: if scrolled, the scrollbar need fire onScroll event.
if (scrolled && !(this.fire('onScroll', ebody.scrollLeft).stopped) && this._nativebar)
if (this._currentLeft != ebody.scrollLeft) {
if (ehead)
ehead.scrollLeft = ebody.scrollLeft;
if (efoot)
efoot.scrollLeft = ebody.scrollLeft;
}
// ZK-2046: should sync currentTop in rod mode see also Bug ZK-353
if (scrolled /* && !this._listbox$rod && !this._grid$rod*/)
this._currentTop = t;
if (scrolled) // always sync for B30-1737660.zul
this._currentLeft = l;
if (!this.paging && !this._paginal)
this.fireOnRender(zk.gecko ? 200 : 60);
if (scrolled)
this._fireOnScrollPos();
if (this.frozen && hScrolled)
this.frozen.syncScrollByParentBody();
}
/** @internal */
_doSyncScroll(): void { //sync scroll for input tab key scroll
var ehead = this.ehead,
ebody = this.ebody,
efoot = this.efoot;
if (ehead && zk(ehead).isVisible()) {
if (this._currentLeft != ehead.scrollLeft) {
if (ebody)
ebody.scrollLeft = ehead.scrollLeft;
if (efoot)
efoot.scrollLeft = ehead.scrollLeft;
}
}
}
/** @internal */
_timeoutId: number | undefined;
/** @internal */
_fireOnScrollPos(time?: number, forceToScroll?: boolean /* overriden in zkmax */): void {
clearTimeout(this._timeoutId);
this._timeoutId = setTimeout(this._onScrollPos.bind(this), time! >= 0 ? time : 300);
}
/** @internal */
_onScrollPos(): void {
// Bug ZK-414
if (this.ebody) {
this._currentTop = zul.mesh.Scrollbar.getScrollPosV(this);
this._currentLeft = zul.mesh.Scrollbar.getScrollPosH(this);
this.fire('onScrollPos', {
top: this._currentTop,
left: this._currentLeft
});
}
}
/** @internal */
_onRender(): boolean { //overriden in zkmax
if (!this.$n())
return false; // the target may not exist. B50-ZK-963
this._pendOnRender = false;
if (this._syncingbodyrows || zAu.processing()) { //wait if busy (it might run outer)
this.fireOnRender(zk.gecko ? 200 : 60); //is syncing rows, try it later
return true;
}
var rows = this.ebodyrows ? this.ebodyrows.rows : undefined;
if (this.inPagingMold() && this._autopaging && rows && rows.length)
if (_fixPageSize(this, rows))
return false; //need to reload with new page size
if (!this.desktop || !this._model || !rows || !rows.length)
return false;
//Note: we have to calculate from top to bottom because each row's
//height might diff (due to different content)
var items: zk.Widget[] = [],
min = zul.mesh.Scrollbar.getScrollPosV(this),
max = min + this.ebody!.offsetHeight;
if (min == 0 && max == 0)
return false; //ZK-2796: Uncessary onRender command triggered when setting tabbox's maximalHeight attribute to true
for (var j = 0, it = this.getBodyWidgetIterator({ skipHidden: true }),
len = rows.length, w: zul.mesh.Item | undefined; (w = it.next()) && j < len; j++) {
if (!w._loaded) {
//B70-ZK-2589: w and rows[j] belongs to different widget,
//w shouldn't depend on rows[j], origin -> row = rows[j];
var row = w.$n();
if (row == null) continue;
var $row = zk(row),
top = $row.offsetTop();
if (top + $row.offsetHeight() < min) continue;
if (top > max) break; //Bug 1822517
items.push(w);
}
}
if (items.length) {
this.fire('onRender', { items: items }, { implicit: true });
}
return false;
}
override onSize(): void {
var devicePixelRatio = zUtl.getDevicePixelRatio();
if (this.isRealVisible()) { // sometimes the caller is not zWatch
var n = this.$n_();
if (n._lastsz && n._lastsz.height == n.offsetHeight
&& n._lastsz.width == n.offsetWidth
&& this._lastDevicePixelRatio == devicePixelRatio) {
this.fireOnRender(155); // force to render while using live grouping
return; // unchanged
}
this._lastDevicePixelRatio = devicePixelRatio;
this._calcSize();// Bug #1813722
this.fireOnRender(155);
if (this._nativebar) { // Bug ZK-355: keep scrollbar position
var ebody = this.ebody!;
// ZK-3647: if Listbox enable ROD and need to init pad sizes, it will adjust scrollTop when _initPadSizes().
if (ebody.scrollHeight >= this._currentTop && !this._adjustScrollTopLater)
ebody.scrollTop = this._currentTop;
if (ebody.scrollWidth >= this._currentLeft) {
ebody.scrollLeft = this._currentLeft;
if (this.ehead)
this.ehead.scrollLeft = this._currentLeft;
if (this.efoot)
this.efoot.scrollLeft = this._currentLeft;
}
}
this._shallSize = false;
if (this._keepScroll) {
this._scrollToIndex(this._targetIndex!);
}
}
}
/** @internal */
_vflexSize(): number {
var n = this.$n(),
pgHgh = 0;
if (this.paging) {
var pgit = this.$n('pgit'),
pgib = this.$n('pgib');
if (pgit) pgHgh += pgit.offsetHeight;
if (pgib) pgHgh += pgib.offsetHeight;
}
// Bug #1815882 and Bug #1835369
var hgh = zk(n).contentHeight()
- (this.ehead ? this.ehead.offsetHeight : 0)
- (this.efoot ? this.efoot.offsetHeight : 0)
- pgHgh;
return this.frozen && this._nativebar ?
hgh - this.efrozen!.offsetHeight : hgh;
}
/** @internal */
override setFlexSize_(flexSize: zk.FlexSize, isFlexMin?: boolean): void {
if (this._cssflex && this.parent!.getFlexContainer_() != null && !isFlexMin)
return;
var n = this.$n_(),
head = this.$n('head');
if (flexSize.height !== undefined) {
if (flexSize.height == 'auto') {
n.style.height = '';
if (head)
head.style.height = '';
} else {
return super.setFlexSize_(flexSize, isFlexMin);
}
}
if (flexSize.width !== undefined) {
if (flexSize.width == 'auto') {
if (this._hflex != 'min')
n.style.width = '';
if (head)
head.style.width = '';
} else {
return super.setFlexSize_(flexSize, isFlexMin);
}
}
}
/* set the height. */
/** @internal */
_setHgh(hgh?: string): void {
var n = this.$n_(),
ebody = this.ebody!,
ebodyStyle = ebody.style;
if (this.isVflex() || (hgh && hgh != 'auto' && !hgh.includes('%'))) {
if (zk.webkit && ebodyStyle.height == jq.px(this._vflexSize()))
return; // Bug ZK-417, ignore to set the same size
ebodyStyle.height = ''; //allow browser adjusting to default size
var h = this._vflexSize();
if (h < 0)
h = 0;
if (this._vflex != 'min')
ebodyStyle.height = `${h}px`;
} else {
//Bug 1556099
ebodyStyle.height = '';
n.style.height = hgh!;
}
}
/** @internal */
_ignoreHghExt(): boolean {
return false;
}
/** Calculates the size. @internal */
_calcSize(): void {
this._beforeCalcSize();
//Bug 1553937: wrong sibling location
//Otherwise,
//IE: element's width will be extended to fit body
//note: we don't solve this bug for paging yet
var n = this.$n_(),
sizedByContent = this.isSizedByContent(),
ehead = this.ehead,
ebodyrows = this.ebodyrows,
efoot = this.efoot,
efootrows = this.efootrows;
if (ehead) {
if (sizedByContent && ebodyrows)
this._adjHeadWd();
} else if (efoot) {
if (efootrows && ebodyrows)
_cpCellWd(this);
}
//check if need to span width
this._adjSpanWd();
// no header case
_fixBodyMinWd(this, true);
// B50-ZK-543, B50-ZK-773
// should re-calculate height because
// the string height maybe changed after width changed.
if (sizedByContent
&& this.getRows && this.getRows() > 1
&& (typeof this._calcHgh == 'function')
&& this.ebody!.style.height) { // check only if height exists for F50-3000339.zul
this._calcHgh(); // recalculate height again ZK-796
}
n._lastsz = { height: n.offsetHeight, width: n.offsetWidth }; // cache for the dirty resizing.
this._afterCalcSize();
}
/** @internal */
_beforeCalcSize(): void {
var ebody = this.ebody!;
if (!this._nativebar && (ebody.scrollLeft || ebody.scrollTop)) {
// ZK-2046: Keep ebody scroll position before calculated size, _setHgh would reset it to 0.
this._ebodyScrollPos = { l: ebody.scrollLeft, t: ebody.scrollTop };
}
this._calcHgh();
}
/** @internal */
_afterCalcSize(): void {
// Fix B96-ZK-5247
if (!this.isRealVisible()) return;
var isCSSFlex = this._cssflex && this.isChildrenFlex();
if (this._ebodyScrollPos) {
// ZK-2046: Restore ebody scroll position after calculated size.
this.ebody!.scrollLeft = this._ebodyScrollPos.l;
this.ebody!.scrollTop = this._ebodyScrollPos.t;
this._ebodyScrollPos = undefined;
}
// Set style width to table to avoid colgroup width not working
// because of width attribute (width="100%") on table
var allWidths = this._isAllWidths(),
head = this.head,
ebody = this.ebody,
ebodyrows = this.ebodyrows,
hdfakerbar = head ? head.$n('hdfaker-bar') : undefined,
hdbar = head ? jq(this).find('.' + head.$s('bar'))[0] : undefined,
ftfakerbar = this.eftfaker ? this.head!.$n('ftfaker-bar') : undefined,
scrollbarWidth = jq.scrollbarWidth(),
hasVScroll = zk(ebody).hasVScroll(),
hasHScroll = zk(ebody).hasHScroll();
if (hasVScroll) {
if (hdfakerbar) {
if (isCSSFlex && hdbar)
hdbar.style.flex = `0 1 ${scrollbarWidth}px`;
else
hdfakerbar.style.width = `${scrollbarWidth}px`;
}
if (ftfakerbar)
ftfakerbar.style.width = `${scrollbarWidth}px`;
} else {
var zero = MeshWidget.WIDTH0;
//refix B70-ZK-2114: remove hdfakerbar when there is no native scrollbar
if (hdfakerbar) {
if (isCSSFlex && hdbar)
hdbar.style.flex = '0 1 ' + zero + 'px';
else
hdfakerbar.style.width = zero;
}
if (ftfakerbar)
ftfakerbar.style.width = zero;
}
if (hasHScroll && ebodyrows && this._vflex == 'min') {
var ehead = this.ehead,
efoot = this.efoot,
hgh = jq(ebodyrows).height()!;
hgh += scrollbarWidth;
ebody!.style.height = jq.px0(hgh);
if (ehead) {
hgh += ehead.offsetHeight;
}
if (efoot) {
hgh += efoot.offsetHeight;
}
this.$n_().style.height = jq.px0(hgh);
}
if (isCSSFlex) {
//update
head!.afterChildrenFlex_();
return;
}
if (allWidths) {
var hdtbl = this.eheadtbl,
bdtbl = this.ebodytbl,
fttbl = this.efoottbl;
if (hdtbl) {
var wd = 0;
for (var w = this.ehdfaker!.firstChild; w; w = w.nextSibling) {
const n = w as HTMLElement;
if (n.style.display != 'none' && !n.id.endsWith('hdfaker-bar')) // B70-ZK-2307 and B70-ZK-2358
wd += zk.parseInt(n.style.width);
}
if (wd > 0) { //ZK-2772, ZK-2903: only when hdfaker has width, set back to table
//ZK-3938: only adjust width in Chrome (ZK-4219: and safari), but zk.chrome returns true in Edge, we need to check !zk.edge
hdtbl.style.width = (hdfakerbar && hasVScroll && (zk.chrome || zk.safari) && !zk.edge_legacy) ? `${wd + scrollbarWidth}px` : `${wd}px`;
if (bdtbl)
bdtbl.style.width = `${wd}px`;
if (fttbl)
fttbl.style.width = `${wd}px`;
} else {
var hideTable = false;
for (var header = this.head!.firstChild; header; header = header.nextSibling) {
if (header.isVisible()) {
hideTable = false;
break;
}
hideTable = true;
}
if (hideTable) {
hdtbl.style.visibility = 'hidden';
if (bdtbl)
bdtbl.style.visibility = 'hidden';
if (fttbl)
fttbl.style.visibility = 'hidden';
}
}
}
} else if (this.frozen) {
//B70-ZK-2468: should sync ebody width with ebodytbl width
var ehead = this.ehead,
hdtbl = this.eheadtbl,
ebody = this.ebody,
bdtbl = this.ebodytbl,
efoot = this.efoot,
fttbl = this.efoottbl;
if (ehead && hdtbl)
hdtbl.style.width = jq.px0(ehead.clientWidth);
if (ebody && bdtbl)
bdtbl.style.width = jq.px0(ebody.clientWidth);
if (efoot && fttbl)
fttbl.style.width = jq.px0(efoot.clientWidth);
if (!this.frozen._smooth)
this._syncFaker();
}
}
/** @internal */
_syncFaker(): void {
var head = this.head;
if (!head || !this.desktop)
return;
var totalCols = head.nChildren,
width0 = zul.mesh.MeshWidget.WIDTH0,
newTotalWidth = this.ebodytbl!.clientWidth,
oldTotalWidth = 0,
epsilon = 1;
for (var i = 0, header = head.firstChild!, hdcol = this.ehdfaker!.firstChild; i < totalCols;
header = header.nextSibling!, hdcol = hdcol!.nextSibling, i++) {
var fakerStyleWidth = (hdcol as HTMLElement).style.width;
if (fakerStyleWidth == '')
return;
var isHidden = fakerStyleWidth == width0,
width = isHidden ? parseFloat(header._origWd!) : parseFloat(fakerStyleWidth);
if (header.getWidth() || header.getHflex())
newTotalWidth -= width;
else
oldTotalWidth += width;
}
if (Math.abs(newTotalWidth - oldTotalWidth) < epsilon)
return;
for (var i = 0, header = head.firstChild!, hdcol = this.ehdfaker!.firstChild; i < totalCols;
header = header.nextSibling!, hdcol = hdcol!.nextSibling, i++) {
if (header.getWidth() || header.getHflex())
continue;
var fakerStyleWidth = (hdcol as HTMLElement).style.width,
multiplier = newTotalWidth / oldTotalWidth,
isHidden = fakerStyleWidth == width0;
if (isHidden)
header._origWd = jq.px0(parseFloat(header._origWd!) * multiplier);
else {
var newStyleWidth = jq.px0(parseFloat(fakerStyleWidth) * multiplier),
bdfaker = header.$n('bdfaker'),
ftfaker = header.$n('ftfaker');
(hdcol as HTMLElement).style.width = newStyleWidth;
if (bdfaker)
bdfaker.style.width = newStyleWidth;
if (ftfaker)
ftfaker.style.width = newStyleWidth;
}
}
}
//return if all widths of columns are fixed (directly or indirectly)
/** @internal */
_isAllWidths(): boolean {
// ZK-2157: should skip if the mesh has no children
if (this.isSizedByContent() && this.ebodyrows && this.ebodyrows.firstChild)
return true;
if (!this.head)
return false;
for (var w = this.head.firstChild; w; w = w.nextSibling) {
if ((w._width == undefined || w._width.indexOf('px') <= 0)
&& (w._hflex != 'min' || w._hflexsz === undefined)
&& w.isVisible()) {
return false;
}
}
return true;
}
/** @internal */
domFaker_(out: string[], fakeId: string): void { //used by redraw
var head = this.head!;
out.push('<colgroup id="', /*safe*/ head.uuid, /*safe*/ fakeId, '">');
for (var w = head.firstChild; w; w = w.nextSibling) {
var wd = MeshWidget._getWidth(w, w._hflexWidth ? `${w._hflexWidth}px` : w.getWidth()),
visibility = w.isVisible() ? '' : 'visibility: collapse;';
// B70-ZK-2036: Style width should end with 'px'.
wd = wd != null ? 'width: ' + wd + ';' : '';
out.push('<col id="', /*safe*/ w.uuid, /*safe*/ fakeId, '" style="', /*safe*/ wd, /*safe*/ visibility, '"></col>');
}
if (fakeId.indexOf('hd') > 0 || fakeId.indexOf('ft') > 0)
out.push('<col id="', /*safe*/ head.uuid, /*safe*/ fakeId, '-bar" style="width: 0px" ></col>');
out.push('</colgroup>');
}
/** @internal */
override onChildAdded_(child: zk.Widget): void {
super.onChildAdded_(child);
if (child instanceof this.getHeadWidgetClass()) {
this.head = child;
this._minWd = undefined;
// TODO: remove the type assertion below. Should be unnecessary, but tsc infers it
// to be `never` which is not wrong.
} else if (!(child instanceof zul.mesh.Auxhead))
return;
var nsib = child.nextSibling;
if (nsib)
for (var hds = this.heads, j = 0, len = hds.length; j < len; ++j)
if (hds[j] == nsib) {
hds.splice(j, 0, child);
return; //done
}
this.heads.push(child);
}
/** @internal */
override onChildRemoved_(child: zk.Widget): void {
super.onChildRemoved_(child);
if (child == this.head) { // If true, child is guaranteed to be a HeadWidget
this._minWd = this.head = undefined;
this.heads.$remove(child as zul.mesh.HeadWidget);
} else if (child instanceof zul.mesh.Auxhead)
this.heads.$remove(child);
else if (child instanceof zul.mesh.Frozen)
this.efrozen = undefined;
}
//bug# 3022669: listbox hflex="min" sizedByContent="true" not work
/** @internal */
override beforeMinFlex_(orient: zk.FlexOrient): number | undefined {
if (this._hflexsz === undefined && orient == 'w' && this._width === undefined) {
if (this.isSizedByContent())
this._calcSize();
if (this.head) {
this._fixHeaders(true);/* B50-3315594.zul */
for (var w = this.head.firstChild; w; w = w.nextSibling)
if (w._hflex == 'min' && w._hflexsz === undefined) //header hflex="min" not done yet!
return undefined;
}
_fixBodyMinWd(this); // sized by content without header
return _getMinWd(this); //grid.invalidate() with hflex="min" must maintain the width
}
return undefined;
}
// fixed for B50-3315594.zul
/** @internal */
override beforeParentMinFlex_(orient: zk.FlexOrient): void {
if (orient == 'w') {
if (this.isSizedByContent())
this._calcSize();
if (this.head)
this._fixHeaders();
} else
this._calcSize();
}
/** @internal */
override clearCachedSize_(): void {
super.clearCachedSize_();
this._clearCachedSize();
var tr: HTMLTableRowElement | undefined;
if (!this.ebdfaker && (tr = _getSigRow(this))) { //empty head case
for (var cells = tr.cells, i = cells.length; i--;)
cells[i].style.width = '';
}
var head = this.getHeadWidget();
if (head) {
for (var w = head.firstChild; w; w = w.nextSibling)
delete w._hflexsz;
}
}
/** @internal */
_clearCachedSize(): void {
const n = this.$n();
if (n)
n._lastsz = this._minWd = undefined;
}
/** @internal */
_calcMinWds(): MeshWidth { //used in HeaderWidgets
if (!this._minWd)
this._minWd = _calcMinWd(this);
return this._minWd;
}
/** @internal */
_adjSpanWd(): void { //used in HeadWidgets
if (!this._isAllWidths() || !this.isSpan())
return;
var hdfaker = this.ehdfaker,
bdfaker = this.ebdfaker,
ftfaker = this.eftfaker;
if (!hdfaker || !bdfaker)
return;
var head = this.head!.$n();
if (!head)
return;
this._calcMinWds();
var wd: number,
wds: number[] = [],
width = 0,
hdcol = hdfaker.firstChild,
bdcol = bdfaker.firstChild,
_minwds = this._minWd!.wds,
hdlen = this.head!.nChildren;
for (var temphdcol = hdcol, w = this.head!.firstChild, i = 0; w; w = w.nextSibling, i++) {
if (zk(temphdcol).isVisible(true)) {
var wdh = w._width;
if (w._hflex == 'min')
wd = wds[i] = _minwds[i];
else if (wdh?.endsWith('px'))
wd = wds[i] = zk.parseInt(wdh);
else
wd = wds[i] = _minwds[i];
width += wd;
}
temphdcol = temphdcol!.nextSibling;
}
var ftcol = ftfaker?.firstChild,
total = this.ebody!.clientWidth,
extSum = total - width,
count = total,
visj = -1,
tblWidth = 0; //refix B70-ZK-2394: should sync colgroup width with table width
//span to all columns
if (this._nspan! < 0) {
var hasFrozenScrolled = this.frozen?.getStart();
for (var i = 0; hdcol && i < hdlen; hdcol = hdcol.nextSibling, i++) {
// ZK-2222: should check visibility
if (!zk(hdcol).isVisible(true)) {
bdcol = bdcol!.nextSibling;
if (ftcol)
ftcol = ftcol.nextSibling;
continue;
} else {
// for bug ZK-2772, we don't span it when frozen column has scrolled.
// Instead, we use its faker width.
if (hasFrozenScrolled) {
if (extSum <= 0) {
wds[i] = wd = wds[i];
} else {
if ((hdcol as HTMLElement).style.width) {
wds[i] = wd = zk.parseInt((hdcol as HTMLElement).style.width);
} else {
wds[i] = wd = Math.round(((wds[i] * total / width) + 0.5) || 0);
}
}
} else {
wds[i] = wd = extSum <= 0 ? wds[i] : Math.round(((wds[i] * total / width) + 0.5) || 0);
}
var stylew = jq.px0(wd);
count -= wd;
visj = i;
(hdcol as HTMLElement).style.width = stylew;
(bdcol as HTMLElement).style.width = stylew;
tblWidth += wd; //refix B70-ZK-2394: store each col's width
bdcol = bdcol!.nextSibling;
if (ftcol) {
(ftcol as HTMLElement).style.width = stylew;
ftcol = ftcol.nextSibling;
}
}
}
//compensate calc error but excluding scrolled frozen column
if (extSum > 0 && count != 0 && visj >= 0) {
tblWidth -= wd!; //refix B70-ZK-2394: subtract the last wd (visj is the last)
wd = wds[visj] + count;
var stylew = jq.px0(wd);
tblWidth += wd; //refix B70-ZK-2394: and add new wd
if (!hasFrozenScrolled) {
(bdfaker.childNodes[visj] as HTMLElement).style.width = stylew;
(hdfaker.childNodes[visj] as HTMLElement).style.width = stylew;
if (ftfaker)
(ftfaker.childNodes[visj] as HTMLElement).style.width = stylew;
}
}
} else { //feature#3184415: span to a specific column
visj = this._nspan! - 1;
for (var i = 0; hdcol && i < hdlen; hdcol = hdcol.nextSibling, i++) {
if (!zk(hdcol).isVisible(true)) {
bdcol = bdcol!.nextSibling;
if (ftcol)
ftcol = ftcol.nextSibling;
continue;
} else {
wd = visj == i && extSum > 0 ? (wds[visj] + extSum) : wds[i];
var stylew = jq.px0(wd);
(hdcol as HTMLElement).style.width = stylew;
(bdcol as HTMLElement).style.width = stylew;
tblWidth += wd; //refix B70-ZK-2394: store each col's width
bdcol = bdcol!.nextSibling;
if (ftcol) {
(ftcol as HTMLElement).style.width = stylew;
ftcol = ftcol.nextSibling;
}
}
}
}
//refix B70-ZK-2394: sync colgroup width with (head, body, foot)table width
var allWidths = this._isAllWidths();
if (allWidths) {
var hdtbl = this.eheadtbl,
bdtbl = this.ebodytbl,
fttbl = this.efoottbl;
if (hdtbl) {
hdtbl.style.width = `${tblWidth}px`;
if (bdtbl)
bdtbl.style.width = `${tblWidth}px`;
if (fttbl)
fttbl.style.width = `${tblWidth}px`;
}
}
//bug 3188738: Opera only. Grid/Listbox/Tree span="x" not working
if (zk.opera)
zk(this.$n()).redoCSS();
}
/** @internal */
_adjHeadWd(): void {
var hdfaker = this.ehdfaker,
bdfaker = this.ebdfaker,
ftfaker = this.eftfaker;
if (!hdfaker || !bdfaker || !this.getBodyWidgetIterator().hasNext())
return;
var hdtable = this.eheadtbl!,
head = this.head!.$n();
if (!head)
return;
// Bug #1886788 the size of these table must be specified a fixed size.
var ebody = this.ebody!,
bdtable = this.ebodytbl!,
bdwd = ebody.offsetWidth,
total = Math.max(hdtable.offsetWidth, bdtable.offsetWidth),
tblwd = Math.min(bdwd, bdtable.offsetWidth);
if (total == bdwd && bdwd > tblwd && bdwd - tblwd < 20)
total = tblwd;
var minWd = this._calcMinWds(),
wds = minWd.wds,
width = minWd.width,
hdcol = hdfaker.firstChild,
bdcol = bdfaker.firstChild,
ftcol = ftfaker ? ftfaker.firstChild : undefined,
hwgt = this.head!.firstChild,
notVisibleWidth = MeshWidget.WIDTH0;
for (var i = 0; hwgt; hwgt = hwgt.nextSibling, i++) {
if (hwgt.isVisible()) {
// sizedByContent shall not override column width
if (hwgt._width || wds[i] == 0) {
if (wds[i] == 0) {
(hdcol as HTMLElement).style.width = notVisibleWidth;
(bdcol as HTMLElement).style.width = notVisibleWidth;
if (ftcol)
(ftcol as HTMLElement).style.width = notVisibleWidth;
}
hdcol = hdcol!.nextSibling;
bdcol = bdcol!.nextSibling;
if (ftcol)
ftcol = ftcol.nextSibling;
} else if (!this.frozen || !this.frozen.getStart() || (hdcol as HTMLElement).style.width === '') {
var wd = jq.px(wds[i]);
(hdcol as HTMLElement).style.width = (bdcol as HTMLElement).style.width = wd;
hdcol = hdcol!.nextSibling;
bdcol = bdcol!.nextSibling;
if (ftcol) {
(ftcol as HTMLElement).style.width = wd;
ftcol = ftcol.nextSibling;
}
}
} else {
(hdcol as HTMLElement).style.width = notVisibleWidth;
(bdcol as HTMLElement).style.width = notVisibleWidth;
hdcol = hdcol!.nextSibling;
bdcol = bdcol!.nextSibling;
if (ftcol) {
(ftcol as HTMLElement).style.width = notVisibleWidth;
ftcol = ftcol.nextSibling;
}
}
}
hdtable.style.width = jq.px(width);
bdtable.style.width = jq.px(width);
if (ftfaker)
this.efoottbl!.style.width = jq.px(width);
_adjMinWd(this);
}
/** @internal */
_getFirstRowCells(tbody: HTMLTableSectionElement | undefined): HTMLCollectionOf<HTMLTableCellElement> | undefined {
var rowsLength: number,
tbodyrows: HTMLCollectionOf<HTMLTableRowElement>;
if (tbody && (tbodyrows = tbody.rows) && (rowsLength = tbodyrows.length)) {
var cells = tbodyrows[0].cells,
length = cells.length,
ncols = 0;
//ZK-2752: find the first visible row
//keep the same behavior even there isn't any row visible to avoid other side effects
if (tbody.offsetHeight > 0) {
for (var i = 0; i < rowsLength; i++) {
if (jq(tbodyrows[i]).css('display') != 'none') {
cells = tbodyrows[i].cells;
length = cells.length;
break;
}
}
}
for (var i = 0; i < length; i++) {
var span = cells[i].colSpan;
ncols += span != 1 ? span : 1;
}
if (ncols == length)
return cells;
else {
var outHTML: string | undefined = '';
outHTML += '<tr id="' + /*safe*/ tbody.id + '-fakeRow" style="visibility:hidden;height:0">';
for (var i = 0; i < ncols; i++)
outHTML += '<td></td>';
outHTML += '</tr>';
jq(tbodyrows[0]).before(/*safe*/ outHTML);
outHTML = undefined;
return tbodyrows[0].cells;
}
}
}
/** @internal */
_deleteFakeRow(tbody: HTMLTableSectionElement | undefined): void {
if (tbody)
jq('#' + tbody.id + '-fakeRow').remove();
} // for Grid.js and Listbox.js
/** @internal */
refreshBar_(showBar?: boolean, scrollToTop?: boolean): void {
var bar = this._scrollbar;
if (bar) {
// ZK-355: Keep scroll position before sync scrollbar size
var currentLeft = this._currentLeft,
currentTop = this._currentTop;
bar.syncSize(showBar || this._shallShowScrollbar);
delete this._shallShowScrollbar; // use undefined rather false
if (scrollToTop)
bar.scrollTo(0, 0);
else
bar.scrollTo(currentLeft, currentTop);
//sync frozen
var frozen = this.frozen,
start: number;
if (frozen && (start = frozen._start) != 0) {
frozen._doScrollNow(start);
bar.setBarPosition(start);
}
this._afterCalcSize(); // for ZK-2370, we need to check the faker-bar again
}
}
onFitSize(): void {
// B50-ZK-598: when having rows, height needs to be determined when onFitSize
if (this._rows)
this._calcHgh();
}
/** @internal */
_calcHgh(): void {
var rows: ArrayLike<HTMLTableRowElement> = this.ebodyrows ? this.ebodyrows.rows : [],
n = this.$n_(),
hgh: string | number = n.style.height,
isHgh = hgh && hgh != 'auto' && !hgh.includes('%');
if (isHgh) {
hgh = zk.parseInt(hgh) - zk(n).padBorderHeight();
if (hgh) {
hgh -= this._headHgh(0);
if (hgh < 0) hgh = 0;
var sz = 0;
l_out:
for (var h, j = 0, rl = rows.length; j < rl; ++sz, ++j) {
//next visible row
var r: HTMLTableRowElement;
for (; ; ++j) {//no need to check length again
if (j >= rl) break l_out;
r = rows[j];
if (zk(r).isVisible()) break;
}
var $r = zk(r);
h = $r.offsetTop() + $r.offsetHeight();
if (h >= hgh) {
if (h > hgh + 2) ++sz; //experimental
break;
}
}
sz = Math.ceil(sz && h ? (hgh * sz) / h : hgh / this._headHgh(20));
this._setOrGetVisibleRows(sz);
hgh -= (this.efoot ? this.efoot.offsetHeight : 0);
//bug# 3036398: frozen scrollbar disappear when listbox with vflex="1"
hgh -= (this.efrozen && this._nativebar ? this.efrozen.offsetHeight : 0);
this.ebody!.style.height = `${Math.max(hgh, 0)}px`;
return; //done
}
}
var nVisiRows = 0,
nRows = this.getRows(),
lastVisiRow: HTMLTableRowElement | undefined,
firstVisiRow: HTMLTableRowElement | undefined,
midVisiRow: HTMLTableRowElement | undefined;
for (var j = 0, rl = rows.length; j < rl; ++j) { //tree might collapse some items
var r = rows[j];
if (zk(r).isVisible()) {
++nVisiRows;
if (!firstVisiRow)
firstVisiRow = r;
if (nRows === nVisiRows) {
midVisiRow = r;
break;
//nVisiRows and lastVisiRow useful only if nRows is larger,
//so ok to break here
}
lastVisiRow = r;
}
}
hgh = 0;
var diff = 2;/*experiment*/
if (!nRows) {
if (this.isVflex()) {
// Since ie6 support was dropped, the argument of _vflexSize is not used.
// Some time later, the argument of _vflexSize is dropped from the signature,
// but `this._vflexSize(n.style.height)` wasn't cleaned up until now.
hgh = this._vflexSize();
if (hgh < 25) hgh = 25;
var rowhgh = firstVisiRow ? zk(firstVisiRow).offsetHeight() : undefined;
if (!rowhgh)
rowhgh = this._headHgh(20);
nRows = Math.round((hgh - diff) / rowhgh);
}
this._setOrGetVisibleRows(nRows);
}
if (nRows) {
if (!hgh) {
if (!nVisiRows) {
hgh = this._headHgh(20, true) * nRows;
} else if (nRows <= nVisiRows) {
var $midVisiRow = zk(midVisiRow);
hgh = $midVisiRow.offsetTop() + $midVisiRow.offsetHeight();
} else {
var $lastVisiRow = zk(lastVisiRow);
hgh = $lastVisiRow.offsetTop() + $lastVisiRow.offsetHeight();
hgh = Math.ceil((nRows * hgh) / nVisiRows);
}
}
this.ebody!.style.height = `${hgh}px`;
} else {
this.ebody!.style.height = '';
var focusEL = this.$n('a');
if ((this.getPagingChild()) && focusEL)
focusEL.style.top = '0px'; // Bug ZK-1715: focus has no chance to sync if don't select item after changing page.
}
}
/**
* @returns the real # of rows (aka., real size).
* @internal
*/
_setOrGetVisibleRows(v?: number): number | undefined {
if ('number' == typeof v) {
this._visiRows = v;
} else
return this.getRows() || this._visiRows || 0;
}
/* Height of the head row. If no header, defval is returned. */
/** @internal */
_headHgh(defVal: number, isExcludeAuxhead?: boolean): number {
var headWidget = this.getHeadWidget(), //Bug ZK-1297: get head height exclude auxhead
head = this.ehead,
hgh = isExcludeAuxhead ? (headWidget ? headWidget.$n_().offsetHeight : 0) : (head ? head.offsetHeight : 0);
if (this.paging) {
var pgit = this.$n('pgit'),
pgib = this.$n('pgib');
if (pgit) hgh += pgit.offsetHeight;
if (pgib) hgh += pgib.offsetHeight;
}
return hgh ? hgh : defVal;
}
/**
* Scroll to the specified item by the given index, used by Grid and Listbox,
* this function could be invoked by server-side or client-side,
* when invoked by client-side, scrollRatio will be undefined.
* @param index - the index of item
* @param scrollRatio - the scroll ratio
* @internal
*/
_scrollToIndex(index: number, scrollRatio?: number): void {
// NOTE: _scrollToIndex will only be called by Grid and Listbox, and both of them
// implements _getFirstItemIndex and _getLastItemIndex.
// @ts-expect-error: calling `_getFirstItemIndex` is safe in `_scrollToIndex`
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
var firstItemIndex = this._getFirstItemIndex(),
// @ts-expect-error: calling `_getLastItemIndex` is safe in `_scrollToIndex`
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
lastItemIndex = this._getLastItemIndex(),
body = this.ebody!;
this._targetIndex = index;
this._keepScroll = true;
if (index >= firstItemIndex && index <= lastItemIndex) {
var itemIterator = this.getBodyWidgetIterator();
while (itemIterator.hasNext()) {
var item = itemIterator.next()!;
if (item._index == index) {
item.$n_().scrollIntoView(true);
this._keepScroll = false;
this._topBoundary = this._bottomBoundary = undefined;
return;
}
}
} else if (scrollRatio != undefined) {
body.scrollTop = body.scrollHeight * scrollRatio;
} else if (index < firstItemIndex) {
this._bottomBoundary = body.scrollTop;
this._topBoundary = this._topBoundary == undefined ? 0 : this._topBoundary;
body.scrollTop -= (body.scrollTop - this._topBoundary) / 2;
} else if (index > lastItemIndex) {
this._topBoundary = body.scrollTop;
this._bottomBoundary = this._bottomBoundary == undefined ? body.scrollHeight : this._bottomBoundary;
body.scrollTop += (this._bottomBoundary - body.scrollTop) / 2;
}
}
/** @internal */
override getContentEdgeHeight_(height: number): number {
var height = super.getContentEdgeHeight_(height),
efoot = this.efoot;
if (efoot) {
var zkefoot = zk(efoot);
height += zkefoot.padBorderHeight() + zkefoot.sumStyles('tb', jq.margins);
}
return height;
}
/** @internal */
override afterChildrenMinFlex_(o: zk.FlexOrient): void {
var n = this.$n_();
if (o == 'h') {
n.style.height = jq.px0(Math.ceil(this._vflexsz!));
}
}
//css flex
isChildrenFlex(): boolean {
if (this.head && this.ehead) {
for (var i = this.heads.length; i-- > 0;) {
var header = this.heads[i];
for (var w = header.firstChild; w; w = w.nextSibling) {
if (w._hflex && w._hflex != 'min') {
return true;
}
}
}
}
return false;
}
/** @internal */
waitForRendered_(): Promise<void> {
return new Promise(resolve => {
const callback = (): void => {
if (!this._pendOnRender) return resolve();
setTimeout(callback);
};
callback();
});
}
// ZK-5028 for Treecols, Listhead, and Columns
/** @internal */
override shallFireSizedLaterWhenAddChd_(): boolean {
if (this.inRerendering_()) {
zWatch.listen({
onResponse: [this, this._fixFireSizedLaterWhenAddChd]
});
return true;
}
return false;
}
// ZK-5028 for Treecols, Listhead, and Columns
/** @internal */
_fixFireSizedLaterWhenAddChd(): void {
zUtl.fireSized(this);
zWatch.unlisten({
onResponse: [this, this._fixFireSizedLaterWhenAddChd]
});
}
static readonly WIDTH0 = zk.webkit ? '0.001px' : '0px';
/** @internal */
static _getWidth(wgt: zk.Widget, width: string | undefined): string | undefined {
if (wgt.isVisible())
return width;
return this.WIDTH0;
}
}
/** @class zul.mesh.Scrollbar
* @import zk.Widget
* The extra Scrollbar for the MeshWidget.
* @since 6.5.0
*/
export var Scrollbar = {
/**
* Initialize the scrollbar of MeshWidget.
* @param wgt - a widget
*/
init(wgt: zul.mesh.MeshWidget): zul.Scrollbar | undefined /* zkmax-override could return undefined */ {
var embed = jq(wgt.$n_()).data('embedscrollbar') !== false, // change default value to true since 7.0.2
frozen = wgt.frozen,
startPositionX = 0;
if (frozen) {
var columns = frozen.getColumns()!;
if (wgt.eheadtbl) {
var cells = wgt._getFirstRowCells(wgt.eheadrows);
if (cells) {
for (var i = 0; i < columns; i++)
startPositionX += cells[i].offsetWidth;
}
wgt._deleteFakeRow(wgt.eheadrows);
}
}
return new zul.Scrollbar(wgt.ebody, wgt.ebodytbl, {
embed: embed,
startPositionX: startPositionX,
onSyncPosition: function () {
if (!this.frozen) {
var pos = this.getCurrentPosition(),
head = wgt.ehead,
foot = wgt.efoot;
if (pos && this.hasHScroll()) {
if (head)
head.scrollLeft = pos.x;
if (foot)
foot.scrollLeft = pos.x;
}
}
},
onScrollEnd: function () {
wgt._doScroll();
}
});
},
/**
* @returns the vertical scroll position of the body element of given MeshWidget.
* @param wgt - the widget
*/
getScrollPosV(wgt: zul.mesh.MeshWidget): number {
var bar = wgt._scrollbar;
if (bar)
return bar.getCurrentPosition()!.y;
return wgt.ebody!.scrollTop;
},
/**
* @returns the horizontal scroll position of the body element of given MeshWidget.
* @param wgt - the widget
* @since 7.0.0
*/
getScrollPosH(wgt: zul.mesh.MeshWidget): number {
var bar = wgt._scrollbar;
if (bar)
return bar.getCurrentPosition()!.x;
return wgt.ebody!.scrollLeft;
}
};
zul.mesh.Scrollbar = Scrollbar;