Sometimes apparently complex things turn out to be very simple to achieve using ADF. And occasionally it is the other way round: things that seem to simple that you never gave it a second thought are in actual fact not so straightforward after all.
One example of this is the situation when a taskflow is embedded in a popup component through a region binding. The taskflow is of course not aware of the fact that is part of a popup. When the taskflow completes, the popup should probably close. Vice versa, when the popup is closed, the taskflow should probably be completed (to ensure that when the popup is next launched, the taskflow starts in the initial activity and not with the siuation and state that existed at the time of closing the popup). However, how can the embedding page in which the taskflow is embedded and that contains the popup inform the taskflow of the fact that it should complete?
Let’s demonstrate the situation.
I have created a simple, straightforward Fusion Web Application. It contains a page with a popup and a button that opens that popup. In the popup is a panelWindow, waiting to be populated with useful content.
The Structure Window displays:
Next, I have created a simple taskflow. It contains a managed bean – pageFlowScope – that contains a value and a method that increases the value. It has a single input parameter that is mapped to the bean’s value property.
The taskflow contains a View activity whose underlying page fragment –
contains a disabled inputText component – value bound to the manahed bean’s value property – and two buttons. One button has inc for its action attribute, the other one does exit. The taskflow’s visual overview makes clear that pressing the exit button leads to navigation to the exitTaskflow return call (end of taskflow instance). The Increase Value button will take the taskflow to a methodCall (increaseValue) that invokes the managed bean’s method to increase the value property by one. Subsequently, navigation back to tfView is performed.
I have then added this taskflow to the popup, dragging it from the application navigator and dropping it in the panelWindow component in the popup.
The input parameter of the taskflow is configured to always have the value 1:
When I run the application, the following page appears:
Clicking the Open Popup button opens the popup and displays the view activity in the taskflow:
Clicking the Increase Value button will indeed increase the value and the new value is presented:
When the user (me) closes the popup with the close icon that is part of the panelWindow – outside the taskflow – the popup disappears. The taskflow is not aware of this. That means that any state in the taskflow at the time of closing the popup is still in the popup. The popup, even when not displayed, is still part of the page and any bindings – such as the region binding of the taskflow – of the popup are defined in the pageDefinition for the entire page and are part of the same BindingContainer as the other page components. Only when the user navigates out of the page will the taskflow cease to hang around.
We can easily see this effect. Close the popup, then reopen it again. It will look exactly as shown in the figure above: it will have the value of 9, just like it had before closing the popup. The fact that the taskflow was temporarily hidden does not affect it at all.
When we use the other way out – the Exit button – things are not the same at all.
The taskflow navigates from the view to the taskflow return – and does not produce a view anymore. The user will see this:
When the now close the popup using the close icon
and then open the popup again:
the popup still does not show any content – because the taskflow is still in the same state as before, which means no view is executed.
There are several ways to make the taskflow ‘restart’. One is by passing in a different value for at least one of the input parameters (provided the refresh property of the taskflow binding in the pageDef has been set to ifNeeded or a refreshCondition expresion has been set and evaluates to true.
If you want to always initialize the taskflow when the popup is re-opened, there is a simple means to accomplish that.
Re-initialize taskflow in popup whenever popup is (re)opened
Instead of using a static region and taskflow binding, we need to use a dynamic region. When the taskflow is dragged and dropped inside the popup component, choose Dynamic Region.
You now need to specify a managed bean that will decide which taskflow to show (if any) in the dynamic region at any moment. We can have JDeveloper generate that managed bean for us.
The dialog presented now for specifying inputParameters is initially empty: it depends on the taskflows we are going to dynamically switch between in this region which parameters should be passed in. In this case, I will still pass in a value for a parameter called inputParameter1 with a value of 1.
The Java code generated by JDeveloper for the taskflow-switching managed bean is simple, straightforward and without further modification giving us exactly the same behavior as with the static region:
When we now run the application, the behavior of the taskflow and the popup is exactly the same as before.
However, if we ensure that the taskflow switching bean returns null after the popup has been closed – and the proper taskflow when it is next opened – we achieve the effect we are after: the re-initialization of the taskflow whenever the popup is (re)opened.
When the popup is closed, a popupCancelListener is invoked if it has been configured on the popup. This listener can then reset the taskflow switching bean.
Define the popupCancelListener :
Also note that the attribute contentDelivery on the popup component should be set to lazyUncached.
The popupTaskflowHandler bean is the bean that was created by JDeveloper earlier on, to serve as the dynamic region’s taskflow switching bean. Change the bean’s memoryscope from backing bean to view.
Change the reference in the PageDefinition, from backbeanScope to viewScope.
Apply some modifications to the generated bean PopupTaskflowHandler:
- add an emptyTaskflowId
- return a String rather than a TaskFlowId
- add the popup cancel listener
Run the application. We can open the popup, see the taskflow’s view, close the popup and open it again. We now see nothing – instead of the taskflow’s view in the state it was in when the popup was closes. That is half the story – we still want the taskflow to be start afresh and be visible.
In order to inform the PopupTaskflowHandler that it is ok to return the ‘real’ taskflow again, we can use a popup launch listener (actually called popupFetchListener) that invokes the same bean as the cancel listener. Or, alternatively, we can use a setPropertyListener that is triggered by the popupFetch event.
and implement this method in the bean:
Now, whenever the popup is opened, the taskflow is re-initialized. So the scenario we went through before:
- open popup
- increase the value a couple of times
- close the popup
- open the popup
that earlier on would show the same value as we left the popup with is now as we want it to be: the popup opens with a fresh value.
Note: at this point, we start the taskflow as if nothing ever happened. See below for the situation where clean up is required after when the taskflow is interrupted by the user closing the popup.
What about the other way out: Taskflow Return
What we have done above has done little to improve the situation where the user presses the Exit button. Well, when the user close the popup window after having pressed Exit, the popup will open the next time with the taskflow in the fresh starting position. So that is good. But pressing Exit will not yet close the popup.
The region component has been configured with a regionNavigationListener, implemented in the same bean that handles all events around this popup-with-taskflow. At the same time, I have added a binding attribute for the popup component, binding it to the (new) popup property in the bean.
The navigation listener is quite simple, code wise:
When the regionNavigationListener fires, method hearNavigation will inspect the event. If the new viewId is null, we assume that the taskflow has reached its end of the line and can be exited, meaning the popup can be closed. The currentTaskflowId is reset and the popup is hidden. Now we can have the user press the Exit button that is part of the taskflow that is unware of the popup it is contained in and still have the popup close.
Cleanup or compensation actions in the taskflow
Suppose that when the taskflow completes, it needs to perform certain clean up activities. The taskflow diagram would look like this:
Obviously, when the user clicks on the Exit button in the tfView shown in the popup, this cleanup activity is executed by the taskflow.
However, when the popup is closed and we just let go of the taskflow instance, this cleanup is not performed.
Fortunately, there is a way of making sure that the cleanup is done, even when the popup is closed. For this to be done, we will have the embedding page queue a navigation event on the taskflow, forcing the taskflow to react as if a button was pressed inside the taskflow. In this case, we will have the popup cancel listener tell the taskflow to do the ‘exit’ navigation. The taskflow itself will then react as if the user had pressed the exit button, navigate to the method call, do cleanup and navigate to the taskflow return.
The cancelPopup method, invoked when the popup is canceled, queues an action event on the region component. This has to be done in a fairly round about way: the queueActionEventInRegion method requires a methodExpression as its first parameter. This method expression will be executed – the method invoked – and the result of that call is the navigation outcome processed in the taskflow. In this case, we always want to return the value ‘exit’. But we have to do so using a method expression. For this purpose, I have created a managed bean – centralBean – in request scope. It is defined in the faces-config.xml file.
It always returns ‘exit’:
The af:region in the page is bound through its binding attribute to the region property in the managed bean:
When we run the application and go through this scenario:
- open popup
- increase the value zero, one or multiple times
- close the popup (not through the Exit button)
the cleanup is still performed because the popupcancel listener intercepts the cancel event, instructs the taskflow to navigate from the view to the exit activity (that does the cleanup) and then allows the cancel of the popup to continue.
Download JDeveloper 11g PS3 application:taskflowInPopupDemo .
Note: another (hyper) correct way of implementing the communication to and from the taskflow is through the use of contextual events. The taskflow can publish a contextual event when it reaches an endpoint, just before the taskflow return call is executed. This event can be captured by the embedding page and as a result the popup can be closed. Vice versa, the page can publish a contextual event captured inside the taskflow that triggers the taskflow to perform its dying routine and perform cleanup activities.
In this article (http://adfdeveloper.blogspot.com/2011/06/how-to-close-taskflow-popup-launched.html) Marvin Reyes describes this situation, addressing it in a slightly different manner (he suggests using a button that is part of the taskflow for ending the taskflow and closing the popup programmatically). His approach requires the taskflow to be aware of the fact that it is (or at least could be) shown in a popup. This sort of breaks the decoupled nature of taskflows as far as I am concerned.
In this posting (http://blogs.oracle.com/jdevotnharvest/entry/how-to_hide_the_close_icon_for_task_flows_opened_in_dialogs) , Frank Nimphius describes the situation where the taskflow is called using a task flow call activity with the property Run as Dialog set to true and the Display Type property set to inline-popupl this then opens the bounded task flow in an inline popup. Frank describes how the close popup icon can be hidden – so the popup can only be closed by reaching an exit point for the taskflow.
Jobinesh does something interesting in this article (http://jobinesh.blogspot.com/2010/08/refresh-parent-view-when-in-line-popup.html): he talks about a taskflow is invoked from a first taskflow as a dialog. The requirement there is to refresh the calling first taskflow when the second taskflow returns. His solution is to define a returnListener on the command button (which invokes second taskflow as dialog).
Marvin Reyes again in (http://adfdeveloper.blogspot.com/2011/07/adf-region-handled-in-richpopup.html) describes how he listens for the region navigation event using a navigation event listener on the region component in the popup. The region binding (which Marvin does not show) is dynamic: a bean is used to specify the tasfklow id for the taskflow to be shown.
Implementing the popup with region binding of taskflow
What I want is simple: when the popup opens, the taskflow should be initialized, any prior history forgotten. The popup offers two main ways out: one is through the popup controls (panel window or dialog), the other is through navigation within the taskflow.
When the user closes the popup – the taskflow may need to be informed, in case any clean up or compensating activity is required. Other than that, the taskflow will simply be open up in a rejuvenated state when next the popup is invoked.
When the user completes the taskflow that is shown in the popup, as a result the popup should close.
One condition for the implementation: the taskflow may not be aware of the fact that it is embedded in a popup. Any dealing with the popup should be done outside of the taskflow.
Before I forget: I would like to introduce something like the taskflow contract – akin to the WSDL for WebServices. The taskflow contract would not only specify parameters but also contextual events produced and consumed by the taskflow, the navigation cases inside the taskflow that make sense for the taskflow consumer – either to force on the taskflow from the outside or to listen to as alternative to contextual events, any side effects the taskflow may have etc. And all of this in a standardized, structured, ideally IDE maintained fashion.