So after several days of debugging, we were eventually able to reproduce some strange interactions between composite components, ui:repeat
, p:remoteCommand
and partial state saving in JSF that we do not understand.
A composite component iterates over a list of objects using ui:repeat
. During each iteration, another composite component is included and arguments are passed.
<ui:composition (...)>
<ui:repeat var="myVar" value="#{cc.attrs.controller.someList}">
<namespace:myRemoteCommand someParam="SomeParam"/>
In the included composite component, there is an auto-run p:remoteCommand
calling a method using parameters defined in the component's interface.
<ui:component (...)>
<p:remoteCommand actionListener="#{someBean.someMethod(cc.attrs.someParam)}"
autoRun="true"
async="true"
global="false">
However, when setting a breakpoint in someMethod(...)
, an empty string is passed. This only happens if partial state saving is set to false.
We tried several solutions and the following ones appear to work (however we do not understand why and cannot foresee any further problems that could occur):
We can set partial state saving to true
.
We can change the composite component pattern to ui:include
.
We can remove one or both of the composite components and directly include the content instead.
Question
Why does JSF behave this way? What is this interaction between composite component, ui:repeat
and argument passing that changes depending on whether we use ui:include
/ partial state saving or not?
We're using Primefaces 5.3, Glassfish 4.1, Mojarra 2.2.12, Java 8.
Your code is all fine. It's just that Mojarra's <ui:repeat>
is broken. You're not the first one facing a state management related problem with <ui:repeat>
.
Root cause of your problem is that #{cc}
is nowhere available at the moment the <ui:repeat>
needs to visit the tree. Effectively, the <ui:repeat value>
is null
. A quick work around is to explicitly push the #{cc}
in UIRepeat#visitTree()
method. Given Mojarra 2.2.12, add below lines right before line 734 with pushComponentToEL(facesContext, null)
.
UIComponent compositeParent = getCompositeComponentParent(this);
if (compositeParent != null) {
compositeParent.pushComponentToEL(facesContext, null);
}
And add below lines right after line 767 with popComponentFromEL(facesContext)
.
if (compositeParent != null) {
compositeParent.popComponentFromEL(facesContext);
}
If you don't build Mojarra from source, copy the entire source code of UIRepeat
into your project, maintaining its package structure and apply above changes on it. Classes in /WEB-INF/classes
have higher classloading precedence than those in /WEB-INF/lib
and server's /lib
. I have at least created issue 4162 to address this.
An alternative is to replace Mojarra by MyFaces, or to replace the <ui:repeat>
by an UIData
based component which got state management right such as <h:dataTable>
or <p:dataList>
.
<p:dataList type="none" var="myVar" value="#{cc.attrs.controller.someList}">
<namespace:myRemoteCommand someParam="SomeParam" />
</p:dataList>
You might only want to apply some CSS to get rid of widget style (border and such), but that's trivial.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With