Search This Blog

Loading...

Thursday, June 9, 2011

JSF2 Flash scope example

JSF 2 introduced new scope called Flash scope that  provides a way to pass temporary objects between user views generated by the faces lifecycle.

By using flash scope you don't have to use session to store objects so that you could access them across requests. In this example I'll show you how to transfer data between three views - inputForm.xhtml, confirm.xhtml and done.xhtml by using flash scope. It is a good practice to use as little as possible session scope to minimize memory consumption. 

So, I have simple RequestScoped bean that will hold user entered values when user submits form and will perform navigation. After user submits form on inputForm.xhtml he is redirected to confirm.xhtml view where he can preview data and confirm submitted data. If user clicks edit he is returned back to inputForm.xhtml page allowing him to change input. If user clicks confirm he is redirected to done.xhtml view where final info is shown after successfull processing.

Navigation between pages is done by using POST REDIRECT GET pattern (Michael Jouravlev, 2004., theserverside.com) that states:
  1. Never show pages as response of POST request
  2. Always load page with GET request
  3. Navigate between POST to GET using redirect
If i don't use redirect after submitting form on inputForm.xhtml page, user  will see confirm.xhtml page content but browser url will point to inputForm.xhtml and that can be confusing. So, to avoid that, I use ?faces-redirect=true that causes POSTback to faces server. Server then responds with 302 REDIRECT HTTP response (with Location http header including confirm.xhtml) and then browser issues GET request to load confirm.xhtml page.
    So, here is an example of my RequestScoped bean:

    @ManagedBean  
     @RequestScoped  
     public class ModelBean {  
            
          private String firstName;  
          private String lastName;  
            
          public ModelBean() {  
               super();  
          }  
            
          /**  
           * set submitted values to flash scope and   
           * redirect to confirm view  
           * @return  
           */  
          public String goToConfirmView() {  
               Flash flash = FacesContext.getCurrentInstance().  
                    getExternalContext().getFlash();  
               flash.put("firstName", firstName);  
               flash.put("lastName", lastName);  
                 
               return "confirm?faces-redirect=true";  
          }  
            
          /**  
           * redirect to confirm view   
           * @return  
           */  
          public String goToInputFormView() {  
               return "inputForm?faces-redirect=true";  
          }  
       
          /**  
           * Called on confirm.xhtml page  
           * here could be some database   
           * processing  
           * @return  
           */  
          public String insertValue() {  
               Flash flash = FacesContext.getCurrentInstance().  
                                        getExternalContext().getFlash();  
               //preserve messages across redirect  
               flash.setKeepMessages(true);  
                 
               pullValuesFromFlash(null);  
       
               //do something with firstName, lastName  
               System.out.println("First name: " + firstName);  
               System.out.println("Last name: " + lastName);  
                 
               FacesContext.getCurrentInstance().addMessage(null,   
                         new FacesMessage("Value inserted"));  
               return "done?faces-redirect=true";  
          }  
            
          /**  
           * System event called before view rendering   
           * used to pull values from flash and set to  
           * bean properties  
           * @param e  
           */  
          public void pullValuesFromFlash(ComponentSystemEvent e) {  
               Flash flash = FacesContext.getCurrentInstance().  
                                   getExternalContext().getFlash();  
               firstName = (String)flash.get("firstName");  
               lastName = (String)flash.get("lastName");  
          }  
            
                 
          public String getFirstName() {  
               return firstName;  
          }  
       
          public void setFirstName(String firstName) {  
               this.firstName = firstName;  
          }  
       
          public String getLastName() {  
               return lastName;  
          }  
       
          public void setLastName(String lastName) {  
               this.lastName = lastName;  
          }  
     }  

    inputForm.xhtml:

    <h:head>  
          <title>Input form</title>  
     </h:head>  
     <h:body>  
          <f:metadata>  
               <f:event type="preRenderView" listener="#{modelBean.pullValuesFromFlash}"/>  
          </f:metadata>  
          <h:form>  
               <h:messages />  
               <h:panelGrid columns="2">  
                    <h:outputText value="First name:" />  
                    <h:inputText value="#{modelBean.firstName}" required="true"/>  
                      
                    <h:outputText value="Last name:" />  
                    <h:inputText value="#{modelBean.lastName}" required="true" />  
               </h:panelGrid>  
               <h:commandButton value="Next"   
                    action="#{modelBean.goToConfirmView}" />  
          </h:form>  
     </h:body>  
    

    After submitting form by clicking on the Next button, values are put to flash scope in goToConfirmView action method and browser is redirected to confirm page. Notice that I am using confirm?faces-redirect=true. Since values put to flash scope survive redirects those values are available on confirm.xhtml page.
    inputForm.xhtml view uses system event preRenderViewEvent (called during render response phase) to load values from flash and set them to ModelBean properties. This is used when user clicks edit button on confirm.xhtml view and is redirected back to inputForm.xhtml with prepopulated values.

    confirm.xhtml:

    <h:head>  
          <title>Confirm page</title>  
     </h:head>  
     <h:body>       
          <h:form>  
               <h:panelGrid columns="2">  
                    <h:outputText value="First name:" />  
                    <h:outputText value="#{flash.keep.firstName}" />  
                      
                    <h:outputText value="Last name:" />  
                    <h:outputText value="#{flash.keep.lastName}" />  
               </h:panelGrid>       
               <h:commandButton value="Edit"   
                    action="#{modelBean.goToInputFormView}" />  
               <h:commandButton value="Confirm"   
                    action="#{modelBean.insertValue}" />  
          </h:form>  
     </h:body>  
    

    Confirm page simply displays entered values. Values are fetched from flash scope object.Notice that I am using #{flash.keep.firstName} instead of #{flash.firstName}. That is because values put to flash survive only one redirect and then are cleared out.Since I put values on inputForm.xhtml page and redirect to confirm.xhtml page I must tell the flash to keep (by keep keyword) values in flash scope for the next request as I want to fetch them also on the done.xhtml view or inputForm.xhtml view. If user clicks Edit button he is redirected to inputForm.xhtml view with already populated values for edit.

    done.xhtml;

    <h:head>  
          <title>Done page</title>  
     </h:head>  
     <h:body>  
          <f:metadata>  
               <f:event type="preRenderView" listener="#{modelBean.pullValuesFromFlash}"/>  
          </f:metadata>  
          <h:messages />  
          <h:panelGrid columns="2">  
                    <h:outputText value="First name:" />  
                    <h:outputText value="#{modelBean.firstName}" />  
                      
                    <h:outputText value="Last name:" />  
                    <h:outputText value="#{modelBean.lastName}" />  
          </h:panelGrid>       
          <h:link outcome="inputForm" value="Back to inputForm"/>  
     </h:body>  
    

    done.xhtml view uses same method to obtain flash values as inputForm.xhtml view by using system preRenderViewEvent to load values from flash and set them to bean properties.

    Source code can be downloaded from the following link:
    FlashExample.war

    4 comments:

    1. This is great, helped me a lot. Clean and clear. Thank you!

      ReplyDelete
    2. Thanks for the awesome article

      ReplyDelete
    3. Thnks for this good article.I am trying to achieve something like this but I am stucked.I would like to populate Data Table from ViewScoped managed bean using flash.
      The basic idea is we have a form for searching and another one for rendering the result of the search in a datatable.

      ReplyDelete