Wednesday Sep 12, 2012

Iterating selected rows in an ADF Faces table

In OTN Harvest May 2012; http://www.oracle.com/technetwork/developer-tools/adf/learnmore/may2012-otn-harvest-1652358.pdf I wrote about "Common mistake when iterating <af:table> rows". In this entry I showed code to access the row associated with a selected table row from the binding layer to avoid the problem of having to programmatically change the selected table row.

As it turns out, my solution only worked fro selected table rows that are in the current iterator query range. So here's a solution that works for all ranges

public String onButtonPress() {
  RowKeySet rks = table.getSelectedRowKeys();
  Iterator it = rks.iterator();
  while (it.hasNext()) {
   List selectedRowKeyPath = (List)it.next();            
   //table is the JSF component reference created using the table's binding
   //property         
   Row row = ((JUCtrlHierNodeBinding)table.getRowData(selectedRowKeyPath)).getRow();
   System.out.println("Print Test: " + row.getAttribute(1));
  }
   return null;
}


Tuesday Jul 17, 2012

How-to add new ADF bound table row at the end of a table

A frequent question on OTN, which I often see answered with new rows created on the ADF BC view object level instead the ADF binding layer, is about how to create a new row for a table and append it as the last row in the table. The following code below shows how to do this properly using the binding layer only. Note that while it adds the row as the last row of the table, it doesn't do this as the last row of all possible rows that haven't yet been queried from the database. There is a difference between the last row in a table and the last row in the database.

public String onRowCreate() {
 BindingContainer bindings = BindingContext.getCurrent().getCurrentBindingsEntry();
 //access the name of the iterator the table is bound to. Its "allDepartmentsIterator"
 //in this sample
 DCIteratorBinding dciter = (DCIteratorBinding) bindings.get("allDepartmentsIterator");
 //access the underlying RowSetIterator
 RowSetIterator rsi = dciter.getRowSetIterator();
 //get handle to the last row
 Row lastRow = rsi.last();
 //obtain the index of the last row
 int lastRowIndex = rsi.getRangeIndexOf(lastRow);
 //create a new row
 Row newRow = rsi.createRow();
 //initialize the row
 newRow.setNewRowState(Row.STATUS_INITIALIZED);
 //add row to last index + 1 so it becomes last in the range set
 rsi.insertRowAtRangeIndex(lastRowIndex +1, newRow); 
 //make row the current row so it is displayed correctly
 rsi.setCurrentRow(newRow);                          
 return null;
}  

For the table to show the newly created row after this ensure:

1. The table is configured to always show the current selected row by setting  its displayRow property to selected

2. The table is PPR'ed after the row is created, which can be done declarative using the PartialTriggers property of the table pointing to the ID of the command creating the new row

But what is wrong with the approach of doing the same on the View Object level in ADF BC if it works? Well, it works but it doesn't ensure separation of the layers unless the functionality is exposed as a method on the view object interface. The code in this blog entry is generic and works the same for other business services, like EJB, POJO and Web Services for creating new table rows (which then however you need to persist explicitly in opposite to what you have to do when using ADF BC). So while the other code works, the one in this is the proper solution.

Wednesday May 30, 2012

Problem with deleting table rows using ctrl+a for row selection

The following code is commonly shown and documented for how to access the row key of selected table rows in an ADF Faces table configured for multi row selection.

public void onRemoveSelectedTableRows(ActionEvent actionEvent) {
   RichTable richTable = … get access to your table instance …
   CollectionModel cm =(CollectionModel)richTable.getValue();
   RowKeySet rowKeySet = (RowKeySet)richTable.getSelectedRowKeys();        
  
   for (Object key : rowKeySet) {
      richTable.setRowKey(key);
      JUCtrlHierNodeBinding rowData = (JUCtrlHierNodeBinding)cm.getRowData();
      // do something with rowData e.g.update, print, copy
   }
   //optional, if you changed data, refresh the table      
   AdfFacesContext adfFacesContext = AdfFacesContext.getCurrentInstance();
   adfFacesContext.addPartialTarget(richTable);  
   return null;
}

The code shown above works for 99.5 % of all use cases that deal with multi row selection enabled ADF Faces tables, except for when users use the ctrl+a key to mark all rows for delete. Just to make sure I am clear: if you use ctrl+a to mark rows to perform any other operation on them – like bulk updating all rows for a specific attribute – then this works with the code shown above. Even for bulk row delete, any other mean of row selection (shift+click and multiple ctrl+click) works like a charm and the rows are deleted.

So apparently it is the use of ctrl+a that causes the problem when deleting multiple rows of an ADF Faces table. To implement code that works for all table selection use cases, including the one to delete all table rows in one go, you use the code shown below.

public void onRemoveSelectedTableRows(ActionEvent actionEvent) {
  RichTable richTable = … get access to your table instance …
  CollectionModel cm = (CollectionModel)richTable.getValue();
  RowKeySet rowKeySet = (RowKeySet)richTable.getSelectedRowKeys();

  Object[] rowKeySetArray = rowKeySet.toArray();   

  for (Object key : rowKeySetArray){           
    richTable.setRowKey(key);
    JUCtrlHierNodeBinding rowData = (JUCtrlHierNodeBinding)cm.getRowData();                           
    rowData.getRow().remove();
  }
  
  AdfFacesContext adfFacesContext = AdfFacesContext.getCurrentInstance();          
  adfFacesContext.addPartialTarget(richTable);
}

Tuesday May 29, 2012

Common mistake when iterating rows

In a blog entry from 2010, Oracle's Jobinesh Purushothaman writes about exceptions that are getting raised after developers iterate the collection of a RichTable instance using calls to setRowKey(key).

http://jobinesh.blogspot.co.uk/2010/02/common-mistake-while-iterating-through.html

The reason for the exception is that the selected row state is change on the table without synchronization to happen with the ADF binding layer. The failure of synchronizing the table selected row state with the ADF binding layer is because no row selection event is raised on the table if the row currency is changed programmatically. In theory, if you call #{bindings.treebinding.treeModel.makeCurrent}using a method expression in EL

The failed binding synchronization is because no row selection event is fired when the selected row state is changed on the table using Java in a managed bean.

A solution to this problem could be to call #{bindings.treebinding.treeModel.makeCurrent}from a method expression in Java each time you called setRowKey(key). But, assuming the iteration of rows in a table is for aggregating numeric values or to compare two rows, do you really want to change the current row in the ADF binding layer for each row you need to access?

A better implementation for this use case in my opinion is to read the data from the ADF binding layer instead of the RichTable table instance, which then will not change the row currency at all.

RichTable table = ... get table reference ...
Iterator iter = selectedRowKeys.iterator();
CollectionModel model = (CollectionModel ) table.getValue();
JUCtrlHierBinding treeBinding = (JUCtrlHierBinding )model.getWrappedData();
if (iter != null && iter.hasNext()) {
  Object rowKey = iter.next();
  JUCtrlHierNodeBinding rowData = treeBinding.findNodeByKeyPath(rowKey);     
  //Do something here

}

Note that the reported problem in Jobinesh's blog is for ADF bound tables configured for single row selection. Still I would prefer my approach over changing the selected row state on a table.


Wednesday Jan 11, 2012

Display selected table row number and total rows

A question on OTN was how to display the current row number and the number of totally available rows in a formatted output string. In the example used below, the format is
row no. [<row index>] of max rows [<total numbers of rows>]

For this example, I used an af:outputText component to display the information on the page. The value of the af:outputText component is calculated from expression language (EL) accessing the ADF binding layer as shown below. Note that for better readability the EL includes line breaks, which in your application you should not add.

 row no. [

#{(bindings.allDepartmentsIterator.rangeStart < 0 ?
1 : bindings.allDepartmentsIterator.rangeStart+1) +

( bindings.allDepartmentsIterator.currentRowIndexInRange == -1 ?

0 : bindings.allDepartmentsIterator.currentRowIndexInRange)}
]

of max rows[

#{bindings.allDepartmentsIterator.estimatedRowCount}]

Note how the expression uses the iterator binding in the ADF binding layer to determine the maximum number of table rows. It then uses the same iterator to determine the current row. Because iterators are zero based, the EL uses additional logic to compensate this.

The af:outputText component needs to have its PartialTriggers property pointing the the af:table component ID to ensure the displayed information is refreshed when the table row currency changes.

 <af:outputText value="row no. [#{(bindings.allDepartmentsIterator.rangeStart &lt; 0 ? 1  :  bindings.allDepartmentsIterator.rangeStart+1) +( bindings.allDepartmentsIterator.currentRowIndexInRange ==  -1 ? 0 : bindings.allDepartmentsIterator.currentRowIndexInRange)}] of max rows [#{bindings.allDepartmentsIterator.estimatedRowCount}]"
                id="ot1" partialTriggers="pc1:t1"/>


Wednesday Jun 22, 2011

JDeveloper 11.1.2 : Command Link in Table Column Work Around

 Just figured that in Oracle JDeveloper 11.1.2, clicking on a command link in a table does not mark the table row as selected as it is the behavior in previous releases of Oracle JDeveloper. For the time being, the following work around can be used to achieve the "old" behavior:

To mark the table row as selected, you need to build and queue the table selection event in the code executed by the command link action listener. To queue a selection event, you need to know about the rowKey of the row that the command link that you clicked on is located in. To get to this information, you add an f:attribute tag to the command link as shown below

<af:column sortProperty="#{bindings.DepartmentsView1.hints.DepartmentId.name}" sortable="false"
   headerText="#{bindings.DepartmentsView1.hints.DepartmentId.label}" id="c1">
  <af:commandLink text="#{row.DepartmentId}" id="cl1" partialSubmit="true"
      actionListener="#{BrowseBean.onCommandItemSelected}">
    <f:attribute name="rowKey" value="#{row.rowKey}"/>
  </af:commandLink>
  ... 
</af:column>

The f:attribute tag references #{row.rowKey} wich in ADF translates to JUCtrlHierNodeBinding.getRowKey(). This information can be used in the command link action listener to compose the RowKeySet you need to queue the selected row. For simplicitly reasons, I created a table "binding" reference to the managed bean that executes the command link action. The managed bean code that is referenced from the af:commandLink actionListener property is shown next:

public void onCommandItemSelected(ActionEvent actionEvent) {

  //Do here what the command link is supposed to do
  // …  

   //In the following, set the current row  to the row that the command
   //link the user pressed is in

  //get access to the clicked command link 
  RichCommandLink comp = (RichCommandLink)actionEvent.getComponent();
  //read the added f:attribute value
  Key rowKey = (Key) comp.getAttributes().get("rowKey");
 
  //get the current selected RowKeySet from the table
  RowKeySet oldSelection = table.getSelectedRowKeys();
  //build an empty RowKeySet for the new selection
  RowKeySetImpl newSelection = new RowKeySetImpl();
  
  //RowKeySets contain List objects with key objects in them
  ArrayList list = new ArrayList();
  list.add(rowKey);
  newSelection.add(list);
  
  //create the selectionEvent and queue it
  SelectionEvent selectionEvent = new SelectionEvent(oldSelection, newSelection, table);
  selectionEvent.queue();   

  //refresh the table
  AdfFacesContext.getCurrentInstance().addPartialTarget(table);
}

Update 07/2011

While the solution above works nice for links that don't navigate off the page, it doesn't work well in a master-detail scenario where the detail collection is displayed on a separate page. To cover this use case I provided an ADF sample workspace that uses EJB and TopLink as a model. The sample shows two use cases that both show on the second page:

1. The employee data displayed on the ViewEmployees.jsf page is read from a dependent collection of the departmentsFindAll collection

2. The employee data displayed on the ViewEmployees.jsf page is read from the return of a finder method getEmployeesFindbydepartmentId.

 The changed code is shown below:

public void onDepartmentsRowSelection(ActionEvent actionEvent) {
  //Set the current row  to the row that the command
  //link the user pressed is in get access to the clicked
  //command link

  //USE CASE 1 ---- DEPENDENT COLLECTION  (departmentsFindAll

  //setting the row currency for the departments will then
  //implicitly query dependent childs in table 1 on pae 2
  RichCommandLink comp = (RichCommandLink)actionEvent.getComponent();
  //read the added f:attribute value
  Key rowKey = (Key) comp.getAttributes().get("rowKey");
  //table sis parent/parent of link
  RichTable table = (RichTable) comp.getParent().getParent();
  CollectionModel model = (CollectionModel) table.getValue();
  JUCtrlHierBinding treeBinding = (JUCtrlHierBinding)model.getWrappedData();
  DCIteratorBinding iterator = treeBinding.getDCIteratorBinding();
  iterator.setCurrentRowWithKey(rowKey.toStringFormat(true));  
 
  //USE CASE 2 ---- INDEPENDENT COLLECTION 

  //prepare attribute for page navigation. This attribute is looked up by
  //the getEmployeesFindByDepartmentId finder method on page 2
  ADFContext adfCtx = ADFContext.getCurrent();
  Map pageFlowScope = adfCtx.getPageFlowScope();
  pageFlowScope.put("rowKey", rowKey.getAttribute(0));    
}

The sample handles both cases in the managed bean that handles the action event on the command link in the table. You can download the workspace from here:

http://blogs.oracle.com/jdevotnharvest/resource/AdfEjbSample.zip

Open the sample application in JDeveloper 11.1.2.0 and change the database connection to a database of yours that has the HR schema installed and enabled.

Note: Once the mentioned selection problem is fixed, this sample is no longer required other than to learn how to access a command link and its parent table from a managed bean. Use case 1 then will work out-of-the-box using pure declarative development gestures.


        
    
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