I have put my problem case below, which simplifies a real world problem I am having. By changing the Bean to @SessionScoped I can solve this, but that is really undesirable. As is @ViewScoped. I think it should work in @RequestScoped. My questions are:
#{simpleBean.outerStrings}
to invoke the #{simpleBean.processInnerClick()}
listener (outerStrings will have died in the previous request)Happy to edit to make clearer - just let me know.
Bean class
import javax.enterprise.context.RequestScoped;
import javax.enterprise.context.SessionScoped;
import javax.inject.Named;
@Named
@RequestScoped
public class SimpleBean implements Serializable {
private List<String> topLevelStrings =
Arrays.asList("TopLevel1", "TopLevel2");
private List<String> outerStrings;
private List<String> innerStrings;
public void processOuterClick() {
System.err.println("processOuterClick()");
outerStrings = Arrays.asList("Outer1", "Outer2", "Outer3");
}
public void processInnerClick() {
System.err.println("processInnerClick()");
innerStrings = Arrays.asList("InnerA", "InnerB", "InnerC");
}
public List<String> getOuterStrings() {
return outerStrings;
}
public List<String> getInnerStrings() {
return innerStrings;
}
public List<String> getTopLevelStrings() {
return topLevelStrings;
}
}
XHTML
<h:form>
<ui:repeat var="toplevel" value="#{simpleBean.topLevelStrings}"
id="toplevel_id">
<h:commandLink id="toplevel_command">
#{toplevel} <br/>
<f:ajax render="outerlevel_panel"
listener="#{simpleBean.processOuterClick()}"/>
</h:commandLink>
<h:panelGroup id="outerlevel_panel">
<ui:repeat var="outerLevel" value="#{simpleBean.outerStrings}"
id="outerlevel_id">
<h:commandLink id="outerlevel_command">
#{outerLevel} <br/>
<f:ajax listener="#{simpleBean.processInnerClick()}" render="innerlevel_panel"/>
</h:commandLink>
<h:panelGroup id="innerlevel_panel">
<ui:repeat var="innerLevel" value="#{simpleBean.innerStrings}"
id="innerlevel_id">
<h:commandLink id="innerlevel_command">
#{innerLevel} <br/>
</h:commandLink>
</ui:repeat>
</h:panelGroup>
</ui:repeat>
</h:panelGroup>
</ui:repeat>
</h:form>
Basically:
#{simpleBean.processOuterClick()}
listener fires ok and the #{outerLevel} command links render#{outerLevel}
command Link the #{simpleBean.processInnerClick()}
listener is never firedThe request scope is the wrong scope for this particular requirement. A request scoped bean is garbaged by end of the response and a new one will be created in the subsequent request with all of its properties set to default. In JSF2 terms, you really need the view scope. The session scope is indeed too broad and much worse for this requirement than the request scope (when an enduser opens the same page in multiple windows/tabs, they will share physically the one and same bean, resulting in unintuitive per-view behaviour when the enduser switches between views after interactions).
All you need to do to fix your particular problem is to use @ManagedBean
@ViewScoped
:
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
@ManagedBean
@ViewScoped
public class SimpleBean implements Serializable {
// ...
}
As you seem to prefer CDI management over JSF management for some reason, the CDI alternative to @ViewScoped
is @ConversationScoped
.
import javax.enterprise.context.Conversation;
import javax.enterprise.context.ConversationScoped;
import javax.inject.Named;
@Named
@ConversationScoped
public class SimpleBean implements Serializable {
@Inject
private Conversation conversation;
@PostConstruct
public void init() {
conversation.begin();
}
public String navigateToOtherPage() {
conversation.end();
return "otherPage?faces-redirect=true";
}
// ...
}
You only have to manage the begin and end of the conversation yourself.