This post is an answer for
BalusC to
our discussion in OmniFaces' issue tracker. The goal is to show how to validate values in an editable
DataTable if they are unique within a column or not. I will not write too much explanation. I will bomb readers with the code :-). The code is written for PrimeFaces
DataTable, but it is easy to adjust it to the JSF standard
DataTable or any other data iteration components like
DataList in PrimeFaces. Validation of unique input values supports lazy loaded tables and dynamic columns as well. First, we will write a tag handler
ValidateUniqueColumn (how to register it in a tag lib. is not a subject of this post).
ValidateUniqueColumn has to be attached to the entire
p:dataTable and have two attributes: int
index (index of the column to be validated) and boolean flag
skipEmpty (if empty values should be skipped when validating; default is
true). XHTML snippet as using example:
<p:dataTable ...>
<p:columns ...>
...
</p:columns>
...
<p:column>
...
</p:column>
<xyz:validateUniqueColumn index="4" skipEmpty="false"/>
<xyz:validateUniqueColumn index="#{bean.columnIndexToValidate}"/>
</p:dataTable>
Implementation of the tag handler:
public class ValidateUniqueColumn extends TagHandler {
public static final String UNSUBSCRIBE_PRERENDER_LISTENERS = "unsubscribePreRenderListeners";
private final TagAttribute index;
private final TagAttribute skipEmpty;
public ValidateUniqueColumn(TagConfig config) {
super(config);
this.index = getRequiredAttribute("index");
this.skipEmpty = getAttribute("skipEmpty");
}
@Override
public void apply(FaceletContext ctx, UIComponent parent) throws IOException {
if (!ComponentHandler.isNew(parent)) {
return;
}
Object objIndex;
if (index.isLiteral()) {
// literal
objIndex = index.getValue();
} else {
// value expression
objIndex = index.getValueExpression(ctx, int.class);
}
Object objSkipEmpty;
if (skipEmpty == null) {
objSkipEmpty = true;
} else if (skipEmpty.isLiteral()) {
// literal
objSkipEmpty = skipEmpty.getValue();
} else {
// value expression
objSkipEmpty = skipEmpty.getValueExpression(ctx, boolean.class);
}
// register a PreRender listener
parent.subscribeToEvent(PreRenderComponentEvent.class, new PreRenderTableListener(objIndex, objSkipEmpty));
// set a flag that all before registered PreRenderTableListener instances must be unsubscribed
parent.getAttributes().put(UNSUBSCRIBE_PRERENDER_LISTENERS, true);
}
}
Implementation of
PreRenderTableListener:
public class PreRenderTableListener implements ComponentSystemEventListener, Serializable {
private static final long serialVersionUID = 20111114L;
private Logger LOG = Logger.getLogger(PreRenderTableListener.class);
private Object index;
private Object skipEmpty;
/**
* This constructor is required for serialization.
*/
public PreRenderTableListener() {
}
public PreRenderTableListener(Object index, Object skipEmpty) {
this.index = index;
this.skipEmpty = skipEmpty;
}
@Override
public void processEvent(ComponentSystemEvent event) {
UIComponent source = event.getComponent();
if (!source.isRendered()) {
return;
}
DataTable dataTable;
if (source instanceof DataTable) {
dataTable = (DataTable) source;
} else {
LOG.warn("Validator ValidateUniqueColumn can be only applied to PrimeFaces DataTable");
return;
}
if (index == null) {
LOG.warn("Column index of the Validator ValidateUniqueColumn is null");
return;
}
Boolean deleteListeners = (Boolean) source.getAttributes().get(
ValidateUniqueColumn.UNSUBSCRIBE_PRERENDER_LISTENERS);
if ((deleteListeners != null) && deleteListeners) {
// unsubscribe all listeners only once - important for AJAX updates
source.getAttributes().remove(ValidateUniqueColumn.UNSUBSCRIBE_PRERENDER_LISTENERS);
Iterator<PostValidateTableListener> iter = getPostValidateTableListeners(dataTable).iterator();
while (iter.hasNext()) {
dataTable.unsubscribeFromEvent(PostValidateEvent.class, iter.next());
}
}
int columnIndex;
if (index instanceof ValueExpression) {
// value expression
Object obj = ((ValueExpression) index).getValue(FacesContext.getCurrentInstance().getELContext());
columnIndex = Integer.valueOf(obj.toString());
} else {
// literal
columnIndex = Integer.valueOf(index.toString());
}
boolean skipEmptyValue;
if (skipEmpty instanceof ValueExpression) {
// value expression
Object obj = ((ValueExpression) skipEmpty).getValue(FacesContext.getCurrentInstance().getELContext());
skipEmptyValue = Boolean.valueOf(obj.toString());
} else {
// literal
skipEmptyValue = Boolean.valueOf(skipEmpty.toString());
}
PostValidateTableListener pvtListener = new PostValidateTableListener(columnIndex, skipEmptyValue);
dataTable.subscribeToEvent(PostValidateEvent.class, pvtListener);
}
protected List<PostValidateTableListener> getPostValidateTableListeners(UIComponent component) {
List<PostValidateTableListener> postValidateTableListeners = new ArrayList<PostValidateTableListener>();
List<SystemEventListener> systemEventListeners = component.getListenersForEventClass(PostValidateEvent.class);
if ((systemEventListeners != null) && !systemEventListeners.isEmpty()) {
for (SystemEventListener systemEventListener : systemEventListeners) {
if (systemEventListener instanceof PostValidateTableListener) {
postValidateTableListeners.add((PostValidateTableListener) systemEventListener);
}
FacesListener wrapped = null;
if (systemEventListener instanceof FacesWrapper<?>) {
wrapped = (FacesListener) ((FacesWrapper<?>) systemEventListener).getWrapped();
}
while (wrapped != null) {
if (wrapped instanceof PostValidateTableListener) {
postValidateTableListeners.add((PostValidateTableListener) wrapped);
}
if (wrapped instanceof FacesWrapper<?>) {
wrapped = (FacesListener) ((FacesWrapper<?>) wrapped).getWrapped();
} else {
wrapped = null;
}
}
}
}
return postValidateTableListeners;
}
}
Implementation of
PostValidateTableListener:
public class PostValidateTableListener implements ComponentSystemEventListener, Serializable {
private static final long serialVersionUID = 20111114L;
private static final Set<VisitHint> VISIT_HINTS = EnumSet.of(VisitHint.SKIP_UNRENDERED);
private int index = -1;
private boolean skipEmpty;
/**
* This constructor is required for serialization.
*/
public PostValidateTableListener() {
}
public PostValidateTableListener(int index, boolean skipEmpty) {
this.index = index;
this.skipEmpty = skipEmpty;
}
public int getIndex() {
return index;
}
@Override
public void processEvent(ComponentSystemEvent event) {
UIComponent source = event.getComponent();
if (!source.isRendered() || (index == -1)) {
return;
}
FacesContext fc = FacesContext.getCurrentInstance();
Map<String, String> requestParamMap = fc.getExternalContext().getRequestParameterMap();
// buffer unique input values during iteration in a list
List<Object> columnValues = new ArrayList<Object>();
DataTable dataTable = (DataTable) source;
int first = dataTable.getFirst();
int rowCount = dataTable.getRowCount();
int rows = dataTable.getRows();
if (dataTable.isLazy()) {
for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) {
if ((rowIndex % rows) == 0) {
dataTable.setFirst(rowIndex);
dataTable.loadLazyData();
}
// get next value of the first editable component in the specified column
Object value = getColumnValue(fc, requestParamMap, dataTable, rowIndex);
// compare with last stored unique values
if (isUnique(fc, columnValues, value)) {
columnValues.add(value);
} else {
break;
}
}
//restore
dataTable.setFirst(first);
dataTable.loadLazyData();
} else {
for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) {
// get next value of the first editable component in the specified column
Object value = getColumnValue(fc, requestParamMap, dataTable, rowIndex);
// compare with last stored unique values
if (isUnique(fc, columnValues, value)) {
columnValues.add(value);
} else {
break;
}
}
//restore
dataTable.setFirst(first);
}
}
private Object getColumnValue(FacesContext fc, Map<String, String> requestParamMap,
DataTable dataTable, int rowIndex) {
dataTable.setRowIndex(rowIndex);
if (!dataTable.isRowAvailable()) {
return null;
}
List<UIColumn> columns = dataTable.getColumns();
if (index < columns.size()) {
int i = -1;
UIColumn foundColumn = null;
for (UIColumn col : columns) {
if (col.isRendered()) {
i++;
}
if (index == i) {
foundColumn = col;
break;
}
}
if (foundColumn == null) {
// column for given index was not found
return null;
}
if (foundColumn instanceof DynamicColumn) {
((DynamicColumn) foundColumn).applyModel();
}
List<UIComponent> children = foundColumn.getChildren();
for (UIComponent component : children) {
// find the first editable rendered component
FirstInputVisitCallback visitCallback = new FirstInputVisitCallback();
component.visitTree(VisitContext.createVisitContext(fc, null, VISIT_HINTS), visitCallback);
EditableValueHolder editableValueHolder = visitCallback.getEditableValueHolder();
if (editableValueHolder != null) {
String clientId = ((UIComponent) editableValueHolder).getClientId(fc);
String value = requestParamMap.get(clientId);
// return converted value for comparison
return ComponentUtils.getConvertedValue(fc, editableValueHolder, value);
}
}
}
return null;
}
private boolean isUnique(FacesContext fc, List<Object> columnValues, Object value) {
if (skipEmpty && ((value == null) || (value.toString().length() < 1))) {
return true;
}
for (Object columnValue : columnValues) {
// compare values with EqualsBuilder from Apache commons project
if (new EqualsBuilder().append(columnValue, value).isEquals()) {
// not unique
fc.addMessage(null, MessageUtils.getMessage("msg_tableNotUniqueValues", index + 1));
fc.validationFailed();
fc.renderResponse();
return false;
}
}
return true;
}
}
How to get converted value from the submitted one is not shown here.