How to associate FacesMessage with a bean base with a specific field in a ui: repeat file?

I have a form with a variable number of input elements, for example:

<ui:repeat var="_lang" value="#{myBean.languages}"> <h:inputTextarea value="${_lang.title}" id="theTitle" /> <h:messages for="theTitle"/> </ui:repeat> 

When running a certain method in the background bean, I want to add a message, say, for the second iteration of ui:repeat , but not for others.

I saw various variations of this question here , and all the problems are that ui:repeat iterations ui:repeat not available in the JSF component tree.

What I have tried so far:

  • Bind h:inputTextarea to a Map<String,UIComponent> in the bean. (a) ... Using ...binding="#{myBean.uiMap[_lang.id]}" (where _lang.id is the only line). This created JBWEB006017: Target Unreachable, "BracketSuffix" "null". (I chose the appropriate line map using identifiers, the same syntax works just fine outside of binding ) (b) ... or using ...binding="#{myBean.uiMap.get()}" . This makes the page beautiful, but when I click the button for my method, the setter is not called, and thus the UIComponent never added to the Map .

  • Bind h:inputTextarea to the UIComponent[] array in the bean, pre-populating it with zero zeros, and then using the ui:repeat line counter as an index in the xhtml file. Having received Null Pointer exceptions, the array setter was never called, and therefore the array was never populated with the actual UIComponent s.

  • Bind the outer h:panelGroup to the bean and try to find input elements recursively among your children in the JSF tree. Only one of the inputs has ever been found, see the Problem with "Iterations Unavailable" above.

  • I also tried replacing ui:repeat with c:forEach and generating line numbers manually (so that, hopefully, they are available in the JSF tree), but there I did not get any output at all.

(Note. The goal is to display a validation error message, but it should come from a backup bean. Using f:validator or the like, even custom ones, is not really an option, because I need to validate the data in the backup bean.)

Honestly, I have no ideas. It can't be that hard, can it?

Edit:

For my third attempt, binding to an external h:panelGroup , here is my JSF search 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 call this on node , which is the binding of the UIComponent to h:panelGroup around ui:repeat . (I use recursion because my live application has a slightly more nested structure). That, I thought, should give me all the "theTitle" text boxes so that I can then add messages and read attributes as I see fit. Alas, the method returns only one "theTitle" component, and the log messages show why:

In the DOM of the generated page, the identifiers are similar to "myform: myPanelGroup: 0: theTitle" (including the iterative counter ui:repeat ), and the bean only sees getClientId (), like myform:myPanelGroup:theTitle - and this only exists once, for the last ( i guess?) iteration.

+3
source share
2 answers

Attempts to associate an input component with a map / array failed because there were not several components in the JSF component tree, but only one. <ui:repeat> does not start while creating the creation time of the tree of JSF components. Instead, it launches during view rendering, generating HTML output. In other words, the <ui:repeat> child components are reused each time during the generation of the HTML output of each iteration.

Special exception: "Target Unreachable", "BracketSuffix" "null null" was chosen because the variable #{_lang} not available during build time creation, at the moment the user interface component tree was built, and all id and binding . It is only available while viewing rendering.

Those binding attempts would succeed if you used <c:forEach> . It starts during the creation of the JSF component tree creation time. Then you will receive physically several instances of the child components, which, in turn, produce each of its own HTML output without repeating it many times.

Turning on the panel group and finding all the children will obviously not work for the reasons mentioned earlier. <ui:repeat> does not physically generate multiple JSF components in the component tree. Instead, it reuses the same component to output HTML output several times depending on the state of the current iteration of the round.

Replacing with <c:forEach> should have worked. You may have encountered a synchronization problem because it works during build time, and you are preparing a model during, for example. preRenderView instead of @PostConstruct or so.

All of the above is easier to understand if you carefully read the JSTL in JSF2 Facelets ... does it make sense?


As for your specific functional requirement, you usually use Validator for the job. If you register it on an input component, it will be called for each round of iteration. You will immediately have the right input component with the correct state in your hands as the second argument to the validate() method, and the represented / converted value as the third argument.

If you really need to complete the task later, for example, because you need to know about all the inputs, then you must programmatically iterate through <ui:repeat> yourself. You can do this with UIComponent#visitTree() , which allows you to collect the state of input components in each round of iteration.

eg.

 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:

+2
source

There is another option: Make your own replacement for the entire FibreMessages shebang. With blackjack. AND...

In any case, based on discussions with Johannes Brodwall , we decided to avoid the whole mess of visitTree and create our own messaging engine. This included:

1) ViewScoped bean containing a map of multiplays:

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

The Object identifier is Object (it can be a bean itself, a user interface component, or even a String generated at runtime inside ui:repeat . Then this identifier can have an arbitrary number of String messages on another arbitrary number of subfields. Very flexible.

The bean also had convenient methods for receiving and setting up messages in fields and subfields, as well as for checking whether any messages were stored at all (for example, if there were checking errors).

2) Simple xhtml enables displaying error messages for a given field, replacing h:messages for...

And this is it. The trick is that this is done during the application phase and life cycle visualization instead of the native JSF validation phase. But since our project already decided to validate the bean instead of checking for a life cycle, this was not a problem.

0
source

Source: https://habr.com/ru/post/982630/


All Articles