Tuesday, May 11, 2010

CRUD operations on a tree table

<af:treeTable> is useful to present hierarchical data to the business users. ADF BC layer got a good support for use cases with tree table components. I'm sharing a simple application illustrating CRUD(Create,Read,Update,Delete) operations on a tree table with ADF Business Components.

You can download the sample workspace from here.
[Runs with Oracle JDeveloper 11g R1 PS2 + HR Schema]

The rest of the post is targeting mainly beginners who started using af:treetable with ADF Business Components. The sample is built using classic Department-Employee use case. Department information is displayed as parent rows and employees belonging to a department are displayed as child rows. The rendered page look like as shown below.



I'm not discussing creation of tree table here. If you want to know about the creation part, please refer chapter23.4 Using Trees to Display Master-Detail Objects from Fusion Developer's Guide.

Adding create functionality to a tree table

A very common(frequent) mistake that I noticed while adding Create/Delete operations on a tree table is that, developer may tend to treat tree table as same as a simple table - where you have only one level of information ( in effect only one RowIterator ). Please note that, dragging and dropping CreateInsert/Delete from the DataControl palette may not help you to deal with child( second level ) nodes of tree table.
Oops...Why? This is well explained in developers guide, please go through this topic View Link Accessors Versus Data Model View Link Instances from Fusion Developer's Guide.

So If you need to create/delete child nodes/rows, you may need to access that specific RowIterator pertaining to the selected node(row) and do the manipulations over there.

Hence, our first step is to identify the selected row's Key and the RowIterator containing this selected row. The below given code written in a manged bean may help you to identify the RowIterator for a selected node/row from a tree table.

public RowIterator getSelectedNodeRowIterator() {
  if (getSampleTreeTable() != null &&
    getSampleTreeTable().getSelectedRowKeys() != null) {
    for (Object opaqueFacesKey :
     getSampleTreeTable().getSelectedRowKeys()) {
    getSampleTreeTable().setRowKey(opaqueFacesKey);
    return ((JUCtrlHierNodeBinding)getSampleTreeTable().getRowData()).getRowIterator();
    }
 }
 return null;
}

public Key getSelectedNodeRowKey() {
  if (getSampleTreeTable() != null &&
    getSampleTreeTable().getSelectedRowKeys() != null) {
    for (Object opaqueFacesKey :
     getSampleTreeTable().getSelectedRowKeys()) {
    getSampleTreeTable().setRowKey(opaqueFacesKey);
    return ((JUCtrlHierNodeBinding)getSampleTreeTable().getRowData()).getRowKey();
    }
  }
  return null;
}

Where do I need to use this Key and the RowIterator?
These parameters needs to supplied to the create method, which we will see soon.

Cool, next step is to define a method to create a child row under the selected node/row. As these operations deal with BC components(EOs/VOs), it's always good to keep this logic inside the BC layer. So ApplicationModule seems to be the better candidate here. Expose a create method from ApplicationModule that takes the above said parameters. Time to explode the create method, this guy need to identify the selected row from RowIterator using the Key. The below piece of code does that.

Row[] found = ri.findByKey(selectedNodeKey, 1); //selected row

Alright...We got the selected row, job pending is to create the child rows below the selected one. This is an easy task, the ViewLink Accessor Attribute may help you to get hold of child rows from a parent row(selected row). ViewLink Accessor Attribute gives the ability to access the object at other end of the view link.

Where can I find the Accessor Attribute name?

I assume that, you have already created EnittyObjects, ViewObjects and necessary Associations and ViewLinks. Accessor Attribute name can be found from the ViewLink that you define between source and a target view object(parent and child). For example, consider the ViewLink defined between Department and Employee ViewObjects, shown below. The destination(EmployeesView) can be accessed from the source(DepartmentsView) by using the accessor attribute 'Employees'.


Alright...enough theory, let us switch back to the the 'create' method that we were discussing. Our basic infrastructure is in place, the remaining step is create the row and fill in the necessary attribute values. As the sample that we discuss here deals with two different types of rows, different treatment needs to be given for both parent and child row creations. This can be done by checking the row type as shown below.
if (nodeDefname.equals(deptViewDefName))....

Now the create method in the ApplicationModule may look like as shown below.

public void createChildren(RowIterator ri, Key selectedNodeKey) {
 final String deptViewDefName = "model.DepartmentsView";
 final String empViewDefName = "model.EmployeesView";
 if (ri != null && selectedNodeKey != null) {

    Row last = ri.last();
    Key lastRowKey = last.getKey();
    // if the select row is not the last row in the row iterator...

    Row[] found = ri.findByKey(selectedNodeKey, 1);
    if (found != null && found.length == 1) {
    Row foundRow = found[0];
    String nodeDefname =
        foundRow.getStructureDef().getDefFullName();

    if (nodeDefname.equals(deptViewDefName)) {
        RowSet parents =
        (RowSet)foundRow.getAttribute("EmployeesView");
        Row childrow = parents.createRow();
        parents.insertRow(childrow);
    } else {
        RowSet parents =
        (RowSet)foundRow.getAttribute("EmployeesView");
        Row childrow = parents.createRow();
        childrow.setAttribute("DepartmentId",
                 foundRow.getAttribute("DepartmentId"));
       parents.insertRow(childrow);
    }

    } else {
    System.out.println("Node not Found for " + selectedNodeKey);
    }

  }
}


You may need to bind the the above 'create' method with a button on your tree table page. The binding defined for the above method in my sample looks like as shown below.



A glance at Run-time Coordination

Below diagram illustrates how the View, Binding and BC Layers coordinates to render the tree table.

Adding delete functionality to a tree table

The method signature remains same as the one that we defined for implementing create functionality. So this part does not need further explanation. I'm copying the code below.

public void deleteChildren(RowIterator ri, Key selectedNodeKey) {
  if (ri != null && selectedNodeKey != null) {

    Row[] rows = ri.findByKey(selectedNodeKey, 1);
    if (rows != null) {

    for (Row row : rows) {
        row.remove();
    }

    } else {
     System.out.println("Node not Found for " + selectedNodeKey);
    }

  }
}

Further Readings..

The below blog posts(from Andrejus, Frank and Steve) may help you to understand the various use cases on a tree table.

4 comments:

manish4u@gmail.com said...

Hi Jobinesh,

I came here with the same requirement, but for POJOs. The one blockade I was facing was, how to get the child iterator.

And then came across the getChildIteratorBinding() method in JUCtrlHierNodeBinding, which gives you the required iterator.

Sample code:
JUCtrlHierNodeBinding selectedRow = getSelectedRows(getSvaTreeTable());
NavigatableRowIterator nrIter = selectedRow.getChildIteratorBinding().getNavigatableRowIterator();

Thanks a lot,
Manish.

Anonymous said...

Hi Jobinesh,
Would you be able to describe how
you made the method createChildren,
I am cinfused with the foundRow.getAttribute:
(RowSet)foundRow.getAttribute("EmployeesView");
What is the string you are providing?
A view?
and then
childrow.setAttribute("DepartmentId",
foundRow.getAttribute("DepartmentId"));

now looks like a parameter not anymore a view.

I've trying for hours to reproduce you sample with other master details and I am getting JBO-25058 definition ... of type Attribute is not found in...

Is it a attribute or a view, I am confused with the code.

Thanks for your help.

Anonymous said...

I have the same problem as the one who said
"I am cinfused with the foundRow.getAttribute:
(RowSet)foundRow.getAttribute("EmployeesView");
What is the string you are providing?
A view?
and then
childrow.setAttribute("DepartmentId",
foundRow.getAttribute("DepartmentId"));
"

Veera Brahmam Adavi said...

Hi Jobinesh,
I have seen your blog and its veryhelful for the developers.

Here I have one requirement.

I want to display data in tree table with master child relation ship.(Ancestors and descendents)
from using one vo, and ow selflink(col1=col2), I'm able to show the data in tree talble format.
For example: if ten levels are there for my ancestor,

now i need to display the data with tree table format with condtion(if my ancestor is male)

With in those ten levels, if 5 are male(may not be in order, may be in random like 1st level(woman), 2nd level(man),3rd level (man), 4th level(woman).....)
Now I need to display those 5 levels as tree table.(here the view link condition get fails col1= col2).

Can we achieve this?
I have tried ur viewcretireia logic and overriding the two method in voimpl.

but when the viewlink condition fails, uptothat level rows are getting displayed. but not full 5 levels out of 10 levels.

Please suggest me in acheiving this.

Thank you.
Veera