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.
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”.
CaptainCasa only knows two ways of navigation:
Embedding pages into one another.
Popup Dialogs.
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:
An outside bean controlling the “page flow”.
Two inside beans: one for the list, one for the detail
It's now interesting to see what possibilities you might use to make these beans talk to one another in a structured way.
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 list bean (Nav1ListUI) provides a “prepare(...)” method – which is the one to pass all initialization data that is required for the list bean.
Part of the data passed by the prepare(..) method is an event listener implementation. So the list has a certain mechanism, by which it tells to outside about things happening inside via a listener interface. In the concrete case the list tells the user of the list that a certain item is selected and allows the user of the item to react correspondingly.
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:
The detail bean is created and initialized by a “prepare(..)” method.
The prepare(..) method receives the item to be displayed, and it again receives an implementation of a listener, this time a listener of the detail bean. The detail bean allows to listen to two events: the user clicking “save” and the user clicking “cancel”.
As reaction on “save” and “cancel” the outside bean navigates back to the list 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:
A prepare(..) method for initialization in which the listener is passed.
A reaction on a double click processing (“onRowExecute()”), in which the listener is called accordingly.
The listener (“IListener”) is defined as inner class directly within the page bean implementation.
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:
A prepare(..) method.
A listener being called at the right point of times (“onCancelAction”, “onSaveAction”).
The listener again is implemented as inner class.
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.
The concept of the previous chapter was based on the fact that each page bean in principle is autarc:
It has a defined interface to build it (“prepare()”).
It has a listener to tell about what's going on inside.
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.
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?
Former scenario: the list bean invoked a listener, the outside bean implemented the listener and triggers the navigation
Now: the list bean directly triggers the page switch.
The layout of the pages are still the same, but now the internal processing is different of course.
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 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.
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.
The navigation logic is split:
The outside page provides the technology for executing the navigation
The inside page decides how to navigate.
Both patterns in this documentation are technically valid, of course:
The listener based approach (“Active Navigation Controller”) requires is the cleanest one, because the navigation is not part of the contained page bean. This ensures a maximum level of re-use for the page bean. The price to pay: you really need to consistently provide an adequate level of events within the IListener-implementations and the consequence of “What happens if a user presses this button?” is somewhere a bit “hidden” in the reaction on the events.
The other approach (“Passive Navigation Controller”) is one, where the navigation is quite hard wired into the page beans that are taking part. This might be “nice” for small scenarios, but clearly is “not nice” for managing hundreds of pages.
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...