A question on the Oracle Developers Community was about how to handle a train stops visited status.
Use Case
The use case behind this was that a train can be used as a workflow visualization. A normal user starts the train, but at one point a manager has to approve something. This approval is one or more stops on the same train. If the manager picks up the workflow he should automatically start with the approval stop. There is no need for him to see the data accumulated in the stops before.
The use case has multiple challenges:
- Securing train stops for different user roles
- Allow starting the train from any stop
- Handling the state of the train stops
The first two challenges are handler by All Aboard, 97. How-to defer train-stop navigation for custom form validation or other developer interaction, and 82. How to programmatically navigate ADF trains.
The missing part is how to handle the train stops ‘visited’ state (see image above). If you start the train directly with ‘Stop 3’ you get this state
UI
To implement this use case, we use a simple UI. It contains an input field, a button and the train which is added to the page as a region.
In the input field names label 1 you can enter the stop where the train should start. If no number is given, the train starts with the first stop. We use this input field to mimic the different starting stop for different users. This is the page when we start the application:
This is the page when we start the final application:
You can navigate between the train stops by using the ‘Back’ and ‘Next’ button, or by clicking the next stop in the train bar. As the stops are set to sequential, you can’t directly click on the 4th stop. You have to go through the stops 1 to 3 first.
Enter a number between 1 and 5 into the input field and tab out of the field will set the parameter for the train task flow and restart the task flow. The navigation is done via a router in the task flow. In the image below the stop number 3 is set as the starting stop for the train
And as you see the stops 1 and 2 are looking like they have visited before.
Implementation
To show how to implement this we start with a simple bounded task flow which builds the train
The start builds a router which we use to navigate to the stop where we want to start the train. The starting stop is passed as parameter to the task flow
In the router, which is marked as default activity, the parameter is used to execute the navigation
The Magic
If you look at the train stop properties in the properties inspector you’ll notice, that there is no property for the visited state
This option is not available in the UI. Oracle has missed or deliberately missed to make this property accessible via the properties. If you dig into the implementation of the train task flow (see the articles provided at the begin of the blog), you’ll see how to access the train and its stops by code:
ViewPortContext currentViewPortCtx = controllerContext.getCurrentViewPort(); TaskFlowContext taskFlowCtx = currentViewPortCtx.getTaskFlowContext(); TaskFlowTrainModel taskFlowTrainModel = taskFlowCtx.getTaskFlowTrainModel(); // get the stop from the map TaskFlowTrainStopModel currentStop = taskFlowTrainModel.getCurrentStop();
The TaskFlowTrainStopModel doesn’t provide any access to the visited state. If you look at the class definition you’ll notice, that it’s only an interface
which doesn’t provide access to the visited property. Setting a breakpoint in the debugger we can inspect an instance of this interface
and we get the class implementing the interface as:
oracle.adfinternal.controller.train.TrainStopModel
This class has the visited property we are looking for.
Solution
Now we can implement a method which we call before a train stop gets rendered and which sets the visited property of all previous stops to true.
CAUTION
THIS IN AN INTERNAL CLASS WHICH YOU SHOULD NOT USE!
However, it’s the class we need to get to the property. You have to understand, that the usage of the class has its risks, but that it’s not forbidden. The risk is that Oracle can change or delete the class without notifying you beforehand. So, in later versions, your code might break.
The method checks the task flow parameter if it’s null to set to a number less or equal to 0. In this case, the method returns an empty string. We do this check to avoid that the method does it’s work every time we navigate the train. It should be done only once when the train starts.
If the check finds a positive number, it sets the task flow parameter to zero (line 37).
It then gets the task flow information from the Context (lines 39-43). In line 50 we acquire the current stop before we loop over all previous stops and set their visited property to true (lines 53-59).
The missing part is how to call this method when a train stop is rendered. For this, we use a technique called Lazy Initalizing Beans. The trick is to use a hidden af:outputText and set e.g. the value property of the component to a bean property.
When the page or fragment is rendered, the method getInitStatus() in the bean is called. This is exactly the method shown above. We add this hidden af:outputText to each train stop before the af:train component.
Sample
You can download the sample from GitHub BlogTrainStopStatus. The sample is build using JDev 12.2.1.3 and doesn’t need a DB connection. You can use the same technique in other JDeveloper versions.