Tuesday Jun 12, 2012

Programmatically disclosing a node in af:tree and af:treeTable

A common developer requirement when working with af:tree or af:treeTable components is to programmatically disclose (expand) a specific node in the tree.

If the node to disclose is not a top level node, like a location in a LocationsView -> DepartmentsView -> EmployeesView hierarchy, you need to also disclose the node's parent node hierarchy for application users to see the fully expanded tree node structure. Working on ADF Code Corner sample #101, I wrote the following code lines that show a generic option for disclosing a tree node starting from a handle to the node to disclose.

The use case in ADF Coder Corner sample #101 is a drag and drop operation from a table component to a tree to relocate employees to a new department. The tree node that receives the drop is a department node contained in a location. In theory the location could be part of a country and so on to indicate the depth the tree may have. Based on this structure, the code below provides a generic solution to parse the current node parent nodes and its child nodes.

The drop event provided a rowKey for the tree node that received the drop. Like in af:table, the tree row key is not of type oracle.jbo.domain.Key but an implementation of java.util.List that contains the row keys. The JUCtrlHierBinding class in the ADF Binding layer that represents the ADF tree binding at runtime provides a method named findNodeByKeyPath that allows you to get a handle to the JUCtrlHierNodeBinding instance that represents a tree node in the binding layer.

CollectionModel model = (CollectionModel) your_af_tree_reference.getValue();
JUCtrlHierBinding treeBinding = (JUCtrlHierBinding ) model.getWrappedData();
JUCtrlHierNodeBinding treeDropNode = treeBinding.findNodeByKeyPath(dropRowKey);

To disclose the tree node, you need to create a RowKeySet, which you do using the RowKeySetImpl class. Because the RowKeySet replaces any existing row key set in the tree, all other nodes are automatically closed.

RowKeySetImpl rksImpl = new RowKeySetImpl();
//the first key to add is the node that received the drop
//operation (departments).            
rksImpl.add(dropRowKey);    

Similar, from the tree binding, the root node can be obtained. The root node is the end of all parent node iteration and therefore important.

JUCtrlHierNodeBinding rootNode = treeBinding.getRootNodeBinding();

The following code obtains a reference to the hierarchy of parent nodes until the root node is found.

JUCtrlHierNodeBinding dropNodeParent = treeDropNode.getParent();
//walk up the tree to expand all parent nodes 
while(dropNodeParent != null && dropNodeParent != rootNode){
   //add the node's keyPath (remember its a List) to the row key set
   rksImpl.add(dropNodeParent.getKeyPath());   
   dropNodeParent = dropNodeParent.getParent();
}

Next, you disclose the drop node immediate child nodes as otherwise all you see is the department node. Its not quite exactly "dinner for one", but the procedure is very similar to the one handling the parent node keys

ArrayList<JUCtrlHierNodeBinding> childList = (ArrayList<JUCtrlHierNodeBinding>) treeDropNode.getChildren();                     
for(JUCtrlHierNodeBinding nb : childList){
  rksImpl.add(nb.getKeyPath());
}

Next, the row key set is defined as the disclosed row keys on the tree so when you refresh (PPR) the tree, the new disclosed state shows

tree.setDisclosedRowKeys(rksImpl); 
AdfFacesContext.getCurrentInstance().addPartialTarget(tree.getParent()); 

The refresh in my use case is on the tree parent component (a layout container), which usually shows the best effect for refreshing the tree component. 

Monday Jun 11, 2012

Solving the context menu problem with drag and drop in trees

The following drag-and-drop problem has been reported on OTN: An ADF Faces tree component is configured with a af:collectionDropTarget tag to handle drop events. The same tree component also has a context menu defined that is shown when users select the tree with the right mouse button. The problem now was - and I could reproduce this - that the context menu stopped working after the first time the tree handled a drop event. The drag and drop use case is to associate employees from a table to a department in the tree using drag and drop.

The drop handler code in the managed bean looked up the tree node that received the drop event to determine the department ID to assign to the employee. For this code similar to the one shown below was used

List dropRowKey = (List) dropEvent.getDropSite();
//if no dropsite then drop area was not a data area
if(dropRowKey == null){
   return DnDAction.NONE;
}                

tree.setRowKey(dropRowKey);
JUCtrlHierNodeBinding dropNode = (JUCtrlHierNodeBinding) tree.getRowData();

So what happens in this code? The drop event contains the dropSite reference, which is the row key of the tree node that received the drop event. The code then sets the key to the tree in a call to getRowDate() returns the node information for the drop target (the department). This however causes the tree state to go out of synch with its model (ADF tree binding), which is known to cause issues.

In this use case the issue caused by this is that the context menu no longer shows up. To fix the problem, the code needs to be changes to read the current row key from the key, then perform the drop operation and at the end set the origin (or model) row key back

//memorize current row key
Object currentRowKey = tree.getRowKey();        
List dropRowKey = (List) dropEvent.getDropSite();
//if no dropsite then drop area was not a data area
  if(dropRowKey == null){
    return DnDAction.NONE;
  }              
tree.setRowKey(dropRowKey);
JUCtrlHierNodeBinding dropNode = (JUCtrlHierNodeBinding) tree.getRowData();
... do your stuff here ....
//set current row key back
tree.setRowKey(currentRowKey);
AdfFacesContext.getCurrentInstance().addPartialTarget(tree);

Note the code line that sets the row key back to its original value.

Monday Mar 05, 2012

Creating a custom master-detail presentation using af:panelAccordion

ADF Code Corner sample #81 explains how-to create a custom layout to render master-detail information. In the sample ADF Faces panel tabs are used to render the master data and DVT components are used in the panel tabs to show the detail information.

http://www.oracle.com/technetwork/developer-tools/adf/learnmore/81-master-detail-tab-with-graphs-394252.pdf

http://www.oracle.com/technetwork/developer-tools/adf/learnmore/81-master-detail-tab-with-graphs-394253.zip

ADF Code Corner Sample 81

The sample reads master information from the selected panel tab to then – using Java in a managed bean – query the detail record for the DVT component. While the solution documented on ADF Code Corner is efficient, there exists another one that is declarative.

Note: of course there are differences in the usage, but this I'll discuss later

The image below shows an ADF Faces panel accordion that displays the HR schema Departments table information on the accordion tabs and information about contained Employees in a panel box that shows when the accordion is expanded.

MdPresentationWithPanelAccordion

This sample uses an ADF tree binding to render the panel accordion tabs and the contained employee detail information. The iteration is done by two af:forEach tags, as shown below

<af:panelAccordion id="pa1" dimensionsFrom="auto">
 <af:forEach items="#{bindings.allDepartmentsWithEmployees.children}" var="departmentsVar">
   <af:showDetailItem text="#{departmentsVar.DepartmentName}" id="sdi1"
                      stretchChildren="first">
    <af:panelBox text="Employees" id="pb1" showDisclosure="false" type="stretch">
       <f:facet name="toolbar"/>
          <af:panelGroupLayout id="pgl1" layout="scroll">
             <af:forEach items="#{departmentsVar.children}" var="employeesVar">
                 <af:outputText id="ot1"
                                value="#{employeesVar.FirstName} #{employeesVar.LastName}"/>
              </af:forEach>
          </af:panelGroupLayout>
      </af:panelBox>
    </af:showDetailItem>
  </af:forEach>
</af:panelAccordion> 

Note that the departmentVar variable is not accessible within the body of the second af:forEach component. While it can be used to configure the second af:forEach component data access, the variable cannot be referenced from the af:panelBox within the second af:forEach component

As shown in the page source above, the first af:forEach tag reads the parent information from the ADF tree binding. Make sure you set the iterator binding for the tree binding to -1 for the RangeSize property as otherwise you would only see the first 10 parent row. 

(PageDef file)

<executables>
  <variableIterator id="variables"/>
  <iterator Binds="allDepartmentsWithEmployees" RangeSize="-1" DataControl="AppModuleDataControl"
            id="allDepartmentsWithEmployeesIterator" Refresh="deferred"/>
</executables> 

Sample Download

You can download the Oracle JDeveloper 11g R2 (11.1.2.1) sample from here. Just configure the database connection to point to an Oracle database with the HR schema unlocked. The ADF BC model is configured to only show departments that have employees (using a ViewCriteria)

Which solution to choose ?

And Finally, what's the difference between this declarative approach and the  more code centric solution in ADF Code Corner sample 81? The difference is that this sample fetches all data upon rendering of the panel accordion, which may be expensive if we talk about thousands of child (employee) records. The sample on ADF Code Corner queries the detail data when a panel tab is selected. Sample 81 on ADF Code Corner however doesn't cache the data of previously selected panels and thus always re-fetches detail data. When working with large data sets however he solution on ADF Code Corner performs better.

Wednesday Jan 18, 2012

How-to determine the ADF tree node type using EL

Creating an ADF tree in ADF produces an entry similar to this in the PageDef file of the view.
<tree IterBinding="AllCountriesIterator" id="AllCountries">
   <nodeDefinition 
        DefName="oracle.summit.model.views.CountryVO" Name="AllCountries0">
     <AttrNames>
       <Item Value="Country"/>
     </AttrNames>
     <Accessors>
       <Item Value="CustomerVO"/>
     </Accessors>
   </nodeDefinition>
   <nodeDefinition 
      DefName="oracle.summit.model.views.CustomerVO" Name="AllCountries1"
      TargetIterator="${bindings.AllCustomersIterator}">
      <AttrNames>
        <Item Value="Id"/>
        <Item Value="Name"/>
     </AttrNames>
   </nodeDefinition>
</tree>

Notice the DefName attribute on each node containing a reference to the actual View Object instance used to render a specific node.

With this information you can now use EL to render the tree nodes differently. For example, the page source below renders the node as a command link if the node presents a customer. For Countries, the node is simply rendered as an output text.
<af:tree value="#{bindings.AllCountries.treeModel}" var="node" ...>
  <f:facet name="nodeStamp">
    <af:group id="g1">
      <af:commandLink id="cl4" text="#{node}"                                                                             
                      rendered="#{node.hierTypeBinding.viewDefName ==  
                                       'oracle.summit.model.views.CustomerVO'}"
      .../>
      
      <af:outputText id="ot4" value="#{node}"                                                                               
               rendered="#{node.hierTypeBinding.viewDefName == 
                                     'oracle.summit.model.views.CountryVO'}"/>
    </af:group>
  </f:facet>
</af:tree>       
The EL expression  #{node.hierTypeBinding.viewDefName} returns the name of the node type, which in ADF is the absolute name of the collection instance rendering the node.

Tuesday Nov 22, 2011

How-to read data from selected tree node

By default, the SelectionListener property of an ADF bound tree points to the makeCurrent method of the FacesCtrlHierBinding class in ADF to synchronize the current row in the ADF binding layer with the selected tree node. To customize the selection behavior, or just to read the selected node value in Java, you override the default configuration with an EL string pointing to a managed bean method property. In the following I show how you change the selection listener while preserving the default ADF selection behavior.

To change the SelectionListener, select the tree component in the Structure Window and open the Oracle JDeveloper Property Inspector. From the context menu, select the Edit option to create a new listener method in a new or an existing managed bean.

For this example, I created a new managed bean. On tree node select, the managed bean code prints the selected tree node value(s)


import java.util.List;

import javax.el.ELContext;
import javax.el.ExpressionFactory;
import javax.el.MethodExpression;
import javax.faces.application.Application;
import javax.faces.context.FacesContext;

import java.util.Iterator;  
import oracle.adf.view.rich.component.rich.data.RichTree;
import oracle.jbo.Row;
import oracle.jbo.uicli.binding.JUCtrlHierBinding;
import oracle.jbo.uicli.binding.JUCtrlHierNodeBinding;
import org.apache.myfaces.trinidad.event.SelectionEvent;

import org.apache.myfaces.trinidad.model.CollectionModel;
import org.apache.myfaces.trinidad.model.RowKeySet;
import org.apache.myfaces.trinidad.model.TreeModel;  
public class TreeSampleBean {
 public TreeSampleBean() {}
 public void onTreeSelect(SelectionEvent selectionEvent) {
  //original selection listener set by ADF
  //#{bindings.allDepartments.treeModel.makeCurrent}
  String adfSelectionListener = "#{bindings.allDepartments.treeModel.makeCurrent}";
  //make sure the default selection listener functionality is 
  //preserved. you don't need to do this for multi select trees 
  //as the ADF binding only supports single current row selection  
 
  /* START PRESERVER DEFAULT ADF SELECT BEHAVIOR */
  FacesContext fctx = FacesContext.getCurrentInstance();
  Application application = fctx.getApplication();
  ELContext elCtx = fctx.getELContext();
  ExpressionFactory exprFactory = application.getExpressionFactory();
  
  MethodExpression me = null;
  me = exprFactory.createMethodExpression(elCtx, adfSelectionListener, 
                                          Object.class, newClass[]{SelectionEvent.class}); 
  me.invoke(elCtx, new Object[] { selectionEvent });  

  /* END PRESERVER DEFAULT ADF SELECT BEHAVIOR */
  RichTree tree = (RichTree)selectionEvent.getSource();
  TreeModel model = (TreeModel)tree.getValue();   
 
  //get selected nodes
  RowKeySet rowKeySet = selectionEvent.getAddedSet(); 
  Iterator rksIterator = rowKeySet.iterator(); 
  //for single select configurations,this only is called once 
  while (rksIterator.hasNext()) {
    List key = (List)rksIterator.next();
    JUCtrlHierBinding treeBinding = null;
    CollectionModel collectionModel = (CollectionModel)tree.getValue();
    treeBinding = (JUCtrlHierBinding)collectionModel.getWrappedData(); 
    JUCtrlHierNodeBinding nodeBinding = null;
    nodeBinding = treeBinding.findNodeByKeyPath(key);
    Row rw = nodeBinding.getRow();
    //print first row attribute. Note that in a tree you have to 
    //determine the node type if you want to select node attributes 
    //by name and not index 
    String rowType = rw.getStructureDef().getDefName();
  
    if(rowType.equalsIgnoreCase("DepartmentsView")){
      System.out.println("This row is a department: " +  
                         rw.getAttribute("DepartmentId"));
    }
    else if(rowType.equalsIgnoreCase("EmployeesView")){
     System.out.println("This row is an employee: " + 
                         rw.getAttribute("EmployeeId"));
    }     
    else{
      System.out.println("Huh????");  
    }
    // ... do more useful stuff here   
  }   

}

--------------------

Download JDeveloper 11.1.2.1 Sample Workspace


About

The Oracle JDeveloper forum ranks in the Top 5 of the most active forums on the Oracle Technology Network (OTN).



The OTN Harvest blog is a summary of selected topics posted on the OTN Oracle JDeveloper forum.



It is an effort to turn knowledge exchange into an interesting read for developers who enjoy little nuggets of wisdom





Frank Nimphius

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today