You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by hn...@apache.org on 2023/11/23 14:15:24 UTC

(myfaces-tobago) 01/02: fix(sheet): lazy loading + sorting

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

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

commit fa57371ac9cdf0db08771cc6379778ab93eadbdb
Author: Henning Noeth <hn...@apache.org>
AuthorDate: Thu Nov 16 14:38:39 2023 +0100

    fix(sheet): lazy loading + sorting
    
    * fix duplicated IDs
    * fix sorting for a sheet with lazy loading
    * fix scroll position if scrollbar jumped
    * fix scroll position if loading previous rows
    * fix scroll position if attribute "first" is used
    * add test
    
    Issue: TOBAGO-2261
    Issue: TOBAGO-2262
    Issue: TOBAGO-2264
---
 .../tobago/internal/component/AbstractUISheet.java |  10 ++
 .../internal/renderkit/renderer/SheetRenderer.java |   6 +-
 .../3000-sheet/80-lazy-sort/Lazy_Sort.test.js      | 175 +++++++++++++++++++++
 .../3000-sheet/80-lazy-sort/Lazy_Sort.xhtml        |  46 ++++++
 .../src/main/ts/tobago-sheet.ts                    | 103 ++++++++----
 5 files changed, 307 insertions(+), 33 deletions(-)

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 608790e68a..336b8d6048 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
@@ -80,6 +80,7 @@ public abstract class AbstractUISheet extends AbstractUIData
   public static final String SORTER_ID = "sorter";
   public static final String NOT_SORTABLE_COL_MESSAGE_ID = "org.apache.myfaces.tobago.UISheet.SORTING_COL";
   public static final String NOT_SORTABLE_MESSAGE_ID = "org.apache.myfaces.tobago.UISheet.SORTING";
+  private static final String LAZY_UPDATE = "isLazyUpdate";
 
   private SheetState state;
   private transient MeasureList columnLayout;
@@ -541,6 +542,15 @@ public abstract class AbstractUISheet extends AbstractUIData
     getState().setFirst(first);
   }
 
+  public boolean getLazyUpdate() {
+    Object lazyUpdate = getStateHelper().get(LAZY_UPDATE);
+    return lazyUpdate != null && (boolean) lazyUpdate;
+  }
+
+  public void setLazyUpdate(boolean lazyUpdate) {
+    getStateHelper().put(LAZY_UPDATE, lazyUpdate);
+  }
+
   @Override
   public boolean isRendersRowContainer() {
     return true;
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 4b8aa11abb..c26913259b 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
@@ -67,7 +67,6 @@ 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;
@@ -137,6 +136,9 @@ public class SheetRenderer<T extends AbstractUISheet> extends RendererBase<T> {
       ComponentUtils.setAttribute(component, Attributes.selectedListString, selectedRows);
     }
 
+    final String sourceId = facesContext.getExternalContext().getRequestParameterMap().get("javax.faces.source");
+    component.setLazyUpdate(sourceId != null && sourceId.equals(clientId + SUFFIX_LAZY));
+
     final String value
         = facesContext.getExternalContext().getRequestParameterMap().get(clientId + SUFFIX_SCROLL_POSITION);
     if (value != null) {
@@ -279,7 +281,7 @@ public class SheetRenderer<T extends AbstractUISheet> extends RendererBase<T> {
     writer.writeAttribute(CustomAttributes.ROWS, component.getRows());
     writer.writeAttribute(CustomAttributes.ROW_COUNT, Integer.toString(component.getRowCount()), false);
     writer.writeAttribute(CustomAttributes.LAZY, component.isLazy());
-    writer.writeAttribute(CustomAttributes.LAZY_UPDATE, component.isLazy() && AjaxUtils.isAjaxRequest(facesContext));
+    writer.writeAttribute(CustomAttributes.LAZY_UPDATE, component.getLazyUpdate());
 
     final boolean autoLayout = component.isAutoLayout();
     if (!autoLayout) {
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/3000-sheet/80-lazy-sort/Lazy_Sort.test.js b/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/3000-sheet/80-lazy-sort/Lazy_Sort.test.js
new file mode 100644
index 0000000000..7d967d5a54
--- /dev/null
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/3000-sheet/80-lazy-sort/Lazy_Sort.test.js
@@ -0,0 +1,175 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {elementByIdFn, querySelectorAllFn, querySelectorFn} from "/script/tobago-test.js";
+import {JasmineTestTool} from "/tobago/test/tobago-test-tool.js";
+
+it("initial load", function (done) {
+  const submit = elementByIdFn("page:mainForm:submit");
+  const sheet = elementByIdFn("page:mainForm:sheet");
+
+  let dataTobagoFirst;
+
+  const test = new JasmineTestTool(done);
+  test.do(() => dataTobagoFirst = Number(sheet().getAttribute("data-tobago-first")));
+  test.setup(() => isLoaded(dataTobagoFirst - 20) && isLoaded(dataTobagoFirst), null, "click", submit);
+  test.do(() => expect(isLoaded(dataTobagoFirst - 21)).toBeFalse());
+  test.do(() => expect(isLoaded(dataTobagoFirst - 20, dataTobagoFirst + 19)).toBeTrue());
+  test.do(() => expect(isLoaded(dataTobagoFirst + 20)).toBeFalse());
+  test.start();
+});
+
+it("focus row index 5 (min row index is 0) and scroll up", function (done) {
+  let timestamp;
+
+  const test = new JasmineTestTool(done);
+  test.do(() => timestamp = Date.now());
+  test.wait(() => Date.now() - timestamp > 100);
+  test.do(() => focusRowIndex(5));
+  test.wait(() => isLoaded(5));
+  test.do(() => expect(isLoaded(4)).toBeFalse());
+  test.do(() => expect(isLoaded(5, 24)).toBeTrue());
+  test.do(() => expect(isLoaded(25)).toBeFalse());
+
+  test.do(() => timestamp = Date.now());
+  test.wait(() => Date.now() - timestamp > 100);
+  test.do(() => focusRowIndex(4));
+  test.wait(() => isLoaded(4));
+  test.do(() => expect(isLoaded(0, 24)).toBeTrue());
+  test.do(() => expect(isLoaded(25)).toBeFalse());
+  test.start();
+});
+
+it("focus row index 9970 (max row index is 9999) and scroll down", function (done) {
+  let timestamp;
+
+  const test = new JasmineTestTool(done);
+  test.do(() => timestamp = Date.now());
+  test.wait(() => Date.now() - timestamp > 100);
+  test.do(() => focusRowIndex(9970));
+  test.wait(() => isLoaded(9970));
+  test.do(() => expect(isLoaded(9969)).toBeFalse());
+  test.do(() => expect(isLoaded(9970, 9989)).toBeTrue());
+  test.do(() => expect(isLoaded(9990)).toBeFalse());
+
+  test.do(() => timestamp = Date.now());
+  test.wait(() => Date.now() - timestamp > 100);
+  test.do(() => focusRowIndex(9971));
+  test.wait(() => isLoaded(9990));
+  test.do(() => expect(isLoaded(9969)).toBeFalse());
+  test.do(() => expect(isLoaded(9970, 9999)).toBeTrue());
+  test.start();
+});
+
+it("focus row index 500 and go up", function (done) {
+  let timestamp;
+
+  const test = new JasmineTestTool(done);
+  test.do(() => timestamp = Date.now());
+  test.wait(() => Date.now() - timestamp > 100);
+  test.do(() => focusRowIndex(500));
+  test.wait(() => isLoaded(500));
+  test.do(() => expect(isLoaded(499)).toBeFalse());
+  test.do(() => expect(isLoaded(500, 519)).toBeTrue());
+  test.do(() => expect(isLoaded(520)).toBeFalse());
+
+  test.do(() => timestamp = Date.now());
+  test.wait(() => Date.now() - timestamp > 100);
+  test.do(() => focusRowIndex(499));
+  test.wait(() => isLoaded(499));
+  test.do(() => expect(isLoaded(479)).toBeFalse());
+  test.do(() => expect(isLoaded(480, 519)).toBeTrue());
+  test.do(() => expect(isLoaded(520)).toBeFalse());
+  test.start();
+});
+
+it("focus row index 1000 and go down", function (done) {
+  let timestamp;
+
+  const test = new JasmineTestTool(done);
+  test.do(() => focusRowIndex(1000));
+  test.wait(() => isLoaded(1000));
+  test.do(() => expect(isLoaded(999)).toBeFalse());
+  test.do(() => expect(isLoaded(1000, 1019)).toBeTrue());
+  test.do(() => expect(isLoaded(1020)).toBeFalse());
+
+  test.do(() => focusRowIndex(1001));
+  test.wait(() => isLoaded(1001));
+  test.do(() => expect(isLoaded(999)).toBeFalse());
+  test.do(() => expect(isLoaded(1000, 1039)).toBeTrue());
+  test.do(() => expect(isLoaded(1040)).toBeFalse());
+  test.start();
+});
+
+it("focus row index 1500 and sort name asc, focus row index 2000 and sort desc", function (done) {
+  const nameColumn = elementByIdFn("page:mainForm:sheet:nameColumn_sorter");
+  const nameOutputs = querySelectorAllFn("tr tobago-out[id$=':name']");
+
+  let timestamp;
+
+  const test = new JasmineTestTool(done);
+  test.do(() => timestamp = Date.now());
+  test.wait(() => Date.now() - timestamp > 100);
+  test.do(() => focusRowIndex(1500));
+  test.wait(() => isLoaded(1500));
+  test.do(() => expect(isLoaded(1499)).toBeFalse());
+  test.do(() => expect(isLoaded(1500, 1519)).toBeTrue());
+  test.do(() => expect(isLoaded(1520)).toBeFalse());
+
+  test.do(() => timestamp = Date.now());
+  test.wait(() => Date.now() - timestamp > 100);
+  test.event("click", nameColumn, () => nameOutputs().length === 40);
+  test.do(() => expect(nameOutputs().length).toEqual(40));
+  test.do(() => expect(nameOutputs()[0].id).not.toEqual("page:mainForm:sheet:0:name"));
+
+  test.do(() => timestamp = Date.now());
+  test.wait(() => Date.now() - timestamp > 100);
+  test.do(() => focusRowIndex(2000));
+  test.wait(() => isLoaded(2000));
+  test.do(() => expect(isLoaded(1999)).toBeFalse());
+  test.do(() => expect(isLoaded(2000, 2019)).toBeTrue());
+  test.do(() => expect(isLoaded(2020)).toBeFalse());
+
+  test.do(() => timestamp = Date.now());
+  test.wait(() => Date.now() - timestamp > 100);
+  test.event("click", nameColumn, () => nameOutputs().length === 40);
+  test.do(() => expect(nameOutputs().length).toEqual(40));
+  test.do(() => expect(isLoaded(1979)).toBeFalse());
+  test.do(() => expect(isLoaded(1980, 2019)).toBeTrue());
+  test.do(() => expect(isLoaded(2020)).toBeFalse());
+  test.start();
+});
+
+function isLoaded(startRow, endRow) {
+  if (endRow === undefined) {
+    endRow = startRow;
+  }
+  for (let i = startRow; i <= endRow; i++) {
+    if (row(i).getAttribute("dummy") != null) {
+      return false;
+    }
+  }
+  return true;
+}
+
+function row(rowIndex) {
+  return querySelectorFn("tr[row-index='" + rowIndex + "']")();
+}
+
+function focusRowIndex(rowIndex) {
+  querySelectorFn("#page\\:mainForm\\:sheet .tobago-body")().scrollTop = row(rowIndex).offsetTop;
+}
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/3000-sheet/80-lazy-sort/Lazy_Sort.xhtml b/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/3000-sheet/80-lazy-sort/Lazy_Sort.xhtml
new file mode 100644
index 0000000000..a65e4660e1
--- /dev/null
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/3000-sheet/80-lazy-sort/Lazy_Sort.xhtml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<ui:composition template="/main.xhtml"
+                xmlns="http://www.w3.org/1999/xhtml"
+                xmlns:tc="http://myfaces.apache.org/tobago/component"
+                xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
+  <tc:button id="submit" label="submit"/>
+
+  <tc:sheet value="#{sheetController.hugeSolarList}" id="sheet" var="luminary"
+            rows="20" markup="small" lazy="true" columns="auto" first="80"
+            showRowRange="none" showDirectLinks="none" showPageRange="none">
+    <tc:style maxHeight="500px"/>
+    <tc:column id="nameColumn" label="Name" sortable="true">
+      <tc:out id="name" value="#{luminary.name}" labelLayout="skip"/>
+    </tc:column>
+    <tc:column label="Orbit" sortable="true">
+      <tc:out value="#{luminary.orbit}" labelLayout="skip"/>
+    </tc:column>
+    <tc:column label="Period (Days)">
+      <tc:out value="#{luminary.period}" labelLayout="skip"/>
+    </tc:column>
+    <tc:column label="Discoverer">
+      <tc:out value="#{luminary.discoverer}" labelLayout="skip"/>
+    </tc:column>
+    <tc:column label="Year">
+      <tc:out value="#{luminary.discoverYear}" labelLayout="skip"/>
+    </tc:column>
+  </tc:sheet>
+</ui:composition>
diff --git a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-sheet.ts b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-sheet.ts
index 73753dc080..d4433b6413 100644
--- a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-sheet.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-sheet.ts
@@ -197,7 +197,7 @@ export class Sheet extends HTMLElement {
         row.addEventListener("click", this.clickOnRow.bind(this));
       }
     }
-    if (selectionMode === "multi"  && this.getSelectorAllCheckbox()) {
+    if (selectionMode === "multi" && this.getSelectorAllCheckbox()) {
       const selectedSet = new Set<number>(JSON.parse(this.getHiddenSelected().value));
       this.calculateSelectorAllChecked(selectedSet);
     }
@@ -219,6 +219,7 @@ export class Sheet extends HTMLElement {
       const sheetBody = this.sheetBody;
       const tableBody = this.tableBody;
       const columns = tableBody.rows[0].cells.length;
+      const firstShownRow = tableBody.rows[0];
       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++) {
@@ -240,6 +241,7 @@ export class Sheet extends HTMLElement {
         }
       }
 
+      this.sheetBody.scrollTop = firstShownRow.offsetTop;
       sheetBody.addEventListener("scroll", this.lazyCheck.bind(this));
 
       // initial
@@ -270,6 +272,7 @@ export class Sheet extends HTMLElement {
       });
     }
   }
+
   // attribute getter + setter ---------------------------------------------------------- //
 
   get lazyActive(): boolean {
@@ -300,6 +303,22 @@ export class Sheet extends HTMLElement {
     return this.hasAttribute("lazy-update");
   }
 
+  get lazyUpdateUpperRows(): boolean {
+    return this.getAttribute("lazy-update-upper-rows") === "true";
+  }
+
+  set lazyUpdateUpperRows(value: boolean) {
+    this.setAttribute("lazy-update-upper-rows", String(value));
+  }
+
+  get lazyUpdatePreviousBodyScrollHeight(): number {
+    return Number(this.getAttribute("lazy-update-previous-body-scroll-height"));
+  }
+
+  set lazyUpdatePreviousBodyScrollHeight(value: number) {
+    this.setAttribute("lazy-update-previous-body-scroll-height", String(value));
+  }
+
   get rows(): number {
     return parseInt(this.getAttribute("rows"));
   }
@@ -316,6 +335,24 @@ export class Sheet extends HTMLElement {
     return this.querySelector(".tobago-body tbody");
   }
 
+  get firstVisibleRowIndex(): number {
+    const rowElements = this.tableBody.rows;
+    let min = 0;
+    let max = rowElements.length;
+    let rowIndex: number;
+    while (min < max) {
+      rowIndex = Math.round((max - min) / 2) + min;
+      if (rowElements[rowIndex].offsetTop > this.sheetBody.scrollTop) {
+        rowIndex--;
+        max = rowIndex;
+      } else {
+        min = rowIndex;
+      }
+    }
+
+    return rowIndex;
+  }
+
   // -------------------------------------------------------------------------------------- //
 
   /*
@@ -351,52 +388,48 @@ export class Sheet extends HTMLElement {
     this.lastCheckMillis = Date.now();
     const next = this.nextLazyLoad();
     // console.info("next %o", next); // @DEV_ONLY
-    if (next) {
+    if (next !== null) {
       this.lazyActive = true;
       const rootNode = this.getRootNode() as ShadowRoot | Document;
       const input = rootNode.getElementById(this.id + ":pageActionlazy") as HTMLInputElement;
-      input.value = String(next);
+      input.value = String(1 + next); //input.value rowIndex starts at 1 (not 0)
+      this.lazyUpdatePreviousBodyScrollHeight = this.sheetBody.scrollHeight;
       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: number;
-    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;
+    const firstVisibleRowIndex = this.firstVisibleRowIndex;
+
+    for (let rowIndex = firstVisibleRowIndex + 1; rowIndex < firstVisibleRowIndex + rows; rowIndex++) {
+      if (rowIndex >= 0 && rowIndex < rowElements.length && rowElements[rowIndex].hasAttribute("dummy")) {
+        this.lazyUpdateUpperRows = false;
+        if (rowElements[firstVisibleRowIndex].hasAttribute("dummy")) {
+          return firstVisibleRowIndex;
+        } else if (rowIndex + rows - 1 < rowElements.length) {
+          return rowIndex;
+        } else {
+          return rowElements.length - rows;
+        }
       }
     }
-    for (i = min; i < min + rows && i < rowElements.length; i++) {
-      if (this.isRowDummy(rowElements[i])) {
-        return i + 1;
+
+    for (let rowIndex = firstVisibleRowIndex; rowIndex > firstVisibleRowIndex - rows; rowIndex--) {
+      if (rowIndex >= 0 && rowIndex < rowElements.length && rowElements[rowIndex].hasAttribute("dummy")) {
+        this.lazyUpdateUpperRows = true;
+        if (rowIndex - rows + 1 >= 0) {
+          return rowIndex - rows + 1;
+        } else {
+          return 0;
+        }
       }
     }
 
     return null;
   }
 
-  isRowAboveVisibleArea(tr: HTMLTableRowElement): boolean {
-    const sheetBody = this.sheetBody;
-    const viewStart = sheetBody.scrollTop;
-    const trEnd = tr.offsetTop + tr.clientHeight;
-    return trEnd < viewStart;
-  }
-
-  isRowDummy(tr: HTMLTableRowElement): boolean {
-    return tr.hasAttribute("dummy");
-  }
-
   lazyResponse(event: EventData): void {
     let updates: NodeListOf<Element>;
     if (event.status === "complete") {
@@ -404,7 +437,7 @@ export class Sheet extends HTMLElement {
       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
+        if (this.id === id) { // 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);
@@ -420,7 +453,7 @@ export class Sheet extends HTMLElement {
       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
+        if (this.id === `${id}::lazy-temporary`) { // 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
@@ -443,6 +476,14 @@ export class Sheet extends HTMLElement {
           this.lazyActive = false;
         }
       }
+
+      const scrollTop: number = JSON.parse(this.getHiddenScrollPosition().value)[1];
+      if (this.lazyUpdateUpperRows) {
+        const additionalHeight: number = this.sheetBody.scrollHeight - this.lazyUpdatePreviousBodyScrollHeight;
+        this.sheetBody.scrollTop = scrollTop + additionalHeight;
+      } else {
+        this.sheetBody.scrollTop = scrollTop;
+      }
     }
   }