Navigating through an ADF editable table with arrow keys

For a client that uses a lot of ADF editable table I was exploring the possibilities of navigating through an editable table with the arrow keys. They often need to edit values in one column for all the rows in the table. The only way to do that now is using the mouse or keep clicking the tab key until the focus ends up in the field you want. That’s not really good user experience, so we implemented a generic arrow navigation script that works on all editable tables in your application.

Navigating (read record selection) in a read only table is out-of-the-box supported by the framework. In an editable table this also works when the focus is not on an input component but on the table cell. When the focus is in an input field and I click on the up/down arrow keys… nothing happens! (well actually the cursor is placed in front (key up) or behind (key down) the value of your input component but that is not what we want)

I will use the HR.EMPLOYEES table as an example.

ADF editable table navigation

Arrow key navigation in an ADF editable table is not available by default

Let’s say we want to alter the salary for all employees. There are 2 ways to do that:
– Alter salary in row 1, select row 2 with your mouse, alter value in row 2, select row 3 with your mouse…
– Alter salary in row 1, click tab 7 times, alter value in row 2, click tab 7 times…

Both are not really an option if you need to edit this regularly so we came up with the following solution.

Important note: This solution contains calls to ADF internal javascripts objects. These are intended to be used only internally by the ADF Faces component framework. Changes to internal objects are applied without further notice and with no guarantee for backward compatibility, which is why application developers should only use the public api’s.
That having said… Sometimes necessity knows no law.

First we register a javascript keyHandler in the onload of the main page

    <af:document title="EditableTable.jsf" id="d1">
        <af:resource type="javascript" source="/resources/js/EditableTableNavigation.js"/>
        <af:clientListener method="registerKeyBoardHandler" type="load"/>

This will run the registerKeyBoardHandler function in the EditableTableNavigation.js

function registerKeyBoardHandler(event) {
    for (var i = keyRegistry.length - 1; i >= 0; i--)
    {
      var keyStroke = AdfKeyStroke.getKeyStrokeFromMarshalledString(keyRegistry[i]);
      AdfRichUIPeer.registerKeyStroke(event.getSource(), keyStroke, callBack);
    }
}

// defines the keys that are registered to be handled by the callback function in this script
var keyRegistry = new Array();
keyRegistry.push("UP");
keyRegistry.push("DOWN");

Now every up or down arrow keystroke will call the callBack function below. I added comments in the code sample below to guide you through the code.

function callBack(keyCode)
{
    var ac = AdfPage.PAGE.getActiveComponent();
    var marshalledKeyCode = keyCode.toMarshalledString(keyCode);
    var componentType = ac.getComponentType();
    if(marshalledKeyCode == "UP" || marshalledKeyCode == "DOWN")
    {
      // Ignore arrow actions in drop down list and mult-line text fields
      // NOTE: for getRows to return correct value, 
      // clientComponent must be set to true on inputText!
      if(componentType=='oracle.adf.RichSelectOneChoice' || ac.getProperty("rows")>1)
      {
        // don't handle as function key up/down
        return false;
      } else if (marshalledKeyCode == "UP" || marshalledKeyCode == "DOWN") {
        // Let's try and find out if we are in a table
        var parent = ac.getParent();
        while (parent)  {
          if (parent instanceof AdfRichTable) {
              break;
          }
          parent = parent.getParent();
        }
        // Did we find a table?
        if (parent instanceof AdfRichTable) {
          //using a matcher to split up the clientId 
          // so we can calculate the new clientId we want to navigate to
          var clientId = ac.getClientId().match("(.*):([0-9]+)(:[^:]+)");
          var newRowKey
          var tablePeer  = parent.getPeer();
          tablePeer.bind(parent);
          // ask for the previous/next rowkey
          switch (marshalledKeyCode) {
            case "UP":
                newRowKey = tablePeer._getPreviousRowKeyAndRow(clientId[2]).rowKey;
                break;
            case "DOWN":
                newRowKey = tablePeer._getNextRowKeyAndRow(clientId[2]).rowKey;            
                break;
          }
          // Check if page is ready (cancel navigation if not)
          // Otherwise when holding down the arrow keys the navigation gets out of sync
          if (!AdfPage.PAGE.isSynchronizedWithServer()) {
              return true;
          }
          // Handle the table row selection (but not the focus to the input field)
          tablePeer._handleTableBodyArrowUpDown(marshalledKeyCode == "UP", false, false);
          // For a click to edit table we need a more extensive approach
          if(!tablePeer._isClickToEdit()) {
            // Edit all mode
            // We use the beginning and the end of our old clientId and replace the rowKey part
            var newComp = AdfPage.PAGE.findComponent(clientId[1] + ":" +newRowKey + clientId[3]);
            // If we can find the component, set focus
            if (newComp!=null) {
              newCompPeer = newComp.getPeer();
              newCompPeer.bind(newComp);
              // find the html input element, set focus and select the value 
              // (so you can edit it immediately)
              var inputElem = newCompPeer.getDomContentElement().firstElementChild;
              inputElem.focus();
              inputElem.select();
              var counter = 0;
             // Check every 10ms if the page is ready (in case of PPR focus gets lost)
              var interval = setInterval(function () {
                if (++counter === 100 || AdfPage.PAGE.isSynchronizedWithServer()) {
                  inputElem.focus();
                  inputElem.select();
                  clearInterval(interval);
                }
              },10);
            }
          } 
          return true;
        }
        // We are not in a table, handle as usual.
        return false;
    }
  } 
}

With this code in place you can navigate vertically through the editable table with the arrow key’s. There is a limitation on date fields because they have a keyhandler on the down arrow that opens the date popup. The only way to fix that is by overriding an ADF internal method.
Below you will find a link to a ADF 12.2.1 project containing a working sample. This sample also contains code for handling the arrow keys in a click-to-edit table.

Download the ADF editable table navigation 12.2.1 demo workspace

7 Comments

  1. Amir December 12, 2018
  2. Leo July 26, 2017
  3. Sergio April 25, 2017
    • Paul Swiggers March 17, 2016
  4. Gary March 16, 2016
    • Paul Swiggers March 17, 2016