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!