Wednesday, April 7, 2010

Custom <af:train> model

The <af:train> component presents a series of 'stops', each representing a task in a multi-step process, carried out sequentially. This component is very useful when you need to develop a 'wizard' sort of screens.


In general, af:train is used along with taskflow where the implementation part is a cakewalk (can be done declaratively with zero effort). Sometimes you may need to customize the default behavior of the train component. ADF Faces does support this customization through the custom menu model. To learn more about this, please go through chapter 18.7 Using Train Components to Create Navigation Items for a Multi-Step Process of Web User Interface Developer's Guide.

I'm attaching a simple af:train demo using customized org.apache.myfaces.trinidad.model.ProcessMenuModel as model. This is actually a simplified version of the af:train component sample, bundled with ADF Faces Rich Client Components Demo.

A brief description of this implementation...

jspx tag snippet is shown below

 <af:train var="node" value="#{viewScope.CustomTrainModel}"
                        id="train">
   <f:facet name="nodeStamp">
     <af:commandNavigationItem text="#{node.label}"
            actionListener="#{node.doActionListener}"
            immediate="#{viewScope.CustomTrainModel.immediate}"
            disabled="#{viewScope.CustomTrainModel.readOnly}"
            visited="#{viewScope.CustomTrainModel.visited}"
            id="commandNavigationItem"/>
  </f:facet>
</af:train>

Class diagram for custom menu model


Custom menu model used for building this sample is TrainIdMenuModel class. This is subclassed from org.apache.myfaces.trinidad.model.ProcessMenuModel. Initialization logic is copied below for your reference. Please note the usage of @PostConstruct in this bean to perform the initialization.

@PostConstruct: Specifies a method that the container will invoke after resource injection is complete but before any of the component's life-cycle methods are called.

    @PostConstruct
    public void initilize() {
        /**
         * Gets the nodes for the train
         */
        List<NavigationItemId> viewEntries =
            ServiceProxy.getInsatnce().getSomeDummyNavigationItemsToTest();

        ChildPropertyTreeModel childPropertyTreeModel =
            new ChildPropertyTreeModel();
        childPropertyTreeModel.setChildProperty("children");
        childPropertyTreeModel.setWrappedData(viewEntries);

        this.setViewIdProperty("id");
        this.setWrappedData(childPropertyTreeModel);
        this.setIdKey("idTrainViewIdKey");
    }

You can download the af:train sample using custom menu model from here.

Adding listeners for each train stop

Sometimes, you may need to invoke custom actions while navigating through train stops. The below sample provides an interesting approach for this requirement. Idea is to enhance NavigationItemId's doActionListener() method, to trigger the registered action listeners. Below sample implements this use case(Please note that, this sample is just intended to give you an idea on this topic).

You can download the af:train sample using custom menu model+custom action from here.

17 comments:

Anonymous said...

Hi Jobinesh,

It's a useful Post. I was trying to implement a simple Train Component in ADF using JDEV 11g and I am getting the following warning in the console:

Either a MenuModel object was not provided or an invalid object was provided.
Either a MenuModel object was not provided or an invalid object was provided.

Also, the train component is not displayed on the page.

It looks like I am hitting the following error:
ADF_FACES-30060: Either a MenuModel object was not provided or an invalid object was provided.
Cause: The train component required a value of type MenuModel.
Action: Correct the train definition.

But I don;t know where to fix it.

Here is my jspx code snippet.

f:view
af:document id="d1"
af:messages id="m1"
af:form id="f1"
af:train value="#{controllerContext.currentViewPort.taskFlowContext.trainModel}"
id="t1"
af:trainButtonBar value="#{controllerContext.currentViewPort.taskFlowContext.trainModel}"
id="tbb1"
af:panelStretchLayout id="psl1"

Any help will be appreciated.

Thanks,
Siva

Jobinesh said...

Siva,
Are you trying to use it as part of an an unbounded task flow?Look like that's the error here

Anonymous said...

Thanks for your kind reply.
No, It's a bounded flow.
Here is the code.


xml version="1.0" encoding="windows-1252" ?>
adfc-config xmlns="http://xmlns.oracle.com/adf/controller" version="1.2">
task-flow-definition id="Train">
default-activity id="__1">Emp
view id="Emp">
page>/Emp.jspx
train-stop id="__2"/>
view>
view id="Address">
train-stop id="__3"/>
view>
train/>
task-flow-definition>
adfc-config>

Thanks,
Siva

Jobinesh said...

Page defn for view id="Address" is missing?

Anonymous said...

Thanks again.
I created the page, but still I am getting the same error.

?xml version="1.0" encoding="windows-1252" ?>
adfc-config xmlns="http://xmlns.oracle.com/adf/controller" version="1.2">
task-flow-definition id="Train">
default-activity id="__1">Emp
view id="Emp">
page>/Emp.jspx
train-stop id="__2"/>
view>
view id="Address">
page>/Addr.jspx
train-stop id="__3"/>
view>
train/>
task-flow-definition>
adfc-config>

Jobinesh said...

Would be great if you can send me a test app, will take a look - jobinesh@gmail.com

Anonymous said...

Thanks for looking into this.
I have sent you the .EAR file.

Thanks again.
Siva

Anonymous said...

Thank you Jobinesh for your help. As you indicated in the email, I was running an individual page, not the task-flow xml. Tried the task flow xml and its working fine.

Anonymous said...

Hi Jobinesh,

This was a great post and this just suits my needs of dynamically creating train model.

One question though, how can I generate a UI on each of the train stops?

Since it will be dynamic then how can I add ADF Faces components on each trainstops?

I am thinking of having a fixed sets of taskflow and I would like to dynamically bind each taskflow on each trainstops?

What could be the best approach given your code?

Thanks a lot.

Regards,
Phu Tek

Phuu Tek said...
This comment has been removed by the author.
Phuu Tek said...
This comment has been removed by the author.
Phuu Tek said...

Blogger might be blocking my code...
af:trainButtonBar value="#{viewScope.CustomTrainModel}"
id="tbb1"/>

sorry for spamming your blog post..

Phuu Tek said...

Hi Jobinesh,

Just to add also.

Why is it not possible to add a train buton bar component on your code?

I tried it with this one.


af:train var="node" value="#{viewScope.CustomTrainModel}"
id="train">
f:facet name="nodeStamp">
af:commandNavigationItem text="#{node.label}"
actionListener="#{node.doActionListener}"
immediate="#{viewScope.CustomTrainModel.immediate}"
disabled="#{viewScope.CustomTrainModel.readOnly}"
visited="#{viewScope.CustomTrainModel.visited}"
id="commandNavigationItem"/>


af:trainButtonBar value="#{viewScope.CustomTrainModel}"
id="tbb1"/>


..but I am unable to navigate the Prev and Next button?

Regards,
Phuu Tek

Jobinesh said...

Phu Tek,
Drop me a mail with the sample you tried, will take a look

Phuu Tek said...

Hi Jobinesh,

I already sent an email to your account.

Thank You!

Phuu Tek said...

Hi Jobinesh,

I think I am making headway on how to implement my use case. Thanks to your post.

But I just would like to know if given your code above, is it possible to set that a particular navigation item is Sequential/Skip/Ignore?

Similar to setting of a train-stop?

Thanks

Anonymous said...

Hi Jobinesh,

Interesting way to create a custom train model.

However, I notice that when I am on Step 5 and I go back to Step 1, I need to go over to Step 2-4.

Isn't it possible such that when I am on the last step, I could navigate to earlier stops without navigating sequentially?

I mean, when I have visited a trainstop already then it should be visited always?

How to do that given this implementation.

Regards,
Rakesh