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: