4

I'm looking for best practices regarding preserving bean values between views using JSF 2.

Consider the following scenario:
* In view A, the view scoped bean ABean is instantiated and retrieves data for display in the view.
* In view A you can click on an entry to view details for it in view B.
* When returning from view B to view A, the data previously retrieved by ABean is displayed.

What is the best way to preserve the data retrieved by ABean, to be able to display it again when returning from view B?
Retrieving the data again, which would normally happen since a new instance of ABean is created when returning from view B, is not an option since it's a time-consuming operation and leads to a bad use experience.
Having ABean being session scoped is not an option, since if you leave the page and then return, the "cached" data would be displayed instead of retrieving new data (i.e. you cannot determine if you're loading view A as a result of navigating to the page or if you're returning from view B).

What I'm looking for is obviously a conversation scope, which solves this nicely (and is what we had before, when using JSF 1 and WebFlow). Unfortunately, JSF doesn't have that and since we're in a portlet environment we cannot use CDI.

Any thoughts?

Robert
  • 485
  • 2
  • 6
  • 12

1 Answers1

1

Using a single view with conditionally rendered content is the easiest.

E.g.

<h:form>
    <h:dataTable id="table" value="#{bean.items}" var="item" rendered="#{empty bean.item}">
        <h:column>
            #{item.foo}
        </h:column>
        <h:column>
            #{item.bar}
        </h:column>
        <h:column>
            <h:commandLink value="edit" action="#{bean.edit(item)}">
                <f:ajax execute="@this" render="@form" />
            </h:commandLink>
        </h:column>
    </h:dataTable>

    <h:panelGrid id="detail" columns="3" rendered="#{not empty bean.item}">
        <h:outputLabel for="foo" />
        <h:inputText id="foo" value="#{bean.item.foo}" />
        <h:message for="foo" />

        <h:outputLabel for="bar" />
        <h:inputText id="bar" value="#{bean.item.bar}" />
        <h:message for="bar" />

        <h:panelGroup />
        <h:commandButton value="save" action="#{bean.save}">
            <f:ajax execute="detail" render="@form" />
        </h:commandButton>
        <h:messages globalOnly="true" layout="table" />
    </h:panelGrid>
</h:form>

with the following view scoped bean

private List<Item> items;
private Item item;

@EJB 
private ItemService itemService;

@PostConstruct
public void init() {
    items = itemService.list();
}

public void edit(Item item) {
    this.item = item;
}

public void save() {
    itemService.save(item);
    item = null;
}

The view parts could if necessary be split out over two <ui:include>s. The bean could if necessary be split out over two beans (one for each part) of which the detail one has the table one as managed property. But that's imo not necessary and only makes it more complicated.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • I've considered this kind of solution and I believe it would work fine for the scenario described. However, what if you have a more complicated page with say 10 - 15 views, with view traversal in more than 1 step, e.g. display list => view details => press delete button => confirmation view? Feels like you would end up with a state machine in the backing bean to determine what content to render in the view. – Robert Aug 18 '11 at 09:37