com.jsftoolkit.base.DataIterator Maven / Gradle / Ivy
package com.jsftoolkit.base;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.component.ContextCallback;
import javax.faces.component.UIColumn;
import javax.faces.component.UIComponent;
import javax.faces.component.UIData;
import javax.faces.context.FacesContext;
import javax.faces.model.DataModel;
import com.jsftoolkit.utils.Utils;
/**
* General purpose data iterator. Unlike {@link UIData}, {@link DataIterator}
* is not coupled to a particular rendering model. i.e. all child components
* will be processed, not just {@link UIColumn}s. Other than this deviation,
* its behavior should be identical to {@link UIData}s, except when using any
* of the extensions mentioned below.
*
* The "rowId" property allows you to specify the client identity of each data
* item. As long as this value is unique, any actions performed on a particular
* row will always be applied to the proper data item, regardless of changes to
* the underlying {@link DataModel}.
*
* In the event that an element with matching id is no longer in the DataModel,
* the action will simply not occur.
*
* Any facets added to this component will be ignored.
*
* @see #getClientId(FacesContext)
* @author noah
*
*/
public class DataIterator extends DataIteratorBase implements JsfIterator {
private static final String ID_FORMAT = "%s" + SEPARATOR_CHAR + "%s";
private static final java.util.logging.Logger LOG = java.util.logging.Logger
.getLogger(DataIterator.class.getCanonicalName());
/**
* If the attribute "rowId" is set, it will be evaluated for the current row
* to determine the clientId prefix for the current rows children.
*
* If it is not set, the rowIndex is used.
*/
@Override
public String getClientId(FacesContext context) {
Utils.notNull(context, "context");
ValueExpression rowId = getValueExpression(ROW_ID);
if (rowId == null || getRowIndex() < 0) {
return super.getClientId(context);
} else {
String thisId = getContainerId(context);
return String.format(ID_FORMAT, thisId, rowId.getValue(context
.getELContext()));
}
}
/**
*
* @param context
* @return the clientId of this UIData component
*/
private String getContainerId(FacesContext context) {
final int originalIndex = super.getRowIndex();
// turn off iteration while we retrieve the id
setRowIndex(-1);
String thisId = super.getClientId(context);
// restore the row index
setRowIndex(originalIndex);
return thisId;
}
/**
* Adds logic to resolve custom row ids if a custom row id expression was
* specified.
*/
@Override
public boolean invokeOnComponent(FacesContext context, String clientId,
ContextCallback callback) throws FacesException {
Utils.notNull(context, "context");
Utils.notNull(clientId, "clientId");
ValueExpression rowExpression = getValueExpression(ROW_ID);
String baseId = getContainerId(context);
// parse the rowId
String rowId = getRowId(clientId, baseId);
if (rowId == null) {
// the component being looked for is not down this branch, so abort
return false;
}
int originalIndex = getRowIndex();
boolean found = false;
try {
if (rowExpression == null) {
// XXX why doesn't super.invokeOnComponent work right?
try {
setRowIndex(Integer.parseInt(rowId));
found = invokeOnChildren(context, clientId, callback);
} catch (NumberFormatException e) {
LOG.warning("Could not parse rowId: " + rowId);
return false;
}
} else {
// iterate over all the data until we find an instance that
// matches the id iterate over the rows
for (int i = 0; !found && isSetRowAvailable(i); i++) {
if (rowId.equals(Utils.toString(rowExpression
.getValue(context.getELContext())))) {
// if we're on a row whose rowExpression matches the
// rowId, try each of the children
found = invokeOnChildren(context, clientId, callback);
// the rowId is suppose to be unique, so if we found a
// match, quit, whether or not found returned true
break;
}
}
}
} catch (Exception e) {
throw new FacesException(e);
} finally {
setRowIndex(originalIndex);
}
return found;
}
/**
* Calls {@link #invokeOnComponent(FacesContext, String, ContextCallback)}
* on each child node, reseting it's id before the call.
*
* @param context
* @param clientId
* @param callback
* @return
*/
protected boolean invokeOnChildren(FacesContext context, String clientId,
ContextCallback callback) {
for (UIComponent child : getChildren()) {
// reset the child's id
child.setId(child.getId());
if (child.invokeOnComponent(context, clientId, callback)) {
return true;
}
}
return false;
}
/**
* Sets the row index and returns {@link #isRowAvailable()}.
*
* @param index
* @return
*/
protected boolean isSetRowAvailable(int index) {
setRowIndex(index);
return isRowAvailable();
}
/**
* Given the child client id and the id of this component, returns the
* portion identifying the row.
*
* @param clientId
* @param thisId
* @return the rowId, parsed from the client id.
*/
public static String getRowId(String clientId, String thisId) {
if (!clientId.startsWith(thisId + SEPARATOR_CHAR)) {
return null;
}
int startIndex = thisId.length() + 1;
// find the next separator so we can parse the rowId
int endIndex = clientId.indexOf(SEPARATOR_CHAR, startIndex);
endIndex = endIndex == -1 ? clientId.length() : endIndex;
if (startIndex <= endIndex && startIndex > 0
&& endIndex <= clientId.length()) {
return clientId.substring(startIndex, endIndex);
}
return null;
}
/**
* Calls {@link UIComponent#processDecodes(FacesContext)} on each child,
* once per row.
*/
@Override
public void processDecodes(FacesContext context) {
Utils.notNull(context, "context");
if (!isRendered()) {
return;
}
// let each child decode
UIDataProcessor.iterate(context, (JsfIterator) this,
new UIDataProcessor() {
public void processChild(FacesContext context,
UIComponent child) {
child.processDecodes(context);
}
});
// let our renderer (if any) decode
decode(context);
}
/**
* Calls {@link UIComponent#processUpdates(FacesContext)} on each child,
* once per row.
*/
@Override
public void processUpdates(FacesContext context) {
Utils.notNull(context, "context");
if (!isRendered()) {
return;
}
UIDataProcessor.iterate(context, (JsfIterator) this,
new UIDataProcessor() {
public void processChild(FacesContext context,
UIComponent child) {
child.processUpdates(context);
}
});
}
/**
* Calls {@link UIComponent#processValidators(FacesContext)} on each child,
* once per row.
*/
@Override
public void processValidators(FacesContext context) {
Utils.notNull(context, "context");
if (!isRendered()) {
return;
}
UIDataProcessor.iterate(context, (JsfIterator) this,
new UIDataProcessor() {
public void processChild(FacesContext context,
UIComponent child) {
child.processValidators(context);
}
});
}
}