I have a form with a variable number of input elements, like this:
<ui:repeat var="_lang" value="#{myBean.languages}">
<h:inputTextarea value="${_lang.title}" id="theTitle" />
<h:messages for="theTitle"/>
</ui:repeat>
When a certain method in the backing bean is triggered, I want to add a message to, say, the second iteration of the ui:repeat
, but not the other ones.
I've seen different variations of this question around here, and all problems appear to be due to the ui:repeat
's iterations not being available in the JSF component tree.
What I have tried so far:
Bind the
h:inputTextarea
s to aMap<String,UIComponent>
in the bean. (a) ...Using...binding="#{myBean.uiMap[_lang.id]}"
(where_lang.id
is a unique String). This produced JBWEB006017: Target Unreachable, ''BracketSuffix'' returned null. (I dumped a corresponding map of strings using the ids, the same syntax works just fine outside ofbinding
) (b) ...or using...binding="#{myBean.uiMap.get()}"
. This renders the page fine, but when I push the button for my method, the setter does not get called, and thus theUIComponent
s never get added to theMap
.Bind the
h:inputTextarea
s to an arrayUIComponent[]
in the bean, prepopulating it with the right number of nulls and then using the row counter ofui:repeat
as an index in the xhtml file. Got Null Pointer exceptions, the setter of the array was never called and thus the array was never populated with the actualUIComponent
s.Bind an outer
h:panelGroup
to the bean and try to find the input elements recursively amongst its children in the JSF tree. Only one of the inputs is ever found, see the "iterations not available" problem above.I also tried replacing the
ui:repeat
withc:forEach
and generate row numbers manually (so that they'd hopefully be available in the JSF tree), but there I didn't get any rendered output at all.
(Note: The goal is to display a validation error message, but they have to come from the backing bean. Using an f:validator
or the likes, even custom ones, is not really an option because I need to validate against data in the backing bean.)
Frankly, I'm out of ideas. This can't be so difficult, can it?
Edit:
For my third attempt, binding to an outer h:panelGroup
, here's my JSF finder function:
private List<UIComponent> findTitleComponents(UIComponent node) {
List<UIComponent> found = new ArrayList<UIComponent>();
for (UIComponent child : node.getChildren()) {
if (child.getId().equals("theTitle")) {
found.add(child);
log.debug("have found "+child.getClientId());
} else {
found.addAll(findTitleComponents(child));
log.debug("recursion into "+child.getClientId());
}
}
return found;
}
I'm calling this on node
, which is the binding UIComponent
of the h:panelGroup
around the ui:repeat
. (I'm using recursion because my live application has a slightly more nested structure) This, I thought, should give me all "theTitle" textareas, so that I then could add messages and read attributes as I pleased. Alas, the method only returns one "theTitle" component, and the log messages show why:
In the DOM of the generated page, the ids are like "myform:myPanelGroup:0:theTitle" (including the iteration counter of the ui:repeat
) while the bean only sees getClientId()s like myform:myPanelGroup:theTitle
- and that only exist once, for the last (I guess?) iteration.