Wednesday, August 19, 2015

ADF: Move using Tab key in AF:Table

Problem description: Let say we have a af:table, which has some editable columns. We can use tab keys to move between these editable fields. Normal tab key moves horizontally first in same row and if there is no more editable item in row, it will jump to next row. Problem is when it jumps to new row, it does not make that row current. Now if you edit the value and have a value change listener, you may not get correct row in model.
To demonstrate the problem let us create following objects
1. Updateable EmpEO/EmpVO/EmployeeAM in our model project. EO can be based on Employee table of HR schema.
2. Drag and drop EmpVO on a jspx page (say ADFTableTabNavigation.jspx) and create a af:table. Make EmployeeId outputtext and first-name, last-name, email as editable input-text.
3. Have a bean say ADFTableTabNavigationBean.
4. Bind table with bean (say #{backingBeanScope.ADFTableTabNavigationBean.employeeTable})
3. Have auto-submit and value-change-listener on first-name, which executes a bean method (say method changeFirstName)
4. changeFirstName code should look like
    public void changeFirstName(ValueChangeEvent valueChangeEvent) {
        // Add event code here...
        String newValue = valueChangeEvent.getNewValue().toString();
        System.out.println("New First Name: " + newValue);
        JUCtrlHierBinding adfModel = (JUCtrlHierBinding)((CollectionModel)getEmployeeTable().getValue()).getWrappedData();
        DCIteratorBinding dciter = adfModel.getDCIteratorBinding();
        NavigatableRowIterator nav=dciter.getNavigatableRowIterator();
        Row row = nav.getCurrentRow();
        System.out.println("EmployeeId modified: " + row.getAttribute("EmployeeId"));
        
    }

Whenever you change first name bean method is going to print, new value of first name and employeeid or row, which is modified.

Now with this let us run our page.

Here is the behaviour that we notice.
1. If I select a row first using mouse and then change first name, it correctly prints new value and empolyeeid.
2. If I am on row1 (say employee id = 100) and then use tab key to move. Once I reach to next row (say employeeid=101), framework does not change current row. If I edit first-name, bean prints new-value of first name correctly but employeeid incorrectly. It shows employeeid of previous row (100).

At times this behaviour can lead to unexpected results because our model codes getCurrentRow will return wrong row, which is not modified. To correct it I tried following approach.

Solution:

To solve this problem we just need two steps
1. Add a custom attribute with input-text FirstName.
2. Change current-row of table before doing any processing.

--------------------------------------------------------------------------------------------------------------

1. Add a custom attribute with input-text FirstName: For this just add below code in your FirstName input text.
<af:inputText value="#{row.bindings.FirstName.inputValue}"
                          label="#{bindings.EmpVO.hints.FirstName.label}"
                          required="#{bindings.EmpVO.hints.FirstName.mandatory}"
                          columns="#{bindings.EmpVO.hints.FirstName.displayWidth}"
                          maximumLength="#{bindings.EmpVO.hints.FirstName.precision}"
                          shortDesc="#{bindings.EmpVO.hints.FirstName.tooltip}"
                          id="it2" autoSubmit="true"
                          valueChangeListener="#{backingBeanScope.ADFTableTabNavigationBean.changeFirstName}">
              <f:validator binding="#{row.bindings.FirstName.validator}"/>
              <f:attribute name="rowKey" value="#{row.EmployeeId}"/>
            </af:inputText>

This code adds a custom attribute in your input text, which holds the value of employeeid. Which means all first name input text in table actually has an attribute (rowKey), which is holding value of employeeid. Anytime if I get hold of inputtext component, I can get value of employeeid associated with it.


2. Change current-row of table before doing any processing: Now in bean method write a new mothod makeRowCurrent as shown below
    private void makeRowCurrent(ValueChangeEvent valueChangeEvent){
        oracle.jbo.domain.Number employeeId = (oracle.jbo.domain.Number)valueChangeEvent.getComponent().getAttributes().get("rowKey");
        JUCtrlHierBinding adfModel = (JUCtrlHierBinding)((CollectionModel)getEmployeeTable().getValue()).getWrappedData();
        DCIteratorBinding dciter = adfModel.getDCIteratorBinding();
        NavigatableRowIterator nav=dciter.getNavigatableRowIterator();
        Object[] objKey = new Object[1];
        objKey[0] = employeeId;
        Key key = new Key(objKey);
        nav.setCurrentRow(nav.getRow(key));
    }

You can change above method if you have different datatype of primary key or if you have composite key as primary key.

Now just call this method from your valuechangelistener
    public void changeFirstName(ValueChangeEvent valueChangeEvent) {
        // Add event code here...
        String newValue = valueChangeEvent.getNewValue().toString();
        System.out.println("New First Name: " + newValue);
        makeRowCurrent(valueChangeEvent);
        JUCtrlHierBinding adfModel = (JUCtrlHierBinding)((CollectionModel)getEmployeeTable().getValue()).getWrappedData();
        DCIteratorBinding dciter = adfModel.getDCIteratorBinding();
        NavigatableRowIterator nav=dciter.getNavigatableRowIterator();
        Row row = nav.getCurrentRow();
        System.out.println("EmployeeId modified: " + row.getAttribute("EmployeeId"));
        
    }

3. Run application and test it: You will find that you can successfully navigate using tab key and whenever you change first name, you get correct employeeid as current row.


--------------------------------------------------------------------------------------------------------------------
Effectively what we are doing: We are associating a custom attribute (rowKey) with all input-text (firstname). This custom attribute holds the value of row's primary key (employeeid). In makeRowCurrent method we try to get hold of inputtext and its corresponding custom attribute (rowKey). We construct key using employeeid and setcurrentrow using that key. Once current row is set correctly we can always know which employee record is actually modified by end user.

NOTE:
1. I searched over net and found that there could be other solutions also like handling tabkey client event and then making row current. I found it a bit too much of work to resolve simple issue.
2. One issue with this approach is that when you switch between rows, you are not making row current. We only make row current if we try to edit a field (firstname). If I don't edit field and try to navigate between rows, my current row still remains old row. I believe that is good only because if I try to to make row current every time I reach to a new row using tab, I need to unnecessary fire client event (tab key) and server event even though there is effectively no change. This will slow down tab navigation.
3. If you have composite key as primary key, you can change makeRowCurrent method as
private void makeRowCurrent(ValueChangeEvent valueChangeEvent){
        oracle.jbo.domain.Number key1 = (oracle.jbo.domain.Number)valueChangeEvent.getComponent().getAttributes().get("rowKey1");
oracle.jbo.domain.Number key2 = (oracle.jbo.domain.Number)valueChangeEvent.getComponent().getAttributes().get("rowKey2");
oracle.jbo.domain.Number key3 = (oracle.jbo.domain.Number)valueChangeEvent.getComponent().getAttributes().get("rowKey3");
        JUCtrlHierBinding adfModel = (JUCtrlHierBinding)((CollectionModel)getEmployeeTable().getValue()).getWrappedData();
        DCIteratorBinding dciter = adfModel.getDCIteratorBinding();
        NavigatableRowIterator nav=dciter.getNavigatableRowIterator();
        Object[] objKey = new Object[3];
        objKey[0] = key1;
        objKey[1] = key2;
        objKey[2] = key3;
        Key key = new Key(objKey);
        nav.setCurrentRow(nav.getRow(key));
    }

4. valueChangeEvent.getComponent().processUpdate() does not change current row so that approach does not work here.


That's it.

5 comments:

  1. Very Very helpful information. Thanks a lot..!!

    ReplyDelete
  2. Thank you Kapil, Mitesh, 'How to tech'.

    ReplyDelete
  3. Hi Sanjeev,
    This is very helpful information, When i tried this in it works but in i cannot add as a child, Is there any options to set current row in value change. Please give a suggestion

    ReplyDelete