Selecting a row in a table with search results to drill down to its details is a fairly common operation in web application. I have implemented such functionality many times, in web applications based on JSP/TopLink, JSP/BC4J, JSF/Pojo, ADF 10g Faces/ADF BC. They are all similar, and they are all different. Today, I will do this in ADF 11g RichFaces on top of ADF BC – although the Model implementation is hardly relevant.
I am quite convinced that there are several ways to implement this functionality. I just picked one – and it works. Based on this article, you can easily achieve other results, for example from a single click on a table row (selection event) or a keypress on a row. My implementation will absorb the double click event on a row in the table and take the user to the detail page with the edit form for that record. The double click is captured on the client, transformed to an event that is queued and consumed by a server listener that invokes a managed bean. The method on this bean that retrieves the selected row from the table – the source of the event – and navigates to the Form Page. This page has a BindingContainer that executes the setCurrentRowWithKey to ensure the right (selected) record is presented for editing.
The steps
We start from the page created in a previous article with a master table on Departments.
1. In this table, I add a client listener to capture the double click event:
<af:table rows="#{bindings.EmployeesInDepartment.rangeSize}" .... binding="#{HrmManagerBean.empTable}" width="1043"> <af:clientListener method="goEditRow" type="dblClick"/> <af:serverListener type="doubleClickOnRow" method="#{HrmManagerBean.goEditCurrentRow}"/> <af:column headerText="#{bindings.EmployeesInDepartment.hints.Empno.label}"
The clientListener captures the double click on the table and invokes the goEditRow function – a JavaScript function that we should include in the page:
<af:document> <trh:script>goEditRow = function(event) { var source = event.getSource(); AdfCustomEvent.queue( source, "doubleClickOnRow" , {}, false); } </trh:script> <af:messages/> ...
The goEditRow function queues an event of type doubleClickOnRow. It indicates the source of the event – the table – but no specific payload.
2. Events such as doubleClickOnRow can be captured by a serverListener. That basically means that an operation on the server is invoked when the clientEvent is published. I add a serverListener under the clientListener. It consumes doubleClickOnRow events and when it does so, it will invoke the goEditCurrentRow method on the HrmManagerBean.
<af:clientListener method="goEditRow" type="dblClick"/> <af:serverListener type="doubleClickOnRow"
method="#{HrmManagerBean.goEditCurrentRow}"/>
<af:column headerText="#{bindings.EmployeesInDepartment.hints.Empno.label}"
3. This bean has been created and configured (in requestScope). It does not do awfully much, except determine which Employee is currently selected in the table and make that Empno available through its selectEmpno property. It also sets a flag shouldSelectEmployee that is used to execute the setCurrentRowWithKey action in the bindingContainer for the page the bean finally forces navigation to:
public class HrmManagerBean { private boolean shouldSelectEmployee = false; private oracle.jbo.domain.Number selectEmpno; private RichTable empTable; public HrmManagerBean() { } public void setShouldSelectEmployee(boolean shouldSelectEmployee) { this.shouldSelectEmployee = shouldSelectEmployee; } public boolean isShouldSelectEmployee() { return shouldSelectEmployee; } public void setSelectEmpno(Number selectEmpno) { this.selectEmpno = selectEmpno; } public Number getSelectEmpno() { return selectEmpno; } public void setEmpTable(RichTable empTable) { this.empTable = empTable; } public RichTable getEmpTable() { return empTable; } public void goEditCurrentRow(ClientEvent clientEvent) { // Determine which row is currently selected in the RichTable Iterator keys = getEmpTable().getSelectedRowKeys().iterator(); while (keys.hasNext()) { Key key = (Key)((List)keys.next()).get(0); this.selectEmpno = (Number)key.getKeyValues()[0]; } // force navigation to EmployeeEditPage.jspx. Note: this happens inside a PPR! FacesContext ctx = FacesContext.getCurrentInstance(); UIViewRoot newPage = ctx.getApplication().getViewHandler().createView(ctx, "/EmployeeEditPage.jspx"); ctx.setViewRoot(newPage); ctx.renderResponse(); // instruct the BindingContainer for the EmployeeEdit page to setCurrentRowWithKey this.setShouldSelectEmployee(true); } }
Note how even in a Partial Page Request navigation can be enforced to another page!
4. Set up the EmployeeEditPage
The page that handles the EmployeeEdit page is simple Databound Form, based on a different Employee ViewObject usage – one that is not a detail under the master departments.
In its page definition, two elements are added:
the action binding for SetCurrentRowWithKeyValue:
<action IterBinding="AllEmployeesIterator" id="setCurrentRowWithKeyValue" InstanceName="HrmServiceDataControl.AllEmployees" DataControl="HrmServiceDataControl" RequiresUpdateModel="false" Action="setCurrentRowWithKeyValue"> <NamedData NDName="rowKey" NDValue="${HrmManagerBean.selectEmpno}" NDType="java.lang.String"/> </action>
Note how the rowKey is based on the selectEmpno property in the HrmManagerBean. This means that the Employee to which the current row indicator is set is the one whose Empno is set in the bean. Which is of course the Empno for the employee that was clicked on in the table.
And to make sure that this action is executed when navigation is forced to this page after the double click, we also add an invokeAction element:
<invokeAction Binds="setCurrentRowWithKeyValue" id="invokeSetCurrentEmployee" Refresh="always" RefreshCondition="#{ HrmManagerBean.shouldSelectEmployee}"/>
Note how its RefreshCondition refers to the flag in the bean – the setCurrentRow is only executed when the flag is set.
When we now run the Departments page and double click on any employee, we are taken to the Form page for that employee where we can start editing.
Double click SCOTT:
Note: currently, we do not actually determine the row that was double clicked on, but assume it is the currently selected row. A more refined implementation would get the selected row from the double click event – but I am not sure how to do that. Perhaps it can only be done through event listeners on every column individually?
Bonus: Add New Employee
Very much along the same lines did we add support for the creation of a new Employee. The steps:
add a button to the toolbar in the panelCollection that wraps the table
<af:panelCollection inlineStyle="width:841px;"> <f:facet name="menus"/> <f:facet name="toolbar"> <af:toolbar> <af:commandToolbarButton text="Add Employee" action="#{HrmManagerBean.createEmployee}"/> </af:toolbar>
have the button’s action link to a bean’s method
in the method, return a navigation outcome that takes the user to the EditEmployee page; set a flag that tells the BindingContainer for that page to execute the create action
public String createEmployee() { shouldCreateEmployee = true; return "create"; }
in the pageDefinition include an action binding for the Create action and an invokeAction that invokes that action binding when the flag in the bean is set.
<invokeAction Binds="Create" id="InvokeCreateEmployee" RefreshCondition="#{HrmManagerBean.shouldCreateEmployee}" Refresh="always"/> <action IterBinding="AllEmployeesIterator" id="Create" InstanceName="HrmServiceDataControl.AllEmployees" DataControl="HrmServiceDataControl" RequiresUpdateModel="true" Action="createRow"/>
Click on Add Employee button to create a new Employee:
Resources
Download JDeveloper 11g Application doubleclicktonavigate.zip.
Hi Lucas,
My af:table has editingMode=”clickToEdit”.
A row becomes editable by double clicking on it – this is a default.
How can I overide this editable behaviour with ONLY SINGLE click instead of Double?
So I want table row to be editable with Single click.
Should I create Client Listener with type (selection event) to achieve this? And what the java script will do?
Please advice.
Hi Lucas,
I have one problem which I think probably related to this article. This is the behavior I want. Let us use the Department – Employee tables. Supposed I have a table of department and when I double click it, the pop up table of employees will appear. When I set to rowSelection=multiple in the department table, it will not immediately highlighted until I refresh the department table. What I want is when I double click the table of department, it will immediately highlighted. When I double clickanother record, the first highlighted(chosen) record will not vanish, then two records are highlighted. At the same time when I refresh the department table, the two chosen records are still highlighted (I think in this case the rowSelection=’mulitple’ is ok).