Iterating List of Lists in JSF view - jsf

I'm trying to iterate a List> on my JSF view using ui:repeat tags. It looks like this:
<h:form>
<ui:repeat value="#{myController.currentEntry.pages}" var="page" varStatus="pageStatus">
<p:fieldset rendered="#{myController.currentEntryPage == pageStatus.index}">
<ui:repeat value="#{page}" var="item" varStatus="itemStatus">
<h:outputText value="#{itemStatus.index + 1}. #{item.anotherClass.text}" />
</ui:repeat>
<p:fieldset>
</ui:repeat>
</h:form>
My class Item, located in models package, contains a reference to an object of AnotherClass, also in models package. I have checked in debugger and, just before the redirect to my view is returned, myController.currentEntry.pages is well formed.
I get the following error, however:
javax.servlet.ServletException: /my_view.xhtml #10,109 value="#{itemStatus.index + 1}. #{item.anotherClass.text}": Property 'anotherClass' not readable on type models.AnotherClass
javax.faces.webapp.FacesServlet.service(FacesServlet.java:422)
org.netbeans.modules.web.monitor.server.MonitorFilter.doFilter(MonitorFilter.java:393)
filters.Auth.doFilter(Auth.java:19)
filters.Encoding.doFilter(Encoding.java:28)
What is this all about? How is JSF managing to map item to item.anotherClass? I know ui:repeat is full of bugs, but none of what I found points to this kind of problem.
Is there any other way to achieve what I'm trying to do? My project uses Primefaces. It is close to the end and even closer to delivery deadline, so switching to another library is not an option.
EDIT:
I've switched to a more elegant solution that doesn't require me to iterate the whole matrix:
<ui:repeat value="#{myController.currentEntryPageItems}" var="item" varStatus="itemStatus">
getCurrentEntryPageItems() being a method that returns myController.currentEntry.pages.get(myController.currentEntryPage).
I would still like to know if 2d iteration can be done with ui:repeat so I'll leave the question open.

Related

Netbeans doesn't autocomplete properties of var of nested <ui:repeat>

Let me say I have a Book Library with a list of Book. A Book has a List of Page. Page has a list of Line.
In JSF, i'm trying to display all the lines, now I'm using <h:dataTable> or <ui:repeat> to iterate through the lists. When I'm on second level of hierarchy which is page, instead of showing the properties of page as lines, netbeans only show the properties of book again as available properties
<ui:repeat value="#{bookLibrary.books}" var="book">
<ui:repeat value="#{book.pages}" var="page">
<ui:repeat value="#{page.???}" var="line">
</ui:repeat>
</ui:repeat> </ui:repeat>
Yes, I now found out that it works if I add the field manually. Its the code completion that's leading me to believe my code is wrong

How to render input component inside dialog upon opening dialog and unrender when closing dialog [duplicate]

So I've found a few answers close to this, and I've found enough to fix the problem I had. But even so, I'm curious as to understand the workings around this. Let me illustrate with an example :
I have a facelet .xhtml page that looks like this (shortned).
<h:form id="resultForm">
<h:panelGroup class="search_form" layout="block">
<h:inputText id="lastname" value="#{search.lastname}"/>
<h:commandButton action="#{search.find}" value="Find">
<f:ajax execute="lastname" render="resultDisplay"/>
</h:commandButton>
</h:panelGroup>
<h:dataTable value="#{search.searchResults}" var="results" id="resultDisplay"
rendered="#{!empty search.searchResults}">
<h:column>
#{results.field}
</h:column>
</h:dataTable>
</h:form>
Now, for the sake of breivity, I will not post all the backing bean code, but I have something of this sort :
public void find() {
searchResults = setResults(true);
}
Where searchResults is an ArrayList<Objects>. After a search, it is not null - checked in multiple tests (can be null, but not in the testing I am doing).
Now. This does NOT work.
But if I nest the dataTable inside another, let's say panelGroup, it will work.
<h:panelGroup id="resultDisplay">
<h:dataTable value="#{search.searchResults}" var="results"
rendered="#{!empty search.searchResults}">
<h:column>
#{results.field}
</h:column>
</h:dataTable>
</h:panelGroup>
Now, this changes allows everything to work fine. I'd be okay with this... but I guess I am seeking a bit of understanding as well. Any insight as to why I have to nest these components? I am surely missing something!
Ajax updating is performed by JavaScript language in the client side. All which JavaScript has access to is the HTML DOM tree. If JSF does not render any component to the HTML output, then there's nothing in the HTML DOM tree which can be obtained by JavaScript upon Ajax update. JavaScript cannot get the desired element by its ID.
It will only work if you wrap the conditionally JSF-rendered component in another component which is always rendered to the HTML output and thus always present in the HTML DOM tree and thus always obtainable by JavaScript. Reference that wrapper component instead during ajax render/update.
See also:
Communication in JSF2 - Ajax rendering of content which is by itself conditionally rendered

How to set a Map value in h:inputText

I'm struggling to implement a fairly trivial functionality with JSF which involves dynamically displaying the content of a nested map on a page and editing capabilities for its values. But it has turned out that the MappedValueExpression$Entry that you get when iterating over a map with c:forEach is not writable!
<c:forEach items='#{inflectionBean.word.inflectionalForms}' var="number" >
<p:fieldset legend="#{number.key}">
<c:forEach items="#{number.value}" var="case" >
<p:panel header="#{case.key}">
<h:inputText value="#{case.value}" />
</p:panel>
</c:forEach>
</p:fieldset>
</c:forEach>
When I am trying to submit the above form I'm getting:
javax.el.PropertyNotWritableException: /inflection.xhtml #39,56 value="#{case.value}": The class 'com.sun.faces.facelets.tag.jstl.core.MappedValueExpression$Entry' does not have a writable property 'value'.
I wonder if there are reasonable workarounds or if I am approaching the problem in a wrong way. Thanks!
Basically, what your code is attempting is invoking Map.Entry#setValue(value). This is indeed not possible in EL. Instead, you should be referencing the map value directly on the map itself by key, so that EL can do Map#put(key, value).
<c:forEach items="#{number.value}" var="case">
...
<h:inputText value="#{number.value[case.key]}" />

Why do I need to nest a component with rendered="#{some}" in another component when I want to ajax-update it?

So I've found a few answers close to this, and I've found enough to fix the problem I had. But even so, I'm curious as to understand the workings around this. Let me illustrate with an example :
I have a facelet .xhtml page that looks like this (shortned).
<h:form id="resultForm">
<h:panelGroup class="search_form" layout="block">
<h:inputText id="lastname" value="#{search.lastname}"/>
<h:commandButton action="#{search.find}" value="Find">
<f:ajax execute="lastname" render="resultDisplay"/>
</h:commandButton>
</h:panelGroup>
<h:dataTable value="#{search.searchResults}" var="results" id="resultDisplay"
rendered="#{!empty search.searchResults}">
<h:column>
#{results.field}
</h:column>
</h:dataTable>
</h:form>
Now, for the sake of breivity, I will not post all the backing bean code, but I have something of this sort :
public void find() {
searchResults = setResults(true);
}
Where searchResults is an ArrayList<Objects>. After a search, it is not null - checked in multiple tests (can be null, but not in the testing I am doing).
Now. This does NOT work.
But if I nest the dataTable inside another, let's say panelGroup, it will work.
<h:panelGroup id="resultDisplay">
<h:dataTable value="#{search.searchResults}" var="results"
rendered="#{!empty search.searchResults}">
<h:column>
#{results.field}
</h:column>
</h:dataTable>
</h:panelGroup>
Now, this changes allows everything to work fine. I'd be okay with this... but I guess I am seeking a bit of understanding as well. Any insight as to why I have to nest these components? I am surely missing something!
Ajax updating is performed by JavaScript language in the client side. All which JavaScript has access to is the HTML DOM tree. If JSF does not render any component to the HTML output, then there's nothing in the HTML DOM tree which can be obtained by JavaScript upon Ajax update. JavaScript cannot get the desired element by its ID.
It will only work if you wrap the conditionally JSF-rendered component in another component which is always rendered to the HTML output and thus always present in the HTML DOM tree and thus always obtainable by JavaScript. Reference that wrapper component instead during ajax render/update.
See also:
Communication in JSF2 - Ajax rendering of content which is by itself conditionally rendered

JSF <h:outputFormat>: use array values as parameters

On my JSF2 page, i'm using internationalized error messages.
In my backing bean, i'm putting the messages into the flash Scope:
flash.put("error", exception.getType());
On the page, this string gets translated this way:
<h:outputText value="#{bundle[flash.error]}"/>
Works fine.
NOW i want to be also able to put (an arbitrary number of) parameters into the message text, that get inserted into the placeholders in the i18n-property in my message.properties. Therefore, i'm putting the parameters as a String array into the Flash Scope, like this:
//exception.getParameters returns String[]
flash.put("errorParams", exception.getParameters())
Now i also want to be able to use this String array as parameters for an outputFormat element, to insert them into a property like Welcome, {0} {1}.
So i tried to achieve this by using ui:repeat:
<h:outputFormat value="#{bundle[flash.error]}" rendered="#{! empty flash.error}" class="invalid">
<ui:repeat value="#{flash.errorParams}" var="_param">
<f:param value="#{bundle[_param]}"/>
<!-- also doesn't work: <f:param value="#{_param}"/>-->
</ui:repeat>
</h:outputFormat>
Unfortunately, the param value is ignored and the placeholders of the i18n-property aren't replaced, so the rendered output is Welcome, {0} {1}. When using a "regular" repeater, displaying the array elements just as an outputtext, it works. So the outputFormat tag doesn't seem to support the use of a repeat as a child.
Damn, so close ;) Anyone knows a good way to do what i want, or is there any component library supporting something like that?
The problem here is that ui:repeat is a render-time child of h:outputFormat which it indeed doesn't support at all. You'd like to put multiple f:param elements directly as children of h:outputFormat during build time.
The c:forEach is suitable for this task. The JSTL core tags (which are already included in Facelets, so you don't need to install any extra JARs) do their job during building the view tree, right before it's JSF turn to process/render the view tree.
<html xmlns:c="http://java.sun.com/jsp/jstl/core">
...
<h:outputFormat value="#{bundle[flash.error]}" rendered="#{! empty flash.error}" class="invalid">
<c:forEach items="#{flash.errorParams}" var="_param">
<f:param value="#{bundle[_param]}"/>
</c:forEach>
</h:outputFormat>

Resources