There's physically only one UIInput
component whose state changes depending on the current iteration round of UIRepeat
. It's available by just its client ID without the UIRepeat
index: findComponent("formId:inputId")
(the UIRepeat
index is only of significance in the client side). However, when the component is programmatically been accessed outside the context of UIRepeat
this way, then it'll indeed return a seemingly empty state.
In order to visit the UIInput
component in all those states as they are inside the UIRepeat
and collect their values, you need to run UIComponent#visitTree()
on the UIRepeat
.
Here's a kickoff example:
<ui:repeat value="#{bean.items}" var="item">
<f:event type="postValidate" listener="#{bean.validateOrder}" />
<h:inputText value="#{item.value}" />
</ui:repeat>
With this validateOrder()
method (again, just a kickoff example, this approach naively assumes that there's only one UIInput
component in the repeater):
@SuppressWarnings("rawtypes")
public void validateOrder(ComponentSystemEvent event) {
final FacesContext context = FacesContext.getCurrentInstance();
final List<Comparable> values = new ArrayList<Comparable>();
event.getComponent().visitTree(VisitContext.createVisitContext(context), new VisitCallback() {
@Override
public VisitResult visit(VisitContext context, UIComponent target) {
if (target instanceof UIInput) {
values.add((Comparable) ((UIInput) target).getValue());
}
return VisitResult.ACCEPT;
}
});
boolean ordered = new ArrayList<Comparable>(new TreeSet<Comparable>(values)).equals(values);
if (!ordered) {
event.getComponent().visitTree(VisitContext.createVisitContext(context), new VisitCallback() {
@Override
public VisitResult visit(VisitContext context, UIComponent target) {
if (target instanceof UIInput) {
((UIInput) target).setValid(false);
}
return VisitResult.ACCEPT;
}
});
context.validationFailed();
context.addMessage(null, new FacesMessage("Values are not in order!"));
}
}
Note that it visits the tree twice; first time to collect the values and second time to mark those inputs invalid. Also note that this very specific requirement can't be done with a standard JSF validator. You can't attach a <f:validator>
on <ui:repeat>
. Attaching it on <h:inputText>
is theoretically possible, but it would cause the very same validator to run as many times as the amount of repeated items, which doens't make sense. Also, the validator would need to take getSubmittedValue()
vs getValue()
into account this way.
OmniFaces has an <o:validateOrder>
component which does a similar thing on fixed components, but it isn't designed for usage in dynamically repeated components.
postvalidate
phase which may have some side effects. However, for me, it seems to work. – Southwestward