ADF 11g RichFaces – handling the client side double click to invoke a server side operation

 

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.

ADF 11g RichFaces - handling the client side double click to invoke a server side operation doubleclickdrilldown0000

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:

ADF 11g RichFaces - handling the client side double click to invoke a server side operation doubleclickdrilldown0001

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.

ADF 11g RichFaces - handling the client side double click to invoke a server side operation doubleclickdrilldown0002

Double click SCOTT:

ADF 11g RichFaces - handling the client side double click to invoke a server side operation doubleclickdrilldown0003

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"/>

ADF 11g RichFaces - handling the client side double click to invoke a server side operation doubleclickdrilldown0004

Click on Add Employee button to create a new Employee:

ADF 11g RichFaces - handling the client side double click to invoke a server side operation doubleclickdrilldown0005

Resources

Download JDeveloper 11g Application doubleclicktonavigate.zip.

 

2 Comments

  1. Vesna January 15, 2010
  2. dreamboy November 5, 2008