optimize: introduce vCell, render time while scrolling.

This commit is contained in:
2024-03-04 11:18:08 +08:00
parent 20a8fbae02
commit a28b56b191
3 changed files with 163 additions and 97 deletions

View File

@ -67,7 +67,13 @@ let r = lang;
/**
* 键值字典
* @template T
* @typedef {Map<string, T>} KeyMap
* @typedef {{[key: string]: T}} KeyMap
*/
/**
* 索引字典
* @template T
* @typedef {Map<number, T>} IndexMap
*/
/**
@ -468,6 +474,24 @@ const GridColumnDirection = {
* @this Grid
*/
/**
* @typedef GridVirtualRow
* @property {boolean} editing - 行处于编辑状态
* @property {IndexMap<GridVirtualCell>} cells - 虚拟单元格数组
* @private
*/
/**
* @typedef GridVirtualCell
* @property {string} background - 单元格背景色
* @property {string} value - 单元格值
* @property {string} tooltip - 单元格提示文本
* @property {boolean} enabled - 单元格是否可用
* @property {string} style - 单元格样式字符串
* @property {string} attrs - 单元格附加属性字符串
* @private
*/
/**
* @typedef GridColumnAttr
* @property {boolean} dragging - 列正在拖拽
@ -630,6 +654,12 @@ export class Grid {
* @private
*/
rowCount: -1,
/**
* 虚拟单元格字典
* @type {IndexMap<GridVirtualRow>}
* @private
*/
virtualRows: {},
/**
* 列类型缓存字典
* @type {KeyMap<GridColumn>}
@ -2401,6 +2431,8 @@ export class Grid {
const readonly = this.readonly;
for (let i = 0; i < count; ++i) {
const row = createElement('tr', 'ui-grid-row');
const virtualRow = { cells: {} };
this._var.virtualRows[exists + i] = virtualRow;
let left = this.expandable ? ExpandableWidth : 0;
if (this.expandable) {
const icon = createIcon('fa-solid', 'caret-right');
@ -2418,6 +2450,7 @@ export class Grid {
}
cols.forEach((col, j) => {
const cell = createElement('td', 'ui-grid-cell');
virtualRow.cells[j] = {};
if (col.visible !== false) {
let style = this._get(col.key, 'style') ?? {};
if (col.isfixed) {
@ -2434,8 +2467,9 @@ export class Grid {
if (style !== '') {
cell.style.cssText = style;
}
let element;
if (!readonly && GridColumnTypeEnum.isCheckbox(col.type)) {
cell.appendChild(GridCheckboxColumn.createEdit(e => this._onRowChanged(e, exists + i, col, e.target.checked, cell)));
element = GridCheckboxColumn.createEdit(e => this._onRowChanged(e, exists + i, col, e.target.checked, cell));
// this._var.colTypes[col.key] = GridCheckboxColumn;
} else {
let type = this._var.colTypes[col.key];
@ -2448,11 +2482,19 @@ export class Grid {
type ??= GridColumn;
this._var.colTypes[col.key] = type;
}
const element = type.create(col, i, this);
element = type.create(col, i, this);
if (typeof col.class === 'string') {
type.setClass(element, col.class);
}
cell.appendChild(element);
}
cell.appendChild(element);
if (col.events != null) {
for (let ev of Object.entries(col.events)) {
element[ev[0]] = e => {
const item = this._var.currentSource[this._var.startIndex + exists + i].values;
ev[1].call(item, e);
};
}
}
} else {
cell.style.display = 'none';
@ -2481,17 +2523,17 @@ export class Grid {
_fillRows(rows, cols, widths) {
const startIndex = this._var.startIndex;
const selectedIndexes = this._var.selectedIndexes;
const stateChanged =
this._var.oldIndex !== startIndex ||
selectedIndexes == null ||
this._var.oldSelectedIndexes?.length !== selectedIndexes.length ||
this._var.oldSelectedIndexes.find((s, i) => s !== selectedIndexes[i]) != null;
if (stateChanged) {
this._var.oldIndex = startIndex;
if (selectedIndexes != null) {
this._var.oldSelectedIndexes = selectedIndexes.slice();
}
}
// const stateChanged =
// this._var.oldIndex !== startIndex ||
// selectedIndexes == null ||
// this._var.oldSelectedIndexes?.length !== selectedIndexes.length ||
// this._var.oldSelectedIndexes.find((s, i) => s !== selectedIndexes[i]) != null;
// if (stateChanged) {
// this._var.oldIndex = startIndex;
// if (selectedIndexes != null) {
// this._var.oldSelectedIndexes = selectedIndexes.slice();
// }
// }
const offset = this.expandable ? 1 : 0;
const readonly = this.readonly;
rows.forEach((row, i) => {
@ -2502,6 +2544,7 @@ export class Grid {
if (!isPositive(row.children.length)) {
return;
}
const virtualRow = this._var.virtualRows[i];
const item = vals.values;
const selected = selectedIndexes.includes(startIndex + i);
if (selected) {
@ -2509,6 +2552,8 @@ export class Grid {
} else if (row.classList.contains('selected')) {
row.classList.remove('selected');
}
const stateChanged = virtualRow.editing !== selected;
virtualRow.editing = selected;
// data
if (this.expandable) {
const expanded = vals.__expanded;
@ -2566,6 +2611,12 @@ export class Grid {
if (cell == null) {
return;
}
let virtualCell;
if (stateChanged) {
virtualRow.cells[j] = virtualCell = {};
} else {
virtualCell = virtualRow.cells[j];
}
let val;
if (col.text != null) {
val = col.text;
@ -2586,10 +2637,13 @@ export class Grid {
if (typeof bg === 'function') {
bg = col.background(item);
}
cell.style.backgroundColor = bg ?? '';
} else if (typeof col.bgFilter === 'function') {
const bgColor = col.bgFilter(item);
cell.style.backgroundColor = bgColor ?? '';
bg = col.bgFilter(item);
}
bg ??= '';
if (bg !== virtualCell.background) {
virtualCell.background = bg;
cell.style.backgroundColor = bg;
}
const isCheckbox = GridColumnTypeEnum.isCheckbox(col.type);
const type = isCheckbox ? GridCheckboxColumn : this._var.colTypes[col.key] ?? GridColumn;
@ -2624,35 +2678,49 @@ export class Grid {
type.setClass(element, col.class);
}
cell.replaceChildren(element);
if (col.events != null) {
for (let ev of Object.entries(col.events)) {
element[ev[0]] = ev[1].bind(item);
}
}
} else {
element = cell.children[0];
}
} else {
element = cell.children[0];
}
let enabled;
if (readonly) {
enabled = false;
} else {
enabled = col.enabled;
if (typeof enabled === 'function') {
enabled = enabled.call(col, item);
} else if (typeof enabled === 'string') {
enabled = item[enabled];
if (val !== virtualCell.value) {
virtualCell.value = val;
type.setValue(element, val, vals, col, this);
}
if (typeof type.setEnabled === 'function') {
let enabled;
if (readonly) {
enabled = false;
} else {
enabled = col.enabled;
if (typeof enabled === 'function') {
enabled = enabled.call(col, item);
} else if (typeof enabled === 'string') {
enabled = item[enabled];
}
}
if (enabled !== virtualCell.enabled) {
virtualCell.enabled = enabled;
type.setEnabled(element, enabled);
}
}
type.setValue(element, val, vals, col, this);
let tip = col.tooltip;
if (typeof tip === 'function') {
tip = tip.call(col, item);
}
if (nullOrEmpty(tip)) {
element.querySelector('.ui-tooltip-wrapper')?.remove();
} else {
setTooltip(element, tip, false, this.element);
}
if (typeof type.setEnabled === 'function') {
type.setEnabled(element, enabled);
if (tip !== virtualCell.tooltip) {
virtualCell.tooltip = tip;
if (nullOrEmpty(tip)) {
element.querySelector('.ui-tooltip-wrapper')?.remove();
} else {
setTooltip(element, tip, false, this.element);
}
}
// auto resize
if (this._var.needResize && widths != null && this._get(col.key, 'autoResize')) {
@ -2667,31 +2735,29 @@ export class Grid {
if (typeof style === 'function') {
style = col.style(item);
}
if (style != null) {
type.setStyle(element, style);
} else {
element.style.cssText = '';
}
} else if (typeof col.styleFilter === 'function') {
const style = col.styleFilter(item);
style = col.styleFilter(item);
}
const styleText = style != null ? convertCssStyle(style) : '';
if (styleText !== virtualCell.style) {
virtualCell.style = styleText;
if (style != null) {
type.setStyle(element, style);
} else {
element.style.cssText = '';
}
}
if (col.events != null) {
for (let ev of Object.entries(col.events)) {
element[ev[0]] = ev[1].bind(item);
}
}
if (col.attrs != null) {
let attrs = col.attrs;
if (typeof attrs === 'function') {
attrs = attrs(item);
}
for (let attr of Object.entries(attrs)) {
element.setAttribute(attr[0], attr[1]);
const attrsText = convertCssStyle(attrs);
if (attrsText !== virtualCell.attrs) {
virtualCell.attrs = attrsText;
for (let attr of Object.entries(attrs)) {
element.setAttribute(attr[0], attr[1]);
}
}
}
});