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 2024/12/17 09:10:37 UTC

(myfaces-tobago) branch main updated: feat: Reimplement paginator for sheet (#5824)

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

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


The following commit(s) were added to refs/heads/main by this push:
     new b0a51ad997 feat: Reimplement paginator for sheet (#5824)
b0a51ad997 is described below

commit b0a51ad9975a74a14c38efa95faa93de78be0b9d
Author: Udo Schnurpfeil <lo...@apache.org>
AuthorDate: Tue Dec 17 10:10:32 2024 +0100

    feat: Reimplement paginator for sheet (#5824)
    
    progress...
    
    issue: TOBAGO-2243
---
 .../apache/myfaces/tobago/component/Pageable.java  |  60 -----
 .../myfaces/tobago/event/PageActionEvent.java      |  13 +-
 .../internal/component/AbstractUIPaginator.java    |  11 +-
 .../tobago/internal/component/AbstractUISheet.java |   9 +-
 .../renderkit/renderer/PaginatorListRenderer.java  |  39 ++--
 .../renderkit/renderer/PaginatorPageRenderer.java  |  34 +--
 .../renderkit/renderer/PaginatorRenderer.java      |  52 +++--
 .../renderkit/renderer/PaginatorRowRenderer.java   |  25 +--
 .../internal/renderkit/renderer/SheetRenderer.java | 173 +++++---------
 .../taglib/component/SheetTagDeclaration.java      |   2 +-
 .../myfaces/tobago/internal/util/JsonUtils.java    |  36 +++
 .../tobago/renderkit/html/DataAttributes.java      |   2 +
 .../tobago/internal/util/JsonUtilsUnitTest.java    |  19 ++
 .../renderer/sheet/sheet-paginatorList.html        |  11 +-
 .../resources/renderer/sheet/sheet-paging.html     |  47 ++--
 .../080-sheet/10-sort/Sheet_Sorting.test.js        | 250 ++++++++++++++++-----
 .../content/080-sheet/15-paginator/Paginator.xhtml |   6 +-
 .../25-selection/Sheet_Selectionchange.test.js     |   5 +-
 .../Sheet_Margin_Bottom.test.js                    |   2 +-
 .../38-column-selector/Column_Selector.test.js     |  28 +--
 .../3000-sheet/40-paginator/Paginator.xhtml        |   3 +-
 .../3000-sheet/80-lazy-sort/Lazy_Sort.test.js      |   2 +-
 .../tobago-theme-standard/src/main/js/tobago.js    |   6 +-
 .../src/main/js/tobago.js.map                      |   2 +-
 .../src/main/ts/tobago-init.ts                     |   1 +
 .../src/main/ts/tobago-paginator-list.ts           |  32 +++
 .../src/main/ts/tobago-paginator.ts                |  70 ++++--
 .../resources/tobago/test/tobago-test-tool.js      |  21 +-
 28 files changed, 564 insertions(+), 397 deletions(-)

diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/component/Pageable.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/component/Pageable.java
deleted file mode 100644
index d77fda9f62..0000000000
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/component/Pageable.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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.
- */
-
-package org.apache.myfaces.tobago.component;
-
-import org.apache.myfaces.tobago.internal.component.AbstractUILink;
-import org.apache.myfaces.tobago.internal.component.AbstractUISheet;
-import org.apache.myfaces.tobago.util.ComponentUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import jakarta.faces.component.UIComponent;
-import jakarta.faces.component.behavior.AjaxBehavior;
-import jakarta.faces.context.FacesContext;
-import java.lang.invoke.MethodHandles;
-import java.util.Map;
-
-public interface Pageable {
-
-  Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
-  String SUFFIX_PAGE_ACTION = "pageAction"; // XXX may be renamed
-
-  default AbstractUILink ensurePagingCommand(
-      final FacesContext facesContext, final AbstractUISheet sheet, final String facet, final String id,
-      final boolean disabled) {
-
-    final Map<String, UIComponent> facets = sheet.getFacets();
-    AbstractUILink command = (AbstractUILink) facets.get(facet);
-    if (command == null) {
-      command = (AbstractUILink) ComponentUtils.createComponent(facesContext, Tags.link.componentType(),
-          RendererTypes.Link, SUFFIX_PAGE_ACTION + id);
-      command.setRendered(true);
-      command.setDisabled(disabled);
-      command.setTransient(true);
-      facets.put(facet, command);
-
-      // add AjaxBehavior
-      final AjaxBehavior behavior = sheet.createReloadBehavior(sheet);
-      command.addClientBehavior("click", behavior);
-    }
-    return command;
-  }
-}
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/event/PageActionEvent.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/event/PageActionEvent.java
index b682b402af..311d45c32b 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/event/PageActionEvent.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/event/PageActionEvent.java
@@ -19,6 +19,8 @@
 
 package org.apache.myfaces.tobago.event;
 
+import org.apache.myfaces.tobago.internal.util.JsonUtils;
+
 import jakarta.faces.event.ActionEvent;
 import jakarta.faces.component.UIComponent;
 
@@ -26,7 +28,7 @@ public class PageActionEvent extends ActionEvent {
 
   private static final long serialVersionUID = 3364193750247386220L;
 
-  private SheetAction action;
+  private final SheetAction action;
   private int value;
 
   public PageActionEvent(final UIComponent component, final SheetAction action) {
@@ -34,6 +36,15 @@ public class PageActionEvent extends ActionEvent {
     this.action = action;
   }
 
+  public PageActionEvent(final UIComponent component, final JsonUtils.SheetActionRecord sheetActionRecord) {
+    super(component);
+    this.action = sheetActionRecord.action();
+    final Integer target = sheetActionRecord.target();
+    if (target != null) {
+      this.value = target;
+    }
+  }
+
   /**
    * Returns the action type ({@link SheetAction}).
    */
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUIPaginator.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUIPaginator.java
index d20ce65608..95f56824cc 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUIPaginator.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUIPaginator.java
@@ -19,19 +19,21 @@
 
 package org.apache.myfaces.tobago.internal.component;
 
+import org.apache.myfaces.tobago.component.SupportFieldId;
 import org.apache.myfaces.tobago.component.Visual;
 import org.apache.myfaces.tobago.util.ComponentUtils;
 
 import jakarta.faces.component.UIComponent;
 import jakarta.faces.component.UIOutput;
+import jakarta.faces.context.FacesContext;
 
-public abstract class AbstractUIPaginator extends UIOutput implements Visual {
+public abstract class AbstractUIPaginator extends UIOutput implements Visual, SupportFieldId {
 
   public abstract String getFor();
 
   public abstract boolean isAlwaysVisible();
 
-    // todo: might be an interface instead of AbstractUISheet
+  // todo: might be an interface instead of AbstractUISheet
   public AbstractUISheet getPageable() {
     final String forId = getFor();
     if (forId != null) {
@@ -42,4 +44,9 @@ public abstract class AbstractUIPaginator extends UIOutput implements Visual {
     }
     return ComponentUtils.findAncestor(this, AbstractUISheet.class);
   }
+
+  @Override
+  public String getFieldId(final FacesContext facesContext) {
+    return getClientId(facesContext) + ComponentUtils.SUB_SEPARATOR + "field";
+  }
 }
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 faa029ea09..2cc6dc2665 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
@@ -20,7 +20,6 @@
 package org.apache.myfaces.tobago.internal.component;
 
 import org.apache.myfaces.tobago.component.Attributes;
-import org.apache.myfaces.tobago.component.Pageable;
 import org.apache.myfaces.tobago.component.Visual;
 import org.apache.myfaces.tobago.event.PageActionEvent;
 import org.apache.myfaces.tobago.event.SheetAction;
@@ -81,8 +80,7 @@ import java.util.function.BiConsumer;
  */
 @ListenerFor(systemEventClass = PreRenderComponentEvent.class)
 public abstract class AbstractUISheet extends AbstractUIData
-    implements SheetStateChangeSource, SortActionSource, ClientBehaviorHolder, Visual, Pageable,
-    ComponentSystemEventListener {
+    implements SheetStateChangeSource, SortActionSource, ClientBehaviorHolder, Visual, ComponentSystemEventListener {
 
   private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
@@ -698,12 +696,13 @@ public abstract class AbstractUISheet extends AbstractUIData
     final String clientId = getClientId(facesContext);
 
     final String sheetClientIdWithAction =
-        clientId + UINamingContainer.getSeparatorChar(facesContext) + Pageable.SUFFIX_PAGE_ACTION + SheetAction.lazy;
+        clientId + UINamingContainer.getSeparatorChar(facesContext) + "pageAction" + SheetAction.lazy;
+//  TODO: please use something like this:
+//    final String sheetClientIdWithAction = clientId + ComponentUtils.SUB_SEPARATOR + SheetAction.lazy;
 
     return sheetClientIdWithAction.equals(sourceId);
   }
 
-
   public void init(final FacesContext facesContext) {
     sort(facesContext, null);
     layoutHeader();
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/PaginatorListRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/PaginatorListRenderer.java
index d2e6c0c610..b0f11ff038 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/PaginatorListRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/PaginatorListRenderer.java
@@ -26,7 +26,9 @@ import org.apache.myfaces.tobago.internal.component.AbstractUISheet;
 import org.apache.myfaces.tobago.layout.Arrows;
 import org.apache.myfaces.tobago.renderkit.css.BootstrapClass;
 import org.apache.myfaces.tobago.renderkit.css.Icons;
+import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes;
 import org.apache.myfaces.tobago.renderkit.html.HtmlElements;
+import org.apache.myfaces.tobago.renderkit.html.HtmlInputTypes;
 import org.apache.myfaces.tobago.util.ComponentUtils;
 import org.apache.myfaces.tobago.webapp.TobagoResponseWriter;
 import org.slf4j.Logger;
@@ -41,10 +43,6 @@ public class PaginatorListRenderer<T extends AbstractUIPaginatorList> extends Pa
 
   private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
-  @Override
-  public void decodeInternal(final FacesContext facesContext, final T paginator) {
-  }
-
   @Override
   public void encodeBeginInternal(final FacesContext facesContext, final T paginator) throws IOException {
 
@@ -58,6 +56,13 @@ public class PaginatorListRenderer<T extends AbstractUIPaginatorList> extends Pa
     writer.writeClassAttribute(
         visible ? null : BootstrapClass.D_NONE,
         paginator.getCustomClass());
+
+    writer.startElement(HtmlElements.INPUT);
+    writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.HIDDEN);
+    writer.writeIdAttribute(paginator.getFieldId(facesContext));
+    writer.writeNameAttribute(clientId);
+    writer.endElement(HtmlElements.INPUT);
+
     if (sheet != null) {
       writer.startElement(HtmlElements.UL);
       writer.writeClassAttribute(BootstrapClass.PAGINATION);
@@ -104,44 +109,42 @@ public class PaginatorListRenderer<T extends AbstractUIPaginatorList> extends Pa
       final Arrows arrows = paginator.getArrows();
       if (arrows == Arrows.show || arrows == Arrows.auto) {
         final boolean disabled = sheet.isAtBeginning();
-        encodeLink(
-            facesContext, sheet, disabled, SheetAction.first, null, Icons.SKIP_START, null);
-        encodeLink(facesContext, sheet, disabled, SheetAction.prev, null, Icons.CARET_LEFT, null);
+        encodeLink(facesContext, disabled, SheetAction.first, null, Icons.SKIP_START, null);
+        encodeLink(facesContext, disabled, SheetAction.prev, null, Icons.CARET_LEFT, null);
       }
 
-      int skip = prevs.size() > 0 ? prevs.get(0) : 1;
+      int skip = !prevs.isEmpty() ? prevs.get(0) : 1;
       if (!(arrows == Arrows.show || arrows == Arrows.auto) && skip > 1) {
         skip -= linkCount - (linkCount / 2);
         skip--;
         if (skip < 1) {
           skip = 1;
         }
-        encodeLink(facesContext, sheet, false, SheetAction.toPage, skip, Icons.THREE_DOTS, null);
+        encodeLink(facesContext, false, SheetAction.toPage, skip, Icons.THREE_DOTS, null);
       }
       for (final Integer prev : prevs) {
-        encodeLink(facesContext, sheet, false, SheetAction.toPage, prev, null, null);
+        encodeLink(facesContext, false, SheetAction.toPage, prev, null, null);
       }
-      encodeLink(facesContext, sheet, false, SheetAction.toPage,
-          sheet.getCurrentPage() + 1, null, BootstrapClass.ACTIVE);
+      encodeLink(facesContext, false, SheetAction.toPage, sheet.getCurrentPage() + 1, null, BootstrapClass.ACTIVE);
 
       for (final Integer next : nexts) {
-        encodeLink(facesContext, sheet, false, SheetAction.toPage, next, null, null);
+        encodeLink(facesContext, false, SheetAction.toPage, next, null, null);
       }
 
-      skip = nexts.size() > 0 ? nexts.get(nexts.size() - 1) : pages;
+      skip = !nexts.isEmpty() ? nexts.get(nexts.size() - 1) : pages;
       if (!(arrows == Arrows.show || arrows == Arrows.auto) && skip < pages) {
         skip += linkCount / 2;
         skip++;
         if (skip > pages) {
           skip = pages;
         }
-        encodeLink(facesContext, sheet, false, SheetAction.toPage, skip, Icons.THREE_DOTS, null);
+        encodeLink(facesContext, false, SheetAction.toPage, skip, Icons.THREE_DOTS, null);
       }
       if (arrows == Arrows.show || arrows == Arrows.auto) {
         final boolean disabled = sheet.isAtEnd();
-        encodeLink(facesContext, sheet, disabled, SheetAction.next, null, Icons.CARET_RIGHT, null);
-        encodeLink(facesContext, sheet, disabled || !sheet.hasRowCount(), SheetAction.last, null,
-            Icons.SKIP_END, null);
+        final boolean disabledEnd = disabled || !sheet.hasRowCount();
+        encodeLink(facesContext, disabled, SheetAction.next, null, Icons.CARET_RIGHT, null);
+        encodeLink(facesContext, disabledEnd, SheetAction.last, null, Icons.SKIP_END, null);
       }
       writer.endElement(HtmlElements.UL);
     } else {
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/PaginatorPageRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/PaginatorPageRenderer.java
index 46ebcdfef7..02775e8a65 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/PaginatorPageRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/PaginatorPageRenderer.java
@@ -21,7 +21,6 @@ package org.apache.myfaces.tobago.internal.renderkit.renderer;
 
 import org.apache.myfaces.tobago.component.Facets;
 import org.apache.myfaces.tobago.event.SheetAction;
-import org.apache.myfaces.tobago.internal.component.AbstractUILink;
 import org.apache.myfaces.tobago.internal.component.AbstractUIPaginatorPage;
 import org.apache.myfaces.tobago.internal.component.AbstractUISheet;
 import org.apache.myfaces.tobago.renderkit.css.BootstrapClass;
@@ -33,23 +32,14 @@ import org.apache.myfaces.tobago.renderkit.html.HtmlInputTypes;
 import org.apache.myfaces.tobago.util.ComponentUtils;
 import org.apache.myfaces.tobago.util.ResourceUtils;
 import org.apache.myfaces.tobago.webapp.TobagoResponseWriter;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import jakarta.faces.context.FacesContext;
 import java.io.IOException;
-import java.lang.invoke.MethodHandles;
 import java.text.MessageFormat;
 import java.util.Locale;
 
 public class PaginatorPageRenderer<T extends AbstractUIPaginatorPage> extends PaginatorRenderer<T> {
 
-  private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
-  @Override
-  public void decodeInternal(final FacesContext facesContext, final T component) {
-  }
-
   @Override
   public void encodeBeginInternal(final FacesContext facesContext, final T paginator) throws IOException {
 
@@ -58,21 +48,20 @@ public class PaginatorPageRenderer<T extends AbstractUIPaginatorPage> extends Pa
     final boolean visible = paginator.isAlwaysVisible() || sheet.needMoreThanOnePage();
 
     writer.startElement(HtmlElements.TOBAGO_PAGINATOR_PAGE);
-    writer.writeIdAttribute(paginator.getClientId(facesContext));
+    final String clientId = paginator.getClientId(facesContext);
+    writer.writeIdAttribute(clientId);
     writer.writeClassAttribute(
         visible ? null : BootstrapClass.D_NONE,
-        BootstrapClass.PAGINATION,
         paginator.getCustomClass());
 
-    final AbstractUILink command
-        = sheet.ensurePagingCommand(facesContext, sheet, Facets.pagerPage.name(), SheetAction.toPage.name(), false);
-    final String pagerCommandId = command.getClientId(facesContext);
+    writer.startElement(HtmlElements.UL);
+    writer.writeClassAttribute(BootstrapClass.PAGINATION);
 
     if (sheet.isShowPageRangeArrows()) {
       final boolean disabled = sheet.isAtBeginning();
       encodeLink(
-          facesContext, sheet, disabled, SheetAction.first, null, Icons.SKIP_START, null);
-      encodeLink(facesContext, sheet, disabled, SheetAction.prev, null, Icons.CARET_LEFT, null);
+          facesContext, disabled, SheetAction.first, null, Icons.SKIP_START, null);
+      encodeLink(facesContext, disabled, SheetAction.prev, null, Icons.CARET_LEFT, null);
     }
     writer.startElement(HtmlElements.LI);
     writer.writeClassAttribute(BootstrapClass.PAGE_ITEM);
@@ -105,8 +94,8 @@ public class PaginatorPageRenderer<T extends AbstractUIPaginatorPage> extends Pa
         writer.writeText(Integer.toString(first));
         writer.endElement(HtmlElements.SPAN);
         writer.startElement(HtmlElements.INPUT);
-        writer.writeIdAttribute(pagerCommandId);
-        writer.writeNameAttribute(pagerCommandId);
+        writer.writeIdAttribute(paginator.getFieldId(facesContext));
+        writer.writeNameAttribute(clientId);
         writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.TEXT);
         writer.writeAttribute(HtmlAttributes.VALUE, first);
         if (!unknown) {
@@ -123,10 +112,11 @@ public class PaginatorPageRenderer<T extends AbstractUIPaginatorPage> extends Pa
     writer.endElement(HtmlElements.LI);
     if (sheet.isShowPageRangeArrows()) {
       final boolean disabled = sheet.isAtEnd();
-      encodeLink(facesContext, sheet, disabled, SheetAction.next, null, Icons.CARET_RIGHT, null);
-      encodeLink(facesContext, sheet, disabled || !sheet.hasRowCount(), SheetAction.last, null,
-          Icons.SKIP_END, null);
+      encodeLink(facesContext, disabled, SheetAction.next, null, Icons.CARET_RIGHT, null);
+      encodeLink(facesContext, disabled || !sheet.hasRowCount(), SheetAction.last, null, Icons.SKIP_END, null);
     }
+
+    writer.endElement(HtmlElements.UL);
   }
 
   @Override
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/PaginatorRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/PaginatorRenderer.java
index 5c7ccced0d..1312a4e968 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/PaginatorRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/PaginatorRenderer.java
@@ -18,42 +18,64 @@
  */
 package org.apache.myfaces.tobago.internal.renderkit.renderer;
 
-import org.apache.myfaces.tobago.component.Attributes;
+import org.apache.myfaces.tobago.event.PageActionEvent;
 import org.apache.myfaces.tobago.event.SheetAction;
-import org.apache.myfaces.tobago.internal.component.AbstractUILink;
 import org.apache.myfaces.tobago.internal.component.AbstractUIPaginator;
 import org.apache.myfaces.tobago.internal.component.AbstractUISheet;
+import org.apache.myfaces.tobago.internal.util.JsonUtils;
 import org.apache.myfaces.tobago.renderkit.RendererBase;
 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.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.util.ComponentUtils;
 import org.apache.myfaces.tobago.util.ResourceUtils;
 import org.apache.myfaces.tobago.webapp.TobagoResponseWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import jakarta.faces.context.FacesContext;
 import java.io.IOException;
+import java.lang.invoke.MethodHandles;
 import java.text.MessageFormat;
 import java.util.Locale;
+import java.util.Map;
 
 public abstract class PaginatorRenderer<T extends AbstractUIPaginator> extends RendererBase<T> {
 
+  private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  @Override
+  public void decodeInternal(final FacesContext facesContext, final T paginator) {
+    final Map<String, String> requestParameterMap = facesContext.getExternalContext().getRequestParameterMap();
+    final String sourceId = requestParameterMap.get("jakarta.faces.source");
+    final String clientId = paginator.getClientId(facesContext);
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("clientId             {}", clientId);
+      LOG.debug("jakarta.faces.source {}", sourceId);
+    }
+    if (clientId.equals(sourceId)) {
+      final String value = requestParameterMap.get(clientId);
+      if (value != null) {
+        final AbstractUISheet sheet = paginator.getPageable();
+        if (sheet != null) {
+          final JsonUtils.SheetActionRecord sheetActionRecord = JsonUtils.decodeSheetAction(value);
+          LOG.debug("record='{}'", sheetActionRecord);
+          sheet.queueEvent(new PageActionEvent(sheet, sheetActionRecord));
+        } else {
+          LOG.warn("No sheet found for paginator {}", clientId);
+        }
+      }
+    }
+  }
+
   protected void encodeLink(
-      final FacesContext facesContext, final AbstractUISheet data,
+      final FacesContext facesContext,
       final boolean disabled, final SheetAction action, final Integer target, final Icons icon, final CssItem liClass)
       throws IOException {
 
-    final String facet = action == SheetAction.toPage || action == SheetAction.toRow
-        ? action.name() + "-" + target
-        : action.name();
-    final AbstractUILink command = data.ensurePagingCommand(facesContext, data, facet, facet, disabled);
-    if (target != null) {
-      ComponentUtils.setAttribute(command, Attributes.pagingTarget, target);
-    }
-
     final Locale locale = facesContext.getViewRoot().getLocale();
     final String message = ResourceUtils.getString(facesContext, action.getBundleKey());
     final String tip = new MessageFormat(message, locale).format(new Integer[]{target}); // needed fot ToPage
@@ -64,9 +86,9 @@ public abstract class PaginatorRenderer<T extends AbstractUIPaginator> extends R
     writer.startElement(HtmlElements.BUTTON);
     writer.writeAttribute(HtmlAttributes.TYPE, HtmlButtonTypes.BUTTON);
     writer.writeClassAttribute(BootstrapClass.PAGE_LINK);
-    writer.writeIdAttribute(command.getClientId(facesContext));
     writer.writeAttribute(HtmlAttributes.TITLE, tip, true);
     writer.writeAttribute(HtmlAttributes.DISABLED, disabled);
+    writer.writeAttribute(DataAttributes.ACTION, JsonUtils.encode(action, target), false);
     if (icon != null) {
       writer.startElement(HtmlElements.I);
       writer.writeClassAttribute(icon);
@@ -74,10 +96,6 @@ public abstract class PaginatorRenderer<T extends AbstractUIPaginator> extends R
     } else {
       writer.writeText(String.valueOf(target));
     }
-    if (!disabled) {
-      encodeBehavior(writer, facesContext, command);
-    }
-    data.getFacets().remove(facet);
     writer.endElement(HtmlElements.BUTTON);
     writer.endElement(HtmlElements.LI);
   }
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/PaginatorRowRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/PaginatorRowRenderer.java
index 8a15796cb6..a9f4460575 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/PaginatorRowRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/PaginatorRowRenderer.java
@@ -19,9 +19,6 @@
 
 package org.apache.myfaces.tobago.internal.renderkit.renderer;
 
-import org.apache.myfaces.tobago.component.Facets;
-import org.apache.myfaces.tobago.event.SheetAction;
-import org.apache.myfaces.tobago.internal.component.AbstractUILink;
 import org.apache.myfaces.tobago.internal.component.AbstractUIPaginatorRow;
 import org.apache.myfaces.tobago.internal.component.AbstractUISheet;
 import org.apache.myfaces.tobago.internal.util.StringUtils;
@@ -29,26 +26,16 @@ import org.apache.myfaces.tobago.renderkit.css.BootstrapClass;
 import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes;
 import org.apache.myfaces.tobago.renderkit.html.HtmlElements;
 import org.apache.myfaces.tobago.renderkit.html.HtmlInputTypes;
-import org.apache.myfaces.tobago.util.ComponentUtils;
 import org.apache.myfaces.tobago.util.ResourceUtils;
 import org.apache.myfaces.tobago.webapp.TobagoResponseWriter;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import jakarta.faces.context.FacesContext;
 import java.io.IOException;
-import java.lang.invoke.MethodHandles;
 import java.text.MessageFormat;
 import java.util.Locale;
 
 public class PaginatorRowRenderer<T extends AbstractUIPaginatorRow> extends PaginatorRenderer<T> {
 
-  private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
-  @Override
-  public void decodeInternal(final FacesContext facesContext, final T component) {
-  }
-
   @Override
   public void encodeBeginInternal(final FacesContext facesContext, final T paginator) throws IOException {
 
@@ -57,7 +44,8 @@ public class PaginatorRowRenderer<T extends AbstractUIPaginatorRow> extends Pagi
     final boolean visible = paginator.isAlwaysVisible() || sheet.needMoreThanOnePage();
 
     writer.startElement(HtmlElements.TOBAGO_PAGINATOR_ROW);
-    writer.writeIdAttribute(paginator.getClientId(facesContext));
+    final String clientId = paginator.getClientId(facesContext);
+    writer.writeIdAttribute(clientId);
     writer.writeClassAttribute(
         visible ? null : BootstrapClass.D_NONE,
         BootstrapClass.PAGINATION,
@@ -71,10 +59,6 @@ public class PaginatorRowRenderer<T extends AbstractUIPaginatorRow> extends Pagi
         BootstrapClass.PAGE_LINK,
         BootstrapClass.TEXT_NOWRAP);
 
-    final AbstractUILink command
-        = sheet.ensurePagingCommand(facesContext, sheet, Facets.pagerRow.name(), SheetAction.toRow.name(), false);
-    final String pagerCommandId = command.getClientId(facesContext);
-
     if (sheet.getRowCount() != 0) {
       final Locale locale = facesContext.getViewRoot().getLocale();
       final int first = sheet.getFirst() + 1;
@@ -99,8 +83,8 @@ public class PaginatorRowRenderer<T extends AbstractUIPaginatorRow> extends Pagi
         writer.writeText(Integer.toString(first));
         writer.endElement(HtmlElements.SPAN);
         writer.startElement(HtmlElements.INPUT);
-        writer.writeIdAttribute(pagerCommandId);
-        writer.writeNameAttribute(pagerCommandId);
+        writer.writeIdAttribute(paginator.getFieldId(facesContext));
+        writer.writeNameAttribute(clientId);
         writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.TEXT);
         writer.writeAttribute(HtmlAttributes.VALUE, first);
         final int maxLength = Integer.toString(sheet.getRowCount()).length();
@@ -114,7 +98,6 @@ public class PaginatorRowRenderer<T extends AbstractUIPaginatorRow> extends Pagi
         writer.writeText(formatted);
       }
     }
-    ComponentUtils.removeFacet(sheet, Facets.pagerRow);
     writer.endElement(HtmlElements.SPAN);
     writer.endElement(HtmlElements.SPAN);
   }
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 a3839ccd92..fe86f94939 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
@@ -22,7 +22,6 @@ package org.apache.myfaces.tobago.internal.renderkit.renderer;
 import org.apache.myfaces.tobago.component.Attributes;
 import org.apache.myfaces.tobago.component.Facets;
 import org.apache.myfaces.tobago.component.LabelLayout;
-import org.apache.myfaces.tobago.component.Pageable;
 import org.apache.myfaces.tobago.component.RendererTypes;
 import org.apache.myfaces.tobago.component.Tags;
 import org.apache.myfaces.tobago.component.UIOut;
@@ -30,8 +29,6 @@ import org.apache.myfaces.tobago.component.UIPaginatorList;
 import org.apache.myfaces.tobago.component.UIPaginatorPage;
 import org.apache.myfaces.tobago.component.UIPaginatorRow;
 import org.apache.myfaces.tobago.context.Markup;
-import org.apache.myfaces.tobago.event.PageActionEvent;
-import org.apache.myfaces.tobago.event.SheetAction;
 import org.apache.myfaces.tobago.event.SortActionEvent;
 import org.apache.myfaces.tobago.internal.component.AbstractUIColumn;
 import org.apache.myfaces.tobago.internal.component.AbstractUIColumnBase;
@@ -77,12 +74,10 @@ import org.apache.myfaces.tobago.webapp.TobagoResponseWriter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import jakarta.faces.application.Application;
 import jakarta.faces.component.NamingContainer;
 import jakarta.faces.component.UIColumn;
 import jakarta.faces.component.UIComponent;
 import jakarta.faces.component.UIData;
-import jakarta.faces.component.UINamingContainer;
 import jakarta.faces.component.behavior.AjaxBehavior;
 import jakarta.faces.context.FacesContext;
 import java.io.IOException;
@@ -158,7 +153,6 @@ public class SheetRenderer<T extends AbstractUISheet> extends RendererBase<T> {
     }
     RenderUtils.decodedStateOfTreeData(facesContext, component);
 
-    decodeSheetAction(facesContext, component);
     decodeColumnAction(facesContext, columns);
 /* this will be done by the jakarta.faces.component.UIData.processDecodes() because these are facets.
     for (UIComponent facet : sheet.getFacets().values()) {
@@ -183,62 +177,6 @@ public class SheetRenderer<T extends AbstractUISheet> extends RendererBase<T> {
     }
   }
 
-  private void decodeSheetAction(final FacesContext facesContext, final AbstractUISheet component) {
-    final String sourceId = facesContext.getExternalContext().getRequestParameterMap().get("jakarta.faces.source");
-
-    final String clientId = component.getClientId(facesContext);
-    if (LOG.isDebugEnabled()) {
-      LOG.debug("sourceId = '{}'", sourceId);
-      LOG.debug("clientId = '{}'", clientId);
-    }
-
-    final String sheetClientIdWithAction
-        = clientId + UINamingContainer.getSeparatorChar(facesContext) + Pageable.SUFFIX_PAGE_ACTION;
-    if (sourceId != null && sourceId.startsWith(sheetClientIdWithAction)) {
-      String actionString = sourceId.substring(sheetClientIdWithAction.length());
-      int index = actionString.indexOf('-');
-      SheetAction action;
-      if (index != -1) {
-        action = SheetAction.valueOf(actionString.substring(0, index));
-      } else {
-        action = SheetAction.valueOf(actionString);
-      }
-      PageActionEvent event = null;
-      switch (action) {
-        case first:
-        case prev:
-        case next:
-        case last:
-          event = new PageActionEvent(component, action);
-          break;
-        case toPage:
-        case toRow:
-        case lazy:
-          event = new PageActionEvent(component, action);
-          final int target;
-          final String value;
-          if (index == -1) {
-            final Map<String, String> map = facesContext.getExternalContext().getRequestParameterMap();
-            value = map.get(sourceId);
-          } else {
-            value = actionString.substring(index + 1);
-          }
-          try {
-            target = Integer.parseInt(value);
-          } catch (final NumberFormatException e) {
-            LOG.error("Can't parse integer value for action " + action.name() + ": " + value);
-            break;
-          }
-          event.setValue(target);
-          break;
-        default:
-      }
-      if (event != null) {
-        component.queueEvent(event);
-      }
-    }
-  }
-
   @Override
   public void encodeBeginInternal(final FacesContext facesContext, final T component) throws IOException {
 
@@ -247,76 +185,81 @@ public class SheetRenderer<T extends AbstractUISheet> extends RendererBase<T> {
     final TobagoResponseWriter writer = getResponseWriter(facesContext);
 
     final PaginatorMode paginator = component.getPaginator();
-    LOG.info("paginator={}", paginator);
+    LOG.debug("paginator={}", paginator);
 
     switch (paginator) {
       case list, auto -> {
-        final UIPaginatorList paginatorList = (UIPaginatorList) ComponentUtils.createComponent(
-            facesContext, Tags.paginatorList.componentType(), RendererTypes.PaginatorList, "_paginator_list");
-        paginatorList.setTransient(true);
-
         final UIComponent after = ensureAfterFacetPaginator(facesContext, component);
-        after.getChildren().add(paginatorList);
+        if (after.getChildCount() == 0) {
+          final UIPaginatorList paginatorList = (UIPaginatorList) ComponentUtils.createComponent(
+              facesContext, Tags.paginatorList.componentType(), RendererTypes.PaginatorList, "_paginator_list");
+          after.getChildren().add(paginatorList);
+        }
       }
       case page -> {
-        final UIPaginatorPage paginatorPage = (UIPaginatorPage) ComponentUtils.createComponent(
-            facesContext, Tags.paginatorPage.componentType(), RendererTypes.PaginatorPage, "_paginator_page");
-        paginatorPage.setTransient(true);
-
         final UIComponent after = ensureAfterFacetPaginator(facesContext, component);
-        after.getChildren().add(paginatorPage);
+        if (after.getChildCount() == 0) {
+          final UIPaginatorPage paginatorPage = (UIPaginatorPage) ComponentUtils.createComponent(
+              facesContext, Tags.paginatorPage.componentType(), RendererTypes.PaginatorPage, "_paginator_page");
+          after.getChildren().add(paginatorPage);
+        }
       }
       case row -> {
-        final UIPaginatorRow paginatorRow = (UIPaginatorRow) ComponentUtils.createComponent(
-            facesContext, Tags.paginatorRow.componentType(), RendererTypes.PaginatorRow, "_paginator_row");
-        paginatorRow.setTransient(true);
-
         final UIComponent after = ensureAfterFacetPaginator(facesContext, component);
-        after.getChildren().add(paginatorRow);
-      }
-      case useShowAttributes -> {
-        final Map<ShowPosition, AbstractUIPaginator> paginatorMap = new HashMap<>();
-
-        if (!ShowPosition.none.equals(component.getShowRowRange()) && component.isPagingVisible()) {
+        if (after.getChildCount() == 0) {
           final UIPaginatorRow paginatorRow = (UIPaginatorRow) ComponentUtils.createComponent(
               facesContext, Tags.paginatorRow.componentType(), RendererTypes.PaginatorRow, "_paginator_row");
-          paginatorRow.setAlwaysVisible(component.isShowPagingAlways());
-          paginatorRow.setTransient(true);
-          paginatorMap.put(component.getShowRowRange(), paginatorRow);
+          after.getChildren().add(paginatorRow);
         }
+      }
+      case useShowAttributes -> {
+        boolean initialized = component.getAttributes().get("useShowAttributesInitialized") != null;
 
-        if (!ShowPosition.none.equals(component.getShowDirectLinks()) && component.isPagingVisible()) {
-          final UIPaginatorList paginatorList = (UIPaginatorList) ComponentUtils.createComponent(
-              facesContext, Tags.paginatorList.componentType(), RendererTypes.PaginatorList, "_paginator_list");
-          paginatorList.setAlwaysVisible(component.isShowPagingAlways());
-          paginatorList.setArrows(component.isShowDirectLinksArrows()? Arrows.show : Arrows.hide);
-          paginatorList.setMax(component.getDirectLinkCount());
-          paginatorList.setTransient(true);
-          paginatorMap.put(component.getShowDirectLinks(), paginatorList);
-        }
+        if (!initialized) {
+          final Map<ShowPosition, AbstractUIPaginator> paginatorMap = new HashMap<>();
 
-        if (!ShowPosition.none.equals(component.getShowPageRange()) && component.isPagingVisible()) {
-          final UIPaginatorPage paginatorPage = (UIPaginatorPage) ComponentUtils.createComponent(
-              facesContext, Tags.paginatorPage.componentType(), RendererTypes.PaginatorPage, "_paginator_page");
-          paginatorPage.setAlwaysVisible(component.isShowPagingAlways());
-          paginatorPage.setTransient(true);
-          paginatorMap.put(component.getShowPageRange(), paginatorPage);
-        }
+          final ShowPosition rangePosition = component.getShowRowRange();
+          if (!ShowPosition.none.equals(rangePosition) && component.isPagingVisible()) {
+            final UIPaginatorRow paginatorRow = (UIPaginatorRow) ComponentUtils.createComponent(
+                facesContext, Tags.paginatorRow.componentType(), RendererTypes.PaginatorRow, "_paginator_row");
+            paginatorRow.setAlwaysVisible(component.isShowPagingAlways());
+            paginatorMap.put(rangePosition, paginatorRow);
+          }
+
+          final ShowPosition listPosition = component.getShowDirectLinks();
+          if (!ShowPosition.none.equals(listPosition) && component.isPagingVisible()) {
+            final UIPaginatorList paginatorList = (UIPaginatorList) ComponentUtils.createComponent(
+                facesContext, Tags.paginatorList.componentType(), RendererTypes.PaginatorList, "_paginator_list");
+            paginatorList.setAlwaysVisible(component.isShowPagingAlways());
+            paginatorList.setArrows(component.isShowDirectLinksArrows() ? Arrows.show : Arrows.hide);
+            paginatorList.setMax(component.getDirectLinkCount());
+            paginatorMap.put(listPosition, paginatorList);
+          }
 
-        if (!paginatorMap.isEmpty()) {
-          final UIComponent after = ensureAfterFacetPaginator(facesContext, component);
+          final ShowPosition pagePosition = component.getShowPageRange();
+          if (!ShowPosition.none.equals(pagePosition) && component.isPagingVisible()) {
+            final UIPaginatorPage paginatorPage = (UIPaginatorPage) ComponentUtils.createComponent(
+                facesContext, Tags.paginatorPage.componentType(), RendererTypes.PaginatorPage, "_paginator_page");
+            paginatorPage.setAlwaysVisible(component.isShowPagingAlways());
+            paginatorMap.put(pagePosition, paginatorPage);
+          }
 
-          final ShowPosition[] order = {ShowPosition.left, ShowPosition.center, ShowPosition.right};
-          for (ShowPosition showPosition : order) {
-            if (paginatorMap.containsKey(showPosition)) {
-              after.getChildren().add(paginatorMap.get(showPosition));
-            } else {
-              final UIOut space = (UIOut) ComponentUtils.createComponent(
-                  facesContext, Tags.out.componentType(), RendererTypes.Out, "_space_" + showPosition.name());
-              space.setTransient(true);
-              after.getChildren().add(space);
+          if (!paginatorMap.isEmpty()) {
+            final UIComponent after = ensureAfterFacetPaginator(facesContext, component);
+
+            final ShowPosition[] order = {ShowPosition.left, ShowPosition.center, ShowPosition.right};
+            for (ShowPosition showPosition : order) {
+              if (paginatorMap.containsKey(showPosition)) {
+                after.getChildren().add(paginatorMap.get(showPosition));
+              } else {
+                final UIOut space = (UIOut) ComponentUtils.createComponent(
+                    facesContext, Tags.out.componentType(), RendererTypes.Out, "_space_" + showPosition.name());
+                after.getChildren().add(space);
+              }
             }
           }
+
+          component.getAttributes().put("useShowAttributesInitialized", Boolean.TRUE);
         }
       }
       case custom -> {
@@ -406,7 +349,6 @@ public class SheetRenderer<T extends AbstractUISheet> extends RendererBase<T> {
 
     final String sheetId = component.getClientId(facesContext);
     final Selectable selectable = component.getSelectable();
-    final Application application = facesContext.getApplication();
     final SheetState state = component.getSheetState(facesContext);
     final List<Integer> columnWidths = component.getState().getColumnWidths();
     final boolean definedColumnWidths = component.getState().isDefinedColumnWidths();
@@ -912,8 +854,7 @@ public class SheetRenderer<T extends AbstractUISheet> extends RendererBase<T> {
             writer.writeAttribute(HtmlAttributes.TITLE, tip, true);
 
             encodeBehavior(writer, behaviorCommands);
-            if (column instanceof AbstractUIColumnSelector) {
-              final AbstractUIColumnSelector selector = (AbstractUIColumnSelector) column;
+            if (column instanceof AbstractUIColumnSelector selector) {
               Selectable currentSelectable = getSelectionMode(selectable, selector);
               writer.startElement(HtmlElements.INPUT);
               if (currentSelectable.isMulti()) {
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 0e8529bc1a..8832f0166a 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
@@ -198,7 +198,7 @@ public interface SheetTagDeclaration
   void setShowDirectLinks(String showDirectLinks);
 
   /**
-   * Flag indicating whether and where the range pages should
+   * Flag indicating whether and where the range pages should be
    * rendered in the sheet's footer. Rendering this range also offers the
    * capability to enter the index displayed page directly.
    *
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/JsonUtils.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/JsonUtils.java
index 3064ac2335..3f80ea2977 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/JsonUtils.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/JsonUtils.java
@@ -21,6 +21,7 @@ package org.apache.myfaces.tobago.internal.util;
 
 import org.apache.myfaces.tobago.component.ClientBehaviors;
 import org.apache.myfaces.tobago.context.Markup;
+import org.apache.myfaces.tobago.event.SheetAction;
 import org.apache.myfaces.tobago.internal.renderkit.Collapse;
 import org.apache.myfaces.tobago.internal.renderkit.Command;
 import org.apache.myfaces.tobago.internal.renderkit.CommandMap;
@@ -355,7 +356,42 @@ public class JsonUtils {
     return builder.toString();
   }
 
+  public static String encode(final SheetAction sheetAction, final Integer target) {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("{");
+    encode(builder, "action", sheetAction.name());
+    if (target != null) {
+      encode(builder, "target", target);
+    }
+    builder.deleteCharAt(builder.length() - 1); // remove last comma
+    builder.append("}");
+    return builder.toString();
+  }
+
+  public static SheetActionRecord decodeSheetAction(final String json) {
+    if (json == null) {
+      return null;
+    }
+    SheetAction action = null;
+    Integer target = null;
+    final String[] split = json.replaceAll("[^a-zA-Z0-9]", " ").split("\\s+");
+    for (int i = 0; i < split.length - 1; i++) {
+      if (split[i].equals("action")) {
+        i++;
+        action = SheetAction.valueOf(split[i]);
+      }
+      if (split[i].equals("target")) {
+        i++;
+        target = Integer.parseInt(split[i]);
+      }
+    }
+    return new SheetActionRecord(action, target);
+  }
+
   public static String encodeEmptyArray() {
     return "[]";
   }
+
+  // todo: rename - find a better name
+  public record SheetActionRecord(SheetAction action, Integer target) {}
 }
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 ad5aa76b75..625fcd6dc6 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
@@ -26,6 +26,8 @@ package org.apache.myfaces.tobago.renderkit.html;
  */
 public enum DataAttributes implements MarkupLanguageAttributes {
 
+  ACTION("data-tobago-action"),
+
   BS_CONTENT("data-bs-content"),
   BS_DISMISS("data-bs-dismiss"),
   BS_TOGGLE("data-bs-toggle"),
diff --git a/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/util/JsonUtilsUnitTest.java b/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/util/JsonUtilsUnitTest.java
index d3fcb74f8a..5fcb3a0367 100644
--- a/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/util/JsonUtilsUnitTest.java
+++ b/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/util/JsonUtilsUnitTest.java
@@ -24,6 +24,7 @@ import org.apache.myfaces.tobago.component.ClientBehaviors;
 import org.apache.myfaces.tobago.component.RendererTypes;
 import org.apache.myfaces.tobago.component.Tags;
 import org.apache.myfaces.tobago.context.Markup;
+import org.apache.myfaces.tobago.event.SheetAction;
 import org.apache.myfaces.tobago.internal.component.AbstractUICommand;
 import org.apache.myfaces.tobago.internal.config.AbstractTobagoTestBase;
 import org.apache.myfaces.tobago.internal.renderkit.Collapse;
@@ -235,6 +236,24 @@ public class JsonUtilsUnitTest extends AbstractTobagoTestBase {
     Assertions.assertEquals("[]", JsonUtils.encode(new ArrayList<>()));
   }
 
+  @Test
+  public void encodeSheetAction() {
+    final SheetAction action = SheetAction.last;
+    final Integer target = 42;
+
+    Assertions.assertEquals("{\"action\":\"last\",\"target\":42}", JsonUtils.encode(action, target));
+    Assertions.assertEquals("{\"action\":\"last\"}", JsonUtils.encode(action, null));
+  }
+
+  @Test
+  public void decodeSheetAction() {
+    JsonUtils.SheetActionRecord last = new JsonUtils.SheetActionRecord(SheetAction.last, null);
+    Assertions.assertEquals(last, JsonUtils.decodeSheetAction("{\"action\":\"last\"}"));
+
+    JsonUtils.SheetActionRecord toPage = new JsonUtils.SheetActionRecord(SheetAction.toPage, 42);
+    Assertions.assertEquals(toPage, JsonUtils.decodeSheetAction("{\"action\":\"toPage\",\"target\":42}"));
+  }
+
   @Test
   public void encodeEmptyArray() {
     Assertions.assertEquals("[]", JsonUtils.encodeEmptyArray());
diff --git a/tobago-core/src/test/resources/renderer/sheet/sheet-paginatorList.html b/tobago-core/src/test/resources/renderer/sheet/sheet-paginatorList.html
index d90f1b3215..b1b18b8e28 100644
--- a/tobago-core/src/test/resources/renderer/sheet/sheet-paginatorList.html
+++ b/tobago-core/src/test/resources/renderer/sheet/sheet-paginatorList.html
@@ -49,21 +49,22 @@
   </footer>
   <tobago-paginator-panel id='sheet:panel' class='d-flex justify-content-center'>
     <tobago-paginator-list id='sheet:list'>
+      <input type='hidden' id='sheet:list::field' name='sheet:list'>
       <ul class='pagination'>
         <li class='active page-item'>
-          <button type='button' class='page-link' id='sheet:pageActiontoPage-1' title='Page 1'>1<tobago-behavior event='click' client-id='sheet:pageActiontoPage-1' execute='sheet sheet:pageActiontoPage-1' render='sheet'></tobago-behavior></button>
+          <button type='button' class='page-link' title='Page 1' data-tobago-action='{"action":"toPage","target":1}'>1</button>
         </li>
         <li class='page-item'>
-          <button type='button' class='page-link' id='sheet:pageActiontoPage-2' title='Page 2'>2<tobago-behavior event='click' client-id='sheet:pageActiontoPage-2' execute='sheet sheet:pageActiontoPage-2' render='sheet'></tobago-behavior></button>
+          <button type='button' class='page-link' title='Page 2' data-tobago-action='{"action":"toPage","target":2}'>2</button>
         </li>
         <li class='page-item'>
-          <button type='button' class='page-link' id='sheet:pageActiontoPage-3' title='Page 3'>3<tobago-behavior event='click' client-id='sheet:pageActiontoPage-3' execute='sheet sheet:pageActiontoPage-3' render='sheet'></tobago-behavior></button>
+          <button type='button' class='page-link' title='Page 3' data-tobago-action='{"action":"toPage","target":3}'>3</button>
         </li>
         <li class='page-item'>
-          <button type='button' class='page-link' id='sheet:pageActiontoPage-4' title='Page 4'>4<tobago-behavior event='click' client-id='sheet:pageActiontoPage-4' execute='sheet sheet:pageActiontoPage-4' render='sheet'></tobago-behavior></button>
+          <button type='button' class='page-link' title='Page 4' data-tobago-action='{"action":"toPage","target":4}'>4</button>
         </li>
         <li class='page-item'>
-          <button type='button' class='page-link' id='sheet:pageActiontoPage-5' title='Page 5'>5<tobago-behavior event='click' client-id='sheet:pageActiontoPage-5' execute='sheet sheet:pageActiontoPage-5' render='sheet'></tobago-behavior></button>
+          <button type='button' class='page-link' title='Page 5' data-tobago-action='{"action":"toPage","target":5}'>5</button>
         </li>
       </ul>
     </tobago-paginator-list>
diff --git a/tobago-core/src/test/resources/renderer/sheet/sheet-paging.html b/tobago-core/src/test/resources/renderer/sheet/sheet-paging.html
index bc71bf407d..7650f9a5b6 100644
--- a/tobago-core/src/test/resources/renderer/sheet/sheet-paging.html
+++ b/tobago-core/src/test/resources/renderer/sheet/sheet-paging.html
@@ -49,43 +49,46 @@
   </footer>
   <tobago-paginator-panel id='sheet:_after' class='d-flex justify-content-between'>
     <tobago-paginator-row id='sheet:_paginator_row' class='pagination'>
-      <span class='page-item' title='Click here, to change the first displayed row'><span class='page-link text-nowrap'>Rows <span>1</span><input id='sheet:pageActiontoRow' name='sheet:pageActiontoRow' type='text' value='1' maxlength='2' pattern='[1-9][0-9]?'> to 2 of 10</span></span>
+      <span class='page-item' title='Click here, to change the first displayed row'><span class='page-link text-nowrap'>Rows <span>1</span><input id='sheet:_paginator_row::field' name='sheet:_paginator_row' type='text' value='1' maxlength='2' pattern='[1-9][0-9]?'> to 2 of 10</span></span>
     </tobago-paginator-row>
     <tobago-paginator-list id='sheet:_paginator_list'>
+      <input type='hidden' id='sheet:_paginator_list::field' name='sheet:_paginator_list'>
       <ul class='pagination'>
         <li class='active page-item'>
-          <button type='button' class='page-link' id='sheet:pageActiontoPage-1' title='Page 1'>1<tobago-behavior event='click' client-id='sheet:pageActiontoPage-1' execute='sheet sheet:pageActiontoPage-1' render='sheet'></tobago-behavior></button>
+          <button type='button' class='page-link' title='Page 1' data-tobago-action='{"action":"toPage","target":1}'>1</button>
         </li>
         <li class='page-item'>
-          <button type='button' class='page-link' id='sheet:pageActiontoPage-2' title='Page 2'>2<tobago-behavior event='click' client-id='sheet:pageActiontoPage-2' execute='sheet sheet:pageActiontoPage-2' render='sheet'></tobago-behavior></button>
+          <button type='button' class='page-link' title='Page 2' data-tobago-action='{"action":"toPage","target":2}'>2</button>
         </li>
         <li class='page-item'>
-          <button type='button' class='page-link' id='sheet:pageActiontoPage-3' title='Page 3'>3<tobago-behavior event='click' client-id='sheet:pageActiontoPage-3' execute='sheet sheet:pageActiontoPage-3' render='sheet'></tobago-behavior></button>
+          <button type='button' class='page-link' title='Page 3' data-tobago-action='{"action":"toPage","target":3}'>3</button>
         </li>
         <li class='page-item'>
-          <button type='button' class='page-link' id='sheet:pageActiontoPage-4' title='Page 4'>4<tobago-behavior event='click' client-id='sheet:pageActiontoPage-4' execute='sheet sheet:pageActiontoPage-4' render='sheet'></tobago-behavior></button>
+          <button type='button' class='page-link' title='Page 4' data-tobago-action='{"action":"toPage","target":4}'>4</button>
         </li>
         <li class='page-item'>
-          <button type='button' class='page-link' id='sheet:pageActiontoPage-5' title='Page 5'>5<tobago-behavior event='click' client-id='sheet:pageActiontoPage-5' execute='sheet sheet:pageActiontoPage-5' render='sheet'></tobago-behavior></button>
+          <button type='button' class='page-link' title='Page 5' data-tobago-action='{"action":"toPage","target":5}'>5</button>
         </li>
       </ul>
     </tobago-paginator-list>
-    <tobago-paginator-page id='sheet:_paginator_page' class='pagination'>
-      <li class='disabled page-item'>
-        <button type='button' class='page-link' id='sheet:pageActionfirst' title='First Page' disabled='disabled'><i class='bi-skip-start'></i></button>
-      </li>
-      <li class='disabled page-item'>
-        <button type='button' class='page-link' id='sheet:pageActionprev' title='Previous Page' disabled='disabled'><i class='bi-caret-left'></i></button>
-      </li>
-      <li class='page-item'>
-        <span class='tobago-paging page-link text-nowrap' title='Click here, to change the displayed page'>Page <span>1</span><input id='sheet:pageActiontoPage' name='sheet:pageActiontoPage' type='text' value='1' maxlength='1'> of 5</span>
-      </li>
-      <li class='page-item'>
-        <button type='button' class='page-link' id='sheet:pageActionnext' title='Next Page'><i class='bi-caret-right'></i><tobago-behavior event='click' client-id='sheet:pageActionnext' execute='sheet sheet:pageActionnext' render='sheet'></tobago-behavior></button>
-      </li>
-      <li class='page-item'>
-        <button type='button' class='page-link' id='sheet:pageActionlast' title='Last Page'><i class='bi-skip-end'></i><tobago-behavior event='click' client-id='sheet:pageActionlast' execute='sheet sheet:pageActionlast' render='sheet'></tobago-behavior></button>
-      </li>
+    <tobago-paginator-page id='sheet:_paginator_page'>
+      <ul class='pagination'>
+        <li class='disabled page-item'>
+          <button type='button' class='page-link' title='First Page' disabled='disabled' data-tobago-action='{"action":"first"}'><i class='bi-skip-start'></i></button>
+        </li>
+        <li class='disabled page-item'>
+          <button type='button' class='page-link' title='Previous Page' disabled='disabled' data-tobago-action='{"action":"prev"}'><i class='bi-caret-left'></i></button>
+        </li>
+        <li class='page-item'>
+          <span class='tobago-paging page-link text-nowrap' title='Click here, to change the displayed page'>Page <span>1</span><input id='sheet:_paginator_page::field' name='sheet:_paginator_page' type='text' value='1' maxlength='1'> of 5</span>
+        </li>
+        <li class='page-item'>
+          <button type='button' class='page-link' title='Next Page' data-tobago-action='{"action":"next"}'><i class='bi-caret-right'></i></button>
+        </li>
+        <li class='page-item'>
+          <button type='button' class='page-link' title='Last Page' data-tobago-action='{"action":"last"}'><i class='bi-skip-end'></i></button>
+        </li>
+      </ul>
     </tobago-paginator-page>
   </tobago-paginator-panel>
 </tobago-sheet>
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/080-sheet/10-sort/Sheet_Sorting.test.js b/tobago-example/tobago-example-demo/src/main/webapp/content/080-sheet/10-sort/Sheet_Sorting.test.js
index 078fae8b60..3f99a8acb9 100644
--- a/tobago-example/tobago-example-demo/src/main/webapp/content/080-sheet/10-sort/Sheet_Sorting.test.js
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/080-sheet/10-sort/Sheet_Sorting.test.js
@@ -21,9 +21,18 @@ import {JasmineTestTool} from "/tobago/test/tobago-test-tool.js";
 it("Basics: Name", function (done) {
   let colNameFn = elementByIdFn("page:mainForm:s1:columnName_sorter");
   let rowsFn = querySelectorAllFn("#page\\:mainForm\\:s1 .tobago-body tbody tr");
-  let leftPagingFn = elementByIdFn("page:mainForm:s1:pageActiontoRow");
+  let leftPagingFn = elementByIdFn("page:mainForm:s1:_paginator_row::field");
 
   let test = new JasmineTestTool(done);
+  if (colNameFn() === null) {
+    test.fail("colName sorter not found!");
+  }
+  if (rowsFn().length !== 4) {
+    test.fail("Not 4 rows!");
+  }
+  if (leftPagingFn() === null) {
+    test.fail("leftPaging button not found!");
+  }
   test.setup(
       () => colNameFn().classList.contains("tobago-ascending"),
       null, "click", colNameFn);
@@ -60,9 +69,18 @@ it("Basics: Name", function (done) {
 it("Basics: Period", function (done) {
   let colPeriodFn = querySelectorFn("#page\\:mainForm\\:s1\\:columnPeriod_sorter");
   let rowsFn = querySelectorAllFn("#page\\:mainForm\\:s1 .tobago-body tbody tr");
-  let leftPagingFn = elementByIdFn("page:mainForm:s1:pageActiontoRow");
+  let leftPagingFn = elementByIdFn("page:mainForm:s1:_paginator_row::field");
 
   let test = new JasmineTestTool(done);
+  if (colPeriodFn() === null) {
+    test.fail("colPeriod sorter not found!");
+  }
+  if (rowsFn().length !== 4) {
+    test.fail("Not 4 rows!");
+  }
+  if (leftPagingFn() === null) {
+    test.fail("leftPaging button not found!");
+  }
   test.setup(
       () => colPeriodFn().classList.contains("tobago-ascending"),
       null, "click", colPeriodFn);
@@ -99,9 +117,18 @@ it("Basics: Period", function (done) {
 it("Basics: Year", function (done) {
   let colYearFn = querySelectorFn("#page\\:mainForm\\:s1\\:columnDiscoverYear_sorter");
   let rowsFn = querySelectorAllFn("#page\\:mainForm\\:s1 .tobago-body tbody tr");
-  let leftPagingFn = elementByIdFn("page:mainForm:s1:pageActiontoRow");
+  let leftPagingFn = elementByIdFn("page:mainForm:s1:_paginator_row::field");
 
   let test = new JasmineTestTool(done);
+  if (colYearFn() === null) {
+    test.fail("colYear sorter not found!");
+  }
+  if (rowsFn().length !== 4) {
+    test.fail("Not 4 rows!");
+  }
+  if (leftPagingFn() === null) {
+    test.fail("leftPaging button not found!");
+  }
   test.setup(
       () => colYearFn().classList.contains("tobago-ascending"),
       null, "click", colYearFn);
@@ -127,16 +154,24 @@ it("Basics: Year", function (done) {
   test.start();
 });
 
-/**
- * 1. goto line 8
- * 2. goto line 9
- */
+ // 1. goto line 8
+ // 2. goto line 9
+
 it("Basics: left paging", function (done) {
   let colNameFn = elementByIdFn("page:mainForm:s1:columnName_sorter");
   let rowsFn = querySelectorAllFn("#page\\:mainForm\\:s1 .tobago-body tbody tr");
-  let leftPagingFn = elementByIdFn("page:mainForm:s1:pageActiontoRow");
+  let leftPagingFn = elementByIdFn("page:mainForm:s1:_paginator_row::field");
 
   let test = new JasmineTestTool(done);
+  if (colNameFn() === null) {
+    test.fail("colName sorter not found!");
+  }
+  if (rowsFn().length !== 4) {
+    test.fail("Not 4 rows!");
+  }
+  if (leftPagingFn() === null) {
+    test.fail("leftPaging button not found!");
+  }
   test.setup(
       () => colNameFn().classList.contains("tobago-ascending"),
       null, "click", colNameFn);
@@ -172,20 +207,28 @@ it("Basics: left paging", function (done) {
   test.start();
 });
 
-/**
- * 1. goto page 7
- * 2. goto page 16
- * 3. goto page 13
- */
+ // 1. goto page 7
+ // 2. goto page 16
+ // 3. goto page 13
+
 it("Basics: center paging", function (done) {
   let colNameFn = elementByIdFn("page:mainForm:s1:columnName_sorter");
   let rowsFn = querySelectorAllFn("#page\\:mainForm\\:s1 .tobago-body tbody tr");
-  let leftPagingFn = elementByIdFn("page:mainForm:s1:pageActiontoRow");
-  let centerPaging7Fn = elementByIdFn("page:mainForm:s1:pageActiontoPage-7");
-  let centerPaging14Fn = elementByIdFn("page:mainForm:s1:pageActiontoPage-14");
-  let centerPaging16Fn = elementByIdFn("page:mainForm:s1:pageActiontoPage-16");
+  let leftPagingFn = elementByIdFn("page:mainForm:s1:_paginator_row::field");
+  let centerPaging7Fn = querySelectorFn("#page\\:mainForm\\:s1 [data-tobago-action*='\\:7\\}']");
+  let centerPaging14Fn = querySelectorFn("#page\\:mainForm\\:s1 [data-tobago-action*='\\:14\\}']");
+  let centerPaging16Fn = querySelectorFn("#page\\:mainForm\\:s1 [data-tobago-action*='\\:16\\}']");
 
   let test = new JasmineTestTool(done);
+  if (colNameFn() === null) {
+    test.fail("colNameFn sorter not found!");
+  }
+  if (rowsFn().length !== 4) {
+    test.fail("Not 4 rows!");
+  }
+  if (leftPagingFn() === null) {
+    test.fail("leftPaging button not found!");
+  }
   test.setup(
       () => colNameFn().classList.contains("tobago-ascending"),
       null, "click", colNameFn);
@@ -229,24 +272,44 @@ it("Basics: center paging", function (done) {
   test.start();
 });
 
-/**
- * 1. goto first page
- * 2. goto page 2 by pressing arrow-right
- * 3. goto last page
- * 4. goto page 21 by pressing arrow-left
- * 5. goto page 14
- */
+ // 1. goto first page
+ // 2. goto page 2 by pressing arrow-right
+ // 3. goto last page
+ // 4. goto page 21 by pressing arrow-left
+ // 5. goto page 14
+
 it("Basics: right paging", function (done) {
   let colNameFn = elementByIdFn("page:mainForm:s1:columnName_sorter");
   let rowsFn = querySelectorAllFn("#page\\:mainForm\\:s1 .tobago-body tbody tr");
-  let leftPagingFn = elementByIdFn("page:mainForm:s1:pageActiontoRow");
-  let rightPagingFirstFn = elementByIdFn("page:mainForm:s1:pageActionfirst");
-  let rightPagingPrevFn = elementByIdFn("page:mainForm:s1:pageActionprev");
-  let rightPagingNextFn = elementByIdFn("page:mainForm:s1:pageActionnext");
-  let rightPagingLastFn = elementByIdFn("page:mainForm:s1:pageActionlast");
-  let jumpToPageFn = elementByIdFn("page:mainForm:s1:pageActiontoPage");
+  let leftPagingFn = elementByIdFn("page:mainForm:s1:_paginator_row::field");
+  let rightPagingFirstFn = querySelectorFn("#page\\:mainForm\\:s1 [data-tobago-action*='first']");
+  let rightPagingPrevFn = querySelectorFn("#page\\:mainForm\\:s1 [data-tobago-action*='prev']");
+  let rightPagingNextFn = querySelectorFn("#page\\:mainForm\\:s1 [data-tobago-action*='next']");
+  let rightPagingLastFn = querySelectorFn("#page\\:mainForm\\:s1 [data-tobago-action*='last']");
+  let jumpToPageFn = elementByIdFn("page:mainForm:s1:_paginator_page::field");
 
   let test = new JasmineTestTool(done);
+  if (colNameFn() === null) {
+    test.fail("colNameFn sorter not found!");
+  }
+  if (rowsFn().length !== 4) {
+    test.fail("Not 4 rows!");
+  }
+  if (leftPagingFn() === null) {
+    test.fail("leftPaging button not found!");
+  }
+  if (rightPagingFirstFn() === null) {
+    test.fail("rightPagingFirst button not found!");
+  }
+  if (rightPagingPrevFn() === null) {
+    test.fail("rightPagingPrev button not found!");
+  }
+  if (rightPagingNextFn() === null) {
+    test.fail("rightPagingNext button not found!");
+  }
+  if (rightPagingLastFn() === null) {
+    test.fail("rightPagingLast button not found!");
+  }
   test.setup(
       () => colNameFn().classList.contains("tobago-ascending"),
       null, "click", colNameFn);
@@ -310,12 +373,22 @@ it("Basics: right paging", function (done) {
       "Ophelia", "0.38", "1986"));
   test.start();
 });
+
 it("Custom Sorting: Name", function (done) {
   let colNameFn = elementByIdFn("page:mainForm:s2:customColumnName_sorter");
   let rowsFn = querySelectorAllFn("#page\\:mainForm\\:s2 .tobago-body tbody tr");
-  let leftPagingFn = elementByIdFn("page:mainForm:s2:pageActiontoRow");
+  let leftPagingFn = elementByIdFn("page:mainForm:s2:_paginator_row::field");
 
   let test = new JasmineTestTool(done);
+  if (colNameFn() === null) {
+    test.fail("colName sorter not found!");
+  }
+  if (rowsFn().length !== 4) {
+    test.fail("Not 4 rows!");
+  }
+  if (leftPagingFn() === null) {
+    test.fail("leftPaging button not found!");
+  }
   test.setup(
       () => colNameFn().classList.contains("tobago-ascending"),
       null, "click", colNameFn);
@@ -352,9 +425,18 @@ it("Custom Sorting: Name", function (done) {
 it("Custom Sorting: Period", function (done) {
   let colPeriodFn = querySelectorFn("#page\\:mainForm\\:s2\\:customColumnPeriod_sorter");
   let rowsFn = querySelectorAllFn("#page\\:mainForm\\:s2 .tobago-body tbody tr");
-  let leftPagingFn = elementByIdFn("page:mainForm:s2:pageActiontoRow");
+  let leftPagingFn = elementByIdFn("page:mainForm:s2:_paginator_row::field");
 
   let test = new JasmineTestTool(done);
+  if (colPeriodFn() === null) {
+    test.fail("colPeriod sorter not found!");
+  }
+  if (rowsFn().length !== 4) {
+    test.fail("Not 4 rows!");
+  }
+  if (leftPagingFn() === null) {
+    test.fail("leftPaging button not found!");
+  }
   test.setup(
       () => colPeriodFn().classList.contains("tobago-ascending"),
       null, "click", colPeriodFn);
@@ -391,9 +473,18 @@ it("Custom Sorting: Period", function (done) {
 it("Custom Sorting: Year", function (done) {
   let colYearFn = querySelectorFn("#page\\:mainForm\\:s2\\:customColumnYear_sorter");
   let rowsFn = querySelectorAllFn("#page\\:mainForm\\:s2 .tobago-body tbody tr");
-  let leftPagingFn = elementByIdFn("page:mainForm:s2:pageActiontoRow");
+  let leftPagingFn = elementByIdFn("page:mainForm:s2:_paginator_row::field");
 
   let test = new JasmineTestTool(done);
+  if (colYearFn() === null) {
+    test.fail("colYear sorter not found!");
+  }
+  if (rowsFn().length !== 4) {
+    test.fail("Not 4 rows!");
+  }
+  if (leftPagingFn() === null) {
+    test.fail("leftPaging button not found!");
+  }
   test.setup(
       () => colYearFn().classList.contains("tobago-ascending"),
       null, "click", colYearFn);
@@ -419,16 +510,24 @@ it("Custom Sorting: Year", function (done) {
   test.start();
 });
 
-/**
- * 1. goto line 8
- * 2. goto line 9
- */
+ // 1. goto line 8
+ // 2. goto line 9
+
 it("Custom Sorting: left paging", function (done) {
   let colNameFn = elementByIdFn("page:mainForm:s2:customColumnName_sorter");
   let rowsFn = querySelectorAllFn("#page\\:mainForm\\:s2 .tobago-body tbody tr");
-  let leftPagingFn = elementByIdFn("page:mainForm:s2:pageActiontoRow");
+  let leftPagingFn = elementByIdFn("page:mainForm:s2:_paginator_row::field");
 
   let test = new JasmineTestTool(done);
+  if (colNameFn() === null) {
+    test.fail("colName sorter not found!");
+  }
+  if (rowsFn().length !== 4) {
+    test.fail("Not 4 rows!");
+  }
+  if (leftPagingFn() === null) {
+    test.fail("leftPaging button not found!");
+  }
   test.setup(
       () => colNameFn().classList.contains("tobago-ascending"),
       null, "click", colNameFn);
@@ -464,20 +563,28 @@ it("Custom Sorting: left paging", function (done) {
   test.start();
 });
 
-/**
- * 1. goto page 7
- * 2. goto page 16
- * 3. goto page 13
- */
+ // 1. goto page 7
+ // 2. goto page 16
+ // 3. goto page 13
+
 it("Custom Sorting: center paging", function (done) {
   let colNameFn = elementByIdFn("page:mainForm:s2:customColumnName_sorter");
   let rowsFn = querySelectorAllFn("#page\\:mainForm\\:s2 .tobago-body tbody tr");
-  let leftPagingFn = elementByIdFn("page:mainForm:s2:pageActiontoRow");
-  let centerPaging7Fn = elementByIdFn("page:mainForm:s2:pageActiontoPage-7");
-  let centerPaging14Fn = elementByIdFn("page:mainForm:s2:pageActiontoPage-14");
-  let centerPaging16Fn = elementByIdFn("page:mainForm:s2:pageActiontoPage-16");
+  let leftPagingFn = elementByIdFn("page:mainForm:s2:_paginator_row::field");
+  let centerPaging7Fn = querySelectorFn("#page\\:mainForm\\:s2 [data-tobago-action*='\\:7\\}']");
+  let centerPaging14Fn = querySelectorFn("#page\\:mainForm\\:s2 [data-tobago-action*='\\:14\\}']");
+  let centerPaging16Fn = querySelectorFn("#page\\:mainForm\\:s2 [data-tobago-action*='\\:16\\}']");
 
   let test = new JasmineTestTool(done);
+  if (colNameFn() === null) {
+    test.fail("colName sorter not found!");
+  }
+  if (rowsFn().length !== 4) {
+    test.fail("Not 4 rows!");
+  }
+  if (leftPagingFn() === null) {
+    test.fail("leftPaging button not found!");
+  }
   test.setup(
       () => colNameFn().classList.contains("tobago-ascending"),
       null, "click", colNameFn);
@@ -521,24 +628,47 @@ it("Custom Sorting: center paging", function (done) {
   test.start();
 });
 
-/**
- * 1. goto first page
- * 2. goto page 2 by pressing arrow-right
- * 3. goto last page
- * 4. goto page 21 by pressing arrow-left
- * 5. goto page 14
- */
+ // 1. goto first page
+ // 2. goto page 2 by pressing arrow-right
+ // 3. goto last page
+ // 4. goto page 21 by pressing arrow-left
+ // 5. goto page 14
+
 it("Custom Sorting: right paging", function (done) {
   let colNameFn = elementByIdFn("page:mainForm:s2:customColumnName_sorter");
   let rowsFn = querySelectorAllFn("#page\\:mainForm\\:s2 .tobago-body tbody tr");
-  let leftPagingFn = elementByIdFn("page:mainForm:s2:pageActiontoRow");
-  let rightPagingFirstFn = elementByIdFn("page:mainForm:s2:pageActionfirst");
-  let rightPagingPrevFn = elementByIdFn("page:mainForm:s2:pageActionprev");
-  let rightPagingNextFn = elementByIdFn("page:mainForm:s2:pageActionnext");
-  let rightPagingLastFn = elementByIdFn("page:mainForm:s2:pageActionlast");
-  let rightPagingToPageFn = elementByIdFn("page:mainForm:s2:pageActiontoPage");
+  let leftPagingFn = elementByIdFn("page:mainForm:s2:_paginator_row::field");
+  let rightPagingFirstFn = querySelectorFn("#page\\:mainForm\\:s2 [data-tobago-action*='first']");
+  let rightPagingPrevFn = querySelectorFn("#page\\:mainForm\\:s2 [data-tobago-action*='prev']");
+  let rightPagingNextFn = querySelectorFn("#page\\:mainForm\\:s2 [data-tobago-action*='next']");
+  let rightPagingLastFn = querySelectorFn("#page\\:mainForm\\:s2 [data-tobago-action*='last']");
+  let rightPagingToPageFn = elementByIdFn("page:mainForm:s2:_paginator_page::field");
 
   let test = new JasmineTestTool(done);
+  if (colNameFn() === null) {
+    test.fail("colName sorter not found!");
+  }
+  if (rowsFn().length !== 4) {
+    test.fail("Not 4 rows!");
+  }
+  if (leftPagingFn() === null) {
+    test.fail("leftPaging button not found!");
+  }
+  if (rightPagingFirstFn() === null) {
+    test.fail("rightPagingFirst button not found!");
+  }
+  if (rightPagingPrevFn() === null) {
+    test.fail("rightPagingPrev button not found!");
+  }
+  if (rightPagingNextFn() === null) {
+    test.fail("rightPagingNext button not found!");
+  }
+  if (rightPagingLastFn() === null) {
+    test.fail("rightPagingLast button not found!");
+  }
+  if (rightPagingToPageFn() === null) {
+    test.fail("rightPagingToPage input not found!");
+  }
   test.setup(
       () => colNameFn().classList.contains("tobago-ascending"),
       null, "click", colNameFn);
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/080-sheet/15-paginator/Paginator.xhtml b/tobago-example/tobago-example-demo/src/main/webapp/content/080-sheet/15-paginator/Paginator.xhtml
index edc800cba1..78e63dd71a 100644
--- a/tobago-example/tobago-example-demo/src/main/webapp/content/080-sheet/15-paginator/Paginator.xhtml
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/080-sheet/15-paginator/Paginator.xhtml
@@ -38,11 +38,12 @@
       </li>
     </ul>
 
-    <tc:badge markup="warning">Todo: Check if 2 or more of the same controls will work on same page!</tc:badge>
+    An alternative way to define the paginator is to use the new tags <code>tc:paginatorPanel</code>,
+    and inside of it <code>tc:paginatorRow</code>, <code>tc:paginatorList</code> and <code>tc:paginatorPage</code>.
+    This way is much more flexible, because you can define the paginators and combinations of it in any way you like.
 
     <tc:sheet id="s1" value="#{sheetController.solarList}" var="object" rows="5"
               paginator="custom">
-      <!--
       <f:facet name="before">
         <tc:paginatorPanel>
           <tc:paginatorRow />
@@ -50,7 +51,6 @@
           <tc:paginatorPage />
         </tc:paginatorPanel>
       </f:facet>
-      -->
       <f:facet name="after">
         <tc:paginatorPanel>
           <tc:paginatorRow/>
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/080-sheet/25-selection/Sheet_Selectionchange.test.js b/tobago-example/tobago-example-demo/src/main/webapp/content/080-sheet/25-selection/Sheet_Selectionchange.test.js
index 1b400a32a5..0bba82c3d9 100644
--- a/tobago-example/tobago-example-demo/src/main/webapp/content/080-sheet/25-selection/Sheet_Selectionchange.test.js
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/080-sheet/25-selection/Sheet_Selectionchange.test.js
@@ -27,9 +27,12 @@ it("select 'Sun', select 'Venus', select all, deselect 'Mercury'", function (don
   const mercury = querySelectorFn("tr[row-index='1']");
   const venus = querySelectorFn("tr[row-index='2']");
   const earth = querySelectorFn("tr[row-index='3']");
-  const firstPageButton = elementByIdFn("page:mainForm:sheet:pageActionfirst");
+  const firstPageButton = querySelectorFn("#page\\:mainForm\\:sheet [data-tobago-action*='first']");
 
   const test = new JasmineTestTool(done);
+  if (firstPageButton() === null) {
+    test.fail("firstPageButton button not found!");
+  }
   test.setup(() => hiddenSelectedField().value === "[]", null, "click", resetSelected);
   test.setup(() => firstPageButton().disabled, null, "click", firstPageButton);
 
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/3000-sheet/30-sheet-margin-bottom/Sheet_Margin_Bottom.test.js b/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/3000-sheet/30-sheet-margin-bottom/Sheet_Margin_Bottom.test.js
index b26fbb80b2..ccd4e009b2 100644
--- a/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/3000-sheet/30-sheet-margin-bottom/Sheet_Margin_Bottom.test.js
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/3000-sheet/30-sheet-margin-bottom/Sheet_Margin_Bottom.test.js
@@ -19,7 +19,7 @@ import {elementByIdFn, querySelectorFn} from "/script/tobago-test.js";
 import {JasmineTestTool} from "/tobago/test/tobago-test-tool.js";
 
 it("Margin from button to sheet/paging must be the same", function (done) {
-  const sheet1pagingFn = querySelectorFn("#page\\:mainForm\\:sheet1 .ms-auto.pagination");
+  const sheet1pagingFn = querySelectorFn("#page\\:mainForm\\:sheet1 .pagination");
   const button1Fn = elementByIdFn("page:mainForm:button1");
   const sheet2Fn = elementByIdFn("page:mainForm:sheet2");
   const button2Fn = elementByIdFn("page:mainForm:button2");
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/3000-sheet/38-column-selector/Column_Selector.test.js b/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/3000-sheet/38-column-selector/Column_Selector.test.js
index f77e2e6013..f60bd28927 100644
--- a/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/3000-sheet/38-column-selector/Column_Selector.test.js
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/3000-sheet/38-column-selector/Column_Selector.test.js
@@ -27,7 +27,7 @@ it("Default sheet: select row 2; select row4", function (done) {
   const setupDefaultSheet = elementByIdFn("page:mainForm:defaultSheet");
   const row2 = querySelectorFn("tr[row-index='2']");
   const row4 = querySelectorFn("tr[row-index='4']");
-  const firstPageButton = elementByIdFn("page:mainForm:sheet:pageActionfirst");
+  const firstPageButton = querySelectorFn("#page\\:mainForm\\:sheet [data-tobago-action*='first']");
 
   const test = new JasmineTestTool(done);
   test.setup(() => rows().value === "5" && !lazy().checked && sheetSelectable().value === "multi"
@@ -65,7 +65,7 @@ it("selectable=none + no column selector: select row 3 cannot happen", function
   const columnPanelRendered = elementByIdFn("page:mainForm:columnPanelRendered::field");
   const setupSelectableNone = elementByIdFn("page:mainForm:selectableNone");
   const row3 = querySelectorFn("tr[row-index='3']");
-  const firstPageButton = elementByIdFn("page:mainForm:sheet:pageActionfirst");
+  const firstPageButton = querySelectorFn("#page\\:mainForm\\:sheet [data-tobago-action*='first']");
 
   const test = new JasmineTestTool(done);
   test.setup(() => rows().value === "5" && !lazy().checked && sheetSelectable().value === "none"
@@ -92,8 +92,8 @@ it("selectable=single: page 1; select row 3; page 4; select row 18; page 1", fun
   const selectAllCheckbox = querySelectorFn("input[name='page:mainForm:sheet::columnSelector']");
   const row3 = querySelectorFn("tr[row-index='3']");
   const row18 = querySelectorFn("tr[row-index='18']");
-  const page4 = elementByIdFn("page:mainForm:sheet:pageActiontoPage-4");
-  const firstPageButton = elementByIdFn("page:mainForm:sheet:pageActionfirst");
+  const page4 = querySelectorFn("#page\\:mainForm\\:sheet [data-tobago-action*='\\:4\\}']");
+  const firstPageButton = querySelectorFn("#page\\:mainForm\\:sheet [data-tobago-action*='first']");
 
   const test = new JasmineTestTool(done);
   test.setup(() => rows().value === "5" && !lazy().checked && sheetSelectable().value === "single"
@@ -136,8 +136,8 @@ it("selectable=singleOrNone: page 1; select row 3; deselect row 3", function (do
   const selectAllCheckbox = querySelectorFn("input[name='page:mainForm:sheet::columnSelector']");
   const row3 = querySelectorFn("tr[row-index='3']");
   const row18 = querySelectorFn("tr[row-index='18']");
-  const page4 = elementByIdFn("page:mainForm:sheet:pageActiontoPage-4");
-  const firstPageButton = elementByIdFn("page:mainForm:sheet:pageActionfirst");
+  const page4 = querySelectorFn("#page\\:mainForm\\:sheet [data-tobago-action*='\\:4\\}']");
+  const firstPageButton = querySelectorFn("#page\\:mainForm\\:sheet [data-tobago-action*='first']");
 
   const test = new JasmineTestTool(done);
   test.setup(() => rows().value === "5" && !lazy().checked && sheetSelectable().value === "singleOrNone"
@@ -168,8 +168,8 @@ it("selectable=multi: page 1; select row 4; page 4; select row 17; page 1", func
   const selectAllCheckbox = querySelectorFn("input[name='page:mainForm:sheet::columnSelector']");
   const row4 = querySelectorFn("tr[row-index='4']");
   const row17 = querySelectorFn("tr[row-index='17']");
-  const page4 = elementByIdFn("page:mainForm:sheet:pageActiontoPage-4");
-  const firstPageButton = elementByIdFn("page:mainForm:sheet:pageActionfirst");
+  const page4 = querySelectorFn("#page\\:mainForm\\:sheet [data-tobago-action*='\\:4\\}']");
+  const firstPageButton = querySelectorFn("#page\\:mainForm\\:sheet [data-tobago-action*='first']");
 
   const test = new JasmineTestTool(done);
   test.setup(() => rows().value === "5" && !lazy().checked && sheetSelectable().value === "multi"
@@ -216,8 +216,8 @@ it("selectable=multi: select all; page 4; select row 17; page 1", function (done
   const row3 = querySelectorFn("tr[row-index='3']");
   const row4 = querySelectorFn("tr[row-index='4']");
   const row17 = querySelectorFn("tr[row-index='17']");
-  const page4 = elementByIdFn("page:mainForm:sheet:pageActiontoPage-4");
-  const firstPageButton = elementByIdFn("page:mainForm:sheet:pageActionfirst");
+  const page4 = querySelectorFn("#page\\:mainForm\\:sheet [data-tobago-action*='\\:4\\}']");
+  const firstPageButton = querySelectorFn("#page\\:mainForm\\:sheet [data-tobago-action*='first']");
 
   const test = new JasmineTestTool(done);
   test.setup(() => rows().value === "5" && !lazy().checked && sheetSelectable().value === "multi"
@@ -288,7 +288,7 @@ it("selectable=multi: select all; deselect row 3; select row 3", function (done)
   const row2 = querySelectorFn("tr[row-index='2']");
   const row3 = querySelectorFn("tr[row-index='3']");
   const row4 = querySelectorFn("tr[row-index='4']");
-  const firstPageButton = elementByIdFn("page:mainForm:sheet:pageActionfirst");
+  const firstPageButton = querySelectorFn("#page\\:mainForm\\:sheet [data-tobago-action*='first']");
 
   const test = new JasmineTestTool(done);
   test.setup(() => rows().value === "5" && !lazy().checked && sheetSelectable().value === "multi"
@@ -359,7 +359,7 @@ it("disabled column selector", function (done) {
   const rowSelector = querySelectorFn("input[name='page:mainForm:sheet_data_row_selector_0']");
   const row2 = querySelectorFn("tr[row-index='2']");
   const row4 = querySelectorFn("tr[row-index='4']");
-  const firstPageButton = elementByIdFn("page:mainForm:sheet:pageActionfirst");
+  const firstPageButton = querySelectorFn("#page\\:mainForm\\:sheet [data-tobago-action*='first']");
 
   const test = new JasmineTestTool(done);
   test.setup(() => rows().value === "5" && !lazy().checked && sheetSelectable().value === "multi"
@@ -396,8 +396,8 @@ it("column panel: page 1; select row 4; page 4; select row 17; page 1", function
   const row17 = querySelectorFn("tr[row-index='17']");
   const columnPanel4 = querySelectorFn(".tobago-column-panel[name='4']");
   const columnPanel17 = querySelectorFn(".tobago-column-panel[name='17']");
-  const page4 = elementByIdFn("page:mainForm:sheet:pageActiontoPage-4");
-  const firstPageButton = elementByIdFn("page:mainForm:sheet:pageActionfirst");
+  const page4 = querySelectorFn("#page\\:mainForm\\:sheet [data-tobago-action*='\\:4\\}']");
+  const firstPageButton = querySelectorFn("#page\\:mainForm\\:sheet [data-tobago-action*='first']");
 
   const test = new JasmineTestTool(done);
   test.setup(() => rows().value === "5" && !lazy().checked && sheetSelectable().value === "multi"
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/3000-sheet/40-paginator/Paginator.xhtml b/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/3000-sheet/40-paginator/Paginator.xhtml
index 1ae72af8d4..54cbb19cc8 100644
--- a/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/3000-sheet/40-paginator/Paginator.xhtml
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/3000-sheet/40-paginator/Paginator.xhtml
@@ -56,7 +56,7 @@
     </tc:sheet>
   </tc:section>
 
-  <tc:section label="Page">
+  <tc:section label="Row">
     <tc:sheet id="r1" value="#{sheetController.solarList}" var="object" rows="5" paginator="row">
       <tc:column label="Id">
         <tc:out value="#{object.id}"/>
@@ -85,5 +85,4 @@
     </tc:sheet>
   </tc:section>
 
-
 </ui:composition>
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
index 4270569070..bb88b7f15c 100644
--- 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
@@ -97,7 +97,7 @@ it("focus row index 1000 and go down", function (done) {
   test.do(() => expect(isLoaded(1020)).toBeFalse());
 
   test.do(() => focusRowIndex(1001));
-  test.wait(() => isLoaded(1001));
+  test.wait(() => isLoaded(1020));
   test.do(() => expect(isLoaded(999)).toBeFalse());
   test.do(() => expect(isLoaded(1000, 1039)).toBeTrue());
   test.do(() => expect(isLoaded(1040)).toBeFalse());
diff --git a/tobago-theme/tobago-theme-standard/src/main/js/tobago.js b/tobago-theme/tobago-theme-standard/src/main/js/tobago.js
index 78ce2d70cf..0739bea2b2 100644
--- a/tobago-theme/tobago-theme-standard/src/main/js/tobago.js
+++ b/tobago-theme/tobago-theme-standard/src/main/js/tobago.js
@@ -4,16 +4,16 @@
       * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
       * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
       */
-const pt=new Map,gt={set(e,t,s){pt.has(e)||pt.set(e,new Map);const i=pt.get(e);i.has(t)||0===i.size?i.set(t,s):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(i.keys())[0]}.`)},get:(e,t)=>pt.has(e)&&pt.get(e).get(t)||null,remove(e,t){if(!pt.has(e))return;const s=pt.get(e);s.delete(t),0===s.size&&pt.delete(e)}},ft="transitionend",mt=e=>(e&&window.CSS&&window.CSS.escape&&(e=e.replace(/#([^\s"#']+)/g,((e,t)=>`#${CSS.escape(t)}`))),e),b [...]
+const pt=new Map,gt={set(e,t,s){pt.has(e)||pt.set(e,new Map);const i=pt.get(e);i.has(t)||0===i.size?i.set(t,s):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(i.keys())[0]}.`)},get:(e,t)=>pt.has(e)&&pt.get(e).get(t)||null,remove(e,t){if(!pt.has(e))return;const s=pt.get(e);s.delete(t),0===s.size&&pt.delete(e)}},ft="transitionend",mt=e=>(e&&window.CSS&&window.CSS.escape&&(e=e.replace(/#([^\s"#']+)/g,((e,t)=>`#${CSS.escape(t)}`))),e),b [...]
 /**
      * @license
      * Copyright 2017 Google LLC
      * SPDX-License-Identifier: BSD-3-Clause
-     */const dr=globalThis,ur=dr.trustedTypes,hr=ur?ur.createPolicy("lit-html",{createHTML:e=>e}):void 0,pr="$lit$",gr=`lit$${Math.random().toFixed(9).slice(2)}$`,fr="?"+gr,mr=`<${fr}>`,br=document,vr=()=>br.createComment(""),_r=e=>null===e||"object"!=typeof e&&"function"!=typeof e,yr=Array.isArray,wr="[ \t\n\f\r]",Er=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,Ar=/-->/g,Sr=/>/g,Lr=RegExp(`>|${wr}(?:([^\\s"'>=/]+)(${wr}*=${wr}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),Or=/'/g,xr [...]
+     */const ur=globalThis,hr=ur.trustedTypes,pr=hr?hr.createPolicy("lit-html",{createHTML:e=>e}):void 0,gr="$lit$",fr=`lit$${Math.random().toFixed(9).slice(2)}$`,mr="?"+fr,br=`<${mr}>`,vr=document,_r=()=>vr.createComment(""),yr=e=>null===e||"object"!=typeof e&&"function"!=typeof e,wr=Array.isArray,Er="[ \t\n\f\r]",Ar=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,Sr=/-->/g,Lr=/>/g,Or=RegExp(`>|${Er}(?:([^\\s"'>=/]+)(${Er}*=${Er}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),xr=/'/g,Tr [...]
   <button type='button'
       class='tobago-button btn btn-secondary badge' aria-label='deselect ${e}'
       ${s>0?" tabindex='"+String(s)+"'":""}
       @click="${this.removeBadge.bind(this)}"
       @focus="${this.focusEvent.bind(this)}"
-      @blur="${this.blurEvent.bind(this)}"><i class='bi-x-lg'></i></button>`}removeBadge(e){var t;const s=e.target.closest(".btn-group").dataset.tobagoValue,i=this.hiddenSelect.querySelector(`[value="${s}"]`);i.selected=!1;const n=this.badges.querySelector(`[data-tobago-value="${s}"]`),o=n.previousElementSibling,r="SPAN"===(null===(t=n.nextElementSibling)||void 0===t?void 0:t.tagName)?n.nextElementSibling:null;o?o.querySelector("button.btn.badge").focus():r?r.querySelector("button.btn.ba [...]
+      @blur="${this.blurEvent.bind(this)}"><i class='bi-x-lg'></i></button>`}removeBadge(e){var t;const s=e.target.closest(".btn-group").dataset.tobagoValue,i=this.hiddenSelect.querySelector(`[value="${s}"]`);i.selected=!1;const n=this.badges.querySelector(`[data-tobago-value="${s}"]`),o=n.previousElementSibling,r="SPAN"===(null===(t=n.nextElementSibling)||void 0===t?void 0:t.tagName)?n.nextElementSibling:null;o?o.querySelector("button.btn.badge").focus():r?r.querySelector("button.btn.ba [...]
 //# sourceMappingURL=tobago.js.map
diff --git a/tobago-theme/tobago-theme-standard/src/main/js/tobago.js.map b/tobago-theme/tobago-theme-standard/src/main/js/tobago.js.map
index 10c7a8020c..43af6bb8be 100644
--- a/tobago-theme/tobago-theme-standard/src/main/js/tobago.js.map
+++ b/tobago-theme/tobago-theme-standard/src/main/js/tobago.js.map
@@ -1 +1 @@
-{"version":3,"file":"tobago.js","sources":["../ts/tobago-css.ts","../ts/tobago-key.ts","../ts/tobago-bar.ts","../ts/tobago-date.ts","../ts/tobago-menu-store.ts","../../../../node_modules/@popperjs/core/lib/enums.js","../../../../node_modules/@popperjs/core/lib/dom-utils/getNodeName.js","../../../../node_modules/@popperjs/core/lib/dom-utils/getWindow.js","../../../../node_modules/@popperjs/core/lib/dom-utils/instanceOf.js","../../../../node_modules/@popperjs/core/lib/modifiers/applyStyles [...]
\ No newline at end of file
+{"version":3,"file":"tobago.js","sources":["../ts/tobago-css.ts","../ts/tobago-key.ts","../ts/tobago-bar.ts","../ts/tobago-date.ts","../ts/tobago-menu-store.ts","../../../../node_modules/@popperjs/core/lib/enums.js","../../../../node_modules/@popperjs/core/lib/dom-utils/getNodeName.js","../../../../node_modules/@popperjs/core/lib/dom-utils/getWindow.js","../../../../node_modules/@popperjs/core/lib/dom-utils/instanceOf.js","../../../../node_modules/@popperjs/core/lib/modifiers/applyStyles [...]
\ No newline at end of file
diff --git a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-init.ts b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-init.ts
index 3607fa9419..117c6d6944 100644
--- a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-init.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-init.ts
@@ -31,6 +31,7 @@ import "./tobago-in";
 import "./tobago-messages";
 import "./tobago-overlay";
 import "./tobago-page";
+import "./tobago-paginator-list";
 import "./tobago-paginator-page";
 import "./tobago-paginator-row";
 import "./tobago-panel";
diff --git a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-paginator-list.ts b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-paginator-list.ts
new file mode 100644
index 0000000000..440a6fdbbb
--- /dev/null
+++ b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-paginator-list.ts
@@ -0,0 +1,32 @@
+/*
+ * 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 {TobagoPaginator} from "./tobago-paginator";
+import {Key} from "./tobago-key";
+
+export class TobagoPaginatorList extends TobagoPaginator {
+
+  constructor() {
+    super();
+  }
+}
+
+document.addEventListener("tobago.init", function (event: Event): void {
+  if (window.customElements.get("tobago-paginator-list") == null) {
+    window.customElements.define("tobago-paginator-list", TobagoPaginatorList);
+  }
+});
diff --git a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-paginator.ts b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-paginator.ts
index 893a1a1a89..253bc48b94 100644
--- a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-paginator.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-paginator.ts
@@ -32,7 +32,7 @@ export class TobagoPaginator extends HTMLElement {
   }
 
   get input(): HTMLInputElement {
-    return this.querySelector(".page-link input");
+    return this.querySelector("input");
   }
 
   get sheet(): HTMLElement {
@@ -45,29 +45,42 @@ export class TobagoPaginator extends HTMLElement {
     }
   }
 
+  get buttons(): NodeListOf<Element> {
+    return this.querySelectorAll("li.page-item:not(.disabled)>button");
+  }
+
+  get action(): HTMLInputElement {
+    return this.querySelector("input[type=hidden]");
+  }
+
   connectedCallback(): void {
-    console.warn("register -------------- click on paging", this.tagName);
-    this.text.addEventListener("click", this.clickOnPaging.bind(this));
+    const text = this.text;
+    if (text) {
+      text.addEventListener("click", this.clickOnPaging.bind(this));
+    }
 
     const input = this.input;
-    console.warn("register -------------- blur on paging", this.tagName);
-    input.addEventListener("blur", this.blurPaging.bind(this));
-
-    console.warn("register -------------- keydown on paging", this.tagName);
-    input.addEventListener("keydown", function (event: KeyboardEvent): void {
-      if (event.key === Key.ENTER) {
-        event.stopPropagation();
-        event.preventDefault();
-        event.currentTarget.dispatchEvent(new Event("blur"));
-      }
-    }.bind(this));
+    if (input) {
+      input.addEventListener("blur", this.blurPaging.bind(this));
+      input.addEventListener("keydown", function (event: KeyboardEvent): void {
+        if (event.key === Key.ENTER) {
+          event.stopPropagation();
+          event.preventDefault();
+          event.currentTarget.dispatchEvent(new Event("blur"));
+        }
+      }.bind(this));
+    }
+
+    const buttons = this.buttons;
+    if (buttons) {
+      buttons.forEach(item => item.addEventListener("click", this.clickOnButton.bind(this)));
+    }
   }
 
   clickOnPaging(event: MouseEvent): void {
     const output: HTMLElement = this.output;
     output.style.display = "none";
 
-    console.warn("execute  -------------- click on paging", this.tagName);
     const input: HTMLInputElement = this.input;
     input.style.display = "initial";
     input.focus();
@@ -75,15 +88,16 @@ export class TobagoPaginator extends HTMLElement {
   }
 
   blurPaging(event: FocusEvent): void {
-    console.warn("execute  -------------- blur on paging", this.tagName);
     const input = event.currentTarget as HTMLInputElement;
     const output: HTMLElement = this.output;
     const number = Number.parseInt(input.value); // sanitizing
     if (number > 0 && number.toString() !== output.innerHTML) {
       console.debug("Reloading sheet '%s' old value='%s' new value='%s'", this.id, output.innerHTML, number);
       output.innerHTML = number.toString();
+      const action = this.tagName == "TOBAGO-PAGINATOR-ROW" ? "toRow" : "toPage";
+      input.value = `{"action":"${action}","target":${number}}`;
       faces.ajax.request(
-          input.id,
+          this.id,
           null,
           {
             params: {
@@ -94,9 +108,25 @@ export class TobagoPaginator extends HTMLElement {
           });
     } else {
       console.info("no update needed");
-      input.value = output.innerHTML;
-      input.style.display = "none";
-      output.style.display = "initial";
     }
+    input.style.display = "none";
+    output.style.display = "initial";
+  }
+
+  clickOnButton(event: MouseEvent): void {
+    const button = event.currentTarget as HTMLButtonElement;
+    const input = this.input;
+    input.value = button.dataset.tobagoAction;
+
+    faces.ajax.request(
+        this.id,
+        null,
+        {
+          params: {
+            "javax.faces.behavior.event": "reload"
+          },
+          execute: this.sheet.id,
+          render: this.sheet.id
+        });
   }
 }
diff --git a/tobago-tool/tobago-tool-test/src/main/resources/META-INF/resources/tobago/test/tobago-test-tool.js b/tobago-tool/tobago-tool-test/src/main/resources/META-INF/resources/tobago/test/tobago-test-tool.js
index 9aeeb98e64..4acb96c4b1 100644
--- a/tobago-tool/tobago-tool-test/src/main/resources/META-INF/resources/tobago/test/tobago-test-tool.js
+++ b/tobago-tool/tobago-tool-test/src/main/resources/META-INF/resources/tobago/test/tobago-test-tool.js
@@ -65,6 +65,7 @@ class JasmineTestTool {
   done;
   timeout;
   waitTimestamp;
+  message;
 
   /**
    * @param done function from Jasmine; must called if all Steps done or timeout
@@ -75,6 +76,13 @@ class JasmineTestTool {
     this.timeout = timeout ? timeout : 20000;
   }
 
+  /**
+   * Set hard to fail!
+   */
+  fail(message) {
+    this.message = message;
+  }
+
   /**
    * Setup a test.
    * @param startConditionFn the desired start condition
@@ -195,10 +203,21 @@ class JasmineTestTool {
     const timeout = this.timeout;
     let lastStepExecution;
 
+    if (this.message != null) {
+      fail(this.message);
+      done();
+      return;
+    }
+
     console.debug("[JasmineTestTool] start");
     registerAjaxReadyStateListener();
     resetTimeout();
-    cycle();
+    try {
+      cycle();
+    } catch (e) {
+      fail(e);
+      done();
+    }
 
     function cycle() {
       const nextStep = getNextStep();