Breadcrumbs are a navigation technique used in many applications, and its popularity grows. Its purpose is to give users a way to keep track of their location. Breadcrumbs typically appear horizontally across the top of a webpage, usually below any title bars or headers. They provide links back to a previous page that the user navigated through in order to get to the current page, for hierarchical structures usually the parent pages of the current one. In hierarchical site layouts, like an ADF application that uses JHeadstart, breadcrumbs indicate the path back to the root page (or JHeadstart master group page) of the hierarchy with links:
Currently we have a challenge in our JHeadstart ADF application;
after an insert or update many of our precious breadcrumbs are missing or don’t refresh to their updated value. In a JHeadstart application, items that are set as a group ‘descriptor item’ can become a breadcrumb and appear on the breadcrumb stack. We have many descriptor items which values are set by a database trigger or are an SQL-calculated attribute in the VO (View Object). For example: we have an address that is set by a trigger that concatenates the street name, street number, postal code and city. After each insert, update or delete our application navigates to a different JSPX called a ‘Success Page’, which (only) purpose is to tell the user that the insert, update or delete has been committed successfully. For example, If we want to change the first name of a ‘kandidaat’ (English: candidate), lets say Boris Becker to Boris Brown, the application navigates to our ‘Success’ JSPX. The breadcrumb is still on the breadcrumb stack, but the problem is that the breadcrumb still reflects the old last name! And our new organization name and organization address, both set by a database trigger, do not even appear at all on the stack! Of course this is not a desired situation.
In our application, its not a problem of the ADFBC layer; after a commit is executed, VO attributes set by a database trigger are already refreshed in the ADFBC layer by setting the ‘refresh after insert’ and ‘refresh after update’ on the EO (Entity Object). And for SQL-calculated attributes a requery is needed in the afterCommit() method of the VO; like a refresh-but-stay-where-you-were requery method ‘refreshQueryKeepingCurrentRow()’ Steve Muench described.
Implementation of Breadcrumbs by JHeadstart (version 10.1.3.2)
First, before we can look for a solution we have to figure out how breadcrumbs are implemented in an ADF application that uses JHeadstart. Breadcrumbs are standard JHeadstart functionality. The only documentation I could find about JHS (JHeadstart) breadcrumbs was in an old JHeadstart developers guide (release 10.1.2.1, 2005, based on Jakarta Struts instead of JSF, but a major part of the breadcrumb documentation is still valid). It tells us that “In (almost) 100% generated applications, chances are you’ll never need to know how the mechanism behind the breadcrumbs functionality works, and how you can influence its behavior. However, if you are manually creating complex pageflows in a JHeadstart application, you might need to have some control over the way the breadcrumbs work.” So, the first challenge is to understand how these breadcrumbs are implemented. I will explain only the relevant basic parts in relation to our issue.
<af:menuPath> tag
In each JSPX file a breadcrumb area is generated with a <af:menuPath> tag. A menuPath component is used in hierarchical site layouts to indicate the path back to the root page of the hierarchy with links. A child representing the current page should be the last child. This menuPath tag has a value of #{jhsBreadcrumbStack}. The nodes in the breadcrumb stack are stamped out with the ‘nodeStamp’ facet, which contains a commandLink component with their corresponding actions ( #{bc.goToDestination} ) (which uses the default JSF actionListener mechanism for page navigation), and labels ( #{bc.label} ). The EL expression #{jhsBreadcrumbStack} will result in a call to the JhsBeadcrumbStack managed bean which extends ViewIdPropertyMenuModel. A ‘MenuModel’ object represents the menu structure of a page or application.
<ServiceName>-Breadcrumb-beans.xml
In <ServiceName>-Breadcrumb-beans.xml, JHeadstart creates for each JHeadstart group a managed bean called ‘<GroupName>Breadcrumb’ of type BreadcrumbConfiguration.java. The JHS template ‘breadcrumbFacesConfig.vm’ is used to create this managed bean. For example the AllEmployees group:
Breadcrumb and BreadcrumbConfiguration
A BreadcrumbConfiguration object is used for the creation of a Breadcrumb object. As we can see, 4 properties of a BreadcrumbConfiguration are set by the JSF Managed Bean facility: a (destination) page, a label (rendered as label for the breadcrumb commanLink), a label-parameter-expression (EL-expression, a parameter for the label), and a unique key (JSPX name without slash and .jspx). A BreadcrumbConfiguration object is a simple JavaBean with getters and setters for these properties and some other. The BreadcrumbManager, responsible for managing/processing the current stack of breadcrumbs, creates a Breadcrumb object by using the properties of a corresponding BreadcrumbConfiguration object. When a new Breadcrumb is created, his destination (a ViewId) is set using the page property, so that clicking on the breadcrumb link will execute the goToDestination() method on the Breadcrumb. The ViewId of the breadcrumb will be set as the new viewRoot and the application navigates to the page corresponding to its breadcrumb.
The label and label-parameter EL-expression of a breadcrumb is the text that that is shown to the end user in the page for this breadcrumb, and is therefore derived from the application’s resource bundle to allow for internationalization. The key for the current breadcrumb is derived as follows:
BREADCRUMB_< JSPX name in uppercase>. It is possible to have ‘dynamic’ labels, by having the value in the resource bundle contain substitution parameters like {0}:
The EL expression of a JHS group descriptor item (generated in the labelParamExpressions property in <ServiceName>-Breadcrumb-beans.xml) will result in the substitution value for ‘{0}’, and pass them to the label of the breadcrumb.
Most important methods of a Breadcrumb object are:
- goToDestination() – The viewed of this Breadcrumb is set as the new ViewRoot of the application.
- getLabel() – returns the current label and parameter value.
- resetLabel() – This method reevaluates the label of the breadcrumb, reevaluates the parameters, and reconstructs the ‘label’ based on the original key and the new parameter values. It calls deriveParamValues() that takes the ‘labelParamExpressions’ property of the BreadcrumbConfiguration, and evaluates the EL expression(s) in it. It will take that EL expression, for example from the AllEmployeesBreadcrumb labelParamExpression ‘bindings.AllEmployeesLastName.inputValue’ and evaluate it:
String paramExp = "#{"+paramExps+"}";
paramValue = JsfUtils.getExpressionValue(paramExp);
Soo, if you are updating employee Bates, the breadcrumb label ‘Edit employee {0}’ should become ‘Edit employee Bates’.
A JHS template ‘common_labels.vm’ creates these labels and ‘labelParamExpressions’ in the resource bundle. In common_labels.vm there are 4 macros:
- BREADCRUMB_LABEL_FORM_PAGE
- BREADCRUMB_LABEL_TABLE_PAGE,
- BREADCRUMB_LABEL_SEARCH_PAGE
- BREADCRUMB_LABEL_SELECT_PAGE
For example:
#macro (BREADCRUMB_LABEL_FORM_PAGE $page)
${JHS.nls(${page.group.displayTitleSingular}, "BREADCRUMB_${page.nameUppercase}", "EDIT_TITLE",false,true)}#end
‘EDIT_TITLE’ in the macro will be replaced by its value in GeneratorText.properties:
EDIT_TITLE = Edit {0} ‘{‘0}
For Employees, the generated resource bundle entry will be ‘Edit employee {0}’.
BreadcrumbStack
Represents the current stack of breadcumbs (Breadcrumb objects – simple javaBean objects) for the current application’s ViewRoot. BreadcrumbStack extends ViewIdPropertyMenuModel (which extends BaseMenuModel, which extends MenuModel), A BreadcrumbStack object can be treated as a MenuModel object what is exactly what a af:menuPath tag needs as its value, so an EL expression of #{jhsBreadcrumbStack} will return the collection of Breadcrumb objects. It is generated in JhsCommon-beans.xml and created/retrieved using the JSF Managed Bean facility. Don’t forget to set the showBreadcrumbCurrentPage to true (default is false) if you want a breadcrumb representing your current page:
Important methods:
- getBreadcrumbStack(). This static method will retrieve and if not present create a Breadcrumb stack object from the session.
- Add(Object o). Adds a Breadcrumb object to the stack.
BreadcrumbManager
Responsible for managing and processing the current stack of breadcrumbs. The BreadcrumbStack is created/retrieved using the JSF Managed Bean facility. Most important method:
- processBreadcrumbs() – This is the ‘main’ method, it will create and remove breadcrumbs, process the Breadcrumb stack and perform any actions necessary on this stack needed to synchronize it with the current state of the application.
Algorithm
- JhsPageLifecycle calls, in its method prepareModel(), the ‘main’ method of the BreadcrumbManager, that processes the breadcrumb stack.
- BreadcrumbManager retrieves the current BreadcrumbStack object.
- BreadcrumbManager gets the BreadcrumbConfiguration object for the requested page.
- The BreadcrumbManager retrieves the BreadcrumbConfiguration object in the breadcrumbConfigs HashMap (corresponding to the requested page), creates a new Breadcrumb object (by setting the BreadcrumbConfiguration object as an argument in the constructor). The Breadcrumb uses the properties of the BreadcrumbConfiguration object to set its own properties like a (destination) page, a label, a label-parameter-expression, and a unique key (JSPX).
- The Breadcrumb object is added to the stack. If the Breadcrumb is already on the stack, it is ‘rolled back’ to the Breadcrumb before the one that is ‘the same’ as the current breadcrumb, and then the current breadcrumb is added.
- JhsPageLifecycle calls the ‘main’ process-method of the BreadcrumbManager again, now in the prepareMode phase in prepareModel(). 2, 3, 4 and 5 are repeated.
- The EL expression #{jhsBreadcrumbStack} in the JSPX is evaluated and the breadcrumb stack is rendered.
Solution
OK, now that we have gained some insight how breadcrumbs are implemented in a JHeadstart application, we can figure out the solution. The main problem, in our case, is that breadcrumbs generated by JHeadstart are created and its labelParamexpressions (EL expressions) are evaluated too early in the ADF lifecycle; in the prepareModel phase. As we saw in the previous section, the JhsPageLifecycle instance calls in its method prepareModel() the method processBreadcrumbs() of the BreadcrumbManager object. This method creates the Breadcrumb instance, gets its label from the resourcebundle (by using the labelKey property) and evaluates the LabelParameter EL expression(s), and adds the Breadcrumb object to the BreadcrumbStack.
The JhsPageLifecycle instance calls, in the ADF prepareRender phase in prepareRender(), again the processBreadcrumbs() method of the BreadcrumbManager object. In a standard JHeadstart application, after an update or insert, the user navigates back to the same page, the processBreadcrumbs() is called twice in the ADF lifecycle and in the prepareRender phase the breadcrumb is nicely refreshed to the current state of the updated or new breadcrumb. This is in most cases desired behavior when you use the standard JHeadstart pageflow. In our application, we use a different pageflow, and our breadcrumbs are not evaluated again to reflect current row changes, because we don’t navigate to the same page. Our current ViewId changes after an update or insert and we navigate to a new JSPX file, in our application called a ‘Success’-page (we give the user a new page with a message that the insert, update or delete has been successful).
In our application, refreshing is not possible anymore in the prepareRender phase, because we navigate to a new JSPX file (our Success.jspx), and in the prepareRender phase the BindingContainer object does not contain the reference values of the binding objects of the previous JSPX anymore. Values that binding objects refer to have request-scope: they are only valid during a request in which that binding container has been prepared by the ADF lifecycle. If the application is navigating to a different viewed, in the prepareRender phase the BindingContainer has already been prepared by the ADF lifecycle to contain the binding objects and the values they reference to serve the new JSPX, and the values of the binding objects of the previous JSPX are released and not accessible anymore.
So, reevaluation of breadcrumbs must be done earlier in the ADF lifecycle phase. After the invokeApplication phase seems to be the right time.
The reference values of the binding objects of the BindingContainer are still accessible for our page, and are now able to access the refreshed values from the ADFBC layer. In the JSF invokeApplication phase new or updated rows are send to the database, a commit is executed, VO attribute values are refreshed in the ADFBC layer by a ‘refresh after insert’ on the EO, an executeQuery() method on the VO or a refresh() method on the EO or VO. If we make a JSF PhaseListener, that listens for the JSF invokeApplication phase, we can access ADFBC’s refreshed values directly after the invokeApplication phase in the afterPhase() method of this PhaseListener. In our case after an update or insert, the ViewId() is changed between the between the beforePhase() and afterPhase() of the invokeApplication phase, so we should set the previous ViewId() in the beforePhase() method, and we can use it in the afterPhase() method:
BreadcrumbManager bcm = (BreadcrumbManager)BreadcrumbManager.getBreadcrumbManager();
if(getOldViewId()!=null) bcm.processBreadcrumbs(null, getOldViewId() , null, null);
Now the label and the EL expression of its parameter(s) are reevaluated (in reality removed from stack and newly created).
public class BreadcrumbRefreshPhaseListener implements PhaseListener {
private String oldViewId = null;
private static final Log sLog = LogFactory.getLog(BreadcrumbRefreshPhaseListener.class);public BreadcrumbRefreshPhaseListener() {}
public PhaseId getPhaseId() {
return PhaseId.INVOKE_APPLICATION;
}
public void beforePhase(PhaseEvent e) {
sLog.debug("PhaseId.INVOKE_APPLICATION - beforePhase() - ViewId() now: "+getCurrentViewId());
setOldViewId(getCurrentViewId());
}public void afterPhase(PhaseEvent e) {
sLog.debug("PhaseId.INVOKE_APPLICATION - afterPhase() - ViewId() now: "+getCurrentViewId());
BreadcrumbManager bcm = (BreadcrumbManager)BreadcrumbManager.getBreadcrumbManager();
bcm.processBreadcrumbs(null, getOldViewId(), null, null);
}public static String getCurrentViewId() {
return FacesContext.getCurrentInstance().getViewRoot().getViewId();
}public void setOldViewId(String oldViewId) {
this.oldViewId = oldViewId;
}public String getOldViewId() {
return oldViewId;
}
}
We also have to register this phaseListener in JhsCommonBeans.xml (and in the JHS template JhsCommonBeans.vm) :
<lifecycle>
<phase-listener>oracle.jheadstart.controller.jsf.lifecycle.JhsADFPhaseListener</phase-listener>
<phase-listener>nl.myproject.jheadstart.controller.BreadcrumbRefreshPhaseListener</phase-listener>
</lifecycle>
Now, our breadcrumbs are added or refreshed after an insert or update when we navigate to the Success.jspx:
Impressive! Great explanation – very detailed and well described including clear illustrations. Thanks for this information.