There are situations where you need a toolbar to provide the user with functionality like insert, update, delete, next, previous and so on. This looks straightforward, but it is not that easy to have only one toolbar to service your whole page. In this post I will show an example of how to implement such a toolbar. I will create a single button that can do an insert on multiple different components. Let’s start with showing how you would usually create insert functionality; a simple example.
The examples in this post are based on default ADF BC from the HR employee and department tables. First we create a page with only one table component. The table is based on the Departments iterator. We also add a button to create a new row. This can be achieved by dropping the createInsert operation from the datacontrol palette onto the page. If you run the page and push the button, a new row is created in the table.
That was easy.
Now let’s make it more complicated.
We create a second table. This one is based on the Employees iterator. Now we actually want to use the same button to insert a row into this table. This is, of course, not possible because the button is designed to insert a row into the Departments iterator.
that can be used to insert a row in the departments table or in a different table ?
Actually we can do that. We need to figure out a couple of things to create such functionality.
- First we need to figure out what table we are trying to insert.
Second we need to create a method to invoke programmatic insert.
Lets start with the first part.
What is the table that needs the new row ?
In this use case, it is in fact the ‘active’ table, or in other words, the table that currently has focus, or even better, the table that we just clicked on. An component has a selectionListener attribute. Usually the standard selection listener is used.
selectionListener="#{bindings.DepartmentsView1.collectionModel.makeCurrent}"
To find out the table that currently has focus we will have to implement a custom table selectionListener that will:
1) set the current row as soon as the user clicks on it (which is more or less default behavior).
2) extend that with functionality to tell the application what the currently active table is.
All this is coded in a newly created bean.
selectionListener="#{pageFlowScope.twoTablesBean.makeSelectionAndSetActiveTable}"
Because the selectionListener uses a SelectionEvent to ‘do its thing’ We can use the same selectionListener for both tables (and any others that you might think of). Based on the SelectionEvent, we can find the table that is associated with the event.
From there we can get to the iterator associated with the table, and set it’s current row.
public void makeSelectionAndSetActiveTable(SelectionEvent selectionEvent) { // Thanks to Frank Nimphius RichTable _table = (RichTable)selectionEvent.getSource(); //the Collection Model is the object that provides the structured data //for the table to render CollectionModel _tableModel = (CollectionModel)_table.getValue(); //the ADF object that implements the CollectionModel is JUCtrlHierBinding. It //is wrapped by the CollectionModel API JUCtrlHierBinding _adfTableBinding = (JUCtrlHierBinding)_tableModel.getWrappedData(); //Acess the ADF iterator binding that is used with ADF table binding DCIteratorBinding _tableIteratorBinding = _adfTableBinding.getDCIteratorBinding(); Object _selectedRowData = _table.getSelectedRowData(); //cast to JUCtrlHierNodeBinding, which is the ADF object that represents //a row JUCtrlHierNodeBinding _nodeBinding = (JUCtrlHierNodeBinding)_selectedRowData; //get the row key from the node binding and set it as the current row in the //iterator Key _rwKey = _nodeBinding.getRowKey(); _tableIteratorBinding.setCurrentRowWithKey(_rwKey.toStringFormat(true)); }
Now that we know what iterator is associated with the selected table (the active table), we need to store this value in a variable.
This variable to hold the value of the current iterator, is created in the TwoTablesBean and is of the type DCIteratorBinding.
DCIteratorBinding theCurrentOne; public void setTheCurrentOne(DCIteratorBinding theCurrentOne) { this.theCurrentOne = theCurrentOne; } public DCIteratorBinding getTheCurrentOne() { return theCurrentOne; }
Now add code in the makeSelectionAndSetActiveTable method to set the value of this variable.
public void makeSelectionAndSetActiveTable(SelectionEvent selectionEvent) { ......... setTheCurrentOne(_tableIteratorBinding); }
The selectionListener is working, the current table is known so all is in place to implement the insert functionality.
The final step.
Create a button, with an associated actionListener, and code in the actionListener to perform a programmatic insert.
<af:commandToolbarButton text="insert" id="cb1" partialSubmit="true" actionListener="#{pageFlowScope.twoTablesBean.createRecord}"/>
This insert action will not be performed on the operationbinding in the bindingcontainer, but we will use a programmatic insert. This is done by invoking createRow() on an iterator. The iterator that the createRow() will be invoked on is the iterator that is stored in the theCurrentOne variable. With invoking the getter on theCurrentOne we can retrieve this iterator, and create a new row.
public void createRecord(ActionEvent evt) { DCIteratorBinding it = getTheCurrentOne(); Row rw = it.getNavigatableRowIterator().createRow(); rw.setNewRowState(Row.STATUS_INITIALIZED); it.getNavigatableRowIterator().insertRow(rw); refreshUpdatedTable(evt); }
And now it works ! We have one button that can insert both Departments and Employees.
You can continue to add buttons to edit/delete/save and more if you like.
Extra
One more example I’ll provide here is to add edit functionality in a popup window.
It is pretty straightforward because we can use ‘theCurrentOne’ to determine what popup to show.
<af:commandToolbarButton text="edit" id="cb2" partialSubmit="true"> <af:showPopupBehavior popupId="#{pageFlowScope.twoTablesBean.theCurrentOne.name}" triggerType="click" partialTrigger="md1 t1" /> </af:commandToolbarButton> <af:popup id="EmpIt" contentDelivery="lazyUncached"> <af:dialog id="d3" type="okCancel" title="Edit #{pageFlowScope.twoTablesBean.theCurrentOne.name}" resize="on"> <af:panelFormLayout id="pfl3"> ......................... </af:panelFormLayout> </af:dialog> </af:popup> <af:popup id="DepIt" contentDelivery="lazyUncached"> <af:dialog id="d2" type="okCancel" title="Edit #{pageFlowScope.twoTablesBean.theCurrentOne.name}" resize="on"> <af:panelFormLayout id="pfl2"> ......................... </af:panelFormLayout> </af:dialog> </af:popup>
I found that theCurrentOne variable must have a default value (or an if null construct). If not, the page will not function properly because popupId=”#{pageFlowScope.twoTablesBean.theCurrentOne.name}” cannot be resolved. Once I figured this out, the rest was pretty straightforward.
In the corresponding popup, you show the either departments ……
…… or employees to edit.
Resources
A sample workspace can be downloaded here.
Frank Nimphius about selection listener
Andrejus Baranovskis about crud operations in tables
Hi,
Can you please give us code sample how to handle when we have one table with formlayout and one with adf table?
regards
murali