2

This is the list page I have:

<h:dataTable value="#{actorSearchBacking.all}" var="actor">
    <h:column>
        <f:facet name="header">
            First Name
        </f:facet>
        #{actor.firstname}
    </h:column>
    <h:column>
        <f:facet name="header">
            Last Name
        </f:facet>
        #{actor.lastname}
    </h:column>
    <h:column>
        <h:form>
            <h:commandButton value="Update Actor" action="pocdetail">
                <f:setPropertyActionListener target="#{actorFormBacking.stupidActor}" value="#{actor}"/>
            </h:commandButton>
        </h:form>
    </h:column>
</h:dataTable>

which looks something like this in my local environment:

Actor List Page

This is pocdetail.xhtml which is the action of Update Actor button:

<h:body>
    <h:form id="updateActorForm"
            prependId="false">
        <h:inputText id="firstname" value="#{actorFormBacking.stupidActor.firstname}"/>
        <h:inputText id="lastname" value="#{actorFormBacking.stupidActor.lastname}"/>
        <h:commandButton id="updateActorButton"
                         value="Update Actor!"
                         action="#{actorFormBacking.updateActor()}"/>
    </h:form>
</h:body>

And finally ActorFormBacking is as follows:

@ManagedBean
@ViewScoped
public class ActorFormBacking implements Serializable {

    private Actor stupidActor;

    public Actor getStupidActor() {
        return stupidActor;
    }

    public void setStupidActor(Actor stupidActor) {
        this.stupidActor = stupidActor;
    }
}

When I debug the application, I see that setStupidActor is called and property stupidActor is set, but then when getter is called, it is again null.

Since this is a ViewScoped bean, I am expecting the stupidActor not to be null and I expect to see the pocdetail.xhtml page to be filled with values, but all I see is empty input texts since stupidActor is null.

What is it that I am missing? Why is the ViewScoped bean created again and the property is null?

Btw, I am using the annotations from the packages:

import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
Koray Tugay
  • 20,438
  • 37
  • 155
  • 276
  • Check if the bean is being recreated. Write a constructor for it and check if it's invoked. If yes, then the problem might be the annotations not being properly evaluated. If not, then you're somewhere else changing the value for the actor property. – Xtreme Biker Sep 28 '16 at 11:37
  • @XtremeBiker I created the constructor and added a out.println("bean created"). When I click on "Update Actor" button, I see this line printed twice.. But why is the first bean gets killed, I can not figure out.. – Koray Tugay Sep 28 '16 at 12:57
  • Hi @BalusC I created an example you can download and immedialty try: https://github.com/koraytugay/viewscopeex I tried with Tomcat 8 and again, the ViewScoped bean will be created twice when I hit "Update" button in this example. I tried all you suggessted, no luck.. – Koray Tugay Oct 01 '16 at 11:37
  • Hi @BalusC here , the code I have uploaded to github in action: https://youtu.be/FHf1Ct-8-68 – Koray Tugay Oct 01 '16 at 11:41
  • I didn't run your sample, but I saw your github code and you are not implementing ```Serializable``` interface in both ```Actor``` class, nor managed beans. Try this, but keep in mind that @BalusC is right, ```ViewScope``` works on the same view, so if you change your view the scope is reset. – malaguna Oct 01 '16 at 15:48

1 Answers1

4

It appears that you're navigating from one view to another view. In other words, you destroy the current view and create a new view. Logically, the view scope will also get destroyed and newly created, including all view scoped managed beans. That the view scoped managed bean happens to be referenced by both views doesn't change this behavior.

A view scoped bean lives as long as the view itself. Like as that a request scoped bean lives as long as the request itself and so on. In order to have a better understanding of the lifetime of various scopes in JSF (and CDI), head to this Q&A: How to choose the right bean scope?

The functional requirement is however understood. You want separate master-detail pages and pass the selected item from the master page to the detail page for editing. There are several ways to achieve this:

  1. The canonical way is to just use a bookmarkable GET link instead of an unbookmarkable POST link. Replace the below piece

    <h:form>
        <h:commandButton value="Update Actor" action="pocdetail">
            <f:setPropertyActionListener target="#{actorFormBacking.stupidActor}" value="#{actor}"/>
        </h:commandButton>
    </h:form>
    

    by this

    <h:link value="Update Actor" outcome="pocdetail">
        <f:param name="stupidActor" value="#{actor.id}" />
    </h:link>
    

    and in the detail page, obtain the Actor by its identifier which is passed-in as query string parameter. This is fleshed out in detail in this Q&A: Creating master-detail pages for entities, how to link them and which bean scope to choose. A @FacesConverter(forClass) is very useful here.


  2. In case you want to stick to POST for some reason, then your best bet is storing it in the request scope.

    FacesContext.getCurrentInstance().getExternalContext().getRequestMap().put("stupidActor", stupidActor);
    

    and retrieve it in the @PostConstruct of the very same bean

    stupidActor = (Actor) FacesContext.getCurrentInstance().getExternalContext().getRequestMap().get("stupidActor");
    

  3. If you happen to use CDI, or are open to (which I strongly recommend though, JSF managed beans are already deprecated in JSF 2.3.0-m06, see also Backing beans (@ManagedBean) or CDI Beans (@Named)?), then consider using MyFaces CODI's @ViewAccessScoped. Beans with this scope will live as long as all postbacked views explicitly reference the bean. Once you navigate out with a GET, or when the navigated view doesn't anywhere reference that bean, then it will get destroyed.

    @Named
    @ViewAccessScoped
    public class ActorFormBacking implements Serializable {}
    

  4. Merge the both views into a single view with conditionally rendered master-detail sections. You can find a kickoff example in this Q&A: Recommended JSF 2.0 CRUD frameworks. Or if you happen to use PrimeFaces, How to show details of current row from p:dataTable in a p:dialog and update after save.

Community
  • 1
  • 1
BalusC
  • 992,635
  • 352
  • 3,478
  • 3,452
  • I wish the ViewScoped bean worked in this situation. Because for security reasons I do not want to use the get parameters, option 2 not maintanable and 3 is not possible. Thanks for the answer. – Koray Tugay Oct 02 '16 at 08:53
  • 1
    I don't see how it's a security problem. In your service layer you already do check if the current user is allowed to edit the given entity, right? A fourth way is to have master and detail in the same view and conditionally render the one or the other. – BalusC Oct 02 '16 at 09:13