Friday Mar 08, 2013

Another dynamic form in ADF

I have been recently helping one of Oracle Partners in migrating their Forms application to the ADF technology. It was a retail store solution and one of the main requirements was to provide quick and automatic way to scan and enter the items at the cashier desk. The bar code scanner is a relatively simple device and can be treated simply as an additional keyboard attached to the cashier’s computer. To automate entering consequential items at the desk we need to have a web page with a form which is able to dynamically create new rows (for new items) detecting some kind of key sequence e.g. enter. This way the cashier does not have to even touch the keyboard to repetively add new items to current transaction.

I have created a simple demo application based on the famous HR schema where the roles of the transaction items play the rows in the REGIONS table – this is just for sake of simplicity to reproduce it without creating additional schemas or tables.

[Read More]

Friday Jul 29, 2011

ADF Dynamic tabs shell and contextual events

Hello to everyone. Inspired by Frank Nimphius’ and Lynn Munsinger’s excellent book “Oracle Fusion Developer Guide” I have been investigating recently the contextual events framework in ADF. I came up with a conclusion that it complements ideally another nice feature – dynamic tab shell template. While the latter allows you to easily and dynamically launch the task flows in separate tabs, the former adds generic and flexible way to provide communication between them. So wiring together those two features gives you complete framework for advanced MDI-like applications. Unfortunately so far dynamic tab template does not cover contextual events communication so we need to add this manually.

I have created a little PoC (you can download it here) showing an example of such cooperation. The application is very simple yet - by using generic mechanism – quite agile and elastic. The main idea is to create a web application which could be used to edit different entities in HR schema. Dynamic tab template allows us to provide navigation area on the left side of the page with the navigation facet. I have used this facet to place a region with a taskflow showing a tree of all HR entities: Regions, Countries, Locations, Departments and, of course, Employees. Right-clicking on the tree node and choosing Edit allows opening a tab with the editor for given node. Each node type has its own taskflow for edition. The goal is to provide generic way to open correct editor for any node and signal the parent tab about operations setting dirty flag so the shell can mark given tab as “dirty” or cleanup the mark when needed. Following the best ADF practices both editor and structure tree taskflows should be generic and reusable in other applications so they should not assume being used by dynamic tab page. The only technology flexible enough to meet those requirements is contextual events framework. So we will use the following types of contextual events:

  • EditNodeEvent: event triggered by the context menu in the tree. The payload for the event is a JUCtrlHierNodeBinding class instance taken directly from the tree
  • CommitEvent: triggered when the editor taskflow commits the changes and should be dismissed
  • RollbackEvent: the same as above but for signaling the rollback operation. Both commit and rollback should also close the corresponding tab. They don’t have any payload.
  • DirtyEvent: event which signals change of the dirty flag. It has a simple payload with Boolean type holding true or false.

Those four types of events are enough to support our framework. Below is a picture showing all those events and their usage.




The consumer of all of those events is the main page with a dynamic tab template. The navigation taskflow (the HR tree) is generating the EditNodeEvent, the editor taskflows publish commit, rollback and dirty events. The only requirement for the editor taskflows is that they should have one input parameter for passing the key value of the corresponding node.

In my application there are only two editor taskflows: for departments and for employees. Of course adding additional editors is relatively easy – just follow the generic rule about passing the key value and producing required events when needed.

Let’s discuss in more details all the events we use in the example.

DirtyEvent should be fired whenever we change any field in the editor. We can define it declaratively in the binding layer, more precisely on the attribute binding. Below is an example from employee editor’s page definition:


<attributeValues IterBinding="EmployeesIterator" id="CommissionPct">

<events xmlns="http://xmlns.oracle.com/adfm/contextualEvent">
<event name="DirtyEvent" customPayLoad="#{true}"/>
</events>

<AttrNames>

<Item Value="CommissionPct"/>
</AttrNames>

</attributeValues>


When we attach the event to the attribute binding it will fire it automatically when the value is changed. To make it work as soon as we change the value in the UI we need to set the AutoSubmit flag on each field as well.

On this particular page the event is fired only for the First Name and Commission fields but for other bindings we can declare it in similar way. Notice that we pass static value “true” here because every time some value changes on the page we need to flag the tab as dirty. But how do we clean the dirty flag? There is only one situation when we need to clean this flag – when we reset the editor to its initial values. If it comes to resetting the form – there are couple of issues here as well. We cannot simply reload the values from the model because the model itself may have been changed (remember the auto submit flag!). The easiest way to reset the model is to invoke application-wise rollback. Then we can fire the DirtyEvent with payload set to false and re-execute the editor taskflow by going to its SetCurrentEmployee activity. This is exactly what Reset button does. Below is a snippet from the code in the backing bean:


public void onReset(ActionEvent actionEvent) {
BindingContext bctx = BindingContext.getCurrent();
BindingContainer bindings = bctx.getCurrentBindingsEntry();

// to reset the form first we need to rollback all changes
// made in the model during auto-submit
// the control flow from current page to the SetCurrentRow activity in the task flow will finish the reset
OperationBinding rollback = bindings.getOperationBinding("Rollback");
rollback.execute();

JUEventBinding eventBinding = null;

// then we can programatically fire the DirtyEvent event using event binding
eventBinding = (JUEventBinding) bindings.get("dirtyEventBinding");
ActionListener al = (ActionListener)eventBinding.getListener();
al.processAction(actionEvent);

}



As you see we invoke both rollback and event firing programmatically using binding definitions. To complete this we need to define the dirtyEventBinding in page definition which is responsible for cleaning the dirty flag:


<eventBinding id="dirtyEventBinding" Listener="javax.faces.event.ActionListener">
<events xmlns="http://xmlns.oracle.com/adfm/contextualEvent">
<event name="DirtyEvent" customPayLoad="#{false}"/>
</events>
</eventBinding>



There is also a way to do it completely declaratively – rollback can be added in the editor taskflow as an activity between the wild card and SetCurrentEmployee and event firing can also be invoked with #{bindings.dirtyEventBinding.listener.processAction} expression in ActionListener property (btw this is how generic rollback and commit events have been defined on the Cancel and Save buttons). But the goal here is to show both ways as you will probably encounter situations when you cannot do it declaratively.

As I mentioned before RollbackEvent and CommitEvent are fired declaratively using EL expression in the buttons and the proper entries in the page definition file:


<eventBinding id="commitEventBinding" Listener="javax.faces.event.ActionListener">

<events xmlns="http://xmlns.oracle.com/adfm/contextualEvent">
<event name="CommitEvent"/>
</events>
</eventBinding>

<eventBinding id="rollbackEventBinding" Listener="javax.faces.event.ActionListener">
<events xmlns="http://xmlns.oracle.com/adfm/contextualEvent">
<event name="RollbackEvent"/>
</events>
</eventBinding>



The last event to explain is the EditNodeEvent. It is fired when user chooses the edit option from context menu or tree menu after selecting particular node in the structure tree. This event invokes opening a new tab with appropriate editor task flow. The payload of this event is the whole node object (JUCtrlHierNodeBinding) from the tree which contains all necessary information about the node we want to edit. Of course before we can use that event it should be declared in the hr-structure page definition file:


<eventBinding id="eventBinding" Listener="javax.faces.event.ActionListener">

<events xmlns="http://xmlns.oracle.com/adfm/contextualEvent">
<event name="EditNodeEvent" eventType="Edit" customPayLoad="#{pageFlowScope.structure_helper.currentNode}"/>
</events>

</eventBinding>


Please notice the payload declaration – this is custom payload taken (by EL) from a helper managed bean. This managed bean (StructureHelperBean) has a property currentNode which is set in our custom selection listener tied to the tree component. Below is the code of this listener:


public void onNodeSelection(SelectionEvent event) {

// get the collection model from injected RichTree object
CollectionModel treeModel = (CollectionModel) tree.getValue();
// extract the binding object
JUCtrlHierBinding treeBinding = (JUCtrlHierBinding) treeModel.getWrappedData();
// get the selected keys
RowKeySet rks = tree.getSelectedRowKeys();
if(!rks.isEmpty()){
// here we get only first key set assuming single selection on the source tree
List firstSet = (List)rks.iterator().next();
// find the current node binding
JUCtrlHierNodeBinding treeNode = treeBinding.findNodeByKeyPath(firstSet);

// eagerly initialize the node default name
// if we omit this the binding object may lose connection with underlying row
// later during the event consumption and then it cannot reinitialize the node string
treeNode.toString();

// save the node
this.currentNode = treeNode;
}
}



We should call toString() method on the node to make the internal structure of the JUCtrlHierNodeBinding class fully initialize itself to make sure we have all necessary information on the consumer side.

So this is the way we are firing all framework events. Of course in your own editor taskflows you can do it in completely different way but as long as you provide correct event names and payloads (when applicable) the main page will behave identically managing the editor tabs.

Now let’s see how to handle all those events. As I mentioned at the beginning there is only one single consumer of all events. This is the main page based on Dynamic Tab Shell template. All events are fired and handled in the binding layer so we need to provide appropriate method bindings to handle the consumption of our events. The quickest way is writing a java class with all necessary methods and then creating a data control out of it to be able to reach those methods from the binding layer. In our application it is the CEHandler class. It has four methods to handle four framework events. Handling commit, rollback and dirty events is really simple – we should call the tab shell API methods to close or set the dirty flag respectively. Also notice we don’t need to bother with checking which tab we need handle – the events always come from the current tab. Below we can see how it is done in the application:


public void handleCommit() {
handleDirty(false);
TabContext.getCurrentInstance().removeCurrentTab();
}

public void handleRollback() {
handleDirty(false);
TabContext.getCurrentInstance().removeCurrentTab();
}

public void handleDirty(Boolean dirty) {
TabContext.getCurrentInstance().markCurrentTabDirty(dirty.booleanValue());
}



The EditNode event is more difficult to handle but due to generic nature of our framework we are flexible enough to add another types of nodes later on without major changes to this method. We can even make it fully configurable in declarative way but that’s out of the scope of this post. Basically all we need to do is to extract the type, row key and tab name from the JUCtrlHierNodeBinding object passed as a payload and open the appropriate taskflow in a new tab using the tab shell API:


public void handleCEEdit(Object payload) {

// check if payload is not null
if (payload != null) {

// check the type of the payload. If during the event firing
// the custom payload expression evaluates to null then ADF is
// replacing the payload with default one e.g. javax.faces.events.ActionEvent
// thus ALWAYS check the payload class!
if (payload instanceof JUCtrlHierNodeBinding) {
JUCtrlHierNodeBinding node = (JUCtrlHierNodeBinding)payload;
// get type of the node
String definition = node.getHierTypeBinding().getStructureDefName();
// get the key value
String rowKeyValue = node.getRowKey().getKeyValues()[0].toString();
// get the node default string (defined in the binding)
String tabName = node.toString();

HashMap<String,Object> params = new HashMap<String,Object>();

if (definition.equalsIgnoreCase("test.hr.model.vo.EmployeesVO")) {
try {
params.put("input_empid", rowKeyValue);

TabContext.getCurrentInstance().addTab(tabName, "/WEB-INF/edit-employee-tf.xml#edit-employee-tf", params);
} catch (TabOverflowException e) {
e.handleDefault();
}
} else if (definition.equalsIgnoreCase("test.hr.model.vo.DepartmentsVO")) {
try {
params.put("input_deptid", rowKeyValue);

TabContext.getCurrentInstance().addTab(tabName, "/WEB-INF/edit-department-tf.xml#edit-department-tf", params);
} catch (TabOverflowException e) {
e.handleDefault();
}
}
}

}



The type is simply the definition name of the node object. It should be mapped then to particular taskflow definition. Btw here is the room for improvement e.g. by reading this mapping from external configuration file or database etc.

The last thing we need to do is to make sure that all events fired inside the editor taskflows are properly mapped to their handlers. This can be done inside the page definition of the main page by using event mapping configuration:


<eventMap xmlns="http://xmlns.oracle.com/adfm/contextualEvent">

<event name="EditNodeEvent">
<producer region="*">
<consumer region="" handler="handleCEEdit" handleCondition="">
<parameters>
<parameter name="payload" value="#{payLoad}"/>
</parameters>
</consumer>
</producer>
</event>
<event name="CommitEvent">
<producer region="*">
<consumer handler="handleCommit"/>
</producer>
</event>
<event name="RollbackEvent">
<producer region="*">
<consumer handler="handleRollback"/>
</producer>
</event>
<event name="DirtyEvent">
<producer region="*">
<consumer handler="handleDirty">
<parameters>
<parameter name="dirty" value="#{payLoad}"/>
</parameters>
</consumer>
</producer>
</event>

</eventMap>


As you can see for each possible framework event we are providing producer and consumer configuration. Producer is in fact each and every region (no matter how deeply nested) – hence the definition region=”*”. Consumer is a method exposed in the binding layer with parameters derived usually from “#{payLoad}” expression. Please pay attention to case – it is always “payLoad” with capital “L” inside.

So that’s it for today, feel free to play with this example and extend it with more functionality. It would be interesting for example to add drag&drop feature from the tree to the tabs area. Stay tuned for more examples!

About

Oracle ECEMEA Partner Hubs Migration Center Team

We share our skills to maximize your revenue!
Our dedicated team of consultants can rapidly and successfully assist you to adopt and implement the latest of Oracle Technology in your solutions.

Stay Connected
partner.imc
@
beehiveonline.oracle-DOT-com
Google+

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
2
3
4
5
6
9
10
11
12
13
14
16
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today