Why does JSF2/Facelet's ui:repeat not accept java.util.Iterator's for value? One can hide so much implementation and memory conservation behind an Iterator since length need not be known, it would be so useful to have. But instead I need to convert my Iterators to Lists and throw away all my advantages in order to ui:repeat.
There are probably phase or timing or Serializable reasons, but my skimming of the docs that are available do not give those reasons. Do we not yet have the science to make this impossibility possible?
You can make <ui:repeat> work with an Iterator, but you must wrap the Iterator with an object that extends javax.faces.model.DataModel. Here is my IteratorDataModel class that can make any Iterator accessible to <ui:repeat>. I tested this with JSF 2.1.26:
/**
* Make a DataModel out of an Iterator that may be used with JSF's ui:repeat tag
*
* This DataModel does not support the use of offset or step attributes in the
* ui:repeat tag, and the size attribute in ui:repeat, when used with this DataModel,
* will report an inaccurate value.
*
* Copyright (c) 2014 Woldrich, Inc.
* Licensed under MIT (https://clubcompy.com/wcm/license/mit.txt)
*
* @author Dave Woldrich
*/
public class IteratorDataModel<T> extends DataModel<T> {
private static final Logger logger = LogManager.getLogger(IteratorDataModel.class);
private Iterator<?> iterator;
private int rowIndex;
private T item;
public IteratorDataModel(Iterator<?> iterator) {
setWrappedData(iterator);
}
@Override
public int getRowCount() {
return Integer.MAX_VALUE;
}
@Override
public T getRowData() {
return this.item;
}
@Override
public int getRowIndex() {
if(this.rowIndex == -1 && this.iterator.hasNext()) {
this.setRowIndex(0);
}
if(logger.isTraceEnabled()) {
logger.trace("getRowIndex returns " + this.rowIndex);
}
return this.rowIndex;
}
@Override
public Object getWrappedData() {
return this.iterator;
}
@Override
public boolean isRowAvailable() {
boolean hasNext = this.item != null || this.iterator.hasNext();
if(logger.isTraceEnabled()) {
logger.trace("isRowAvailable " + hasNext);
}
return hasNext;
}
@SuppressWarnings("unchecked")
@Override
public void setRowIndex(int newIndex) {
if(logger.isTraceEnabled()) {
logger.trace("setRowIndex (" + newIndex + ")");
}
if(newIndex == this.rowIndex+1) {
this.rowIndex = newIndex;
this.item = (T) (this.iterator.hasNext() ? this.iterator.next() : null);
}
else if(newIndex > -1 || newIndex != this.rowIndex) {
if(logger.isTraceEnabled()) {
logger.trace("setRowIndex not incrementing by 1 as expected, ignored");
}
}
}
@Override
public void setWrappedData(Object data) {
this.iterator = (Iterator<?>) data;
this.rowIndex = -1;
this.item = null;
}
}
And in your facelets code, for example, you may use:
<ul>
<ui:repeat value="#{chatRoom.allComments}" var="aComment" varStatus="status">
<li><h:outputText value="#{aComment.text}" /></li>
</ui:repeat>
</ul>
where chatRoom.getAllComments() would return a IteratorDataModel<Comment>
that is constructed passing an Iterator<Comment>
.