Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sheet): readonlyRows attribute #5752

Merged
merged 1 commit into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ public enum Attributes {
preferredWidth,
preformated,
readonly,
readonlyRows,
reference,
rel,
relative,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@
import javax.faces.component.ContextCallback;
import javax.faces.component.UIComponent;
import javax.faces.component.UINamingContainer;
import javax.faces.component.visit.VisitCallback;
import javax.faces.component.visit.VisitContext;
import javax.faces.context.FacesContext;
import javax.faces.model.DataModel;
import javax.swing.tree.TreeNode;
Expand Down Expand Up @@ -289,56 +287,4 @@ public List<Integer> getRowIndicesOfChildren() {
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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,15 @@
import javax.el.ValueExpression;
import javax.faces.component.UIColumn;
import javax.faces.component.UIComponent;
import javax.faces.component.UINamingContainer;
import javax.faces.component.behavior.AjaxBehavior;
import javax.faces.component.behavior.ClientBehavior;
import javax.faces.component.behavior.ClientBehaviorContext;
import javax.faces.component.behavior.ClientBehaviorHolder;
import javax.faces.component.visit.VisitCallback;
import javax.faces.component.visit.VisitContext;
import javax.faces.component.visit.VisitHint;
import javax.faces.component.visit.VisitResult;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ComponentSystemEvent;
Expand All @@ -62,8 +68,12 @@
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}
Expand Down Expand Up @@ -326,9 +336,19 @@ public int getFirstRowIndexOfLastPage() {
}
}

@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) {
Expand All @@ -339,6 +359,112 @@ public void processUpdates(final FacesContext context) {
}
}

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];
Expand Down Expand Up @@ -411,6 +537,132 @@ public void broadcast(final FacesEvent facesEvent) throws AbortProcessingExcepti
}
}

@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();
Expand Down Expand Up @@ -650,4 +902,6 @@ public void setHeaderGrid(final Grid headerGrid) {
public abstract Integer getLazyRows();

public abstract PaginatorMode getPaginator();

public abstract boolean isReadonlyRows();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 javax.faces.event.PhaseId#APPLY_REQUEST_VALUES} and
* {@link javax.faces.event.PhaseId#PROCESS_VALIDATIONS} and
* {@link javax.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 javax.faces.component.EditableValueHolder} components in the sheet rows.
*
*/
@TagAttribute
@UIComponentTagAttribute(type = "boolean", defaultValue = "false")
void setReadonlyRows(String readonly);

}
Loading