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!

25 May 2013

ADF Task Flow Display Name

ADF Task Flow definition has a set of description properties:



In this post I am going to show how we can make use of these properties on example of Display Name attribute. Let's consider a region with a running task flow within it. In common case the region is dynamic one, so we don't know in advance which task flow it is going to run. Moreover, the task flaw can invoke another inner task flow, and the inner task flow can invoke its inner task flow, etc. Our goal is to show in the region's title the name of the currently running task flow. Let's assume, that all our task flow definitions have initialized property Display Name with a corresponding name as it is shown on the screenshot above. We can use a managed bean method returning a display name of the currently running task flow:

public String getTaskFlowDisplayName() {
    MetadataService metadataService = MetadataService.getInstance();

    //Get the task flow binding defined in our pageDef file
    DCTaskFlowBinding taskFlowBinding = getTaskFlowBinding();

    //Get Id of the currently running task flow
    TaskFlowId taskFlowId = taskFlowBinding.getViewPort().getTaskFlowContext().getTaskFlowId();

    //Get the definition of the currently running task flow by its Id
    //and return its display name
    return metadataService.getTaskFlowDefinition(taskFlowId).getDisplayName();
}

The getTaskFlowDisplayName() method uses some helper method getTaskFlowBinding() returning a task flow binding defined in our page definition file:

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;
}


And a code snippet for our region should look like this:

<af:region value="#{bindings.taskflowdefinition1.regionModel}"
           text="#{RegionBean.taskFlowDisplayName}"
           id="r1"
           />


That's it!

21 May 2013

Switching Lists of Values

Some time ago I blogged about a feature when a VO's attribute can have multiple LOVs and showed how to switch to the desired LOV depending on the value of another VO's attribute. I this post I'm going to use this technique again, but I'm going to choose the LOV for the attribute depending on its value, on the value of the attribute for which the LOV is defined.
Let's consider a simple use-case: We have an input field for a currency code. When a user inputs a currency code, we have to show corresponding currency description near the input field. So, it should look like this:


The implementation of this use-case is pretty obvious - we have to define a LOV for the currency attribute and set autoSubmit=true for the inputText.




<af:inputText value="#{bindings.Ccy.inputValue}"
              label="#{bindings.Ccy.hints.label}"
              required="#{bindings.Ccy.hints.mandatory}"
              columns="#{bindings.Ccy.hints.displayWidth}"
              maximumLength="#{bindings.Ccy.hints.precision}"
              shortDesc="#{bindings.Ccy.hints.tooltip}" id="it1"
              autoSubmit="true">
    <f:validator binding="#{bindings.Ccy.validator}"/>
</af:inputText>

<af:outputText value="#{bindings.CurrencyName.inputValue}" id="ot1"/>


Actually besides a symbolic code like USD and EUR a currency has a numeric code like 840 and 978. Sometimes a user prefers to input a symbolic code and sometimes a numeric one. Let's allow users to input whatever they want - either symbolic or numeric code, and we will take care of both and show correct currency description. We are going to add to the VO a transient updatable attribute with two LOVs. A user inputs a value of this attribute and depending on the value's type (string or numeric)  we'll use a corresponding LOV. The first LOV looks for the currency description by symbolic code and the second one by numeric code.

The LOV for the symbolic code (Ccy):



And the LOV for the numeric code (Nbuid):



In order to switch between LOVs we use additional transient attribute CcyLovSwitcher:



And the value of the CcyLovSwitcher is going to be some Groovy expression:


The expression adf.object.ccyLovName refers to the method of our custom ViewRowImpl  class:

public String getCcyLovName() {

  //Return a corresponding LOV's name
  //depending on the value's type - string or numeric

  String looukupValue = getCcyLookup();    
  if (isNumeric(looukupValue))
      return "LOV_CcyLookupByID";
  else
      return "LOV_CcyLookupByCcy";

}



//Check whether the value is numeric
private boolean isNumeric(String value) {
  return value.matches("\\d+");
}


And now our users are free to input either symbolic or numeric currency code:


That's it!