Technology: ADF11g
Developed in: JDeveloper 11.1.1.3.0
Browsers tested: Firefox 3.6.13 and Internet explorer 7 (7.0.6002.18005)
Used database schema: HR
Used tables: EMPLOYEES, DEPARTMENTS
Summary
One of the strength of ADF11 compared to 10 are the task-flows. Task flows increase reusability of parts of applications. But when you want to use an overall menu bar that should be accessible from any place in the applications and you use bounded task flows then some extra code is required to make the menu work from any place in the application.
In this blog a solution is provided to make the menu work when using bounded (and unbounded) taskflows.
Overview of the page flow:
Setup example application
For this blog an example application is created based on the HR schema. The example application contains an employees table width detail form and a departments table and detail form. The detail forms are created in a separate page. For the employees table and form a bounded task flow is created this bounded task flow is started from the menu. For the departments table and form another bounded task flow is created which is also started from the menu.
Model layer
Create the following entities:
Entity name |
Based on table of HR schema |
Customizations made |
Employee |
EMPLOYEES |
None |
Department |
DEPARTMENTS |
None |
Create the following view objects:
View object name |
Based on entities |
Customizations made |
EmployeesView |
Employee |
None |
DepartmentsView |
Department |
None |
Create an application module HrAppModule which exposes the EmployeesView and DepartmentsView.
Task flow
Unbounded task flow
The unbounded task flow form where we start with the solution looks like this:
There are no customizations made, the task flow is created by drag and drop.
Bounded task flow
The employees task flow and the departments task flow are bounded task flows. Both look like this (but for departments the view is DepartmentsTable and DepartmentForm).
The next properties are set (for both bounded task flows):
Property |
Value |
usePageFragments |
false
|
Share data controls with calling task flow |
true
|
Home page
An empty page only containing the menu is created.
Menu
The menu is a JSFF page that’s included in all pages (or in our case in the page template).
The menu contains:
- Menu: Employees
- Command menu item: Maintain employees with action startEmployees and immediate true.
- Menu: Departments
- Command menu item: Maintain departments with action startDepartments and immediate true.
- Menu: Help
- Command menu item: About with a showPopupBehaviour for an OK popup displaying ‘This a demo HR application.’
Table page
The table pages are created by drag and drop from the Data Controls. The table is dropped as ADF Read-only table with Row Selection and Sorting checked, all columns are displayed.
The Row Selection property must be checked, this causes the following properties to be set in the table:
Property | Value |
selectedRowKeys | #{bindings.EmployeesView.collectionModel.selectedRow} |
selectionListener | #{bindings.EmployeesView.collectionModel.makeCurrent} |
rowSelection | single |
Although in the JSPX page the selectedRowKeys and selectionListener statements contains warnings that the references methods cannot be found they can be found runtime.
Add the end of the table another column is added. This column contains a commandLink that triggers the edit navigation to the form page:
<af:column headerText="Edit employee"
id="editColumn">
<af:commandLink text="Edit"
id="edit"
action="edit"/>
</af:column>
Underneath the table a Cancel button (af:commandButton) is added which ends the task flow.
af:commandButton property | Value |
text | Cancel |
id | cancel |
action | cancel |
immediate | true |
The DepartmentsTable is created in the same way but then for the DepartmentsView.
Form page
The form pages are created by drag and drop from the Data Controls. The form is dropped as ADF Form all attributes are displayed.
To navigate back from the form to the table a rollback button (af:commandButton) is added by drag and drop the Rollback operation from the datacontrol palette as a button.
The submit button is created in the same way but then from the Commit operation. The DepartmentForm is created in the same way but then for the DepartmentsView.
Fix the menu navigation
When the application described above is deployed the navigation from the menu only works when the user is in the Home page (an empty page containing only the menu). When the user opens the Employees table page (from the Employees menu) and then tries to open the Departments table page (from the Departments menu) it doesn’t work. The user can navigate to this page using the cancel button in the Employees table page which navigates back to the home page and then start the departments table.
The reason the menu doesn’t work is that when the Employees table is opened the user is in the employees-task-flow. In this task-flow the Departments menu action (startDepartments) is not defined so it cannot be found.
Now we know what causes the issue we can correct it:
- When a menu item action is returned:/li>
- Check if the user is currently in a bounded task flow
- If so quit this task flow (with a task flow return activity)
- Then retry to execute the action
Menu
First we need to know if a menu item action is returned or not.
For all menu items (that do not trigger a popup), change the action precede all returned values by a fix string for example menu_.
So the Employees menu containing the commandMenuItem Maintain employees triggered the action startEmployees, this is changed in menu_startEmployees.
Change action of the menu item, add ‘menu_’ in front of the action string.
Note: the action results defined in the unbounded task flow should not be changed!
Navigation handler
A custom navigation handler class is created. The handleNavigation method of this class is used for all navigations in the application.
Create a class:
Class name |
Extends |
CustomNavigationHandlerImpl |
oracle.adfinternal.controller.application.NavigationHandlerImpl |
To let the application use this class it should be defined in the faces-congfig.xml, this can be done in the overview tab option Application:
In the CustomNavigationHandlerImpl the handleNavigation is overridden. In the outcome parameter of this method the resulting string of the action that is called is passed. If this outcome starts with menu_ (this should equal the string that was added in front of the original action of the menu page) then a menu item was triggered. If so we need to check whether the user is currently in a bounded task flow or not, this is done by the abandonTaskFlowIfNeeded method.
public void handleNavigation(FacesContext facesContext, String action, String outcome) {
if (outcome != null && outcome.startsWith("menu_")) {
abandonTaskFlowIfNeeded(facesContext, action, outcome);
} else {
super.handleNavigation(facesContext, action, outcome);
}
}
The abandonTaskFlowIfNeeded method check whether the user is currently in a bounded task flow, if this is not the case then just call back to handleNavigation but pass instead of the original outcome the outcome without menu_. If the user is in a bounded task flow then store the outcome without menu_ on the request (with name triggeredMenuItem) and execute the action “abondonTaskflow” instead of the called menu item action.
public void abandonTaskFlowIfNeeded(FacesContext facesContext,String action, String outcome) {
String strippedOutcome = outcome.substring("menu_".length());
TaskFlowContext tfc = ControllerContext.getInstance().getCurrentViewPort().getTaskFlowContext();
if (tfc.getTaskFlowId() == null) {
handleNavigation(facesContext, action, strippedOutcome);
} else {
Map requestMap = FacesContext.getCurrentInstance().getExternalContext().getRequestMap();
requestMap.put("triggeredMenuItem", strippedOutcome);
handleNavigation(facesContext, null, "abandonTaskflow");
}
}
Bounded task flow
Now when the user is in a bounded task flow and triggers a menu item to open the action abandonTaskflow is executed instead of the menu item action. This action must be defined in the bounded task flow.
Because the same solution holds for all bounded task flows used in the application we create a task flow template:
The following properties are set on the ExecuteMenuCommand Task Flow Return activity:
Property |
Value |
Outcome |
startMenuItem |
All bounded task flows (in this example the employees-task-flow and departments-task-flow) must be based on this template.
The Task Flow Return activity has outcome startMenuItem, this outcome is passed to the unbounded taskflow. When this action is returned there is parameter triggeredMenuItem on the request. This parameter contains the original menu action that was triggered.
Unbounded task flow
Define a method call in the unbounded task flow for the outcome startMenuItem.
The following properties are set on the StartMenuItemMethod Method Call activity:
Property |
Value |
Method |
#{customRouter.getMenuItemFromRequest} |
toString |
true |
Parameters |
Class: java.lang.String
Value: #{requestScope.triggeredMenuItem} |
The customRouter refers to a managed bean defined in the unbounded task flow:
Managed bean property |
Value |
Name |
customRouter |
Class |
nl.hr.demo.view.util.CustomRouter |
Scope |
Request |
This bean class contains 1 method, getMenuItemFromRequest that returns the value passed as a parameter (which is filled this the triggeredMenuItem value on the request filled by the CustomNavigationHandlerImpl class):
public String getMenuItemFromRequest(String request) {
return request;
}
Now the menu items work whether we’re in a bounded task flow or not.