This is a problem of build vs render time I guess. I've a comment section which is a tree of comments. Everything works fine... except for some comments the bean setters is not called.
So my understanding is that :
when using ui:repeat the composite component is included once at build time, then for each item is rendered with its appropriate value. Why it doesn't work ? no clue.
when c:forEach is used, the composite component is integrated multiple times then at render time the item is lost when the commandButton action is to be populated by it.
Here is full working example to reproduce the bug:
WorkingTest Bean:
import java.io.Serializable;
import javax.faces.view.ViewScoped;
import javax.inject.Named;
@Named
@ViewScoped
public class WorkingTest implements Serializable{
private static final long serialVersionUID = 1L;
private Post topNode;
private String replyContent;
private int counter;
public WorkingTest(){
topNode = new Post();
Post p1 = new Post(1, "reply here works");
p1.getReplies().add(new Post(3, "Reply to this comment <b style=\"color:red\">doesn't works</b>, the content of the comment is null"));
Post p2 = new Post(1, "reply here works");
topNode.getReplies().add(p1);
topNode.getReplies().add(p2);
counter = 6;
}
public void addReply(Post post){
post.getReplies().add(new Post(counter, replyContent));
replyContent = "";
counter++;
}
public Post getTopNode() {
return topNode;
}
public void setTopNode(Post topNode) {
this.topNode = topNode;
}
public String getReplyContent() {
return replyContent;
}
public void setReplyContent(String replyContent) {
System.out.println("settting reply content");
this.replyContent = replyContent;
}
}
Post class:
import java.util.ArrayList;
import java.util.List;
public class Post {
private long idpost;
private String content;
private List<Post> replies = new ArrayList<Post>();
public Post(){}
public Post(long id, String content){
this.content = content;
this.idpost = id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public List<Post> getReplies() {
return replies;
}
public void setReplies(List<Post> replies) {
this.replies = replies;
}
public long getIdpost() {
return idpost;
}
public void setIdpost(long id) {
this.idpost = id;
}
}
Test.xhtml:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:c="http://java.sun.com/jsp/jstl/core">
<h:head>
<title>
<ui:insert>Test</ui:insert>
</title>
</h:head>
<h:body id="whole">
Refresh your page between tries, you will see that for some comments the
value of the comment doesn't appear.
Because of a bug in mojarra the viewstate is lost after ajax request, that is why u gotta refresh.
see http://balusc.omnifaces.org/2011/09/communication-in-jsf-20.html#AutomaticallyFixMissingJSFViewStateAfterAjaxRendering if you want more info
<h:panelGroup id="replies">
<div class="replies">
<ui:include src="pv.xhtml" >
<ui:param name="node" value="#{workingTest.topNode.replies}"/>
<ui:param name="count" value="8"/>
<ui:param name="backgroundVar" value="0"/>
<ui:param name="isFirst" value="true"/>
</ui:include>
</div>
</h:panelGroup>
</h:body>
</html>
pv.xhtml:
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:my="http://xmlns.jcp.org/jsf/composite/cc">
<ui:repeat value="#{node}" var="forumPost">
<div style="padding: 0.4rem; border: 1px solid grey; margin-bottom: #{isFirst ? '1rem' : '0' }">
<h:panelGroup id="c">
<!-- comment content -->
<div class="replyContent">
<h:outputText value="#{forumPost.content}" escape="false"/>
</div>
<!-- reply block -->
<div class="replyAuthor" id="r-#{forumPost.idpost}">
<my:commentForm value="#{workingTest.replyContent}" actionMethod="#{workingTest.addReply(forumPost)}"/>
</div>
<!-- reply block end -->
</h:panelGroup>
<!-- Replies -->
<c:if test="#{count gt 0}">
<ui:include src="pv.xhtml">
<ui:param name="node" value="#{forumPost.replies}"/>
<ui:param name="count" value="#{count-1}"/>
<ui:param name="backgroundVar" value="#{backgroundVar+1}" />
<ui:param name="isFirst" value="false" />
</ui:include>
</c:if>
</div>
</ui:repeat>
</ui:composition>
commentForm.xhtml ( composite component )
<ui:composition
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:cc="http://xmlns.jcp.org/jsf/composite"
xmlns:my="http://xmlns.jcp.org/jsf/composite/cc">
<cc:interface>
<cc:attribute name="value" type="java.lang.String" required="true" />
<cc:attribute name="actionMethod" method-signature="void action()"/>
</cc:interface>
<cc:implementation>
<h:form>
<h:inputTextarea value="#{cc.attrs.value}"/>
<div class="replyBtn">
<h:commandButton value="Reply" action="#{cc.attrs.actionMethod}">
<f:ajax execute="@form" render="replies"/>
</h:commandButton>
</div>
</h:form>
</cc:implementation>
</ui:composition>
For the comments that works and for those that don't the http form data is the same. However for the JSF lifecycle the validators are not called for those that don't work and update phase doesn't call setters :
Working :
20:34:35,120 INFO [stdout] (default task-41) START PHASE RESTORE_VIEW 1
20:34:35,228 INFO [stdout] (default task-41) END PHASE RESTORE_VIEW 1
20:34:35,229 INFO [stdout] (default task-41) START PHASE APPLY_REQUEST_VALUES 2
20:34:35,229 INFO [stdout] (default task-41) before msg The form component needs to have a UIForm in its ancestry.
20:34:35,240 INFO [stdout] (default task-41) END PHASE APPLY_REQUEST_VALUES 2
20:34:35,240 INFO [stdout] (default task-41) START PHASE PROCESS_VALIDATIONS 3
20:34:35,240 INFO [stdout] (default task-41) before msg The form component needs to have a UIForm in its ancestry.
20:34:35,245 INFO [stdout] (default task-41) DummyConverter getAsObject
20:34:35,245 INFO [stdout] (default task-41) FieldsNotTooShort: validate
20:34:35,252 INFO [stdout] (default task-41) END PHASE PROCESS_VALIDATIONS 3
20:34:35,252 INFO [stdout] (default task-41) START PHASE UPDATE_MODEL_VALUES 4
20:34:35,253 INFO [stdout] (default task-41) before msg The form component needs to have a UIForm in its ancestry.
20:34:35,257 INFO [stdout] (default task-41) setting 5555555555555555555555
20:34:35,261 INFO [stdout] (default task-41) END PHASE UPDATE_MODEL_VALUES 4
Not working :
20:39:12,312 INFO [stdout] (default task-30) START PHASE RESTORE_VIEW 1
20:39:12,432 INFO [stdout] (default task-30) END PHASE RESTORE_VIEW 1
20:39:12,432 INFO [stdout] (default task-30) START PHASE APPLY_REQUEST_VALUES 2
20:39:12,432 INFO [stdout] (default task-30) before msg The form component needs to have a UIForm in its ancestry.
20:39:12,436 INFO [stdout] (default task-30) END PHASE APPLY_REQUEST_VALUES 2
20:39:12,436 INFO [stdout] (default task-30) START PHASE PROCESS_VALIDATIONS 3
20:39:12,437 INFO [stdout] (default task-30) before msg The form component needs to have a UIForm in its ancestry.
20:39:12,440 INFO [stdout] (default task-30) END PHASE PROCESS_VALIDATIONS 3
20:39:12,441 INFO [stdout] (default task-30) START PHASE UPDATE_MODEL_VALUES 4
20:39:12,441 INFO [stdout] (default task-30) before msg The form component needs to have a UIForm in its ancestry.
20:39:12,444 INFO [stdout] (default task-30) END PHASE UPDATE_MODEL_VALUES 4
lifecyclelistener:
public class LifeCycleListener implements PhaseListener {
public PhaseId getPhaseId() {
return PhaseId.ANY_PHASE;
}
public void beforePhase(PhaseEvent event) {
System.out.println("START PHASE " + event.getPhaseId());
List<FacesMessage> msgs = event.getFacesContext().getMessageList();
for (FacesMessage msg : msgs) {
System.out.println("before msg " + msg.getSummary() + " :: " + msg.getDetail());
}
}
public void afterPhase(PhaseEvent event) {
System.out.println("END PHASE " + event.getPhaseId());
}
}