
org.primefaces.component.patch.UIDataPatch Maven / Gradle / Ivy
/*
* The MIT License
*
* Copyright (c) 2009-2025 PrimeTek Informatics
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.primefaces.component.patch;
import org.primefaces.util.ComponentTraversalUtils;
import org.primefaces.util.ComponentUtils;
import org.primefaces.util.SharedStringBuilder;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.application.Application;
import javax.faces.application.FacesMessage;
import javax.faces.application.StateManager;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIColumn;
import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.component.UIData;
import javax.faces.component.UIForm;
import javax.faces.component.UINamingContainer;
import javax.faces.component.UIViewRoot;
import javax.faces.component.UniqueIdVendor;
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.FacesEvent;
import javax.faces.event.FacesListener;
import javax.faces.event.PhaseId;
import javax.faces.event.PostValidateEvent;
import javax.faces.event.PreValidateEvent;
import javax.faces.model.DataModel;
import javax.faces.render.Renderer;
// ------------------------------------------------------------- Private Classes
// Private class to represent saved state information
/**
* {@link UIDataPatch} is largely a copy of Mojarra 2.3.9's {@code UIData} and few bits from MyFaces
* The idea is to make a clear distinction between what belongs to the JSF implementation
* and PrimeFaces. The code replicates exactly the code of the original class with a few exceptions:
*
* - All members become protected, so that it's possible for PrimeFaces to override methods if necessary.
* - Few methods are either copied or become abstract as they are tightly coupled with Mojarra.
* - {@link UIDataPatch#getClientId(FacesContext)} and {@link UIDataPatch#getContainerClientId(FacesContext)} copied from MyFaces (see MYFACES-2744)
* - Support of MyFaces view pooling in {@link UIDataPatch#saveState(FacesContext)}
*
*
* UIData is a {@link UIComponent} that
* supports data binding to a collection of data objects represented by
* a {@link DataModel} instance, which is the current value of this
* component itself (typically established via a {@link
* ValueExpression}). During iterative processing over the rows of data
* in the data model, the object for the current row is exposed as a
* request attribute under the key specified by the var
* property.
Only children of type {@link UIColumn} should
* be processed by renderers associated with this component.
* By default, the rendererType
property is set to
* javax.faces.Table
. This value can be changed by calling
* the setRendererType()
method.
*/
public abstract class UIDataPatch extends UIData {
protected static final String SB_ID = UIDataPatch.class.getName() + "#id";
// ------------------------------------------------------ Instance Variables
/**
* Properties that are tracked by state saving.
*/
protected enum PropertyKeys {
/**
* The zero-relative index of the current row number, or -1 for no
* current row association.
*/
rowIndex,
/**
* This map contains SavedState
instances for each
* descendant component, keyed by the client identifier of the descendant.
* Because descendant client identifiers will contain the
* rowIndex
value of the parent, per-row state information is
* actually preserved.
*/
saved,
/**
* The request scope attribute under which the data object for the
* current row will be exposed when iterating.
*/
var,
}
/**
* During iteration through the rows of this table, This ivar is used to
* store the previous "var" value for this instance. When the row iteration
* is complete, this value is restored to the request map.
*/
protected Object oldVar;
/**
*
Holds the base client ID that will be used to generate per-row
* client IDs (this will be null if this UIData is nested within another).
*
* This is not part of the component state.
*/
protected String baseClientId = null;
/**
* Flag indicating whether or not this UIData instance is nested
* within another UIData instance
*
* This is not part of the component state.
*/
protected Boolean isNested = null;
protected Map _rowDeltaStates = new HashMap<>();
protected Map _rowTransientStates = new HashMap<>();
protected Object _initialDescendantFullComponentState = null;
// -------------------------------------------------------------- Properties
/**
* Return the zero-relative index of the currently selected row. If we
* are not currently positioned on a row, return -1. This property is
* not enabled for value binding expressions.
*
* @return the row index.
*
* @throws FacesException if an error occurs getting the row index
*/
public int getRowIndex() {
return (Integer) getStateHelper().eval(PropertyKeys.rowIndex, -1);
}
/**
* Set the zero
* relative index of the current row, or -1 to indicate that no row
* is currently selected, by implementing the following algorithm.
* It is possible to set the row index at a value for which the
* underlying data collection does not contain any row data.
* Therefore, callers may use the isRowAvailable()
* method to detect whether row data will be available for use by
* the getRowData()
method.
* To support transient state among
* descendents, please consult the specification for {@link
* #setRowStatePreserved}, which details the requirements
* for setRowIndex()
when the
* rowStatePreserved
JavaBeans property is set
* to true
.
*
* - Save current state information for all descendant components (as
* described below).
*
- Store the new row index, and pass it on to the {@link DataModel}
* associated with this {@link UIData} instance.
* - If the new
rowIndex
value is -1:
*
* - If the
var
property is not null,
* remove the corresponding request scope attribute (if any).
* - Reset the state information for all descendant components
* (as described below).
*
* - If the new
rowIndex
value is not -1:
*
* - If the
var
property is not null, call
* getRowData()
and expose the resulting data object
* as a request scope attribute whose key is the var
* property value.
* - Reset the state information for all descendant components
* (as described below).
*
*
*
* To save current state information for all descendant components,
* {@link UIData} must maintain per-row information for each descendant
* as follows:
*
* - If the descendant is an instance of
EditableValueHolder
, save
* the state of its localValue
property.
* - If the descendant is an instance of
EditableValueHolder
,
* save the state of the localValueSet
property.
* - If the descendant is an instance of
EditableValueHolder
, save
* the state of the valid
property.
* - If the descendant is an instance of
EditableValueHolder
,
* save the state of the submittedValue
property.
*
*
* To restore current state information for all descendant components,
* {@link UIData} must reference its previously stored information for the
* current rowIndex
and call setters for each descendant
* as follows:
*
* - If the descendant is an instance of
EditableValueHolder
,
* restore the value
property.
* - If the descendant is an instance of
EditableValueHolder
,
* restore the state of the localValueSet
property.
* - If the descendant is an instance of
EditableValueHolder
,
* restore the state of the valid
property.
* - If the descendant is an instance of
EditableValueHolder
,
* restore the state of the submittedValue
property.
*
*
* @param rowIndex The new row index value, or -1 for no associated row
*
* @throws FacesException if an error occurs setting the row index
* @throws IllegalArgumentException if rowIndex
* is less than -1
*/
public void setRowIndex(int rowIndex)
{
if (isRowStatePreserved())
{
setRowIndexRowStatePreserved(rowIndex);
}
else
{
setRowIndexWithoutRowStatePreserved(rowIndex);
}
}
protected void setRowIndexWithoutRowStatePreserved(int rowIndex) {
// Save current state for the previous row index
saveDescendantState();
// Update to the new row index
//this.rowIndex = rowIndex;
getStateHelper().put(PropertyKeys.rowIndex, rowIndex);
DataModel localModel = getDataModel();
localModel.setRowIndex(rowIndex);
// if rowIndex is -1, clear the cache
if (rowIndex == -1) {
setDataModel(null);
}
// Clear or expose the current row data as a request scope attribute
String var = (String) getStateHelper().get(PropertyKeys.var);
if (var != null) {
Map requestMap =
getFacesContext().getExternalContext().getRequestMap();
if (rowIndex == -1) {
oldVar = requestMap.remove(var);
} else if (isRowAvailable()) {
requestMap.put(var, getRowData());
} else {
requestMap.remove(var);
if (null != oldVar) {
requestMap.put(var, oldVar);
oldVar = null;
}
}
}
// Reset current state information for the new row index
restoreDescendantState();
}
protected void setRowIndexRowStatePreserved(int rowIndex)
{
if (rowIndex < -1)
{
throw new IllegalArgumentException("rowIndex is less than -1");
}
if (getRowIndex() == rowIndex)
{
return;
}
FacesContext facesContext = getFacesContext();
if (_initialDescendantFullComponentState != null)
{
//Just save the row
Map sm = saveFullDescendantComponentStates(facesContext, null, getChildren().iterator(), false);
if (sm != null && !sm.isEmpty())
{
_rowDeltaStates.put(getContainerClientId(facesContext), sm);
}
if (getRowIndex() != -1)
{
_rowTransientStates.put(getContainerClientId(facesContext), saveTransientDescendantComponentStates(facesContext, null, getChildren().iterator(), false));
}
}
// Update to the new row index
//this.rowIndex = rowIndex;
getStateHelper().put(PropertyKeys.rowIndex, rowIndex);
DataModel localModel = getDataModel();
localModel.setRowIndex(rowIndex);
// if rowIndex is -1, clear the cache
if (rowIndex == -1) {
setDataModel(null);
}
// Clear or expose the current row data as a request scope attribute
String var = (String) getStateHelper().get(PropertyKeys.var);
if (var != null) {
Map requestMap =
getFacesContext().getExternalContext().getRequestMap();
if (rowIndex == -1) {
oldVar = requestMap.remove(var);
} else if (isRowAvailable()) {
requestMap.put(var, getRowData());
} else {
requestMap.remove(var);
if (null != oldVar) {
requestMap.put(var, oldVar);
oldVar = null;
}
}
}
if (_initialDescendantFullComponentState != null)
{
Object rowState = _rowDeltaStates.get(getContainerClientId(facesContext));
if (rowState == null)
{
//Restore as original
restoreFullDescendantComponentStates(facesContext, getChildren().iterator(), _initialDescendantFullComponentState, false);
}
else
{
//Restore first original and then delta
restoreFullDescendantComponentDeltaStates(facesContext, getChildren().iterator(), rowState, _initialDescendantFullComponentState, false);
}
if (getRowIndex() == -1)
{
restoreTransientDescendantComponentStates(facesContext, getChildren().iterator(), null, false);
}
else
{
rowState = _rowTransientStates.get(getContainerClientId(facesContext));
if (rowState == null)
{
restoreTransientDescendantComponentStates(facesContext, getChildren().iterator(), null, false);
}
else
{
restoreTransientDescendantComponentStates(facesContext, getChildren().iterator(), (Map) rowState, false);
}
}
}
}
/**
* Return the request-scope attribute under which the data object for the
* current row will be exposed when iterating. This property is
* not enabled for value binding expressions.
*
* @return he request-scope attribute.
*/
public String getVar() {
return (String) getStateHelper().get(PropertyKeys.var);
}
/**
* Set the request-scope attribute under which the data object for the
* current row wil be exposed when iterating.
*
* @param var The new request-scope attribute name
*/
public void setVar(String var) {
getStateHelper().put(PropertyKeys.var, var);
}
// ----------------------------------------------------- UIComponent Methods
/**
* Bypass Mojarra {@link UIData#getClientId(FacesContext)} impl
* see MYFACES-2744 (same as {@link UIComponentBase#getClientId(FacesContext)}
*/
@Override
public String getClientId(FacesContext context) {
if (baseClientId != null) {
return baseClientId;
}
String id = getId();
if (id == null) {
UniqueIdVendor parentUniqueIdVendor = ComponentTraversalUtils.closestUniqueIdVendor(this);
if (parentUniqueIdVendor == null) {
UIViewRoot viewRoot = context.getViewRoot();
if (viewRoot != null) {
id = viewRoot.createUniqueId();
}
else {
throw new FacesException("Cannot create clientId for " + getClass().getCanonicalName());
}
}
else {
id = parentUniqueIdVendor.createUniqueId(context, null);
}
setId(id);
}
UIComponent namingContainer = ComponentTraversalUtils.closestNamingContainer(this);
if (namingContainer != null) {
String containerClientId = namingContainer.getContainerClientId(context);
if (containerClientId != null) {
StringBuilder sb = SharedStringBuilder.get(context, SB_ID, containerClientId.length() + 10);
baseClientId = sb.append(containerClientId).append(UINamingContainer.getSeparatorChar(context)).append(id).toString();
}
else {
baseClientId = id;
}
}
else {
baseClientId = id;
}
Renderer renderer = getRenderer(context);
if (renderer != null) {
baseClientId = renderer.convertClientId(context, baseClientId);
}
return baseClientId;
}
/**
* From MyFaces, see MYFACES-2744
*/
@Override
public String getContainerClientId(FacesContext context) {
//MYFACES-2744 UIData.getClientId() should not append rowIndex, instead use UIData.getContainerClientId()
String clientId = getClientId(context);
int rowIndex = getRowIndex();
if (rowIndex == -1) {
return clientId;
}
StringBuilder sb = SharedStringBuilder.get(context, SB_ID, clientId.length() + 4);
return sb.append(clientId).append(context.getNamingContainerSeparatorChar()).append(rowIndex).toString();
}
@Override
public void setId(String id) {
super.setId(id);
//clear
baseClientId = null;
}
/**
* Override the default {@link UIComponentBase#queueEvent} processing to
* wrap any queued events in a wrapper so that we can reset the current row
* index in broadcast()
.
*
* @param event {@link FacesEvent} to be queued
*
* @throws IllegalStateException if this component is not a descendant of a
* {@link UIViewRoot}
* @throws NullPointerException if event
is null
*/
@Override
public void queueEvent(FacesEvent event) {
super.queueEvent(new WrapperEvent(this, event, getRowIndex()));
}
/**
* Override the default {@link UIComponentBase#broadcast} processing to
* unwrap any wrapped {@link FacesEvent} and reset the current row index,
* before the event is actually broadcast. For events that we did not wrap
* (in queueEvent()
), default processing will occur.
*
* @param event The {@link FacesEvent} to be broadcast
*
* @throws AbortProcessingException Signal the JavaServer Faces
* implementation that no further
* processing on the current event should
* be performed
* @throws IllegalArgumentException if the implementation class of this
* {@link FacesEvent} is not supported by
* this component
* @throws NullPointerException if event
is null
*/
@Override
public void broadcast(FacesEvent event)
throws AbortProcessingException {
if (!(event instanceof WrapperEvent)) {
super.broadcast(event);
return;
}
FacesContext context = event.getFacesContext();
// Set up the correct context and fire our wrapped event
WrapperEvent revent = (WrapperEvent) event;
if (isNestedWithinIterator()) {
setDataModel(null);
}
int oldRowIndex = getRowIndex();
setRowIndex(revent.getRowIndex());
FacesEvent rowEvent = revent.getFacesEvent();
UIComponent source = rowEvent.getComponent();
UIComponent compositeParent = null;
try {
if (!UIComponent.isCompositeComponent(source)) {
compositeParent = UIComponent.getCompositeComponentParent(source);
}
if (compositeParent != null) {
compositeParent.pushComponentToEL(context, null);
}
source.pushComponentToEL(context, null);
source.broadcast(rowEvent);
} finally {
source.popComponentFromEL(context);
if (compositeParent != null) {
compositeParent.popComponentFromEL(context);
}
}
setRowIndex(oldRowIndex);
}
/**
* In addition to the default behavior, ensure that any saved per-row
* state for our child input components is discarded unless it is needed to
* rerender the current page with errors.
*
* @param context FacesContext for the current request
*
* @throws IOException if an input/output error occurs while
* rendering
* @throws NullPointerException if context
is null
*/
@Override
public void encodeBegin(FacesContext context) throws IOException {
preEncode(context);
super.encodeBegin(context);
}
/**
*
Override the default {@link UIComponentBase#processDecodes} processing
* to perform the following steps.
- If the
rendered
* property of this {@link UIComponent} is false
, skip further
* processing. - Set the current
rowIndex
to -1.
* - Call the
processDecodes()
method of all facets of this
* {@link UIData}, in the order determined by a call to
* getFacets().keySet().iterator()
. - Call the
*
processDecodes()
method of all facets of the {@link
* UIColumn} children of this {@link UIData}. - Iterate over the set
* of rows that were included when this component was rendered (i.e. those
* defined by the
first
and rows
properties),
* performing the following processing for each row: - Set the
* current
rowIndex
to the appropriate value for this row.
* - If
isRowAvailable()
returns true
, iterate
* over the children components of each {@link UIColumn} child of this
* {@link UIData} component, calling the processDecodes()
* method for each such child.
- Set the current
*
rowIndex
to -1. - Call the
decode()
* method of this component. - If a
RuntimeException
is
* thrown during decode processing, call {@link FacesContext#renderResponse}
* and re-throw the exception.
*
* @param context {@link FacesContext} for the current request
*
* @throws NullPointerException if context
is null
*/
@Override
public void processDecodes(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
if (!isRendered()) {
return;
}
pushComponentToEL(context, this);
preDecode(context);
iterate(context, PhaseId.APPLY_REQUEST_VALUES);
decode(context);
popComponentFromEL(context);
}
/**
* Override the default {@link UIComponentBase#processValidators}
* processing to perform the following steps.
- If the
*
rendered
property of this {@link UIComponent} is
* false
, skip further processing. - Set the current
*
rowIndex
to -1. - Call the
processValidators()
* method of all facets of this {@link UIData}, in the order determined by a
* call to getFacets().keySet().iterator()
. - Call the
*
processValidators()
method of all facets of the {@link
* UIColumn} children of this {@link UIData}. - Iterate over the set
* of rows that were included when this component was rendered (i.e. those
* defined by the
first
and rows
properties),
* performing the following processing for each row: - Set the
* current
rowIndex
to the appropriate value for this row.
* - If
isRowAvailable()
returns true
, iterate
* over the children components of each {@link UIColumn} child of this
* {@link UIData} component, calling the processValidators()
* method for each such child.
- Set the current
*
rowIndex
to -1.
*
* @param context {@link FacesContext} for the current request
* @throws NullPointerException if context
is null
* @see javax.faces.event.PreValidateEvent
* @see javax.faces.event.PostValidateEvent
*/
@Override
public void processValidators(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
if (!isRendered()) {
return;
}
pushComponentToEL(context, this);
Application app = context.getApplication();
app.publishEvent(context, PreValidateEvent.class, this);
preValidate(context);
iterate(context, PhaseId.PROCESS_VALIDATIONS);
app.publishEvent(context, PostValidateEvent.class, this);
popComponentFromEL(context);
}
/**
* Override the default {@link UIComponentBase#processUpdates}
* processing to perform the following steps.
*
* - If the
rendered
property of this {@link UIComponent}
* is false
, skip further processing.
* - Set the current
rowIndex
to -1.
* - Call the
processUpdates()
method of all facets
* of this {@link UIData}, in the order determined
* by a call to getFacets().keySet().iterator()
.
* - Call the
processUpdates()
method of all facets
* of the {@link UIColumn} children of this {@link UIData}.
* - Iterate over the set of rows that were included when this
* component was rendered (i.e. those defined by the
first
* and rows
properties), performing the following
* processing for each row:
*
* - Set the current
rowIndex
to the appropriate
* value for this row.
* - If
isRowAvailable()
returns true
,
* iterate over the children components of each {@link UIColumn}
* child of this {@link UIData} component, calling the
* processUpdates()
method for each such child.
*
* - Set the current
rowIndex
to -1.
*
*
* @param context {@link FacesContext} for the current request
*
* @throws NullPointerException if context
is null
*/
@Override
public void processUpdates(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
if (!isRendered()) {
return;
}
pushComponentToEL(context, this);
preUpdate(context);
iterate(context, PhaseId.UPDATE_MODEL_VALUES);
popComponentFromEL(context);
// This is not a EditableValueHolder, so no further processing is required
}
/**
* Override the behavior
* in {@link UIComponent#visitTree} to handle iteration
* correctly.
*
*
* If the {@link UIComponent#isVisitable} method of this instance
* returns false
, take no action and return.
* Call {@link UIComponent#pushComponentToEL} and
* invoke the visit callback on this UIData
instance as
* described in {@link UIComponent#visitTree}. Let the result of
* the invoctaion be visitResult. If visitResult
* is {@link VisitResult#COMPLETE}, take no further action and
* return true
. Otherwise, determine if we need to
* visit our children. The default implementation calls {@link
* VisitContext#getSubtreeIdsToVisit} passing this
as
* the argument. If the result of that call is non-empty, let
* doVisitChildren be true
. If
* doVisitChildren is true
and
* visitResult is {@link VisitResult#ACCEPT}, take the
* following action.
*
* If this component has facets, call {@link
* UIComponent#getFacets} on this instance and invoke the
* values()
method. For each
* UIComponent
in the returned Map
,
* call {@link UIComponent#visitTree}.
* -
*
*
If this component has children, for each
* UIColumn
child:
*
* Call {@link VisitContext#invokeVisitCallback} on that
UIColumn
instance.
* If such a call returns true
, terminate visiting and
return true
from this method.
*
* If the child UIColumn
has facets, call
* {@link UIComponent#visitTree} on each one.
*
* Take no action on non-UIColumn
children.
*
*
*
*
* -
*
*
*
Save aside the result of a call to {@link
* #getRowIndex}.
* For each child component of this UIData
that is
* also an instance of {@link UIColumn},
*
* Iterate over the rows.
*
*
* Let rowsToProcess be the return from {@link
* #getRows}.
* Let rowIndex be the return from {@link
* #getFirst} - 1.
* While the number of rows processed is less than
* rowsToProcess, take the following actions.
* Call {@link #setRowIndex}, passing the current row index.
* If {@link #isRowAvailable} returns false
, take no
* further action and return false
.
*
* Call {@link
* UIComponent#visitTree} on each of the children of this
* UIColumn
instance.
*
*
*
*
* Call {@link #popComponentFromEL} and restore the saved row
* index with a call to {@link #setRowIndex}.
* Return false
to allow the visiting to
* continue.
*
*
* @param context the VisitContext
that provides
* context for performing the visit.
*
* @param callback the callback to be invoked for each node
* encountered in the visit.
* @throws NullPointerException if any of the parameters are
* null
.
*
*/
@Override
public boolean visitTree(VisitContext context,
VisitCallback callback) {
// First check to see whether we are visitable. If not
// short-circuit out of this subtree, though allow the
// visit to proceed through to other subtrees.
if (!isVisitable(context))
return false;
FacesContext facesContext = context.getFacesContext();
// NOTE: that the visitRows local will be obsolete once the
// appropriate visit hints have been added to the API
boolean visitRows = requiresRowIteration(context);
// Clear out the row index is one is set so that
// we start from a clean slate.
int oldRowIndex = -1;
if (visitRows) {
oldRowIndex = getRowIndex();
setRowIndex(-1);
}
// Push ourselves to EL
pushComponentToEL(facesContext, null);
try {
// Visit ourselves. Note that we delegate to the
// VisitContext to actually perform the visit.
VisitResult result = context.invokeVisitCallback(this, callback);
// If the visit is complete, short-circuit out and end the visit
if (result == VisitResult.COMPLETE)
return true;
// Visit children, short-circuiting as necessary
// NOTE: that the visitRows parameter will be obsolete once the
// appropriate visit hints have been added to the API
if ((result == VisitResult.ACCEPT) && doVisitChildren(context, visitRows)) {
// First visit facets
// NOTE: that the visitRows parameter will be obsolete once the
// appropriate visit hints have been added to the API
if (visitFacets(context, callback, visitRows))
return true;
// Next column facets
// NOTE: that the visitRows parameter will be obsolete once the
// appropriate visit hints have been added to the API
if (visitColumnsAndColumnFacets(context, callback, visitRows))
return true;
// And finally, visit rows
// NOTE: that the visitRows parameter will be obsolete once the
// appropriate visit hints have been added to the API
if (visitRows(context, callback, visitRows))
return true;
}
}
finally {
// Clean up - pop EL and restore old row index
popComponentFromEL(facesContext);
if (visitRows) {
setRowIndex(oldRowIndex);
}
}
// Return false to allow the visit to continue
return false;
}
/**
* Override the base class method to
* take special action if the method is being invoked when {@link
* StateManager#IS_BUILDING_INITIAL_STATE} is true
* and the rowStatePreserved
* JavaBeans property for this instance is true
.
*
* The additional action taken is to
* traverse the descendents and save their state without regard to
* any particular row value.
*
* @since 2.1
*/
@Override
public void markInitialState()
{
if (isRowStatePreserved())
{
if (getFacesContext().getAttributes().containsKey(StateManager.IS_BUILDING_INITIAL_STATE))
{
_initialDescendantFullComponentState = saveDescendantInitialComponentStates(getFacesContext(), getChildren().iterator(), false);
}
}
super.markInitialState();
}
protected void restoreFullDescendantComponentStates(FacesContext facesContext,
Iterator childIterator, Object state,
boolean restoreChildFacets)
{
Iterator extends Object[]> descendantStateIterator = null;
while (childIterator.hasNext())
{
if (descendantStateIterator == null && state != null)
{
descendantStateIterator = ((Collection extends Object[]>) state)
.iterator();
}
UIComponent component = childIterator.next();
// reset the client id (see spec 3.1.6)
component.setId(component.getId());
if (!component.isTransient())
{
Object childState = null;
Object descendantState = null;
if (descendantStateIterator != null
&& descendantStateIterator.hasNext())
{
Object[] object = descendantStateIterator.next();
childState = object[0];
descendantState = object[1];
}
component.clearInitialState();
component.restoreState(facesContext, childState);
component.markInitialState();
Iterator childsIterator;
if (restoreChildFacets)
{
childsIterator = component.getFacetsAndChildren();
}
else
{
childsIterator = component.getChildren().iterator();
}
restoreFullDescendantComponentStates(facesContext, childsIterator,
descendantState, true);
}
}
}
protected Collection