1
votes

I have to retain information across a 3 page flow which uses ajax functionlity across all the three pages . So this is how i have designed the structure

1 ) One view scoped bean ( @ ViewScoped) containing action methods, Ajax methods as well as attributes needed for three pages

2)        Wizard.xhtml 
          |_  Step1.xhtml
          |_  step2.xhtml
          |_  step3.xhtml 

The Wizard xhtml includes these three facelets with conditional rendering based on which page the user is currently operating . So basically while navigating from step 1 to step 2 I would mention wizard.xhtml . so my doubt how does the bean instance live here . Does it live throughout the flow ( and if yes will it cause any performance issues) or it gets destroyed and then recreated again ?

Using this approach , I am able to retain the values across all pages but I wanted to know if this is really a good design approach and what implications/disadvantages it might bring to the table .? Thanks in advance :)

2
If you are using CDI try ConversationScoped. Otherwise conditionally render using panelGroups or other similar components from component libraries.Johny T Koshy

2 Answers

2
votes

You basically have 2 options of constructing a wizard-type flow. The first option is to show all steps within one view, the other one is to have the number of views as there are steps in your wizard.

All wizard steps within one view

As Xtreme Biker rightfully mentions the most natural way of designing the view is separating every step in a conditionally rendered component, like <h:panelGroup> and updating bean's property currentStep upon entering a different wizard step.

The basic view setup:

<h:panelGroup id="step1">
    <h:panelGroup rendered="#{bean.currentStep eq 1}">
        <ui:include src="step1.xhtml"/>
    </h:panelGroup>
</h:panelGroup>
...

The included page (step2.xhtml):

...
<h:commandButton value="Back" action="#{bean.back}">
    <f:ajax execute="step2" render="step1 step2"/>
</h:commandButton>
<h:commandButton value="Forward" action="#{bean.forward}">
    <f:ajax execute="step2" render="step1 step2"/>
</h:commandButton>
...

Backing bean:

@ManagedBean
@ViewScoped
public class Bean implements Serializable {

    ...
    private int currentStep = 1;//getter+setter

    public String forward() {
        ...
        if(currentStep == 2) {
             doSomethingWithValues();
             currentStep++;
        }
        ...
    }

    public String back() {
        ...
        if(currentStep == 2) {
             clearNecessaryValues();
             currentStep--;
        }
        ...
    }

}

This approach is good if you want to embed a customized content in your view. If you are good with a 'standard' approach, you would rather not reinvent the wheel and use <p:wizard> tag of Primefaces library, which does basically the same under the covers.

Every wizard step in a different view

If you are going to navigate to a different view by calling back/forward buttons and returning different navigation case outcomes each time your job can be done by using a flash object to transfer needed data to the next view.

So, the setup is going to be: wizard/step2.xhtml (one view per step) and one view scoped bean Bean.

One of the views (the second view)

...
<h:commandButton value="Back" action="#{bean.back}">
</h:commandButton>
<h:commandButton value="Forward" action="#{bean.forward}">
</h:commandButton>
...

Backing bean:

@ManagedBean
@ViewScoped
public class Bean implements Serializable {

    ...
    private int currentStep = 1;//getter+setter

    @ManagedProperty("#{flash}")
    private Flash flash;//getter+setter

    private Data step1Data;
    private Data step2Data;
    private Data step3Data;
    ...

    @PostConstruct
    public void init() {
        int step = Integer.parseInt(flash.get("newStep"));
        Data step1 = (Data)flash.get("step1");
        Data step2 = (Data)flash.get("step2");
        Data step3 = (Data)flash.get("step3");
        this.currentStep = step;
        this.step1Data = step1;
        this.step2Data = step2;
        this.step3Data = step3;
        ...
    }

    public String forward() {
        ...
        if(currentStep == 2) {
             doSomethingWithValues();
             currentStep++;
             flash.put("step", currentStep);
             flash.put("step1", step1Data);
             flash.put("step2", step2Data);
             return "wizard/step3?faces-redirect=true"
        }
        ...
    }

    public String back() {
        ...
        if(currentStep == 2) {
             clearNecessaryValues();
             currentStep--;
             flash.put("step", currentStep);
             flash.put("step1", step1Data);
             return "wizard/step1?faces-redirect=true"
        }
        ...
    }

}
1
votes

If your flow is going to be ajax based, you have no problem to use @ViewScoped bean. That kind of bean is going to be destroyed only when a navigation result is returned, so, as long as your ajax calls to switch between content are returning null or empty String values, you'll have no problem. Basically, you should use a component rendering based flow, use a variable in order to control them:

<h:outputPanel rendered="#{bean.screen eq 'first'}">
    //your first screen
</h:outputPanel>
<h:outputPanel rendered="#{bean.screen eq 'second'}">
    //your second screen
</h:outputPanel>
<h:outputPanel rendered="#{bean.screen eq 'third'}">
    //your third screen
</h:outputPanel>

Keep in mind you must take care about including your xhtml using <ui:include> tag because it is a tag handler an is being evaluated before the components themselves. So if you put one of them into the components above, they will be evaluated all of them, doesn't matter if they're going to be rendered or not. Have a look to a similar problem I experienced before.