4

I'm wondering what the best practices are to pass data (an object) between two ViewScoped beans.

They need to be view scoped because of the problem that's brilliantly explained here (to put it short: In both views I'm using a h:commandLink from within a h:dataTable which requires the data model to still be present when submitting).

My problem now is that clicking the link also navigates to a new view, so using the following code, my object gets passed but the DetailViewController instance gets killed right after that and a new one is created when the view changes (as you would expect).

View:

<h:dataTable value="#{searchController.dataModel}" var="item">
    ...
    <h:column>
        <f:facet name="header">Action</f:facet>
        <h:commandLink id="open" value="open" action="#{searchController.showDetail(item)}" />
    </h:column>
</h:dataTable>

Bean:

@ManagedBean
@ViewScoped
public class SearchController {

    @ManagedProperty(value="#{detailViewController}")
    private DetailViewController detailViewController;

    // getters, setters, etc. ...

    public String showDetail(Item i) {
        detailViewController.setItem(i);
        return "view_detail.xhtml";
    }

}

How would you solve this? I thought about putting the object inside Flash: FacesContext.getExternalContext.getFlash()... Is there an easier or more elegant solution?

Community
  • 1
  • 1
syntaxerror
  • 106
  • 1
  • 6
  • I forgot to mention that I'm on Servlet 3.0/JSF 2.2 (Mojarra), if that's relevant. – syntaxerror Jan 24 '14 at 17:59
  • Don't set the `detailViewController` as a managed property, that's plain wrong. Return its view id (can be `view_detail` in your case, note I'm pruning the `.xhtml`). Later on, reference `detailViewController` from *view_detail.xhtml* and you'll get it instantiated by the framework. For passing the param, you have a wide range of options, as view param, using flash scope, using the flow scope... Just google it and you'll find interesting references at SO, here you've got my own ones: [flash](http://stackoverflow.com/a/21277621/1199132) and [view](http://stackoverflow.com/a/20882154/1199132) – Aritz Jan 24 '14 at 19:28
  • You can actually return a file name instead of a ViewID in JSF 2. ;-) As for the `ManagedProperty`, it's a solution that works perfectly with RequestScoped beans: The purpose is just getting a reference to the next view's bean, so you can initialize it beforehand. But with ViewScoped beans, there is no point obviously. So I guess, using flash scope isn't the worst idea here. Thank you for your answer, I was thinking I might be overlooking something and it could be achieved even easier ;-)) – syntaxerror Jan 24 '14 at 23:07
  • Initializing the beans before could work, but is not JSF way to do it and has never been AFAIK. You're in fact promoting an unecessary code coupling, a view scoped or session scoped bean should never know about other beans that are at the same or narrower scope. You just have to take care about passing the proper params to the target view and the framework will take care about initializing it. – Aritz Jan 25 '14 at 00:54

1 Answers1

1

You can use view parameters. (See How do you pass view parameters when navigating from an action in JSF2?)

Typically, your method return the url with query parameters:

 public String showDetail(Item i) {
    return "view_detail.xhtml?id="+i.getId();
 }

And in your view_detail.xhtml file, you add a f:viewParam tag evaluating to on of your bean field:

<f:metadata>
    <f:viewParam name="id" value="#{myBean.id}" />
</f:metadata>

Then from your backing bean, you use that field to get your Item instance in your @postConstruct method. If you don't use the f:viewparam tag, you can also fetch the request parameters to obtain the id.

private String id;
private Item item;

@PostConstruct
public void init() {
  if (id != null) {
    item = fetchItem(id);
  } else {
    FacesContext facesContext = FacesContext.getCurrentInstance();
    ExternalContext externalContext = facesContext.getExternalContext();
    Map<String, String> requestParameterMap = externalContext.getRequestParameterMap();
    if (requestParameters.containsKey("id")) {
       id = requestParameters.get("id");
       item = fetchItem(id);
    } else {
       throw new WebServiceException("No item id in request parameters");
    }
  }
}
Community
  • 1
  • 1
cghislai
  • 1,751
  • 15
  • 29