Towards Ultra-Reusability for ADF - Adaptive Bindings
- by Duncan Mills
The task flow mechanism embodies one of the key value propositions of the ADF Framework, it's primary contribution being the componentization of your applications and implicitly the introduction of a re-use culture, particularly in large applications.
However, what if we could do more? How could we make task flows even more re-usable than they are today? Well one great technique is to take advantage of a feature that is already present in the framework, a feature which I will call, for want of a better name, "adaptive bindings".
What's an adaptive binding? well consider a simple use case. I have several screens within my application which display tabular data which are all essentially identical, the only difference is that they happen to be based on different data collections (View Objects, Bean collections, whatever) , and have a different set of columns. Apart from that, however, they happen to be identical; same toolbar, same key functions and so on. So wouldn't it be nice if I could have a single parametrized task flow to represent that type of UI and reuse it?
Hold on you say, great idea, however, to do that we'd run into problems. Each different collection that I want to display needs different entries in the pageDef file and:
I want to continue to use the ADF Bindings mechanism rather than dropping back to passing the whole collection into the taskflow
If I do use bindings, there is no way I want to have to declare iterators and tree bindings for every possible collection that I might want the flow to handle
Ah, joy! I reply, no need to panic, you can just use adaptive bindings.
Defining an Adaptive Binding
It's easiest to explain with a simple before and after use case. Here's a basic pageDef definition for our familiar Departments table.
<executables>
<iterator Binds="DepartmentsView1"
DataControl="HRAppModuleDataControl"
RangeSize="25"
id="DepartmentsView1Iterator"/>
</executables>
<bindings>
<tree IterBinding="DepartmentsView1Iterator" id="DepartmentsView1">
<nodeDefinition DefName="oracle.demo.model.vo.DepartmentsView" Name="DepartmentsView10">
<AttrNames>
<Item Value="DepartmentId"/>
<Item Value="DepartmentName"/>
<Item Value="ManagerId"/>
<Item Value="LocationId"/>
</AttrNames>
</nodeDefinition>
</tree>
</bindings>
Here's the adaptive version:
<executables>
<iterator Binds="${pageFlowScope.voName}"
DataControl="HRAppModuleDataControl"
RangeSize="25"
id="TableSourceIterator"/>
</executables>
<bindings>
<tree IterBinding="TableSourceIterator" id="GenericView">
<nodeDefinition Name="GenericViewNode"/>
</tree>
</bindings>
You'll notice three changes here.
Most importantly, you'll see that the hard-coded View Object name that formally populated the iterator Binds attribute is gone and has been replaced by an expression (${pageFlowScope.voName}). This of course, is key, you can see that we can pass a parameter to the task flow, telling it exactly what VO to instantiate to populate this table!
I've changed the IDs of the iterator and the tree binding, simply to reflect that they are now re-usable
The tree binding itself has simplified and the node definition is now empty. Now what this effectively means is that the #{node} map exposed through the tree binding will expose every attribute of the underlying iterator's collection - neat! (kudos to Eugene Fedorenko at this point who reminded me that this was even possible in his excellent "deep dive" session at OpenWorld this year)
Using the adaptive binding in the UI
Now we have a parametrized binding we have to make changes in the UI as well, first of all to reflect the new ID that we've assigned to the binding (of course) but also to change the column list from being a fixed known list to being a generic metadata driven set:
<af:table value="#{bindings.GenericView.collectionModel}"
rows="#{bindings.GenericView.rangeSize}"
fetchSize="#{bindings.GenericView.rangeSize}"
emptyText="#{bindings.GenericView.viewable ? 'No data to display.' : 'Access Denied.'}"
var="row" rowBandingInterval="0"
selectedRowKeys="#{bindings.GenericView.collectionModel.selectedRow}"
selectionListener="#{bindings.GenericView.collectionModel.makeCurrent}"
rowSelection="single" id="t1">
<af:forEach items="#{bindings.GenericView.attributeDefs}" var="def">
<af:column headerText="#{bindings.GenericView.labels[def.name]}" sortable="true"
sortProperty="#{def.name}" id="c1">
<af:outputText value="#{row[def.name]}" id="ot1"/>
</af:column>
</af:forEach>
</af:table>
Of course you are not constrained to a simple read only table here. It's a normal tree binding and iterator that you are using behind the scenes so you can do all the usual things, but you can see the value of using ADFBC as the back end model as you have the rich pantheon of UI hints to use to derive things like labels (and validators and converters...)
One Final Twist
To finish on a high note I wanted to point out that you can take this even further and achieve the ultra-reusability I promised. Here's the new version of the pageDef iterator, see if you can notice the subtle change?
<iterator Binds="{pageFlowScope.voName}"
DataControl="${pageFlowScope.dataControlName}"
RangeSize="25"
id="TableSourceIterator"/>
Yes, as well as parametrizing the collection (VO) name, we can also parametrize the name of the data control. So your task flow can graduate from being re-usable within an application to being truly generic. So if you have some really common patterns within your app you can wrap them up and reuse then across multiple developments without having to dictate data control names, or connection names. This also demonstrates the importance of interacting with data only via the binding layer APIs. If you keep any code in the task flow generic in that way you can deal with data from multiple types of data controls, not just one flavour. Enjoy!