Tuesday, January 5, 2010

EJB and ADF Faces RC: Inserting and Deleting Records to an Editable ADF Table that is Bound to an Entity (ArrayList) in a Managed Bean

My inspiration to write this post are the challenges that I have encountered in getting the selected row data, the method to remove rows and the inconsistent state of the table rows after removing a record. The Web User Interface Developers' Guide do not have a straight forward illustration on removing a row on a table that is not bound to an ADF Model/ADF BC type of data. Please note that I have not tried to run the codes below, but I have tried to demonstrate as much as possible the idea from the existing project that we have.

We have a requirement to have an editable master-detail information in which the master should be an editable form and the detail should be an editable table. This is a usual user-interface requirement for a accounting application especially on journal voucher entry screens (believe me here because I am a Certified Public Accountant :) ). In my encounter, the challenge was on the editable detail table. As much as possible I am sticking to the drag and drop from Data Controls panel style of creating the UI, but I cannot stick to it this time especially that this detail table has dependency on the master form (like "What's the applicable exchange rate defined in the master form?"- which is to be used by the rows of the detail table to translate an original currency Debits and Credits to base currency amounts) plus the fact that I need to conditionally set the visibility and required attribute of other components in the detail-table row based on the property of a selected account (an object on the same row).
Below is an illustrative code fragment to demonstrate the idea.
<af:panelCollection id="pc1">
  <f:facet name="toolbar">
    <af:toolbar id="t3">
      <af:commandToolbarButton text="Add"
                               id="ctb2"
                               actionListener="#{myBackingBean.addNewMyDetailObject}"/>
      <af:commandToolbarButton text="Remove"
                               id="ctb3"
                               actionListener="#{myBackingBean.removeSelectedMyDetailObject}"
                               immediate="true" />
    </af:toolbar>
  </f:facet>
  <af:table value="#{myBackingBean.myMasterObject.myDetailObjectList}"
                      var="row" id="t2"
                      partialTriggers="::ctb2 ::ctb3"
                      binding="#{myBackingBean.myDetailObjectRichTable}"
                      rowSelection="single"
                     
    <af:column sortable="false" headerText="Property 1" id="c1"
      <af:inputText value="#{row.myDetailObjectProperty1}" id="it1"/>
    </af:column>
      <af:column sortable="false" headerText="Property 2" id="c2"
    <af:inputText value="#{row.myDetailObjectProperty2}" id="it2"/>
      </af:column>
  </af:table>
</af:panelCollection>
I made a dummy backing bean class below to show the sample methods to insert and remove a row from the editable detail table:
package blogspot.soadev.view.backing;

import java.io.Serializable;

import java.util.List;

import javax.faces.event.ActionEvent;

import oracle.adf.view.rich.component.rich.data.RichTable;

public class MyBackingBean implements Serializable {
    private MyMasterObject myMasterObject;
    private RichTable myDetailObjectRichTable;
    
  public void addNewMyDetailObject(ActionEvent e) {
    MyDetailObject myDetailObject = new MyDetailObject();
    // add my deatil object to master
    myMasterObject.addMyDetailObject(myDetailObject);
    //can initialize myDetailObject properties if needed
    myDetailObject.setMyDetailObjectProperty1("property1 can be complex type");
    myDetailObject.setMyDetailObjectProperty2("property2 can be complex type");
  }
  public void removeSelectedMyDetailObject(ActionEvent e) {
    //get the selected row data
    MyDetailObject myDetailObject = (MyDetailObject)myDetailObjectRichTable.getSelectedRowData();
    //remove it from the master object
    myMasterObject.removeMyDetailObject(myDetailObject);
    //this my tip - refreshes the table stamping.
    //this is especilly needed if you conditionally set the attributes 
    //of other components (like visibility, rendered, required, etc)
    //if you will not invoke this, you will have inconsistent table row state.
    myDetailObjectRichTable.resetStampState();
  }


  public void setMyDetailObjectRichTable(RichTable myDetailObjectRichTable) {
    this.myDetailObjectRichTable = myDetailObjectRichTable;
  }

  public RichTable getMyDetailObjectRichTable() {
    return myDetailObjectRichTable;
  }

  public void setMyMasterObject(MyMasterObject myMasterObject) {
    this.myMasterObject = myMasterObject;
  }

  public MyMasterObject getMyMasterObject() {
    return myMasterObject;
  }
}

class MyDetailObject{
    private MyMasterObject myMasterObject;
    private Object myDetailObjectProperty1;
    private Object myDetailObjectProperty2;

  public void setMyMasterObject(MyMasterObject myMasterObject) {
    this.myMasterObject = myMasterObject;
  }

  public MyMasterObject getMyMasterObject() {
    return myMasterObject;
  }

  public void setMyDetailObjectProperty2(Object myDetailObjectProperty2) {
    this.myDetailObjectProperty2 = myDetailObjectProperty2;
  }

  public Object getMyDetailObjectProperty2() {
    return myDetailObjectProperty2;
  }

  public void setMyDetailObjectProperty1(Object myDetailObjectProperty1) {
    this.myDetailObjectProperty1 = myDetailObjectProperty1;
  }

  public Object getMyDetailObjectProperty1() {
    return myDetailObjectProperty1;
  }
}

class MyMasterObject{
    private List<MyDetailObject> myDetailObjectList;  
    public void addMyDetailObject(MyDetailObject myDetailObject){
          getMyDetailObjectList().add(myDetailObject);
          myDetailObject.setMyMasterObject(this);
    }
    public void removeMyDetailObject(MyDetailObject myDetailObject){
        getMyDetailObjectList().remove(myDetailObject);
        myDetailObject.setMyMasterObject(null);
    }

    public void setMyDetailObjectList(List<MyDetailObject> myDetailObjectList) {
      this.myDetailObjectList = myDetailObjectList;
    }

    public List<MyDetailObject> getMyDetailObjectList() {
      if(myDetailObjectList == null){
          myDetailObjectList = new ArrayList<MyDetailObject>();
      return myDetailObjectList;
    }
}
Please note of my tip on the table resetStampState() method above.
You maybe asking by now- "Where is the EJB part?", well, my master object is bound to an EJB DataControl ;) .

5 comments:

  1. am getting a null pointer exception on
    myMasterObject.addMyDetailObject(myDetailObject);
    how am I supposed to bind myMasterObject or initialize it

    ReplyDelete
  2. Hi Peter,

    As I have noted above this was just an illustrative code, nevertheless, I have modified to code to fix some unescaped characters which were not rendered and updated the "getMyDetailObjectList" method to resolve the NullPointerException.

    regards,

    Pino

    ReplyDelete
  3. Am still getting the same error at the same place.
    I think there should be some binding on myMasterObject

    ReplyDelete
  4. Hi Peter,

    In my case, I have something like the following:
    public MyMasterObject getMyMasterObject(){
    if(myMasterObject == null){
    myMasterObject = (MyMasterObject)getCurrentRowDataProvider("findMyMasterObjectByIdIterator");
    }
    return myMasterObject;
    }

    notes:
    ->For the "getCurrentRowDataProvider", method refer to the following post: http://soadev.blogspot.com/2010/03/adf-model-how-to-programmatically.html
    ->It is assumed that you are using data control for the master object.

    regards,
    Pino

    ReplyDelete
  5. If I will have enough time this weekend, I will update this post with a sample project for your convenience.

    ReplyDelete