26 Apr 2014

Working with the Array Data Type in a Table

In this post I would like to follow up on my previous article about Oracle collection data types and I am going to focus on working with oracle.jbo.domain.Array attributes in af:table component.

So, in my database I have the following SQL type:

create or replace type varchar2_array_type as table of varchar2(200)  

And I've got the following table:

create table testarray (
 SomeField Number,
 ArrValue VARCHAR2_ARRAY_TYPE)

nested table ArrValue store as arrvalue_tab return as value;  

There is an entity in ADF BC model which is based on the testarray table:


The data type of the attribute Arrvalue is oracle.jbo.domain.Array. 

There is a corresponding attribute binding in the binding container:

    <attributeValues IterBinding="VTestarrayIterator" id="Arrvalue">
      <AttrNames>
        <Item Value="Arrvalue"/>
      </AttrNames>
    </attributeValues>
The easiest way to display the value of this attribute could be like this:
<af:table value="#{bindings.Arrvalue.inputValue.array}" var="row" 
          id="t1">
   <af:column sortable="false" headerText="Array Values" id="c1">
      <af:inputText value="#{row}" id="ot3"/>             
   </af:column>
</af:table>
And the result looks pretty nice:

The only problem with this approach is that the table is not updatable. It is read only one.  

The EL expression "#{bindings.Arrvalue.inputValue.array}" is going to invoke the method oracle.jbo.domain.Array.getArray() which returns an immutable Object[] array and all modifications to this array will be lost.

If we need to be able to update the data in the table, we've got to do the following:
  1. Make a copy of the bindings.Arrvalue.inputValue.array
  2. Set this copy as table's value
  3. At the Update Model Values phase wrap the copy back into oracle.jbo.domain.Array and put it to the Arrvalue.inputValue. 

So, we are going to make a copy and keep it in a request scope managed bean:

private Object[] array = null;

private Object[] createArray() {
  JUCtrlValueBinding dcb = getArrayCtrlBinding();
  if (dcb!=null){
      Array arr = (Array) dcb.getInputValue();
      if (arr!=null) {
          array = arr.getArray();
      }          
  }
  return array;
}


public void setArray(Object[] array) {
    this.array = array;
}

public Object[] getArray() {
    return (array == null ? createArray() : array);
}

    
private JUCtrlValueBinding getArrayCtrlBinding() {
  BindingContext bc = BindingContext.getCurrent();
  DCBindingContainer binding = (DCBindingContainer) bc.getCurrentBindingsEntry();
  return (JUCtrlValueBinding ) binding.findCtrlBinding("Arrvalue");
}
 
   
When it comes to using this copy as table's value, we can do the following:
  <af:table value="#{TheBean.array}" var="row" 
            id="t1"
            varStatus="status">
    <af:column sortable="false" headerText="Array Values" id="c1">
       <af:inputText value="#{TheBean.array[status.index]}" id="ot3"/>             
    </af:column>
  </af:table>
 
Note, that we didn't use just #{row} as inputText's value. It wouldn't work, since #{row} would just return an immutable String. Instead of that we used the varStatus table attribute. The EL expression #{TheBean.array[status.index]} makes the framework able to call a corresponding setter method at the Update Model Values phase, so all modifications made in the table will be saved to the TheBean.array.  
 
The last step is to put TheBean.array back into the attribute value at the Update Model Values phase. We can use a fake invisible inputText for that purpose:
<af:inputText value="#{TheBean.dummy}" 
              visible="false" 
              converter="EmptyConverter"
              id="it2"/>

  
This input text should be placed below the table on a page. The beauty of this approach is that the framework will try to update the inputText value on each request. So, the setter method TheBean.setDummy(String dummy) will be invoked on each request at the Update Model Values phase right after the table values have been saved to the TheBean.array. And at this moment we're going to wrap the array into oracle.jbo.domain.Array and put it back to the Arrvalue.inputValue:

public void setDummy(String dummy) {
  getArrayCtrlBinding().setInputValue(new Array(array));   
  array = null;
}
 
The secret of this dummy inputText is hidden in the EmptyConverter:
public class EmptyConverter implements Converter {
 public Object getAsObject(FacesContext facesContext,
                           UIComponent uIComponent, String string) {
     return null;
 }

 public String getAsString(FacesContext facesContext,
                           UIComponent uIComponent, Object object) {
     return null;
 }
}
It emulates that null value has been submitted for this component with the request. On the other hand, the dummy getter always returns a not null value:
 public String getDummy() {
    return DUMMY;
 }   

So, the framework has no option, but to invoke the setDummy method at the Update Model Values phase.

The sample application for this post requires JDeveloper 11.1.1.7.

That's it! 

No comments:

Post a Comment

Post Comment