1

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:

  • Why does JSF need access to #{simpleBean.outerStrings} to invoke the #{simpleBean.processInnerClick()} listener (outerStrings will have died in the previous request)
  • What is the best pattern for solving this?

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:

  • The #{simpleBean.processOuterClick()} listener fires ok and the #{outerLevel} command links render
  • But when I click on the #{outerLevel} command Link the #{simpleBean.processInnerClick()} listener is never fired
planetjones
  • 12,469
  • 5
  • 50
  • 51

1 Answers1

2

The 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.

See also:

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Thanks for the reply - but why does Jsf need access to outerStrings? The listener for the inner click makes no reference to it and I am not asking the Ajax call to evaluate the component (at least directly) which uses outerStrings for rendering – planetjones Jan 16 '12 at 17:24
  • It needs it in order to find the component associated with the action, so that it can be invoked. See also the 1st "See also" link. An alternative is to use `` instead to pass a request parameter and prepare exactly the desired model in the (post)constructor. See also http://stackoverflow.com/questions/4994458/how-can-i-pass-a-parameter-to-a-commandlink-inside-a-datatable – BalusC Jan 16 '12 at 17:27
  • Oh ok I see it. Thanks. It's Problems like this that I don't think jsf is that well suited to - it has the component tree and I just want to render a new component, not check my model data the original view was rendered with is still there. The end result is that data which I only want in the DOM has to live longer than I want on the server. – planetjones Jan 16 '12 at 17:30
  • You should not confuse JSF view state with HTML DOM tree. – BalusC Jan 16 '12 at 17:31
  • Hmm. Well in an action based framework I woul just call the server with Ajax, evaluate a JSP and send the output back. I would then append the content to my dom element. For my requirement that's what I want jsf to do - I don't want an Ajax call to need the whole set of data on the server which was used previously – planetjones Jan 16 '12 at 17:37
  • JSF is not an action based framework, but a stateful component based MVC framework. If you dislike it, then just don't use a stateful component based MVC framework from the beginning on. You only have to write all that HTML/CSS/JS boilerplate yourself. – BalusC Jan 16 '12 at 17:40
  • Yeah - I'm not trying to discredit JSf or anything, but my expected behaviour would be the Ajax calls do not need the whole model state to be there to work. Obviously not. And not always my choice whether JSF is used or not :) – planetjones Jan 16 '12 at 17:45
  • It only needs the "whole" model state if the links are dynamically generated, like as in your case inside a repeater component. In the view there's physically only one link component. JSF needs to reitarate over the value of the repeater component in order to find the action associated with the event and to prepare the right EL context during the action invocation. – BalusC Jan 16 '12 at 17:46