You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by lo...@apache.org on 2020/04/02 15:27:09 UTC

[myfaces-tobago] branch master updated (71cabc1 -> cf42e95)

This is an automated email from the ASF dual-hosted git repository.

lofwyr pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/myfaces-tobago.git.


    from 71cabc1  tobago-tree: custom elements
     new 0848847  TOBAGO-2021: Sheet should be able to lazy load rows by scroll events
     new cf42e95  Update tobago-sheet.ts

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../myfaces/tobago/component/Attributes.java       |   1 +
 .../apache/myfaces/tobago/event/SheetAction.java   |   7 +-
 .../tobago/internal/component/AbstractUISheet.java |  22 +-
 .../internal/renderkit/renderer/SheetRenderer.java |  19 +-
 .../renderkit/renderer/TreeNodeRenderer.java       |   2 +-
 .../taglib/component/SheetTagDeclaration.java      |   6 +
 .../tobago/renderkit/html/CustomAttributes.java    |  20 ++
 .../tobago/renderkit/html/DataAttributes.java      |   5 -
 .../tobago/example/demo/SheetController.java       |   9 +-
 .../080-sheet/90-lazy/Sheet_Lazy.xhtml}            |   7 +-
 .../src/main/npm/ts/tobago-sheet.ts                | 286 +++++++++++++++++++--
 11 files changed, 343 insertions(+), 41 deletions(-)
 rename tobago-example/tobago-example-demo/src/main/webapp/content/{40-test/3000-sheet/20-1000-entries/1000_Entries.xhtml => 20-component/080-sheet/90-lazy/Sheet_Lazy.xhtml} (88%)


[myfaces-tobago] 02/02: Update tobago-sheet.ts

Posted by lo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

lofwyr pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/myfaces-tobago.git

commit cf42e951d3d61c958f7cdcc58754e0de120cc2ce
Author: Udo Schnurpfeil <gi...@schnurpfeil.de>
AuthorDate: Thu Apr 2 17:25:39 2020 +0200

    Update tobago-sheet.ts
---
 .../tobago-theme-standard/src/main/npm/ts/tobago-sheet.ts         | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-sheet.ts b/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-sheet.ts
index 5b6604c..f355373 100644
--- a/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-sheet.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-sheet.ts
@@ -744,7 +744,7 @@ class Sheet extends HTMLElement {
     this.dataset.tobagoLastClickedRowIndex = String(row.sectionRowIndex);
     if (checkbox && !checkbox.disabled) {
       const selected = this.getHiddenSelected();
-      const rowIndex = this.getDataIndex(row);
+      const rowIndex = Number(row.getAttribute("row-index"));
       if (this.isSelected(rowIndex)) {
         this.deselectRow(selected, rowIndex, row, checkbox);
       } else {
@@ -777,7 +777,7 @@ class Sheet extends HTMLElement {
       const row = rows.item(i);
       const checkbox = this.getSelectorCheckbox(row);
       if (checkbox && !checkbox.disabled) {
-        const rowIndex = this.getDataIndex(row);
+        const rowIndex = Number(row.getAttribute("row-index"));
         const on = value.has(rowIndex);
         if (selectDeselected && !on) {
           this.selectRow(selected, rowIndex, row, checkbox);
@@ -788,10 +788,6 @@ class Sheet extends HTMLElement {
     }
   }
 
-  getDataIndex(row: HTMLTableRowElement): number {
-    return parseInt(row.dataset.tobagoRowIndex);
-  }
-
   /**
    * @param selected input-element type=hidden: Hidden field with the selection state information
    * @param rowIndex int: zero based index of the row.


[myfaces-tobago] 01/02: TOBAGO-2021: Sheet should be able to lazy load rows by scroll events

Posted by lo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

lofwyr pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/myfaces-tobago.git

commit 0848847e2931ed7c78f2622c9460275fd4299d4b
Author: Udo Schnurpfeil <ud...@irian.eu>
AuthorDate: Thu Apr 2 08:36:37 2020 +0200

    TOBAGO-2021: Sheet should be able to lazy load rows by scroll events
---
 .../myfaces/tobago/component/Attributes.java       |   1 +
 .../apache/myfaces/tobago/event/SheetAction.java   |   7 +-
 .../tobago/internal/component/AbstractUISheet.java |  22 +-
 .../internal/renderkit/renderer/SheetRenderer.java |  19 +-
 .../renderkit/renderer/TreeNodeRenderer.java       |   2 +-
 .../taglib/component/SheetTagDeclaration.java      |   6 +
 .../tobago/renderkit/html/CustomAttributes.java    |  20 ++
 .../tobago/renderkit/html/DataAttributes.java      |   5 -
 .../tobago/example/demo/SheetController.java       |   9 +-
 .../080-sheet/90-lazy/Sheet_Lazy.xhtml}            |   7 +-
 .../src/main/npm/ts/tobago-sheet.ts                | 280 +++++++++++++++++++--
 11 files changed, 342 insertions(+), 36 deletions(-)

diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/component/Attributes.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/component/Attributes.java
index 1317934..be4813f 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/component/Attributes.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/component/Attributes.java
@@ -146,6 +146,7 @@ public enum Attributes {
   labelWidth,
   large,
   layoutOrder,
+  lazy,
   left,
   level,
   lang,
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/event/SheetAction.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/event/SheetAction.java
index 07a1236..c9d04ec 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/event/SheetAction.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/event/SheetAction.java
@@ -54,7 +54,12 @@ public enum SheetAction {
   /**
    * Sorting
    */
-  sort();
+  sort,
+
+  /**
+   * A lazy load is requested
+   */
+  lazy;
 
   private String bundleKey;
 
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISheet.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISheet.java
index fdb0841..dc134eb 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISheet.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISheet.java
@@ -93,6 +93,21 @@ public abstract class AbstractUISheet extends AbstractUIData
   @Override
   public void encodeAll(FacesContext facesContext) throws IOException {
 
+    if (isLazy()) {
+      if (getRows() == 0) {
+        LOG.warn("Sheet id={} has lazy=true set, but not set the rows attribute!", getClientId(facesContext));
+      }
+      if (getShowRowRange() != ShowPosition.none) {
+        LOG.warn("Sheet id={} has lazy=true set, but also set showRowRange!=none!", getClientId(facesContext));
+      }
+      if (getShowPageRange() != ShowPosition.none) {
+        LOG.warn("Sheet id={} has lazy=true set, but also set showPageRange!=none!", getClientId(facesContext));
+      }
+      if (getShowDirectLinks() != ShowPosition.none) {
+        LOG.warn("Sheet id={} has lazy=true set, but also set showDirectLinks!=none!", getClientId(facesContext));
+      }
+    }
+
     final AbstractUIReload reload = ComponentUtils.getReloadFacet(this);
 
     if (reload != null && AjaxUtils.isAjaxRequest(facesContext) && reload.isRendered() && !reload.isUpdate()) {
@@ -202,7 +217,7 @@ public abstract class AbstractUISheet extends AbstractUIData
       return getRowCount();
     }
     final int last = getFirst() + getRows();
-    return last < getRowCount() ? last : getRowCount();
+    return Math.min(last, getRowCount());
   }
 
   /**
@@ -510,7 +525,7 @@ public abstract class AbstractUISheet extends AbstractUIData
         break;
       case prev:
         first = getFirst() - getRows();
-        first = first < 0 ? 0 : first;
+        first = Math.max(first, 0);
         break;
       case next:
         if (hasRowCount()) {
@@ -528,6 +543,7 @@ public abstract class AbstractUISheet extends AbstractUIData
         first = getFirstRowIndexOfLastPage();
         break;
       case toRow:
+      case lazy:
         first = pageEvent.getValue() - 1;
         if (hasRowCount() && first > getFirstRowIndexOfLastPage()) {
           first = getFirstRowIndexOfLastPage();
@@ -593,4 +609,6 @@ public abstract class AbstractUISheet extends AbstractUIData
   public abstract ShowPosition getShowPageRange();
 
   public abstract ShowPosition getShowDirectLinks();
+
+  public abstract boolean isLazy();
 }
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SheetRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SheetRenderer.java
index f2e6c2b..72992ab 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SheetRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SheetRenderer.java
@@ -58,11 +58,13 @@ import org.apache.myfaces.tobago.renderkit.css.BootstrapClass;
 import org.apache.myfaces.tobago.renderkit.css.CssItem;
 import org.apache.myfaces.tobago.renderkit.css.Icons;
 import org.apache.myfaces.tobago.renderkit.css.TobagoClass;
+import org.apache.myfaces.tobago.renderkit.html.CustomAttributes;
 import org.apache.myfaces.tobago.renderkit.html.DataAttributes;
 import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes;
 import org.apache.myfaces.tobago.renderkit.html.HtmlButtonTypes;
 import org.apache.myfaces.tobago.renderkit.html.HtmlElements;
 import org.apache.myfaces.tobago.renderkit.html.HtmlInputTypes;
+import org.apache.myfaces.tobago.util.AjaxUtils;
 import org.apache.myfaces.tobago.util.ComponentUtils;
 import org.apache.myfaces.tobago.util.ResourceUtils;
 import org.apache.myfaces.tobago.webapp.TobagoResponseWriter;
@@ -71,6 +73,7 @@ import org.slf4j.LoggerFactory;
 
 import javax.el.ValueExpression;
 import javax.faces.application.Application;
+import javax.faces.component.NamingContainer;
 import javax.faces.component.UIColumn;
 import javax.faces.component.UIComponent;
 import javax.faces.component.UIData;
@@ -96,6 +99,7 @@ public class SheetRenderer extends RendererBase {
   private static final String SUFFIX_COLUMN_RENDERED = ComponentUtils.SUB_SEPARATOR + "rendered";
   private static final String SUFFIX_SCROLL_POSITION = ComponentUtils.SUB_SEPARATOR + "scrollPosition";
   private static final String SUFFIX_SELECTED = ComponentUtils.SUB_SEPARATOR + "selected";
+  private static final String SUFFIX_LAZY = NamingContainer.SEPARATOR_CHAR + "pageActionlazy";
   private static final String SUFFIX_PAGE_ACTION = "pageAction";
 
   @Override
@@ -193,6 +197,7 @@ public class SheetRenderer extends RendererBase {
           break;
         case toPage:
         case toRow:
+        case lazy:
           event = new PageActionEvent(component, action);
           final int target;
           final String value;
@@ -273,6 +278,10 @@ public class SheetRenderer extends RendererBase {
     }
     writer.writeAttribute(DataAttributes.SELECTION_MODE, sheet.getSelectable().name(), false);
     writer.writeAttribute(DataAttributes.FIRST, Integer.toString(sheet.getFirst()), false);
+    writer.writeAttribute(CustomAttributes.ROWS, sheet.getRows());
+    writer.writeAttribute(CustomAttributes.ROW_COUNT, Integer.toString(sheet.getRowCount()), false);
+    writer.writeAttribute(CustomAttributes.LAZY, sheet.isLazy());
+    writer.writeAttribute(CustomAttributes.LAZY_UPDATE, sheet.isLazy() && AjaxUtils.isAjaxRequest(facesContext));
 
     final boolean autoLayout = sheet.isAutoLayout();
     if (!autoLayout) {
@@ -337,6 +346,10 @@ public class SheetRenderer extends RendererBase {
           sheetId + SUFFIX_SELECTED);
     }
 
+    if (sheet.isLazy()) {
+      encodeHiddenInput(writer,null, sheetId + SUFFIX_LAZY);
+    }
+
     StringBuilder expandedValue = null;
     if (sheet.isTreeModel()) {
       expandedValue = new StringBuilder(",");
@@ -630,11 +643,7 @@ public class SheetRenderer extends RendererBase {
       }
 
       writer.startElement(HtmlElements.TR);
-      if (rowRendered instanceof Boolean) {
-        // if rowRendered attribute is set we need the rowIndex on the client
-        writer.writeAttribute(DataAttributes.ROW_INDEX, rowIndex);
-      }
-
+      writer.writeAttribute(CustomAttributes.ROW_INDEX, rowIndex);
       final boolean selected = selectedRows.contains(rowIndex);
       final String[] rowMarkups = (String[]) sheet.getAttributes().get("rowMarkup");
       Markup rowMarkup = Markup.NULL;
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeNodeRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeNodeRenderer.java
index 2569227..54ee3a6 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeNodeRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeNodeRenderer.java
@@ -150,7 +150,7 @@ public class TreeNodeRenderer extends RendererBase {
       writer.writeAttribute(HtmlAttributes.VALUE, clientId, true);
       writer.writeIdAttribute(clientId);
       writer.writeAttribute(HtmlAttributes.SELECTED, selectedState.isAncestorOfSelected(path));
-      writer.writeAttribute(DataAttributes.ROW_INDEX, data.getRowIndex());
+      writer.writeAttribute(CustomAttributes.ROW_INDEX, data.getRowIndex());
     } else {
       writer.startElement(HtmlElements.TOBAGO_TREE_NODE);
 
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SheetTagDeclaration.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SheetTagDeclaration.java
index d845e5b..7918612 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SheetTagDeclaration.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SheetTagDeclaration.java
@@ -260,6 +260,12 @@ public interface SheetTagDeclaration
       methodSignature = "javax.faces.event.ActionEvent")
   void setSortActionListener(String sortActionListener);
 
+  /**
+   * Lazy loading by scroll event.
+   */
+  @TagAttribute
+  @UIComponentTagAttribute(type = "boolean", defaultValue = "false")
+  void setLazy(String lazy);
 
   /**
    * Flag indicating if paging arrows are shown near direct links
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/CustomAttributes.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/CustomAttributes.java
index 7b8030b..1f1201c 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/CustomAttributes.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/CustomAttributes.java
@@ -37,6 +37,14 @@ public enum CustomAttributes implements MarkupLanguageAttributes {
    */
   INDEX("index"),
   ITEMS("items"),
+  /**
+   * Lazy loading in sheet.
+   */
+  LAZY("lazy"),
+  /**
+   * Is this request/response an update of lazy loaded data in sheet.
+   */
+  LAZY_UPDATE("lazy-update"),
   LOCALE("locale"),
   LOCAL_MENU("local-menu"),
   MAX_ITEMS("max-items"),
@@ -51,6 +59,18 @@ public enum CustomAttributes implements MarkupLanguageAttributes {
    */
   RENDER("render"),
   /**
+   * Number of rows to show/load for lazy loading in sheet.
+   */
+  ROWS("rows"),
+  /**
+   * Number of all rows in sheet.
+   */
+  ROW_COUNT("row-count"),
+  /**
+   * Index of a specific row in the sheet.
+   */
+  ROW_INDEX("row-index"),
+  /**
    * The mode of the tab switch: client, reloadTab, reloadPage.
    */
   SWITCH_TYPE("switch-type"),
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/DataAttributes.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/DataAttributes.java
index 385f88a..41736ab 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/DataAttributes.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/DataAttributes.java
@@ -121,11 +121,6 @@ public enum DataAttributes implements MarkupLanguageAttributes {
    */
   RELOAD("data-tobago-reload"),
 
-  /*
-   * Holds the index of the row in a sheet, if the sheet has a rowRendered attribute.
-   */
-  ROW_INDEX("data-tobago-row-index"),
-
   SELECTION_MODE("data-tobago-selection-mode"),
 
   /**
diff --git a/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/SheetController.java b/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/SheetController.java
index 46b529f..53fff0f 100644
--- a/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/SheetController.java
+++ b/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/SheetController.java
@@ -83,12 +83,17 @@ public class SheetController implements Serializable {
   private void init() {
     solarList = astroData.findAll().collect(Collectors.toList());
 
+    int j = 1;
     hugeSolarList = new ArrayList<>();
-    for (int i = 1; i <= 1000; i++) {
+    for (; ; ) {
       for (final SolarObject solarObject : solarList) {
         final SolarObject solarObjectClone = new SolarObject(solarObject);
-        solarObjectClone.setName(solarObject.getName() + " (" + i + ". entry)");
         hugeSolarList.add(solarObjectClone);
+        solarObjectClone.setName("#" + j++ + " " + solarObject.getName());
+
+        if (j > 10000) {
+          return;
+        }
       }
     }
   }
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/40-test/3000-sheet/20-1000-entries/1000_Entries.xhtml b/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/080-sheet/90-lazy/Sheet_Lazy.xhtml
similarity index 88%
rename from tobago-example/tobago-example-demo/src/main/webapp/content/40-test/3000-sheet/20-1000-entries/1000_Entries.xhtml
rename to tobago-example/tobago-example-demo/src/main/webapp/content/20-component/080-sheet/90-lazy/Sheet_Lazy.xhtml
index e64270b..5e46039 100644
--- a/tobago-example/tobago-example-demo/src/main/webapp/content/40-test/3000-sheet/20-1000-entries/1000_Entries.xhtml
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/080-sheet/90-lazy/Sheet_Lazy.xhtml
@@ -21,10 +21,11 @@
                 xmlns="http://www.w3.org/1999/xhtml"
                 xmlns:tc="http://myfaces.apache.org/tobago/component"
                 xmlns:ui="http://java.sun.com/jsf/facelets">
-
+  <ui:param name="title" value="Lazy Loading Large Data"/>
   <tc:sheet value="#{sheetController.hugeSolarList}" id="sheet" var="luminary"
-            rows="1000" markup="small" >
-    <tc:style maxHeight="600px"/>
+            rows="20" markup="small" lazy="true"
+            showRowRange="none" showDirectLinks="none" showPageRange="none">
+    <tc:style maxHeight="500px"/>
     <tc:column label="Name">
       <tc:out value="#{luminary.name}" labelLayout="skip"/>
     </tc:column>
diff --git a/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-sheet.ts b/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-sheet.ts
index 375fda5..5b6604c 100644
--- a/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-sheet.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-sheet.ts
@@ -25,6 +25,8 @@ class Sheet extends HTMLElement {
   mousemoveData: any;
   mousedownOnRowData: any;
 
+  lastCheckMillis: number;
+
   private static getScrollBarSize(): number {
     const body = document.getElementsByTagName("body").item(0);
 
@@ -48,12 +50,23 @@ class Sheet extends HTMLElement {
     return ["INPUT", "TEXTAREA", "SELECT", "A", "BUTTON"].indexOf(element.tagName) > -1;
   }
 
+  private static getRowTemplate(columns: number, rowIndex: number) : string {
+    return `<tr row-index="${rowIndex}" class="tobago-sheet-row" dummy="dummy">
+<td class="tobago-sheet-cell" colspan="${columns}"> </td>
+</tr>`;
+  }
+
   constructor() {
     super();
   }
 
   connectedCallback(): void {
 
+    if (this.lazyUpdate) {
+      // nothing to do here, will be done in method lazyResponse()
+      return;
+    }
+
     // synchronize column widths ----------------------------------------------------------------------------------- //
 
     // basic idea: there are two possible sources for the sizes:
@@ -153,7 +166,7 @@ class Sheet extends HTMLElement {
     const selectionMode = this.dataset.tobagoSelectionMode;
     if (selectionMode === "single" || selectionMode === "singleOrNone" || selectionMode === "multi") {
 
-      for (const row of this.getRows()) {
+      for (const row of this.getRowElements()) {
         row.addEventListener("mousedown", this.mousedownOnRow.bind(this));
 
         row.addEventListener("click", this.clickOnRow.bind(this));
@@ -167,6 +180,43 @@ class Sheet extends HTMLElement {
       });
     }
 
+    // lazy load by scrolling ----------------------------------------------------------------- //
+
+      const lazy = this.lazy;
+
+      if (lazy) {
+        // prepare the sheet with some auto-created (empty) rows
+        const rowCount = this.rowCount;
+        const sheetBody = this.tableBodyDiv;
+        const tableBody = this.tableBody;
+        const columns = tableBody.rows[0].cells.length;
+        let current: HTMLTableRowElement = tableBody.rows[0]; // current row in this algorithm, begin with first
+        // the algorithm goes straight through all rows, not selectors, because of performance
+        for (let i = 0; i < rowCount; i++) {
+          if (current) {
+            const rowIndex = Number(current.getAttribute("row-index"));
+            if (i < rowIndex) {
+              const template = Sheet.getRowTemplate(columns, i);
+              current.insertAdjacentHTML("beforebegin", template);
+            } else if (i === rowIndex) {
+              current = current.nextElementSibling as HTMLTableRowElement;
+            // } else { TBD: I think this is not possible
+            //   const template = Sheet.getRowTemplate(columns, i);
+            //   current.insertAdjacentHTML("afterend", template);
+            //   current = current.nextElementSibling as HTMLTableRowElement;
+            }
+          } else {
+            const template = Sheet.getRowTemplate(columns, i);
+            tableBody.insertAdjacentHTML("beforeend", template);
+          }
+        }
+
+        sheetBody.addEventListener("scroll", this.lazyCheck.bind(this));
+
+        // initial
+        this.lazyCheck();
+      }
+
     // ---------------------------------------------------------------------------------------- //
 
     for (const checkbox of <NodeListOf<HTMLInputElement>>this.querySelectorAll(
@@ -191,7 +241,212 @@ class Sheet extends HTMLElement {
         }
       });
     }
+  }
+
+  // attribute getter + setter ---------------------------------------------------------- //
 
+  get lazyActive():boolean {
+    return this.hasAttribute("lazy-active");
+  }
+
+  set lazyActive(update:boolean) {
+    if (update) {
+      this.setAttribute("lazy-active", "");
+    } else {
+      this.removeAttribute("lazy-active");
+    }
+  }
+
+  get lazy():boolean {
+    return this.hasAttribute("lazy");
+  }
+
+  set lazy(update:boolean) {
+    if (update) {
+      this.setAttribute("lazy", "");
+    } else {
+      this.removeAttribute("lazy");
+    }
+  }
+
+  get lazyUpdate():boolean {
+    return this.hasAttribute("lazy-update");
+  }
+
+  get rows():number {
+    return parseInt(this.getAttribute("rows"));
+  }
+
+  get rowCount():number {
+    return parseInt(this.getAttribute("row-count"));
+  }
+
+  get tableBodyDiv(): HTMLDivElement {
+    return this.querySelector(".tobago-sheet-body");
+  }
+
+  get tableBody(): HTMLTableSectionElement {
+    return this.querySelector(".tobago-sheet-bodyTable>tbody");
+  }
+
+  // -------------------------------------------------------------------------------------- //
+
+  /*
+    when an event occurs (initial load OR scroll event OR AJAX response)
+
+    then -> Tobago.Sheet.lazyCheck()
+            1. check, if the lazy reload is currently active
+               a) yes -> do nothing and exit
+               b) no  -> step 2.
+            2. check, if there are data need to load (depends on scroll position and already loaded data)
+               a) yes -> set lazy reload to active and make an AJAX request with Tobago.Sheet.reloadLazy()
+               b) no  -> do nothing and exit
+
+     AJAX response -> 1. update the rows in the sheet from the response
+                      2. go to the first part of this description
+  */
+
+  /**
+   * Checks if a lazy update is required, because there are unloaded rows in the visible area.
+   */
+  lazyCheck(event?): void {
+
+    if (this.lazyActive) {
+      // nothing to do, because there is an active AJAX running
+      return;
+    }
+
+    if (this.lastCheckMillis && Date.now() - this.lastCheckMillis < 100) {
+      // do nothing, because the last call was just a moment ago
+      return;
+    }
+
+    this.lastCheckMillis = Date.now();
+    const next = this.nextLazyLoad();
+    // console.info("next %o", next); // @DEV_ONLY
+    if (next) {
+      this.lazyActive = true;
+      const rootNode = this.getRootNode() as ShadowRoot | Document;
+      const input = rootNode.getElementById(this.id + ":pageActionlazy") as HTMLInputElement;
+      input.value = String(next);
+      this.reloadWithAction(input);
+    }
+  }
+
+  nextLazyLoad(): number {
+    // find first tr in current visible area
+    const rows = this.rows;
+    const rowElements = this.tableBody.rows;
+    let min = 0;
+    let max = rowElements.length;
+    // binary search
+    let i;
+    while (min < max) {
+      i = Math.floor((max - min) / 2) + min;
+      // console.log("min i max -> %d %d %d", min, i, max); // @DEV_ONLY
+      if (this.isRowAboveVisibleArea(rowElements[i])) {
+        min = i + 1;
+      } else {
+        max = i;
+      }
+    }
+    for (i = min; i < min + rows && i < rowElements.length; i++) {
+      if (this.isRowDummy(rowElements[i])) {
+        return i + 1;
+      }
+    }
+
+    return null;
+  }
+
+  isRowAboveVisibleArea(tr: HTMLTableRowElement): boolean {
+    const sheetBody = this.tableBodyDiv;
+    const viewStart = sheetBody.scrollTop;
+    const trEnd = tr.offsetTop + tr.clientHeight;
+    return trEnd < viewStart;
+  }
+
+  isRowDummy(tr): boolean {
+    return tr.hasAttribute("dummy");
+  }
+
+  lazyResponse(event): void {
+    let updates;
+    if (event.status === "complete") {
+      updates = event.responseXML.querySelectorAll("update");
+      for (let i = 0; i < updates.length; i++) {
+        const update = updates[i];
+        const id = update.getAttribute("id");
+        if (id.indexOf(":") > -1) { // is a JSF element id, but not a technical id from the framework
+          console.debug("[tobago-sheet][complete] Update after jsf.ajax complete: #" + id); // @DEV_ONLY
+
+          const sheet = document.getElementById(id);
+          sheet.id = id + "::lazy-temporary";
+
+          const page = Page.page();
+          page.insertAdjacentHTML("beforeend", `<div id="${id}"></div>`);
+          const sheetLoader = document.getElementById(id);
+        }
+      }
+    } else if (event.status === "success") {
+      updates = event.responseXML.querySelectorAll("update");
+      for (let i = 0; i < updates.length; i++) {
+        const update = updates[i];
+        const id = update.getAttribute("id");
+        if (id.indexOf(":") > -1) { // is a JSF element id, but not a technical id from the framework
+          console.debug("[tobago-sheet][success] Update after jsf.ajax complete: #" + id); // @DEV_ONLY
+
+          // sync the new rows into the sheet
+          const sheetLoader = document.getElementById(id);
+          const sheet = document.getElementById(id + "::lazy-temporary");
+          sheet.id = id;
+          const tbody = sheet.querySelector(".tobago-sheet-bodyTable>tbody");
+
+          const newRows = sheetLoader.querySelectorAll(".tobago-sheet-bodyTable>tbody>tr");
+          for (i = 0; i < newRows.length; i++) {
+            const newRow = newRows[i];
+            const rowIndex = Number(newRow.getAttribute("row-index"));
+            const row = tbody.querySelector("tr[row-index='" + rowIndex + "']");
+            // replace the old row with the new row
+            row.insertAdjacentElement("afterend", newRow);
+            tbody.removeChild(row);
+          }
+
+          sheetLoader.parentElement.removeChild(sheetLoader);
+          this.lazyActive = false;
+        }
+      }
+    }
+  }
+
+  lazyError(data): void {
+    console.error("Sheet lazy loading error:"
+        + "\nError Description: " + data.description
+        + "\nError Name: " + data.errorName
+        + "\nError errorMessage: " + data.errorMessage
+        + "\nResponse Code: " + data.responseCode
+        + "\nResponse Text: " + data.responseText
+        + "\nStatus: " + data.status
+        + "\nType: " + data.type);
+  }
+
+  // tbd: how to do this in Tobago 5?
+  reloadWithAction(source: HTMLElement): void {
+    console.debug("reload sheet with action '" + source.id + "'"); // @DEV_ONLY
+    const executeIds = this.id;
+    const renderIds = this.id;
+    const lazy = this.lazy;
+
+    jsf.ajax.request(
+        source.id,
+        null,
+        {
+          "javax.faces.behavior.event": "reload",
+          execute: executeIds,
+          render: renderIds,
+          onevent: lazy ? this.lazyResponse.bind(this) : undefined,
+          onerror: lazy ? this.lazyError.bind(this) : undefined
+        });
   }
 
   loadColumnWidths(): number[] {
@@ -358,7 +613,7 @@ class Sheet extends HTMLElement {
         window.getSelection().removeAllRanges();
       }
 
-      const rows = this.getRows();
+      const rows = this.getRowElements();
       const selector = this.getSelectorCheckbox(row);
       const selectionMode = this.dataset.tobagoSelectionMode;
 
@@ -464,7 +719,7 @@ class Sheet extends HTMLElement {
     return row.querySelector("tr>td>input.tobago-sheet-columnSelector");
   }
 
-  getRows(): NodeListOf<HTMLTableRowElement> {
+  getRowElements(): NodeListOf<HTMLTableRowElement> {
     return this.getBodyTable().querySelectorAll("tbody>tr");
   }
 
@@ -473,11 +728,7 @@ class Sheet extends HTMLElement {
   }
 
   isRowSelected(row: HTMLTableRowElement): boolean {
-    let rowIndex = +row.dataset.tobagoRowIndex;
-    if (!rowIndex) {
-      rowIndex = row.sectionRowIndex + this.getFirst();
-    }
-    return this.isSelected(rowIndex);
+    return this.isSelected(parseInt(row.dataset.tobagoRowIndex));
   }
 
   isSelected(rowIndex: number): boolean {
@@ -503,17 +754,17 @@ class Sheet extends HTMLElement {
   }
 
   selectAll(): void {
-    const rows = this.getRows();
+    const rows = this.getRowElements();
     this.selectRange(rows, 0, rows.length - 1, true, false);
   }
 
   deselectAll(): void {
-    const rows = this.getRows();
+    const rows = this.getRowElements();
     this.selectRange(rows, 0, rows.length - 1, false, true);
   }
 
   toggleAll(): void {
-    const rows = this.getRows();
+    const rows = this.getRowElements();
     this.selectRange(rows, 0, rows.length - 1, true, true);
   }
 
@@ -538,12 +789,7 @@ class Sheet extends HTMLElement {
   }
 
   getDataIndex(row: HTMLTableRowElement): number {
-    const rowIndex = parseInt(row.dataset.tobagoRowIndex);
-    if (rowIndex) {
-      return rowIndex;
-    } else {
-      return row.sectionRowIndex + this.getFirst();
-    }
+    return parseInt(row.dataset.tobagoRowIndex);
   }
 
   /**