Page Bean Patterns

Introduction

In the Developer's Guide you find the definition what a page bean is and how is is used. This technical documentation shows some typical usage patterns.

First of all: page beans are just normal beans, representing the interaction processing of one page. The page bean tells at runtime about the name of this page (“xxx.jsp”) and about how the bean is addressed within the page (“#{d.XxxUI”).

We often see that developers need to be encouraged to apply all their Java-thinking that they normally have also to page beans. You can build up your own class hierarchy, you can set up events and event listening, you can basically do everything that is possible with Java – also with page beans!

The great advantage of page beans is that they can be re-used in a simple and flexible way. Once having developed a page bean, you can embed it into other pages (component ROWPAGEBEANINCLUDE) or you can open it as popup dialog.

Example Scenario

The following scenarios is the one to be discussed in the following:



The user sees a list of items. By double clicking an item the details of the item are shown:



From the details the user gets back to the list when pressing “Save” or “Cancel”.

General Arrangement of Pages

CaptainCasa only knows two ways of navigation:

In the scenario we clearly do not have any popup, so there is only one way: the embedding way. This means, we have three page beans to form the scenario:

It's now interesting to see what possibilities you might use to make these beans talk to one another in a structured way.

Pattern “Active Navigation Controller”

The outside Page Bean

The layout of the outside page is simple:

<t:rowtitlebar id="g_1" text="Example - Active Navigation Controller" />

<t:rowpagebeaninclude id="g_2" pagebeanbinding="#{d.Nav1OutestUI.contentBean}" />

<t:rowstatusbar id="g_3" />

 

It contains a title bar, a status bar and in between a ROWPAGEBEANINCLUDE component, which is bound to the corresponding page bean's “contentBean” property:

package managedbeans;

 

import java.io.Serializable;

 

import org.eclnt.editor.annotations.CCGenClass;

import org.eclnt.jsfserver.pagebean.IPageBean;

import org.eclnt.jsfserver.pagebean.PageBean;

 

import entities.PersonEntity;

 

@CCGenClass (expressionBase="#{d.Nav1OutestUI}")

 

public class Nav1OutestUI

    extends PageBean

    implements Serializable

{

    // ------------------------------------------------------------------------

    // members

    // ------------------------------------------------------------------------

    

    IPageBean m_contentBean;

 

    // ------------------------------------------------------------------------

    // constructors

    // ------------------------------------------------------------------------

    

    public Nav1OutestUI()

    {

        switchToList();

    }

 

    public String getPageName() { return "/ccworkshop/nav1/nav1outest.jsp"; }

    public String getRootExpressionUsedInPage() { return "#{d.Nav1OutestUI}"; }

 

    // ------------------------------------------------------------------------

    // public usage

    // ------------------------------------------------------------------------

    

    public IPageBean getContentBean() { return m_contentBean; }

    

    public void switchToList()

    {

        Nav1ListUI lui = new Nav1ListUI();

        lui.prepare(new Nav1ListUI.IListener()

        {

            @Override

            public void reactOnSelection(Nav1ListUI source, PersonEntity p)

            {

                switchToDetail(p);

            }

        });

        m_contentBean = lui;

    }

    

    public void switchToDetail(PersonEntity p)

    {

        Nav1DetailUI dui = new Nav1DetailUI();

        dui.prepare(p,new Nav1DetailUI.IListener()

        {

            @Override

            public void reactOnSave(Nav1DetailUI source)

            {

                switchToList();

            }

            @Override

            public void reactOnCancel(Nav1DetailUI source)

            {

                switchToList();

            }

        });

        m_contentBean = dui;

    }

    

}

 

The member “m_contentBean” is holdingthe page bean to be embedded within the page. In the constructor the “switchToList()” method is called, within this method the list page bean (Nav1ListUI) is created and set as content. Result: inside then content of the outer bean the list is displayed.

The interesting aspect is the creation of the list bean – which is designed to be an autarc page bean:

The reaction of the outside bean when an item was selected in the list bean is, that the method “switchtoDetail(...)” is called. Things happening now are very similar:

As reaction on “save” and “cancel” the outside bean navigates back to the list bean.

The List Page Bean

The layout of the list page bean is:

<t:rowbodypane id="g_1" >

<t:row id="g_2" >

<t:fixgrid id="g_3" bordercolor="#00000030" borderheight="1" borderwidth="1" height="100%" objectbinding="#{d.Nav1ListUI.grid}" sbvisibleamount="20" width="100%" >

  <t:gridcol id="g_4" text="Id" width="100" >

    <t:label id="g_5" text=".{p.id}" />

  </t:gridcol>

  <t:gridcol id="g_6" text="First Name" width="50%" >

    <t:label id="g_7" text=".{p.firstName}" />

  </t:gridcol>

  <t:gridcol id="g_8" text="Last Name" width="50%" >

    <t:label id="g_9" text=".{p.lastName}" />

  </t:gridcol>

</t:fixgrid>

</t:row>

</t:rowbodypane>

 

The code of the list page bean is:

package managedbeans;

 

import java.io.Serializable;

import java.util.List;

 

import logic.Facade;

 

import org.eclnt.editor.annotations.CCGenClass;

import org.eclnt.jsfserver.elements.impl.FIXGRIDItem;

import org.eclnt.jsfserver.elements.impl.FIXGRIDListBinding;

import org.eclnt.jsfserver.pagebean.PageBean;

 

import entities.PersonEntity;

 

@CCGenClass (expressionBase="#{d.Nav1ListUI}")

 

public class Nav1ListUI

    extends PageBean

    implements Serializable

{

    // ------------------------------------------------------------------------

    // inner classes

    // ------------------------------------------------------------------------

    

    public interface IListener

    {

        public void reactOnSelection(Nav1ListUI source, PersonEntity p);

    }

    

    public class GridItem extends FIXGRIDItem implements java.io.Serializable

    {

        PersonEntity i_p;

        public GridItem(PersonEntity p)

        {

            i_p = p;

        }

        public PersonEntity getP() { return i_p; }

        @Override

        public void onRowExecute()

        {

            if (m_listener != null)

                m_listener.reactOnSelection(Nav1ListUI.this,i_p);

        }

    }

    

    // ------------------------------------------------------------------------

    // members

    // ------------------------------------------------------------------------

    

    FIXGRIDListBinding<GridItem> m_grid = new FIXGRIDListBinding<GridItem>();

    IListener m_listener;

 

    // ------------------------------------------------------------------------

    // constructors

    // ------------------------------------------------------------------------

    

    public Nav1ListUI()

    {

        List<PersonEntity> pes = Facade.readAllPersons(null,null);

        for (PersonEntity pe: pes)

            m_grid.getItems().add(new GridItem(pe));

    }

 

    public String getPageName() { return "/ccworkshop/nav1/nav1list.jsp"; }

    public String getRootExpressionUsedInPage() { return "#{d.Nav1ListUI}"; }

 

    // ------------------------------------------------------------------------

    // public usage

    // ------------------------------------------------------------------------

    

    public void prepare(IListener listener)

    {

        m_listener = listener;

    }

    

    public FIXGRIDListBinding<GridItem> getGrid() { return m_grid; }

 

    // ------------------------------------------------------------------------

    // private usage

    // ------------------------------------------------------------------------

 

}

 

The bean provides:

The listener (“IListener”) is defined as inner class directly within the page bean implementation.

The Detail Page Bean

The layout of the detail page bean is:

<t:rowheader id="g_1" >

  <t:button id="g_2" actionListener="#{d.Nav1DetailUI.onSaveAction}" text="Save" />

  <t:coldistance id="g_3" width="100%" />

  <t:button id="g_4" actionListener="#{d.Nav1DetailUI.onCancelAction}" text="Cancel" />

</t:rowheader>

<t:rowbodypane id="g_5" rowdistance="5" >

  <t:row id="g_6" >

    <t:label id="g_7" text="Id" width="100" />

    <t:field id="g_8" enabled="false" text="#{d.Nav1DetailUI.p.id}" width="100%" />

  </t:row>

  <t:row id="g_9" >

    <t:label id="g_10" text="First Name" width="100" />

    <t:field id="g_11" text="#{d.Nav1DetailUI.p.firstName}" width="100%" />

  </t:row>

  <t:row id="g_12" >

    <t:label id="g_13" text="Last Name" width="100" />

    <t:field id="g_14" text="#{d.Nav1DetailUI.p.lastName}" width="100%" />

  </t:row>

</t:rowbodypane>

 

The code of the detail page bean is:

package managedbeans;

 

import java.io.Serializable;

 

import javax.faces.event.ActionEvent;

 

import logic.Facade;

 

import org.eclnt.editor.annotations.CCGenClass;

import org.eclnt.jsfserver.pagebean.PageBean;

 

import entities.PersonEntity;

 

@CCGenClass (expressionBase="#{d.Nav1DetailUI}")

 

public class Nav1DetailUI

    extends PageBean

    implements Serializable

{

    // ------------------------------------------------------------------------

    // inner classes

    // ------------------------------------------------------------------------

    

    public interface IListener

    {

        public void reactOnCancel(Nav1DetailUI source);

        public void reactOnSave(Nav1DetailUI source);

    }

    

    // ------------------------------------------------------------------------

    // members

    // ------------------------------------------------------------------------

    

    PersonEntity m_p;

    IListener m_listener;

    

    // ------------------------------------------------------------------------

    // constructors

    // ------------------------------------------------------------------------

    

    public Nav1DetailUI()

    {

    }

 

    public String getPageName() { return "/ccworkshop/nav1/nav1detail.jsp"; }

    public String getRootExpressionUsedInPage() { return "#{d.Nav1DetailUI}"; }

 

    // ------------------------------------------------------------------------

    // public usage

    // ------------------------------------------------------------------------

    

    public void prepare(PersonEntity p, IListener listener)

    {

        m_p = p.clone();

        m_listener = listener;

    }

    

    public PersonEntity getP() { return m_p; }

    

    public void onCancelAction(ActionEvent event)

    {

        if (m_listener != null) m_listener.reactOnCancel(this);

    }

 

    public void onSaveAction(ActionEvent event)

    {

        Facade.savePerson(m_p);

        if (m_listener != null) m_listener.reactOnSave(this);

    }

    

    // ------------------------------------------------------------------------

    // private usage

    // ------------------------------------------------------------------------

 

}

 

Once again:

The listener again is implemented as inner class.

More abstract View on Pattern

In this pattern each bean that is used is an autarc bean with a defined interface to its user:

The outside page is controlling the navigation by creating the beans to form its content, and it listens to the beans by using the listener they expose:

In our case the navigation logic is an explicit one: the logic that rules the page flow is coded. Of course you can now start to build abstractions and introduce navigation concepts according to your need.

Autarc Page Bean – Use Listeners!

The concept of the previous chapter was based on the fact that each page bean in principle is autarc:

This is a very strong concept from our point of view because it grants the re-usability of the page bean. When creating a page bean in the CaptainCasa toolset we already create it with having a listener and a prepare method.

Please also pay attention: what we described in the previous chapter has nothing to do with CaptainCasa at all! It's just a way to make three different objects talk to one another in some consolidated way.

Pattern “Passive Navigation Controller”

The same scenario, but now implemented in a different way. Whereas the control about the navigation in the previous chapter was completely in the hand of the outside page, you might want to push the responsibility for navigation a bit close into the corresponding page beans.

Example: what happens when the user double clicks an item in the list?

The layout of the pages are still the same, but now the internal processing is different of course.

The outside Page Bean

The code looks as follows:

There is an interface by which you can trigger a nivgation:

package managedbeans;

 

import org.eclnt.jsfserver.pagebean.IPageBean;

 

public interface INav2Controller

{

    public void showNextPage(IPageBean pageBean);

    public void back();

}

 

The interface is implemented by the outside page bean:

package managedbeans;

 

import java.io.Serializable;

import java.util.Stack;

 

import org.eclnt.editor.annotations.CCGenClass;

import org.eclnt.jsfserver.pagebean.IPageBean;

import org.eclnt.jsfserver.pagebean.PageBean;

 

@CCGenClass (expressionBase="#{d.Nav1OutestUI}")

 

public class Nav2OutestUI

    extends PageBean

    implements Serializable, INav2Controller

{

    // ------------------------------------------------------------------------

    // inner classes

    // ------------------------------------------------------------------------

    

    // ------------------------------------------------------------------------

    // members

    // ------------------------------------------------------------------------

    

    Stack<IPageBean> m_contentBeans = new Stack<IPageBean>();

 

    // ------------------------------------------------------------------------

    // constructors

    // ------------------------------------------------------------------------

    

    public Nav2OutestUI()

    {

        Nav2ListUI lui = new Nav2ListUI();

        showNextPage(lui);

    }

 

    public String getPageName() { return "/ccworkshop/nav2/nav2outest.jsp"; }

    public String getRootExpressionUsedInPage() { return "#{d.Nav2OutestUI}"; }

 

    // ------------------------------------------------------------------------

    // public usage

    // ------------------------------------------------------------------------

    

    public IPageBean getContentBean()

    {

        if (m_contentBeans.size() == 0)

            return null;

        else

            return m_contentBeans.peek();

    }

 

    @Override

    public void showNextPage(IPageBean pageBean)

    {

        if (pageBean instanceof INav2Controlled)

        {

            ((INav2Controlled)pageBean).prepareNavController(this);

        }

        m_contentBeans.push(pageBean);

    }

 

    @Override

    public void back()

    {

        if (m_contentBeans.size() != 0)

            m_contentBeans.pop();

        IPageBean newContentBean = getContentBean();

        if (newContentBean instanceof INav2Controlled)

        {

            ((INav2Controlled)newContentBean).refreshContent();

        }

    }

    

    // ------------------------------------------------------------------------

    // private usage

    // ------------------------------------------------------------------------

 

}

 

The outside page bean internally holds a stack of page beans. When a new page is to be shown, then the corresponding page bean is put on top of the stack. When a page is removed (“back()”) then the top page is pulled from the stack. The actual content of the bean is always the top most page bean within the stack.

You see: no more navigation logic (if this happens, then this navigation) is part of the outside bean anymore. It just provides an interface in which the methods for technically executing a navigation are part of.

This interface is passed into the “prepareNavController(..)” method when navigating to a certain bean. The code of the interface is:

package managedbeans;

 

public interface INav2Controlled

{

    public void prepareNavController(INav2Controller navController);

    public void refreshContent();

}

 

The List Page Bean

The list page bean now does not operated with listeners anymore but actively triggers the navigation itself:

package managedbeans;

 

import java.io.Serializable;

import java.util.List;

 

import logic.Facade;

 

import org.eclnt.editor.annotations.CCGenClass;

import org.eclnt.jsfserver.elements.impl.FIXGRIDItem;

import org.eclnt.jsfserver.elements.impl.FIXGRIDListBinding;

import org.eclnt.jsfserver.pagebean.PageBean;

 

import entities.PersonEntity;

 

@CCGenClass (expressionBase="#{d.Nav1ListUI}")

 

public class Nav2ListUI

    extends PageBean

    implements Serializable, INav2Controlled

{

    // ------------------------------------------------------------------------

    // inner classes

    // ------------------------------------------------------------------------

    

    public class GridItem extends FIXGRIDItem implements java.io.Serializable

    {

        PersonEntity i_p;

        public GridItem(PersonEntity p)

        {

            i_p = p;

        }

        public PersonEntity getP() { return i_p; }

        @Override

        public void onRowExecute()

        {

            if (m_navController != null)

            {

                Nav2DetailUI dui = new Nav2DetailUI();

                dui.prepare(i_p);

                m_navController.showNextPage(dui);

            }

        }

    }

    

    // ------------------------------------------------------------------------

    // members

    // ------------------------------------------------------------------------

    

    FIXGRIDListBinding<GridItem> m_grid = new FIXGRIDListBinding<GridItem>();

    INav2Controller m_navController;

 

    // ------------------------------------------------------------------------

    // constructors

    // ------------------------------------------------------------------------

    

    public Nav2ListUI()

    {

        refreshContent();

    }

 

    public String getPageName() { return "/ccworkshop/nav2/nav2list.jsp"; }

    public String getRootExpressionUsedInPage() { return "#{d.Nav2ListUI}"; }

 

    // ------------------------------------------------------------------------

    // public usage

    // ------------------------------------------------------------------------

    

    @Override

    public void prepareNavController(INav2Controller navController)

    {

        m_navController = navController;

    }

 

    @Override

    public void refreshContent()

    {

        m_grid.getItems().clear();

        List<PersonEntity> pes = Facade.readAllPersons(null,null);

        for (PersonEntity pe: pes)

            m_grid.getItems().add(new GridItem(pe));

    }

 

    public FIXGRIDListBinding<GridItem> getGrid() { return m_grid; }

 

    // ------------------------------------------------------------------------

    // private usage

    // ------------------------------------------------------------------------

 

}

 

When being double clicked then the bean creates a new instance of a detail page bean, initializes it and tells the navigation to show it.

The Detail Page Bean

package managedbeans;

 

import java.io.Serializable;

 

import javax.faces.event.ActionEvent;

 

import logic.Facade;

 

import org.eclnt.editor.annotations.CCGenClass;

import org.eclnt.jsfserver.pagebean.PageBean;

 

import entities.PersonEntity;

 

@CCGenClass (expressionBase="#{d.Nav1DetailUI}")

 

public class Nav2DetailUI

    extends PageBean

    implements Serializable, INav2Controlled

{

    // ------------------------------------------------------------------------

    // members

    // ------------------------------------------------------------------------

    

    PersonEntity m_p;

    INav2Controller m_navController;

    

    // ------------------------------------------------------------------------

    // constructors

    // ------------------------------------------------------------------------

    

    public Nav2DetailUI()

    {

    }

 

    public String getPageName() { return "/ccworkshop/nav2/nav2detail.jsp"; }

    public String getRootExpressionUsedInPage() { return "#{d.Nav2DetailUI}"; }

 

    // ------------------------------------------------------------------------

    // public usage

    // ------------------------------------------------------------------------

    

    @Override

    public void prepareNavController(INav2Controller navController)

    {

        m_navController = navController;

    }

 

    @Override

    public void refreshContent()

    {

    }

 

    public void prepare(PersonEntity p)

    {

        m_p = p.clone();

    }

    

    public PersonEntity getP() { return m_p; }

    

    public void onCancelAction(ActionEvent event)

    {

        if (m_navController != null) m_navController.back();

    }

 

    public void onSaveAction(ActionEvent event)

    {

        Facade.savePerson(m_p);

        if (m_navController != null) m_navController.back();

    }

    

    // ------------------------------------------------------------------------

    // private usage

    // ------------------------------------------------------------------------

 

}

 

Within the “onCancelAction” and “onSaveAction” methods the navigation controller explicitly is called to navigate back to its previous content.

More Abstract View on Pattern

The navigation logic is split:

Discussion

Both patterns in this documentation are technically valid, of course:

What you also see is, that you are the owner of the question how navigation is done. And that you can use any Java-thinking in order to implement it. At the end it's just the question of objects talking to one another...