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:
The value of the breadCrumbs is going to be some menu model, provided by a managed bean method:
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.
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:
That's it!
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!
Hello Eugene,
ReplyDeletethis 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!
Hello Andreas,
ReplyDeleteThank you! The sample application for R1 is available
here
In R1 we should use ControllerContextFwk instead of AdfcContext.
Hello can u suggest how to implement the same for jdeveloper 11.1.1.7.0 ??
ReplyDeleteHello!
DeleteThe sample application for R1 is available
here
Hello Eugene,
ReplyDeleteThanks 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()
Ya when i'm ruuning ur application i'm getting error in this part of code..
ReplyDeletepublic 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.
Hello!
ReplyDeleteThank 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
Hello Eugene!
ReplyDeleteCould you please let me know how to implement this in 12.1.3 version?
Thanks
Srihari
Hello,
ReplyDeleteIf 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