How do I attach a FacesMessage from the backing bean to a specific field in a ui:repeat?

家住魔仙堡 提交于 2019-12-01 20:56:13
BalusC

Your attempts to bind the input component to a map/array failed because there are not multiple of those components in the JSF component tree, but only one. The <ui:repeat> doesn't run during view build time producing the JSF component tree. Instead, it runs during view render time producing the HTML output. In other words, the child components of <ui:repeat> are reused everytime during generating HTML output of each iteration.

The particular exception, "Target Unreachable, ''BracketSuffix'' returned null" is been thrown because the variable #{_lang} isn't available during view build time, that moment when the UI component tree is constructed and all id and binding attributes are evaluated. It's only available during view render time.

Those binding attempts would have succeeded if you used <c:forEach> instead. It runs during view build time producing the JSF component tree. You would then end up with physically multiple instances of the child components which in turn produce each their own HTML output without being reused multiple times.

Putting in a panel group and attempting to find all children obviously won't work for the reasons mentioned before. The <ui:repeat> doesn't generate physically multiple JSF components in the component tree. Instead, it reuses the same component to produce the HTML output multiple times depending on the state of the current iteration round.

Replacing by <c:forEach> should have worked. Perhaps you were facing a timing issue because it runs during view build time and you're preparing the model during e.g. preRenderView instead of @PostConstruct or so.

All of above is easier to understand if you carefully read JSTL in JSF2 Facelets... makes sense?


As to your concrete functional requirement, you would normally use a Validator for the job. If you register it on the input component, then it would be invoked for every iteration round. You would immediately have the right input component with the right state at hands as 2nd argument of validate() method and the submitted/converted value as 3rd argument.

If you really need to perform the job afterwards, for example because you need to know about all inputs, then you should be programmatically iterating over the <ui:repeat> yourself. You can do that with help of UIComponent#visitTree() this allows you to collect the input component's state of every iteration round.

E.g.

final FacesContext facesContext = FacesContext.getCurrentInstance();
UIComponent repeat = getItSomehow(); // findComponent, binding, etc.

repeat.visitTree(VisitContext.createVisitContext(facesContext), new VisitCallback() {
    @Override
    public VisitResult visit(VisitContext context, UIComponent target) {
        if (target instanceof UIInput && target.getId().equals("theTitle")) {
            String clientId = target.getClientId(facesContext);
            Object value = ((UIInput) target).getValue();
            // ...
            facesContext.addMessage(clientId, message);                
        }
        return VisitResult.ACCEPT;
    }
});

See also:

There is another option: Make your own replacement of the whole FacesMessages shebang. With blackjack. And...

Anyway, based on discussions with Johannes Brodwall we opted to avoid the whole visitTree mess and build our own messages mechanism. This included:

1) A ViewScoped bean containing a Map of Multimaps:

private Map<Object, Multimap<String, String>> fieldValidationMessages = new HashMap<>();

This takes an Object as a field identifier (could be the respective bean itself, a UI component or even a String generated at runtime inside the ui:repeat. That identifier then can have an arbitrary number of String messages on another arbitrary number of sub-fields. Very flexible.

The bean also had convenience methods for getting and setting messages on fields and subfields, and for checking whether any messages are stored at all (i.e. whether there were validation errors).

2) A simple xhtml include that displays error messages for a given field, replacing h:messages for...

And that's already it. The catch is that this runs during the application and rendering phase of the lifecycle instead of JSF's own validation phase. But since our project already decided to do bean validation instead of lifecycle validation, this was not an issue.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!