29 May 2013

Displaying ADF Task Flow Stack with BreadCrumbs

Let's consider a page with a region running a task flow. The task flow can invoke some internal task flow and this internal task flow can invoke its internal task flow and so on. After a few navigations of that sort our users are going to get lost. They have no idea how deep they are and how they got there. There is a special component in ADF Faces af:breadCrumbs. Usually it is used to show users their path through the application's menu, so users can know how they got to this page and how they can get back.  For example:

Let's use this component to help users in case of a deep task flow stack.In this post I am going to show how we can display the task flow stack with af:breadCrumbs component and how we can use it to stop currently running task flow and allow a user to get back.

So, we have a region:
<af:region value="#{bindings.taskflowdefinition1.regionModel}"
           text="#{RegionBean.taskFlowDisplayName}"
           id="r1"
           />
The region uses the technique described here to display the region's name. And we have af:breadCrumbs on the same page:
<af:breadCrumbs id="bc1" value="#{RegionBean.menuModel}"
                var="task">
    <f:facet name="nodeStamp">
        <af:commandNavigationItem id="comID"
                                  text="#{task.label}"
                                  actionListener="#{RegionBean.commandAction}">
             <f:attribute name="depth" value="#{task.depth}"/>
        </af:commandNavigationItem>
    </f:facet>
</af:breadCrumbs>


The value of the breadCrumbs is going to be some menu model, provided by a managed bean method:
public MenuModel getMenuModel() {

  TaskFlowLink taskFlowLink = getTaskFlowLink();
  if (taskFlowLink!=null)
    return new ChildPropertyMenuModel(taskFlowLink, "child",
            Collections.nCopies(taskFlowLink.getDepth(), 0));
  else
    return null;

}


The method uses some hierarchical data structure represented by TaskFlowLink class and converts it into a menu model using some internal helper class ChildPropertyMenuModel.  The TaskFlowLink class is our custom wrapper of the PageFlowStackEntry internal class. Furthermore, it supports the hierarchical structure by providing the child field.

public class TaskFlowLink {
  TaskFlowLink child;
  PageFlowStackEntry stackEntry;
  int depth;


  public TaskFlowLink(PageFlowStackEntry stackEntry, TaskFlowLink child, int depth) {
      this.stackEntry = stackEntry;
      this.child = child;
      this.depth = depth;
  }

  //Extracting the definition of the task flow 
  //corresponding to the stack entry
  private TaskFlowDefinition getTaskFlowDefinition() {
    MetadataService metadataService = MetadataService.getInstance();
    return metadataService.getTaskFlowDefinition(
                             stackEntry.getTaskFlowDefinitionId());
  }


  public String getLabel() {
      return getTaskFlowDefinition().getDisplayName();
  }

  public int getDepth() {
      return depth;
  }

  public TaskFlowLink getChild() {
      return child;
  }
    
}
  

And the getTaskFlowLink() method converts the page flow stack into the TaskFlowLink structure:
private TaskFlowLink getTaskFlowLink() {
  TaskFlowLink taskFlowLink = null;
  
  //Get the page flow stack for the region's view port
  PageFlowStack pfs = getViewPort().getPageFlowStack();
  
  //Convert the stack into array. Just for convenience. 
  PageFlowStackEntry[] pageFlowStack = 
      pfs.toArray(new PageFlowStackEntry[pfs.size()]);
  
  //Convert the array into the TaskFlowLink structure
  for (int i = pageFlowStack.length-1; i>=0; i--)
      taskFlowLink = new TaskFlowLink(pageFlowStack[i], 
                                      taskFlowLink, 
                                      pageFlowStack.length - i);

  return taskFlowLink;
}


The getTaskFlowLink() method uses a couple of helper methods to get access to the view port:
//Get the task flow binding
private DCTaskFlowBinding getTaskFlowBinding() {  
  BindingContainer bc = BindingContext.getCurrent().getCurrentBindingsEntry();  
  
  //taskflowdefinition1 is Id of the task flow binding in the page def file  
  //like  <taskFlow id="taskflowdefinition1" ...  
  DCTaskFlowBinding dtb = (DCTaskFlowBinding) 
    ((DCBindingContainer) bc).findExecutableBinding("taskflowdefinition1");  
  
  return dtb;  
} 

//Get the view port
private ViewPortContextImpl getViewPort() {
    DCTaskFlowBinding dtb = getTaskFlowBinding();
    return (ViewPortContextImpl) dtb.getViewPort();
}


And we are almost happy:

So, we built a menu model acceptable by the af:breadCrumbs component based on the page flow stack. That's all indeed cool, but it would be better if a user could click on a crumb and return back to the corresponding task flow. For example, clicking on "Task Flow One" I want to abandon currently running "Task Flow Two" and return back to the "Task Flow One". Moreover, I want to return exactly to the same view activity from which I got to the "Task Flow Two".
Alrighty, let's do it! Did you notice that our af:breadCrumbs has a commandNavigationItem within its nodeStamp facet. So, we're going to do something in the commandAction method when we're clicking on the item:
public void commandAction(ActionEvent actionEvent) {
    UIComponent component = actionEvent.getComponent();
    
    //Get the flow's depth in the stack 
    int depth = Integer.valueOf(component.getAttributes().get("depth").toString());
    
    //Abandon all deepper flows and return 
    //to the calling view activity 
    popTaskFlow(depth);
}


And, finally, let's have a look at the popTaskFlow method:
private void popTaskFlow(int depth) {

  //Remember current view port
  AdfcContext adfcContext = AdfcContext.getCurrentInstance();
  ViewPortContextImpl currViewPort = adfcContext.getCurrentViewPort();


  try
  {
     //Set region's view port as a current one
     //This allows task flow's finalizers to work correctly
     ViewPortContextImpl viewPort = getViewPort();
     adfcContext.getControllerState().setCurrentViewPort(adfcContext,
                                                         viewPort.getViewPortId());
     viewPort.makeCurrent(adfcContext);

     PageFlowStack stack = viewPort.getPageFlowStack();
     PageFlowStackEntry entry = null;

     //Abandon all deeper flows
     for (int i=1; i<depth; i++) {
       TASK_FLOW_RETURN_LOGIC.abandonTaskFlow(adfcContext, stack.peek());
       entry = stack.pop(adfcContext);
      }

     //Update the view port's current view activity ID to point 
     //to the view that was displayed before the popped 
     //task flow was called.         
     ActivityId newViewActivityId = entry.getCallingViewActivity();
     viewPort.setViewActivityId(adfcContext, newViewActivityId);

  }
  finally
  {//Restore current view port
   adfcContext.getControllerState().setCurrentViewPort(adfcContext, 
                                                      currViewPort.getViewPortId());
   currViewPort.makeCurrent(adfcContext);
  }

}

private static final TaskFlowReturnActivityLogic TASK_FLOW_RETURN_LOGIC 
   = new TaskFlowReturnActivityLogic();


The sample application for this post is available here. It requires JDeveloper R2.

That's it!

9 comments:

  1. Hello Eugene,

    this is really impressive! Great solution!

    Any idea if similiar solution is possible with R1 ? Looks like AdfcContext and some of the Controller State API is not available in R1.

    Regards!

    ReplyDelete
  2. Hello Andreas,
    Thank you! The sample application for R1 is available
    here

    In R1 we should use ControllerContextFwk instead of AdfcContext.

    ReplyDelete
  3. Hello can u suggest how to implement the same for jdeveloper 11.1.1.7.0 ??

    ReplyDelete
    Replies
    1. Hello!
      The sample application for R1 is available


      here


      Delete
  4. Hello Eugene,

    Thanks for the post, but its not working for jdeveloper 11.1.1.7.0?? Its showing following error.

    Project: C:\JDeveloper\mywork\ExploreTaskFlowStackR1\VC\VC.jpr
    C:\JDeveloper\mywork\ExploreTaskFlowStackR1\VC\src\com\cs\blog\exploretaskflowstack\view\RegionBean.java
    Error(152,13): cannot find class TaskFlowDefinition
    Error(71,36): cannot find method getViewPort()
    Error(79,22): cannot find method getViewPort()

    ReplyDelete
  5. Ya when i'm ruuning ur application i'm getting error in this part of code..

    public String getTaskFlowDisplayName() {
    MetadataService metadataService = MetadataService.getInstance();
    DCTaskFlowBinding taskFlowBinding = getTaskFlowBinding();
    TaskFlowId taskFlowId =
    taskFlowBinding.getViewPort().getTaskFlowContext().getTaskFlowId();
    return metadataService.getTaskFlowDefinition(taskFlowId).getDisplayName();
    }

    in the above lines of code..

    getViewPort().getTaskFlowContext().getTaskFlowId(); is showing error..!!

    do i have to include any libraries or have to make configurations??

    suggest the answer plz!!

    Thank you.

    ReplyDelete
  6. Hello!
    Thank you for your feedback. I tested the sample application for R1 with 11.1.1.2.0 and 11.1.1.4.0 versions, and it worked perfectly.
    The application for Jdeveloper 11.1.1.7.0 is available
    here

    ReplyDelete
  7. Hello Eugene!
    Could you please let me know how to implement this in 12.1.3 version?

    Thanks
    Srihari

    ReplyDelete
  8. Hello,

    If I want add a new task flow and want to use it in breadcrumb what I need to do as per your application.

    Thanks,
    Ram

    ReplyDelete

Post Comment