Skip to content

Commit

Permalink
docs(ui5-table): test page for the virtualization (#10380)
Browse files Browse the repository at this point in the history
* feat(ui5-table): Table row actions

* docs(ui5-table): test page for the virtualization
  • Loading branch information
aborjinik authored Dec 30, 2024
1 parent 37af390 commit 1f285ef
Show file tree
Hide file tree
Showing 7 changed files with 216 additions and 90 deletions.
10 changes: 5 additions & 5 deletions packages/main/src/Table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ interface ITableFeature extends UI5Element {
* Called when the table is activated.
* @param table table instance
*/
onTableActivate(table: Table): void;
onTableActivate?(table: Table): void;
/**
* Called when the table finished rendering.
*/
onTableAfterRendering?(): void;
onTableAfterRendering?(table?: Table): void;
}

/**
Expand Down Expand Up @@ -363,7 +363,7 @@ class Table extends UI5Element {
ResizeHandler.register(this, this._onResizeBound);
}
this._events.forEach(eventType => this.addEventListener(eventType, this._onEventBound));
this.features.forEach(feature => feature.onTableActivate(this));
this.features.forEach(feature => feature.onTableActivate?.(this));
this._tableNavigation = new TableNavigation(this);
this._tableDragAndDrop = new TableDragAndDrop(this);
}
Expand Down Expand Up @@ -391,7 +391,7 @@ class Table extends UI5Element {
}

onAfterRendering(): void {
this.features.forEach(feature => feature.onTableAfterRendering?.());
this.features.forEach(feature => feature.onTableAfterRendering?.(this));
}

_getSelection(): TableSelection | undefined {
Expand Down Expand Up @@ -504,7 +504,7 @@ class Table extends UI5Element {
}

_isFeature(feature: any) {
return Boolean(feature.onTableActivate && feature.onTableAfterRendering);
return Boolean(feature.onTableActivate || feature.onTableAfterRendering);
}

_isGrowingFeature(feature: any) {
Expand Down
32 changes: 14 additions & 18 deletions packages/main/src/TableVirtualizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,22 +125,20 @@ class TableVirtualizer extends UI5Element implements ITableFeature {
this._onRowInvalidateBound = this._onRowInvalidate.bind(this);
}

onTableActivate(table: Table): void {
this._table = table;
this._scrollContainer.addEventListener("scroll", this._onScrollBound, { passive: true });
this._onScroll();
}

onAfterRendering(): void {
this._table && this._table._invalidate++;
}

onTableAfterRendering(): void {
onTableAfterRendering(table: Table): void {
if (!this._table) {
return;
this._table = table;
this._scrollContainer.addEventListener("scroll", this._onScrollBound, { passive: true });
this._updateRowsHeight();
this._onScroll();
} else {
this._updateRowsHeight();
}

this._updateRowsHeight();
if (this._tabBlockingState & TabBlocking.Released) {
const tabBlockingRow = this._table.rows.at(this._tabBlockingState & TabBlocking.Next ? -1 : 0) as HTMLElement;
const tabForwardingElement = getTabbableElements(tabBlockingRow).at(this._tabBlockingState & TabBlocking.Next ? 0 : -1);
Expand All @@ -161,10 +159,12 @@ class TableVirtualizer extends UI5Element implements ITableFeature {
reset(): void {
this._lastRowPosition = -1;
this._firstRowPosition = -1;
if (this._scrollContainer.scrollTop > 0) {
this._scrollContainer.scrollTop = 0;
} else {
this._onScroll();
if (this._table) {
if (this._scrollContainer.scrollTop > 0) {
this._scrollContainer.scrollTop = 0;
} else {
this._onScroll();
}
}
}

Expand All @@ -177,11 +177,7 @@ class TableVirtualizer extends UI5Element implements ITableFeature {
}

_onScroll(): void {
if (!this._table) {
return;
}

const headerRow = this._table.headerRow[0];
const headerRow = this._table!.headerRow[0];
const headerHeight = headerRow.offsetHeight;
let scrollTop = this._scrollContainer.scrollTop;
let scrollableHeight = this._scrollContainer.clientHeight;
Expand Down
133 changes: 66 additions & 67 deletions packages/main/test/pages/TableVirtualizer.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,85 +15,84 @@
</style>
</head>

<body>
<!-- toolbar with ui5-bar -->
<ui5-bar design="Header" accessible-name-ref="title" style="position: sticky; top: 0; z-index: 2; height: 50px;">
<ui5-title tabindex="0" level="H3" id="title" slot="startContent">My Selectable Products (1000)</ui5-title>
<ui5-slider id="slider" min="0" max="100" step="1" value="100"
label-interval="0"></ui5-slider>
</ui5-bar>
<body style="background-color: var(--sapBackgroundColor)" class="ui5-content-density-compact">
<div class="section">
<ui5-table id="table" loading-delay="100" style="height: 150px;">
<ui5-table-virtualizer id="virtualizer" slot="features" row-count="1000" row-height="32"></ui5-table-virtualizer>
<ui5-table-selection slot="features"></ui5-table-selection>
<ui5-table-header-row slot="headerRow" sticky>
<ui5-table-header-cell width="300px">Product Name</ui5-table-header-cell>
<ui5-table-header-cell>Dimensions</ui5-table-header-cell>
<ui5-table-header-cell>Weight</ui5-table-header-cell>
<ui5-table-header-cell horizontal-align="Right">Price</ui5-table-header-cell>
</ui5-table-header-row>
</ui5-table>
<template id="rowTemplate">
<ui5-table-row position="-1" row-key="-1">
<ui5-table-cell data="name"></ui5-table-cell>
<ui5-table-cell data="height"></ui5-table-cell>
<ui5-table-cell data="weight"></ui5-table-cell>
<ui5-table-cell data="price"></ui5-table-cell>
</ui5-table-row>
</template>
</div>

<ui5-table id="table" loading-delay="0" accessible-name-ref="title" no-data-text="No data found" overflow-mode="Scroll" style="height: 375px;">
<ui5-table-virtualizer id="virtualizer" slot="features" row-height="51" row-count="10"></ui5-table-virtualizer>
<ui5-table-selection id="selection" selected="2 5" slot="features"></ui5-table-selection>
<ui5-table-header-row slot="headerRow" sticky>
<ui5-table-header-cell width="200px" id="produtCol">Product</ui5-table-header-cell>
<ui5-table-header-cell width="max-content" id="supplierCol">Supplier</ui5-table-header-cell>
<ui5-table-header-cell width="max-content" id="dimensionsCol">Dimensions</ui5-table-header-cell>
<ui5-table-header-cell width="max-content" id="weightCol">Weight</ui5-table-header-cell>
<ui5-table-header-cell width="100px" id="priceCol">Price</ui5-table-header-cell>
</ui5-table-heπader-row>
</ui5-table>

<ui5-input value="after table" data-sap-ui-fastnavgroup="true"></ui5-input>
<script>
const slider = document.getElementById("slider");
const table = document.getElementById("table");
let timer = 0;
slider.addEventListener("input", (e) => {
table.style.width = `${e.target.value}%`;
});
table.addEventListener("row-click", (e) => {
console.log(`${Date.now()}: Row with the row-key ${e.detail.row.row-key} is clicked`);
});

const virtualizerData = [];
for (let i = 0; i <= 1000; i++) {
virtualizerData.push(Math.random() * 200 + 32);
}
class ProductStore {
constructor() {
this.products = [];
}

const virtualizer = document.getElementById("virtualizer");
virtualizer.addEventListener("range-change", (e) => {
async fetchProducts(first, last) {
const products = [];
for (let i = first; i < last; i++) {
this.products[i] ??= await this.fetchProduct(i);
products.push(this.products[i]);
}
return products;
}

if (table.rows.length) {
clearTimeout(timer);
table.loading = true;
timer = setTimeout(() => {
for (let i = e.detail.first; i < e.detail.last; i++) {
const index = i - e.detail.first;
const content = table.rows[index].cells[0];
table.rows[index].rowKey = i + "";
table.rows[index].position = i;
content.querySelector("b").firstChild.nodeValue = `Notebook Basic ${i}`;
content.querySelector("a").firstChild.nodeValue = `HT-100${i}`;
}
table.loading = false;
}, 500);
return;
async fetchProduct(index) {
return new Promise(resolve => {
setTimeout(() => {
resolve({
key: `P${index}`,
name: `Product ${index}`,
height: `${(Math.random() * 100).toFixed(0)} cm`,
weight: `${(Math.random() * 100).toFixed(1)} KG`,
price: `${(Math.random() * 1000).toFixed(2)} EUR`
});
}, Math.random() * 10); // Simulate network delay
});
}
}

table.rows.forEach(row => row.remove());
const productStore = new ProductStore();
const table = document.getElementById("table");
const rowTemplate = document.getElementById("rowTemplate");
const virtualizer = document.getElementById("virtualizer");

for (let i = e.detail.first; i < e.detail.last; i++) {
const newRow = document.createElement("ui5-table-row");
newRow.setAttribute("row-key", i.toString());
newRow.position = i;
newRow.innerHTML = `
<ui5-table-cell><ui5-label><b>Notebook Basic ${i}</b><br><a href="#">HT-100${i}</a></ui5-label></ui5-table-cell>
<ui5-table-cell><ui5-label>Technocom</ui5-label></ui5-table-cell>
<ui5-table-cell><ui5-input></ui5-input></ui5-table-cell>
<ui5-table-cell><ui5-label style="color: #2b7c2b"><b>3.7</b> KG</ui5-label></ui5-table-cell>
<ui5-table-cell><ui5-label><b>29</b> EUR</ui5-label></ui5-table-cell>
`;
table.appendChild(newRow);
virtualizer.addEventListener("range-change", async (e) => {
const { first, last } = e.detail;
const products = await productStore.fetchProducts(first, last);
for (let i = first; i < last; i++) {
const rowIndex = i - first;
const product = products[rowIndex];
const row = table.rows[rowIndex] || table.appendChild(rowTemplate.content.firstElementChild.cloneNode(true));
row.setAttribute("position", i);
row.setAttribute("row-key", product.key);
row.querySelectorAll("[data]").forEach(el => {
el.textContent = product[el.getAttribute("data")];
});
}
for (let i = last; i < table.rows.length; i++) {
table.rows[i].remove();
}
});

const selection = document.getElementById("selection");
selection.addEventListener("change", (e) => {
console.log(e.target.selected);
});
</script>

</body>

</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
slug: ../../TableVirtualizer
sidebar_class_name: newComponentBadge expComponentBadge
---

import Virtualizer from "../../../_samples/main/Table/Virtualizer/Virtualizer.md";

<%COMPONENT_OVERVIEW%>

<%COMPONENT_METADATA%>

## Basic Sample

<Virtualizer/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import html from '!!raw-loader!./sample.html';
import js from '!!raw-loader!./main.js';

Enhance your table with virtualization capabilities by incorporating the **Virtualizer** feature.

For effective table virtualization, the `range-change` event with its `first` and `last` parameters determines which rows are currently visible and need to be rendered. To ensure proper virtualization, you must set the following attributes:

- `row-count` for the `Table`: This attribute specifies the total number of rows in the table. It helps the virtualizer determine the number of rows to manage.

- `row-height` for the `Table`: This attribute defines the height of each row in the table. Consistent row height allows the virtualizer to calculate which rows are currently visible and need to be rendered.

- `position` for the `TableRow`: This attribute determines the position of each row within the table. Proper positioning ensures that rows are rendered in the correct location when the user scrolls.

By setting these attributes and handling the `range-change` event properly, the `TableVirtualizer` can efficiently manage and render only the rows that are visible when the user scrolls.

<Editor html={html} js={js} />
61 changes: 61 additions & 0 deletions packages/website/docs/_samples/main/Table/Virtualizer/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import "@ui5/webcomponents/dist/Table.js";
import "@ui5/webcomponents/dist/TableRow.js";
import "@ui5/webcomponents/dist/TableCell.js";
import "@ui5/webcomponents/dist/TableHeaderRow.js";
import "@ui5/webcomponents/dist/TableHeaderCell.js";
import "@ui5/webcomponents/dist/TableVirtualizer.js";
import "@ui5/webcomponents/dist/TableSelection.js";

class ProductStore {
constructor() {
this.products = [];
}

async fetchProducts(first, last) {
const products = [];
for (let i = first; i < last; i++) {
this.products[i] ??= await this.fetchProduct(i);
products.push(this.products[i]);
}
return products;
}

async fetchProduct(index) {
return new Promise(resolve => {
setTimeout(() => {
resolve({
key: `P${index}`,
name: `Product ${index}`,
height: `${(Math.random() * 100).toFixed(0)} cm`,
weight: `${(Math.random() * 100).toFixed(1)} KG`,
price: `${(Math.random() * 1000).toFixed(2)} EUR`
});
}, Math.random() * 10); // Simulate network delay
});
}
}

const productStore = new ProductStore();
const table = document.getElementById("table");
const rowTemplate = document.getElementById("rowTemplate");
const virtualizer = document.getElementById("virtualizer");

virtualizer.addEventListener("range-change", async (e) => {
const { first, last } = e.detail;
const products = await productStore.fetchProducts(first, last);
for (let i = first; i < last; i++) {
const rowIndex = i - first;
const product = products[rowIndex];
const row = table.rows[rowIndex] || table.appendChild(rowTemplate.content.firstElementChild.cloneNode(true));
row.setAttribute("position", i);
row.setAttribute("row-key", product.key);
row.querySelectorAll("[data]").forEach(el => {
el.textContent = product[el.getAttribute("data")];
});
}
for (let i = last; i < table.rows.length; i++) {
table.rows[i].remove();
}
});

requestAnimationFrame(() => virtualizer.reset());
40 changes: 40 additions & 0 deletions packages/website/docs/_samples/main/Table/Virtualizer/sample.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!-- playground-fold -->
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sample</title>
</head>

<body style="background-color: var(--sapBackgroundColor)">
<div class="section">
<template id="rowTemplate">
<ui5-table-row position="-1" row-key="-1">
<ui5-table-cell data="name"></ui5-table-cell>
<ui5-table-cell data="height"></ui5-table-cell>
<ui5-table-cell data="weight"></ui5-table-cell>
<ui5-table-cell data="price"></ui5-table-cell>
</ui5-table-row>
</template>
<!-- playground-fold-end -->
<ui5-table id="table" loading-delay="100" style="height: 150px;" class="ui5-content-density-compact">
<ui5-table-virtualizer id="virtualizer" slot="features" row-count="1000" row-height="32"></ui5-table-virtualizer>
<!-- playground-fold -->
<ui5-table-selection slot="features"></ui5-table-selection>
<ui5-table-header-row slot="headerRow" sticky>
<ui5-table-header-cell min-width="150px">Product Name</ui5-table-header-cell>
<ui5-table-header-cell>Dimensions</ui5-table-header-cell>
<ui5-table-header-cell>Weight</ui5-table-header-cell>
<ui5-table-header-cell horizontal-align="Right">Price</ui5-table-header-cell>
</ui5-table-header-row>
<!-- playground-fold-end -->
</ui5-table>
<!-- playground-fold -->
</div>
<script type="module" src="main.js"></script>
</body>

</html>
<!-- playground-fold-end -->

0 comments on commit 1f285ef

Please sign in to comment.