JDeveloper allows to easily create tables with the af:table component. The table allows easy access to the selected row or rows. However, if you are interested in which cell of a table has been clicked, ADF needs some tweaking. This blog is about how to tweak an af:table to get exactly this info.

Use Case

You like to know which cell in an af:table a user has clicked, e.g. to get some detailed information about the clicked item or cell in the selected row. The sample I show gets the information about the current row, and column of the cell and the value of the cell clicked. The final sample will show the info like

Final solution example - Getting all information on selected af:table cell

Final solution example – Getting all information on selected af:table cell

How to do it?

The normal af:table component doesn’t give information about the cell a user has clicked on. The adf pivot table offers this, but is complex to use.

We use JavaScript in form of a clientListener to intercept the click on a cell and a serverListener to call a bean method to get more data on the cell. This article 011. ADF Faces RC – How-to use the Client and Server Listener Component shows how to use clientListener and serverListener in detail.

As we are interested in the selected cell, we add a clientListerer to each af:outputText which shows the column value in the af:table which fires on the click event. The clientListener calls a JavaScript method. In the JavaScript method, we build a payload of the UIComponent which is used to show the column value and the column name of the cell. To get this information we have several possible ways:

  1. We can use our knowledge of the DOM tree and get the column via the parent of the component which fired the event. The parent component should be the af:column.
  2. We add a client attribute to the component which shows the cell value adding the column name from the af:column as EL

In this sample, we choose the second solution. With this information, we call the serverListener from the JavaScript method. The serverListener method is implemented in a request scope bean and uses the information passed to get the details about the clicked cell we show in the UI.

Implementation

The sample uses the HR DB schema and only needs one table, Employees in this case. We create a simple page with the table in read-only mode, sortable and filterable. As you see in the image above the table is just build be dragging the Employees VO onto the page and drop it as read-only table.

Now we add a clientListener and a serverListener to each outputText component which is used to show the cell value

<af:table value="#{bindings.EmployeesView.collectionModel}" var="row" rows="#{bindings.EmployeesView.rangeSize}"
          emptyText="#{bindings.EmployeesView.viewable ? 'No data to display.' : 'Access Denied.'}" fetchSize="#{bindings.EmployeesView.rangeSize}"
          rowBandingInterval="0" filterModel="#{bindings.EmployeesViewQuery.queryDescriptor}"
          queryListener="#{bindings.EmployeesViewQuery.processQuery}" filterVisible="true" varStatus="vs"
          selectedRowKeys="#{bindings.EmployeesView.collectionModel.selectedRow}"
          selectionListener="#{bindings.EmployeesView.collectionModel.makeCurrent}" rowSelection="single" id="t1">
  <af:column sortProperty="#{bindings.EmployeesView.hints.EmployeeId.name}" filterable="true" sortable="true"
             headerText="#{bindings.EmployeesView.hints.EmployeeId.label}" id="c5">
    <af:outputText value="#{row.EmployeeId}" id="ot5">
      <af:convertNumber groupingUsed="false" pattern="#{bindings.EmployeesView.hints.EmployeeId.format}"/>
      <af:clientAttribute name="columnName" value="#{bindings.EmployeesView.hints.EmployeeId.label}"/>
      <af:clientListener type="click" method="clientCellSelectionCall"/>
      <af:serverListener type="cellSelection" method="#{TableCellSelectionBean.handleTableCellSelection}"/>
    </af:outputText>
  </af:column>
  <af:column sortProperty="#{bindings.EmployeesView.hints.FirstName.name}" filterable="true" sortable="true"
             headerText="#{bindings.EmployeesView.hints.FirstName.label}" id="c4">
    <af:outputText value="#{row.FirstName}" id="ot6">
      <af:clientAttribute name="columnName" value="#{bindings.EmployeesView.hints.FirstName.label}"/>
      <af:clientListener type="click" method="clientCellSelectionCall"/>
      <af:serverListener type="cellSelection" method="#{TableCellSelectionBean.handleTableCellSelection}"/>
    </af:outputText>
  </af:column>
  <af:column sortProperty="#{bindings.EmployeesView.hints.LastName.name}" filterable="true" sortable="true"
             headerText="#{bindings.EmployeesView.hints.LastName.label}" id="c1">
    <af:outputText value="#{row.LastName}" id="ot4">
      <af:clientAttribute name="columnName" value="#{bindings.EmployeesView.hints.LastName.label}"/>
      <af:clientListener type="click" method="clientCellSelectionCall"/>
      <af:serverListener type="cellSelection" method="#{TableCellSelectionBean.handleTableCellSelection}"/>
    </af:outputText>
  </af:column>
  <af:column sortProperty="#{bindings.EmployeesView.hints.Email.name}" filterable="true" sortable="true"
             headerText="#{bindings.EmployeesView.hints.Email.label}" id="c3">
    <af:outputText value="#{row.Email}" id="ot2">
      <af:clientAttribute name="columnName" value="#{bindings.EmployeesView.hints.Email.label}"/>
      <af:clientListener type="click" method="clientCellSelectionCall"/>
      <af:serverListener type="cellSelection" method="#{TableCellSelectionBean.handleTableCellSelection}"/>
    </af:outputText>
  </af:column>
  <af:column sortProperty="#{bindings.EmployeesView.hints.PhoneNumber.name}" filterable="true" sortable="true"
             headerText="#{bindings.EmployeesView.hints.PhoneNumber.label}" id="c2">
    <af:outputText value="#{row.PhoneNumber}" id="ot3">
      <af:clientAttribute name="columnName" value="#{bindings.EmployeesView.hints.PhoneNumber.label}"/>
      <af:clientListener type="click" method="clientCellSelectionCall"/>
      <af:serverListener type="cellSelection" method="#{TableCellSelectionBean.handleTableCellSelection}"/>
    </af:outputText>
  </af:column>
</af:table>

In the image above we see the listener for two columns. In addition, we add an af:clientAttribute with the name ‘columnName’ which we pass the EL of the af:column headerText property.

Next, we add an af:resource component to the af:document where we specify the JavaScript for the clientListener method ‘clientCellSelectionCall’. We could have added the method to the page directly, but if we want to reuse the pattern, it’s better to use a JavaScript file

<af:resource type="javascript" source="/javascript/columnSelection.js"/>

The file is located in the public_html folder (Web Content) in a subdirectory ‘javascript’

Javascript file location

Javascript file location

The method code is

function clientCellSelectionCall(event) {
    component = event.getSource();
    var columnName = component.getProperty("columnName");
    AdfCustomEvent.queue(component, "cellSelection",
    {
        payload : component, column : columnName
    },
    true);
}

The click event on the af:outputText component triggers a call to the javascript method ‘clientCellSelectionCall’ (via the clientListener) with the source of the event, the af:outputText component. The method reads the clientAttribute added (line 3) and calls the serverListener of type ‘cellSelection’. This event is defined by the af:serverListener on the af:outputText. The component which triggered the event and the column name added as client attribute are passed to the serverListener.

The serverListener is a bean method defined in a request scope bean on the af:outputText component as

method="#{TableCellSelectionBean.handleTableCellSelection}"

In the bean the method look like

    public void handleTableCellSelection(ClientEvent event) {
        // get payload which is the ui component which fired the event
        UIComponent ui = (UIComponent)event.getParameters().get("payload");
        // get the column from the event which is sent too
        String column = (String)event.getParameters().get("column");
        RichOutputText rt = (RichOutputText)ui;
        // get current row key
        DCBindingContainer bindingContainer = (DCBindingContainer)BindingContext.getCurrent().getCurrentBindingsEntry();
        DCIteratorBinding binding = bindingContainer.findIteratorBinding("EmployeesViewIterator");
        Row currentRow = binding.getCurrentRow();
        Key key = currentRow.getKey();
        // compile info about clicked cell
        String out = "Payload:" + ui + "<br>column: "+ column + "<br>val: " + rt.getValue() + "<br>key: "+key.toString();
        logger.info(out);
        setCellInfo(out);
    }

Here we get the component which triggered the event (as payload) and the name of the column. Using this information we can get e.g. to the value of the column (via the ui component). The row of the cell we get via the current row of the iterator. With this information we get the key of the row. We can get much more information here, like historical data about the current employees salary, if the salary cell was clicked.

We just create a string from the information which we show in the UI to the user.

First example

First example

Here are some images of different cells clicked in the UI:

Second example

Second example

Third example

Third example

Download

You can download the sample, which was built using JDeveloper 11.1.1.9, from GitHub BlogTableCellSelection. The sample uses the HR DB schema.

Timo Hahn