Posts tonen met het label taskflow. Alle posts tonen
Posts tonen met het label taskflow. Alle posts tonen

dinsdag 1 november 2011

How to render different pages for each tree node

Technology: ADF11g
Developed in: JDeveloper 11.1.2.1.0
Browsers tested: Internet Explorer 8.0.7601.17514 and Firefox 7.0.1
Used database schema: HR
Used tables: EMPLOYEES, DEPARTMENTS, LOCATIONS and COUNTRIES


Summary



I received the question to help out how to display different pages for each child node that is clicked in an af:tree. In this blog a solution is provided for this question.

I choose for a solution that uses a dynamic region which is refreshed every time a node is selected in the tree.

The tree I created displays all departments and the subtree all employees for that department, if a department is selected I render in the region a page contianing 'nothing here'. If a employee underneath a department is selected and the department has a manager, the information of the manager is displayed, and if the department does not have a manager its location details are displayed.

For this blog I updated some data in the database: Employee ID 116 belongs to department 130.

update employees set department_id = 130 where employee_id = 116;

Now department Corporate Tax has an employee but no manager so if the employee is selected the location details are displayed.

Model



For this blog the EMPLOYEES, DEPARTMENTS, LOCATIONS and COUNTRIES tables of the HR schema are used.

Entities



Create the following entities:
Entity name Based on table of HR schema Customizations made
Employee EMPLOYEES None
Department DEPARTMENTS None
Country COUNTRIES None
Locations LOCATIONS None


Two associations have been created:
  • Between Department and Employee
  • Between Country and Location

View objects



Create the following view objects and create a view object and view row class for all of the view objects:
View object name Based on entities Customizations made
DepartmentsView Department None
EmployeesView Employee None
LocationsView Location and Country Bind variable b_id of type Integer, the where clause is extended with Location.LOCATION_ID = :b_id
ManagersView Employee Bind variable b_id of type Integer, the where clause is extended with Employee.EMPLOYEE_ID = :b_id


One view link is created:
  • Between DepartmentsView and EmployeesView using the created association

Application module



An application module is created with name HrAppModule which exposes:
  • DepartmentsView
    • EmployeesView (using the created view link)
  • LocationsView
  • ManagersView
Generate the application module java class.

ViewController



The model layer is finished so we can start designing the pages.

In the unbounded taskflow the main page is created this page will contain the tree and a region, in this region different bounded task flows can be rendered.

Unbounded taskflow



This taskflow contains the main page (jsf) and defines two different beans.

Tree.jsf



The main page is called Tree.jsf in this page I created a panelGroupLayout with horizontal layout. The first component in this panelGroupLayout is the tree.

The tree is created by drag and drop the DepartmentsView and choosing Tree - ADF tree.




In the Edit Tree Binding dialog that pops up add (with the green plus sign) the second level to display the employees. For the departments I set the DepartmentName as display attribute and for the Employees view the FirstName and LastName.



In the created af:tree tag add a selectionListener:

<af:tree value="#{bindings.DepartmenstView.treeModel}"

var="node"

selectionListener="#{hrTreeBean.selectionListener}"

rowSelection="single"

id="t1">

This selection listener will be implemented in a custom managed bean class.

In the unbounded task flow define the managed bean:
Managed bean property Value
Name hrTreeBean
Class nl.capgemini.marianneHorsch.adfTree.view.beans.TreeBean
Scope Request


In the implementation of the bean we define the selectionListener method, in this method we check:
  • Is only a department selected
  • Has the selected department (or if a employee is selected the department the employee is in) a manager.
In the first phase we only store and print out this information.

package nl.capgemini.marianneHorsch.adfTree.view.beans;



import java.util.Iterator;

import java.util.List;

import javax.faces.context.FacesContext;

import nl.capgemini.marianneHorsch.adfTree.model.services.HrAppModuleImpl;

import nl.capgemini.marianneHorsch.adfTree.model.views.DepartmenstViewRowImpl;

import nl.capgemini.marianneHorsch.adfTree.model.views.EmployeesViewRowImpl;

import oracle.adf.model.binding.DCBindingContainer;

import oracle.adf.model.binding.DCDataControl;

import oracle.binding.BindingContainer;

import oracle.jbo.Key;

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

import org.apache.myfaces.trinidad.event.SelectionEvent;



public class TreeBean {

private static final Log log = LogFactory.getLog(TreeBean.class);



public TreeBean() {

super();

}



public void selectionListener(SelectionEvent selectionEvent) {

Key departmentKey = null;

Key employeeKey = null;

if (selectionEvent.getAddedSet() != null) {

Iterator iter = selectionEvent.getAddedSet().iterator();

while (iter.hasNext()) {

List objList = (List)iter.next();

if (objList != null && !objList.isEmpty()) {

Object objKey = objList.get(0);

if (objKey instanceof Key) {

departmentKey = (Key)objKey;

}

}

if (objList.size() > 1) {

Object objKey = objList.get(1);

if (objKey != null && objKey instanceof Key) {

employeeKey = (Key)objKey;

}

}

log.debug("Department key: " + (departmentKey == null ? "" : departmentKey));

log.debug("Employee key: " + (employeeKey == null ? "" : employeeKey));

}

}



if (departmentKey != null) {

DepartmenstViewRowImpl row =

(DepartmenstViewRowImpl)getService().getDepartmenstView().getRow(departmentKey);

getService().getDepartmenstView().setCurrentRow(row);

}

if (employeeKey != null) {

EmployeesViewRowImpl row =

(EmployeesViewRowImpl)getService().getEmployeesView().getRow(employeeKey);

getService().getEmployeesView().setCurrentRow(row);

}



if (employeeKey == null) {

log.debug("No employee selected.");

} else {

DepartmenstViewRowImpl row =

(DepartmenstViewRowImpl)getService().getDepartmenstView().getCurrentRow();

if (row.getManagerId() == null) {

log.debug("NO manager.");

} else {

log.debug("There is a manager.");

}

}

}



private HrAppModuleImpl getService() {

DCBindingContainer bc =

(DCBindingContainer)FacesContext.getCurrentInstance().getApplication().evaluateExpressionGet(FacesContext.getCurrentInstance(), "#{bindings}", BindingContainer.class);

if (bc == null) {

return null;

}

DCDataControl dc = bc.findDataControl("HrAppModuleDataControl");

if (dc == null) {

return null;

}

return (HrAppModuleImpl)dc.getDataProvider();

}

}

If we run this application we see the tree and logging about what we have selected.

In the example I distinguish 3 situations:
  • If a department is selected I render a taskflow displaying nothing.
  • If a employee is selected that belongs to a department that has a manager the manager is displayed.
  • If a employee is selected that belongs to a department that does not have a manager the location of the department is displayed.

Empty flow



The empty flow is a bounded taskflow with a page fragment that only has an output text saying Nothing here.

The taskflow shares the data control with the calling taskflow.

Manager flow



The manager flow is a bounded taskflow with a page fragment that only contains a simple read only form for the ManagersView (by drag and drop from the data controls palette).

The taskflow shares the data control with the calling taskflow.

Location flow



The location flow is a bounded taskflow with a page fragment that only contains a simple read only form for the LocationsView (by drag and drop from the data controls palette).

The taskflow shares the data control with the calling taskflow.

Now the region can be added to the Tree.jsf. Drag and drop the EmptyFlow from the project navigator onto the Tree.jsf (underneath the af:tree tag). Choose Dynamic Region.


A popup appears where a bean can be selected use the green plus to create a new Bean leave the input parameter map empty. This new bean should also be configured in the unbounded taskflow:
Managed bean property Value
Name regionBean
Class nl.capgemini.marianneHorsch.adfTree.view.beans.HrRegionBean
Scope PageFlow


In the implementation of the bean class define all 3 bounded taskflows and creates methods to set the taskflow as being the current to display:

package nl.capgemini.marianneHorsch.adfTree.view.beans;



import oracle.adf.controller.TaskFlowId;



public class HrRegionBean {

private String taskFlowId = "/WEB-INF/EmptyFlow.xml#EmptyFlow";

private static final String EMPTY_TF = "/WEB-INF/EmptyFlow.xml#EmptyFlow";

private static final String LOCATION_TF = "/WEB-INF/LocationFlow.xml#LocationFlow";

private static final String MANAGER_TF = "/WEB-INF/ManagerFlow.xml#ManagerFlow";



public HrRegionBean() {

}



public TaskFlowId getDynamicTaskFlowId() {

return TaskFlowId.parse(taskFlowId);

}



public void startEmpty() {

taskFlowId = EMPTY_TF;

}

public void startLocation() {

taskFlowId = LOCATION_TF;

}

public void startManager() {

taskFlowId = MANAGER_TF;

}

}

The last part that is missing know is to start the taskflow when a node is started, before starting it the correct row is queried by binding the bind variable for the value in the departments row.

At the end of the selectionListener method of the TreeBean add:

HrRegionBean bean = (HrRegionBean) getBeanInstance("#{pageFlowScope.regionBean}");

if (employeeKey == null) {

bean.startEmpty();

} else {

DepartmenstViewRowImpl row =

(DepartmenstViewRowImpl)getService().getDepartmenstView().getCurrentRow();

if (row.getManagerId() == null) {

getService().getLocationsView().setb_id(row.getLocationId());

getService().getLocationsView().executeQuery();

getService().getLocationsView().setCurrentRow(getService().getLocationsView().first());

bean.startLocation();

} else {

getService().getManagersView().setb_id(row.getManagerId());

getService().getManagersView().executeQuery();

getService().getManagersView().setCurrentRow(getService().getLocationsView().first());

bean.startManager();

}

}

And the new method to retrieve the current HrRegionBean instance:

private Object getBeanInstance(String expression) {

FacesContext fc = FacesContext.getCurrentInstance();

ELContext elctx = fc.getELContext();

ExpressionFactory elFactory = fc.getApplication().getExpressionFactory();

return elFactory.createValueExpression(elctx, expression, Object.class).getValue(elctx);

}

When we run this we see:
  • On page open:
  • When we open Marketing and select Michael Hartstein:
  • And when we open Corporate Tax and select Shelli Baida:
  • And select IT:

woensdag 23 februari 2011

How to fix menu item navigation when using bounded task-flows

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:
PropertyValue
selectedRowKeys#{bindings.EmployeesView.collectionModel.selectedRow}
selectionListener#{bindings.EmployeesView.collectionModel.makeCurrent}
rowSelectionsingle


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 propertyValue
textCancel
idcancel
actioncancel
immediatetrue


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.