You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by bo...@apache.org on 2024/12/02 18:42:31 UTC
(myfaces-tobago) branch main updated: feat(sheet): readonlyRows attribute (#5722)
This is an automated email from the ASF dual-hosted git repository.
bommel 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 1c4bbca32d feat(sheet): readonlyRows attribute (#5722)
1c4bbca32d is described below
commit 1c4bbca32d91bc6adb11beb494ac9e21947c61e7
Author: Bernd Bohmann <bo...@apache.org>
AuthorDate: Mon Dec 2 19:42:25 2024 +0100
feat(sheet): readonlyRows attribute (#5722)
Performance optimization hint used during
ApplyRequestValues, Validation and UpdateModel
on columnChildren.
issue: TOBAGO-2370
---
.../myfaces/tobago/component/Attributes.java | 1 +
.../tobago/internal/component/AbstractUIData.java | 54 -----
.../tobago/internal/component/AbstractUISheet.java | 256 ++++++++++++++++++++-
.../taglib/component/SheetTagDeclaration.java | 17 ++
4 files changed, 273 insertions(+), 55 deletions(-)
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/component/Attributes.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/component/Attributes.java
index 03b89f3a8c..f95f7248b2 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/component/Attributes.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/component/Attributes.java
@@ -341,6 +341,7 @@ public enum Attributes {
preferredWidth,
preformated,
readonly,
+ readonlyRows,
reference,
rel,
relative,
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUIData.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUIData.java
index e4f2ec8338..8e7ae19964 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUIData.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUIData.java
@@ -33,8 +33,6 @@ import jakarta.faces.FacesException;
import jakarta.faces.component.ContextCallback;
import jakarta.faces.component.UIComponent;
import jakarta.faces.component.UINamingContainer;
-import jakarta.faces.component.visit.VisitCallback;
-import jakarta.faces.component.visit.VisitContext;
import jakarta.faces.context.FacesContext;
import jakarta.faces.model.DataModel;
@@ -290,56 +288,4 @@ public abstract class AbstractUIData extends jakarta.faces.component.UIData impl
return null;
}
}
-
- /**
- * This is, because we need to visit the UIRow for each row, which is not done in the base implementation.
- */
- @Override
- public boolean visitTree(final VisitContext context, final VisitCallback callback) {
-
- if (super.visitTree(context, callback)) {
- return true;
- }
-
- // save the current row index
- final int oldRowIndex = getRowIndex();
- // set row index to -1 to process the facets and to get the rowless clientId
- setRowIndex(-1);
- // push the Component to EL
- pushComponentToEL(context.getFacesContext(), this);
-
- try {
- // iterate over the rows
- int rowsToProcess = getRows();
- // if getRows() returns 0, all rows have to be processed
- if (rowsToProcess == 0) {
- rowsToProcess = getRowCount();
- }
- int rowIndex = getFirst();
- for (int rowsProcessed = 0; rowsProcessed < rowsToProcess; rowsProcessed++, rowIndex++) {
- setRowIndex(rowIndex);
- if (!isRowAvailable()) {
- return false;
- }
- // visit the children of every child of the UIData that is an instance of UIColumn
- for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
- final UIComponent child = getChildren().get(i);
- if (child instanceof AbstractUIRow) {
- if (child.visitTree(context, callback)) {
- return true;
- }
-
- }
- }
- }
- } finally {
- // pop the component from EL and restore the old row index
- popComponentFromEL(context.getFacesContext());
- setRowIndex(oldRowIndex);
- }
-
- // Return false to allow the visiting to continue
- return false;
- }
-
}
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 6036c9115a..15893d147c 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
@@ -19,6 +19,12 @@
package org.apache.myfaces.tobago.internal.component;
+import jakarta.faces.component.UINamingContainer;
+import jakarta.faces.component.behavior.ClientBehaviorContext;
+import jakarta.faces.component.visit.VisitCallback;
+import jakarta.faces.component.visit.VisitContext;
+import jakarta.faces.component.visit.VisitHint;
+import jakarta.faces.component.visit.VisitResult;
import org.apache.myfaces.tobago.component.Attributes;
import org.apache.myfaces.tobago.component.Pageable;
import org.apache.myfaces.tobago.component.Visual;
@@ -62,8 +68,12 @@ import jakarta.faces.event.PreRenderComponentEvent;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
+import java.util.function.BiConsumer;
/**
* {@link org.apache.myfaces.tobago.internal.taglib.component.SheetTagDeclaration}
@@ -326,9 +336,19 @@ public abstract class AbstractUISheet extends AbstractUIData
}
}
+ @Override
+ public void processDecodes(FacesContext context) {
+ process(context, isReadonlyRows(), (fc, uic) -> uic.processDecodes(fc));
+ }
+
+ @Override
+ public void processValidators(FacesContext context) {
+ process(context, isReadonlyRows(), (fc, uic) -> uic.processValidators(fc));
+ }
+
@Override
public void processUpdates(final FacesContext context) {
- super.processUpdates(context);
+ process(context, isReadonlyRows(), (fc, uic) -> uic.processUpdates(fc));
final SheetState sheetState = getSheetState(context);
if (sheetState != null) {
@@ -339,6 +359,112 @@ public abstract class AbstractUISheet extends AbstractUIData
}
}
+ private void process(FacesContext context, boolean skipColumnChildren,
+ BiConsumer<FacesContext, UIComponent> consumer) {
+ try {
+ pushComponentToEL(context, this);
+ if (!isRendered()) {
+ return;
+ }
+ setRowIndex(-1);
+ if (this.getFacetCount() > 0) {
+ for (UIComponent facet : getFacets().values()) {
+ consumer.accept(context, facet);
+ }
+ }
+ boolean[] columnRendered = new boolean[getChildCount()];
+ for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
+ UIComponent child = getChildren().get(i);
+ if (child instanceof UIColumn) {
+ try {
+ child.pushComponentToEL(context, child);
+ if (child.isRendered()) {
+ columnRendered[i] = true;
+ } else {
+ continue;
+ }
+ } finally {
+ child.popComponentFromEL(context);
+ }
+ if (child.getFacetCount() > 0) {
+ for (UIComponent facet : child.getFacets().values()) {
+ consumer.accept(context, facet);
+ }
+ }
+ }
+ }
+ if (skipColumnChildren) {
+ // process action source
+ int rowIndex = getRowFromActionSource(context);
+ processRow(context, columnRendered, consumer, rowIndex);
+ } else {
+ processColumnChildren(context, columnRendered, consumer);
+ }
+ setRowIndex(-1);
+ try {
+ decode(context);
+ } catch (RuntimeException e) {
+ context.renderResponse();
+ throw e;
+ }
+ } finally {
+ popComponentFromEL(context);
+ }
+ }
+
+ private void processColumnChildren(FacesContext context, boolean[] childRendered,
+ BiConsumer<FacesContext, UIComponent> consumer) {
+ int first = getFirst();
+ int rows = getRows();
+ int last;
+ if (rows == 0) {
+ last = getRowCount();
+ } else {
+ last = first + rows;
+ }
+ for (int rowIndex = first; last == -1 || rowIndex < last; rowIndex++) {
+ if (processRow(context, childRendered, consumer, rowIndex)) {
+ break;
+ }
+ }
+ }
+
+ private boolean processRow(FacesContext context, boolean[] childRendered,
+ BiConsumer<FacesContext, UIComponent> consumer, int rowIndex) {
+ setRowIndex(rowIndex);
+
+ // scrolled past the last row
+ if (!isRowAvailable()) {
+ return true;
+ }
+ for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
+ if (childRendered[i]) {
+ UIComponent child = getChildren().get(i);
+ if (child instanceof AbstractUIRow) {
+ consumer.accept(context, child);
+ } else {
+ for (int j = 0, columnChildCount = child.getChildCount(); j < columnChildCount; j++) {
+ UIComponent columnChild = child.getChildren().get(j);
+ consumer.accept(context, columnChild);
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private int getRowFromActionSource(FacesContext facesContext) {
+ String clientId = getClientId(facesContext);
+ int clientIdLengthPlusOne = clientId.length() + 1;
+ char separatorChar = UINamingContainer.getSeparatorChar(facesContext);
+ final String sourceId = facesContext
+ .getExternalContext().getRequestParameterMap().get(ClientBehaviorContext.BEHAVIOR_SOURCE_PARAM_NAME);
+ if (sourceId != null && sourceId.startsWith(clientId)) {
+ return getRowIndexFromSubtreeId(sourceId, separatorChar, clientIdLengthPlusOne);
+ }
+ return -1;
+ }
+
@Override
public Object saveState(final FacesContext context) {
final Object[] saveState = new Object[2];
@@ -411,6 +537,132 @@ public abstract class AbstractUISheet extends AbstractUIData
}
}
+ @Override
+ public boolean visitTree(final VisitContext context, final VisitCallback callback) {
+ boolean skipIterationHint = context.getHints().contains(VisitHint.SKIP_ITERATION);
+ if (skipIterationHint) {
+ return super.visitTree(context, callback);
+ }
+ FacesContext facesContext = context.getFacesContext();
+ pushComponentToEL(facesContext, this);
+ if (!isVisitable(context)) {
+ return false;
+ }
+
+ // save the current row index
+ int oldRowIndex = getRowIndex();
+ try {
+ // set row index to -1 to process the facets and to get the rowless clientId
+ setRowIndex(-1);
+ VisitResult visitResult = context.invokeVisitCallback(this, callback);
+ switch (visitResult) {
+ case COMPLETE:
+ //we are done nothing has to be processed anymore
+ return true;
+ case REJECT:
+ return false;
+ default:
+ // accept; determine if we need to visit our children
+ Collection<String> subtreeIdsToVisit = context.getSubtreeIdsToVisit(this);
+ boolean doVisitChildren = subtreeIdsToVisit != null && !subtreeIdsToVisit.isEmpty();
+ if (doVisitChildren) {
+ // visit the facets of the component
+ if (getFacetCount() > 0) {
+ for (UIComponent facet : getFacets().values()) {
+ if (facet.visitTree(context, callback)) {
+ return true;
+ }
+ }
+ }
+ // visit every column directly without visiting its children
+ // (the children of every UIColumn will be visited later for
+ // every row) and also visit the column's facets
+ for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
+ UIComponent child = getChildren().get(i);
+ if (child instanceof UIColumn) {
+ VisitResult columnResult = context.invokeVisitCallback(child, callback);
+ if (columnResult == VisitResult.COMPLETE) {
+ return true;
+ }
+ if (child.getFacetCount() > 0) {
+ for (UIComponent facet : child.getFacets().values()) {
+ if (facet.visitTree(context, callback)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ Set<Integer> rowsToVisit = getRowsToVisit(context);
+ if (rowsToVisit.isEmpty()) {
+ return false;
+ }
+ // iterate over the rows to visit
+ for (Integer rowIndex : rowsToVisit) {
+ setRowIndex(rowIndex);
+ if (!isRowAvailable()) {
+ return false;
+ }
+ // visit the children of every child of the UIData that is an instance of UIColumn
+ for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
+ final UIComponent child = getChildren().get(i);
+ if (child instanceof UIColumn) {
+ if (child instanceof AbstractUIRow) {
+ if (child.visitTree(context, callback)) {
+ return true;
+ }
+ } else {
+ for (int j = 0, grandChildCount = child.getChildCount();
+ j < grandChildCount; j++) {
+ UIComponent grandchild = child.getChildren().get(j);
+ if (grandchild.visitTree(context, callback)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ } finally {
+ // pop the component from EL and restore the old row index
+ popComponentFromEL(facesContext);
+ setRowIndex(oldRowIndex);
+ }
+ // Return false to allow the visiting to continue
+ return false;
+ }
+
+ private Set<Integer> getRowsToVisit(final VisitContext context) {
+ Set<Integer> rowsToVisit = new HashSet<>();
+ FacesContext facesContext = context.getFacesContext();
+ String clientId = getClientId(facesContext);
+ int clientIdLengthPlusOne = clientId.length() + 1;
+ char separatorChar = UINamingContainer.getSeparatorChar(facesContext);
+ Collection<String> subtreeIdsToVisit = context.getSubtreeIdsToVisit(this);
+ for (String subtreeId : subtreeIdsToVisit) {
+ int rowIndex = getRowIndexFromSubtreeId(subtreeId, separatorChar, clientIdLengthPlusOne);
+ if (rowIndex != -1) {
+ rowsToVisit.add(rowIndex);
+ }
+ }
+ return rowsToVisit;
+ }
+
+ private int getRowIndexFromSubtreeId(String sourceId, char separatorChar, int clientIdLengthPlusOne) {
+ int index = sourceId.indexOf(separatorChar, clientIdLengthPlusOne);
+ if (index != -1) {
+ String possibleRowIndex = sourceId.substring(clientIdLengthPlusOne, index);
+ try {
+ return Integer.parseInt(possibleRowIndex);
+ } catch (final NumberFormatException e) {
+ // ignore
+ }
+ }
+ return -1;
+ }
+
public void init(final FacesContext facesContext) {
sort(facesContext, null);
layoutHeader();
@@ -650,4 +902,6 @@ public abstract class AbstractUISheet extends AbstractUIData
public abstract Integer getLazyRows();
public abstract PaginatorMode getPaginator();
+
+ public abstract boolean isReadonlyRows();
}
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 9af7a4d4ac..0e8529bc1a 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
@@ -380,4 +380,21 @@ public interface SheetTagDeclaration
},
defaultCode = "org.apache.myfaces.tobago.layout.PaginatorMode.useShowAttributes")
void setPaginator(String paginator);
+
+ /**
+ * Flag indicating that the rows of the sheet are readonly.
+ * The readonly attribute is a performance optimization hint used during
+ * {@link jakarta.faces.event.PhaseId#APPLY_REQUEST_VALUES} and
+ * {@link jakarta.faces.event.PhaseId#PROCESS_VALIDATIONS} and
+ * {@link jakarta.faces.event.PhaseId#UPDATE_MODEL_VALUES}.
+ * When set to true, it signals the rows of the sheet are read-only und
+ * doesn't require updates potentially saving processing time.
+ * This optimization should only be applied when there are no non-readonly
+ * {@link jakarta.faces.component.EditableValueHolder} components in the sheet rows.
+ *
+ */
+ @TagAttribute
+ @UIComponentTagAttribute(type = "boolean", defaultValue = "false")
+ void setReadonlyRows(String readonly);
+
}