Thoughts on JavaOne
I spend last week up at the JavaOne conference in San Francisco, my schedule was pretty full but I did at least manage to touch base with some old acquaintances and catch up with a few friends.
I the end I only managed (keynotes aside) to get to three sessions, one on the semantic Web, once on Engineers as an endangered species and most importantly the JSF 2.0 specification.
The former sessions where pretty good and thought provoking, so certainly a good use of time. The JSF session, however, was, how shall I put this, disappointing...
Disappointing on two levels I have to point out, first of all the presentation itself was not at all well rehearsed or timed and much as I might admire Ed Burns and Roger Kitan from a technical perspective, the session and demos failed to impress.
However, I can be really forgiving on presentation, it's bread and butter to me so I'm probably overcritical. However the big let down was the content of the specification itself. This really worries me.
The theme of the release to borrow Ed's phrasing is "Sow and Harvest", basically the strategy of seeing how the wider JSF community takes the existing infrastructure and invents on top of it and then taking that invention and standardizing this in the Spec.
Now that's a good approach, but it really looks like too little has been harvested in this round, most things seem to be catchup with JSF 1.0 tweaks like Shale, not stuff building on top of 1.2 like ADF. For example, although there is mention of a new memory scope, there was no mention of addressing the inadequacies in the controller itself. How you can do one without the other?
Apparently we'll get a preview of the 2.0 specification document shortly, maybe there will be more nuggets of gold in there to cheer me up, I can only wish...
More on Dumping out Page Flow Scope
In the last piece on this subject I was using a method activity to call the dumpTaskFlowState method in the flow itself as the default activity. Now this works just fine, but it's a little intrusive, so I wanted to improve on that. The it occurred to me that we have the ideal spot. Task flows have an initializer plug point which is called before the flow is formally started. Just adding the call into that works beautifully. You can set the initializer from the Common tab of the task flow editor or of course just hack it into the XML:
<managed-bean>
<managed-bean-name>debugUtil</managed-bean-name>
<managed-bean-class>com.tuhra.ddt.view.util.TaskFlowUtils</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
<initializer>#{debugUtil.dumpTaskFlowState}</initializer>
Dumping out pageFlowScope
One of the biggest issues we see with complex applications based on ADF R11 and is the debugging of complex task flows which are nested within regions in pages. Many of these problems resolve down into the task flow simply not having the state that you thought. So you need a simple way to inspect the pageFlowScope variables. Debugging is one option but I also like to have a little interactive output onto the console whilst I'm in the process of running the app. So I put together this simple utility method which I call as the default activity in my bounded task flows:
public String dumpTaskFlowState(){
AdfFacesContext afctx = AdfFacesContext.getCurrentInstance();
Map flowScopeMap = afctx.getPageFlowScope();
String viewPortName =
ControllerState.getInstance().getCurrentViewPort().getClientId(); System.out.println("----------------------------------------------------");
System.out.println("Dumping pageFlowScope Variables for: ");
System.out.println(viewPortName);
Iterator iter = flowScopeMap.entrySet().iterator();
while (iter.hasNext()){
Map.Entry entry = (Map.Entry)iter.next();
String mapKey = entry.getKey().toString();
Object mapValue = entry.getValue();
StringBuilder bldr = new StringBuilder(" ");
bldr.append(mapKey);
bldr.append("=");
bldr.append((mapValue==null)?"<null>":mapValue.toString());
System.out.println(bldr.toString());
}
return "continue";
}
The corresponding method activity that calls this is registered in the task flow xml thus:
<method-call id="dumpState">
<method>#{debugUtil.dumpTaskFlowState}</method>
<outcome>
<to-string/>
</outcome>
</method-call>
when the class containing the dump method is defined in the debugUtil managed bean in the same task flow thus:
<managed-bean>
<managed-bean-name>debugUtil</managed-bean-name>
<managed-bean-class>com.tuhra.ddt.view.util.TaskFlowUtils</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
Finally to wire everything up we set the dumpState method as the default activity and wire up the "continue" outcome to the real start point of the flow.
In time I'll look at moving this onto a listener rather than having to have it in the flow itself, but it works pretty well as it is.
The output for this thing just appears in the console because I've used println() you could use a logger instead. Here's a sample of the output:
------------------------------------------------------------
Dumping pageFlowScope Variables for:
data.mainShellPageDef.tabletabflowdefinition1.tableTabContentsPageDef.dynamicQueryRegionflowdefinition1
currentOwner=FOD
currentObject=ADDRESSES
currentType=TABLE
currentSQL=<null>
Decorating the Train
If you've had a chance to play with the ADF Task Flow features of JDeveloper / ADF Release 11 you may have come across the rather nifty ability to create Task Flows as trains (it's just a checkbox on the create task flow dialog). When you do so, every page you add into the taskflow will be implicitly wired into a train model for you.
Furthermore, when you then drop a <af:tain> or a <af:tainButtonBar> control onto the page it gets (optionally) bound to a train model based automatically on the view activities in the task flow. (using the expression: #{controllerContext.currentViewPort.taskFlowContext.trainModel}. So pretty neat stuff, for free you get all this wiring done.
However, once you play with this four questions tend to spring to mind:
- How do I change the ordering in the train? - Easy, select the train stop (view activity) in the diagram and choose Train > from the context menu. You'll see options to move the stop forwards and backwards. Or you can just reorder the view elements in the XML source view!
- How do I change the label for the train stop in the train component? - Again easy. Select the train stop (view activity) in the diagram and look at the property inspector, choose the Train Stop tab and set the value of display-name property to a string or an EL Expression. If you look at the XML this results in a structure like this:
<view id="page1">
<page>/page1.jspx</page>
<train-stop>
<display-name>My Custom Label</display-name>
</train-stop>
</view>
- How can I prevent access to a stop? - Use an EL expression in the skip
property of the train stop. This expression needs to return a boolean value which can be
based on whatever criteria you have in mind - How do I execute stuff between stops - For example, between pages 3 and 4 of a wizard I want to create a new row in the database to display on page 4. This is slightly less intuitive so I've outlined it in detail below
Executing Activities between Train Stops
When you first look at task flows it seems that there is just no way to do this short of creating your own command buttons and wiring up manual navigation rules to effectively override the trains default navigation. That's OK but it kind of defeats the whole benefit of this auto-generated train model. But it turns out that the developers are much smarter than me (I knew that anyway) and have thought of this requirement.
You need to start out by perhaps turning your thinking on its head. If you want to execute say a router or a method activity between page 1 and page 2 of a wizard you should not think of it in terms of executing that activity after page 1, but rather of thinking about executing the activity before page 2. The user can use the train to jump between pages without passing through the intervening nodes so it's important to think this way around. So, what do you have to do? Let's take the simple example of a two page wizard where I want to execute a method called doSomething before I get to page 2. Here are the steps (assuming that I already have the task flow as a train and page 1 and page 2 in place):
- Drop on a method activity and wire it up to the actual method it's executing.
- Create a Wildcard Control Flow rule on the diagram (if you don't have one) - just drag and drop it from the component palette
- Wire the wildcard activity to your method activity with a meaningful name. I'll use callTheBeforePage2Method here.
- Draw an outcome from the method activity to the associated train stop (page 2 in this case). Call this rule something useful like continue
- Bring up the properties of the method activity and set its fixed-outcome property to the same outcome you used above i.e. continue (it will be in the drop-down list for you to choose)
- Finally select the train stop view activity that this will all execute before (page 2) and select the Train Stop tab in the property inspector, then set the outcome property to the name of your global navigation rule (i.e. callTheBeforePage2Method)
Here's a picture of it being defined.

In this case I've just put one non-view activity to be executed before a stop, in reality you could have several steps, including routers. There is no problem with that as long as the outcome defined in the train stop matches the entry point to the sequence of actions as defined by the global navigation rule. However, if you start to get complex like this, consider encapsulating the train stop and all it's activities in their own bounded task flow. that can still be a train stop and will keep everything neater.
Setting the intial cursor position and default action button
A couple of small things that have come up during the polish phase of one of the demos I'm writing for 11 have been positioning the cursor somewhere sensible when the user gets to the screen, and doing the "right thing" when the user presses enter..
In this case I have a custom login screen with 4 significant components:
- A user name field
- A password field
- A Remember Me checkbox
- The login button
The behavior required here is:
- If the remember me checkbox had not been selected in the past (which implicitly sets a cookie remembering the username) then then focus should be on the username field
- If the username was remembered then focus should be on the password field instead
- When Enter is pressed, the login button action should execute.
Managing the Input Focus
The input focus on an ADF Faces rich client screen is controlled by the <:af:document/> tag. This tag has a property initialFocusId which defines the component to place focus in. The component that you reference here must be identified using it's ID property and it must have the clientComponent property set to true. So for example my password field looks like this:
<af:inputText secret="true"
shortDesc="#{res['login.password.hint']}"
binding="#{loginBacking.passwordInput}"
required="true"
labelAndAccessKey="#{res['login.password.label']}"
requiredMessageDetail="#{res['login.password.required']}"
id="passwordInput"
clientComponent="true"/>
As a further twist here, the ID of the field on it's own may not be enough. In my case I'm using a template on the page which is a naming container so to refer to the password field I'll need to specify this in the id as well using "t
asswordInput" to identify the field, where "t" is the id of my template.
Finally I can use an expression in the initialFocusId property to decide if focus should go into the username or password thus:
<af:document title="#{res['login.pageTitle']}"
initialFocusId="#{!empty cookie.fod_mpl_user.value?'t:passwordInput':'t:usernameInput'}">
Managing the Default Action
Default action on a form is also controlled by a simple property referring to an ID. In this case it's the defaultCommand property on <af:form>. Again, we need to take the naming container hierarchy into account when referring to the target Id:
<af:form defaultCommand="t:loginButton">
This time, the component in question (loginButton) just needs the ID set and does not need clientComponent = true.
:: Next Page >>