31 Jan 2014

Binding Container for Declarative Component

Usually a binding container for a declarative component is provided by a page or a page fragment this component is placed on. A declarative component doesn't have its own binding container. For example, there is Main.jspx page with the following code:
    <af:form id="f1">
        <af:declarativeComponent viewId="/component.jsff" id="dc1"/> 
    </af:form>


This page has a page definition file (representing its binding container) MainPageDef.xml:
  <executables>
    <iterator Binds="ViewObj" RangeSize="25" DataControl="AppModuleDataControl"
              id="ViewObjIterator"/>
  </executables>
  <bindings>
     <attributeValues IterBinding="ViewObjIterator" id="EmployeeId">
      <AttrNames>
        <Item Value="EmployeeId"/>
      </AttrNames>
    </attributeValues>
   ...


The component.jsff page fragment looks like this:
   <af:componentDef var="attrs" componentVar="comp">
     <af:panelFormLayout id="pfl1">
        <af:inputText label="#{bindings.EmployeeId.hints.label}"
                      value="#{bindings.EmployeeId.inputValue}"
                      id="ot1"/>
        <af:inputText  label="#{bindings.FirstName.hints.label}"
                       value="#{bindings.FirstName.inputValue}"
                       id="ot2"/>
        <af:inputText  label="#{bindings.LastName.hints.label}"
                       value="#{bindings.LastName.inputValue}"
                       id="ot3"/>
     </af:panelFormLayout>              
   </af:componentDef>

So, the #{bindings} expression will return parent page's binding container and it will work if this container has EmployeeId, FirstName, LastName attribute bindings. We can decouple the declarative component from the exact Ids and pass the bindings to the component as its attributes: 

   <af:componentDef var="attrs" componentVar="comp">
    <af:xmlContent>
        <component xmlns="http://xmlns.oracle.com/adf/faces/rich/component">
          
           <attribute>
              <attribute-name>empId</attribute-name>
           </attribute>
           <attribute>
              <attribute-name>fName</attribute-name>
           </attribute>
           <attribute>
              <attribute-name>lName</attribute-name>
           </attribute>
           
        </component>
     </af:xmlContent>
     <af:panelFormLayout id="pfl1">
        <af:inputText label="#{attrs.empId.hints.label}"
                      value="#{attrs.empId.inputValue}"
                      id="ot1"/>
        <af:inputText  label="#{attrs.fName.hints.label}"
                       value="#{attrs.fName.inputValue}"
                       id="ot2"/>
        <af:inputText  label="#{attrs.lName.hints.label}"
                       value="#{attrs.lName.inputValue}"
                       id="ot3"/>
     </af:panelFormLayout>              
   </af:componentDef>

On the Main,jspx page:
<af:declarativeComponent viewId="/component.jsff" id="dc1">
 <f:attribute name="empId" value="#{bindings.EmployeeId}"/>                       
 <f:attribute name="fName" value="#{bindings.FirstName}"/>                       
 <f:attribute name="lName" value="#{bindings.LastName}"/>                       
</af:declarativeComponent>
 
Let's add to the declarative component a commandButton invoking a method from an application module. For sure, it would be nice to invoke an AM's method through the binding layer. And this method is always the same. It doesn't depend on where the the component is placed on. So, it'd be cool if the component had its own binding container. All we need to do is to comment the af:componentDef tag and leave only af:panelFormLayout, and afterwards we can create a pageDef file as we usually do for any page fragment or a page. If we drag-n-drop an AM's method customMethod from the Data Controls palette onto the declarative component as a commandButton, the framework will create a pageDef file componentPageDef.xml, make the changes in the DataBinding.cpx and the button will look this:
   <af:commandButton text="Button" id="dc_cb1"
                     actionListener="#{bindings.customMethod.execute}"
                     disabled="#{!bindings.customMethod.enabled}"/>


Once the pageDef file is created we have to uncomment back the af:componentDef tag.

But the commandButton is not going to work, since #{bindings} expression will return parent page's binding container but not the component's one.  We can fix that using a backing bean. The ContactInfoBean class has the following methods:
//Get the component's binding container
public BindingContainer getBindings() {
    BindingContext bc = BindingContext.getCurrent();
    return bc.findBindingContainerByPath(getMyself().getViewId());        
}


//Get the reference to the declarative component  
public RichDynamicDeclarativeComponent getMyself() {           
    RichDynamicDeclarativeComponent  _this = 
        (RichDynamicDeclarativeComponent) getValueObject("#{comp}", RichDynamicDeclarativeComponent.class);
    return _this;
}

//Just a helper method for EL evaluation 
public static Object getValueObject(String expr, Class returnType){
  FacesContext fc = FacesContext.getCurrentInstance();
  ELContext elctx  = fc.getELContext();
  ExpressionFactory elFactory = fc.getApplication().getExpressionFactory();
  ValueExpression valueExpr = elFactory.createValueExpression(elctx,expr,returnType);
  return valueExpr.getValue(elctx);
}
 
The command button's declaration should be modified a little bit. We're going to add backingBeanScope.ContactInfoBean before bindings in the EL expressions:
<af:commandButton text="Button" id="dc_cb1"
  actionListener=
  "#{backingBeanScope.ContactInfoBean.bindings.customMethod.execute}"
  disabled="#{!backingBeanScope.ContactInfoBean.bindings.customMethod.enabled}"/>
 
And it works fine now.

The sample application for this post requires JDeveloper 11gR2.

That's it!

No comments:

Post a Comment

Post Comment