Train Stop Status Handling

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:

  1. Securing train stops for different user roles
  2. Allow starting the train from any stop
  3. 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.

Advertisements

JDeveloper: How to setup and use a converter

JDeveloper: How to setup and use a converter

In this post I show how to setup the server side part of a converter and how to use it in an application. Converters can have a client side too and all af:converter do have one. For a nice sample on what you can do with client side converters see ADF: Smart Input Date Client Converter. The big difference is that the client side converter is done on the client side with JavaScript and no server round trip is done for the conversation.

Why are converters needed at all?

Sometimes the data you get from a source like the database table is not in a format you like to show to the user. Common cases are showing strings in special formatting, e.g. social security numbers or phone numbers. You can use converters to show the content of clob and blob columns in the UI too.

The ADF framework provided some converters out of the box:

These can be used without the need to program anything.

What is missing from the out of the box converters is one which can be used to format a string.

One thing to remember that the new format should only be used in the UI to show the data in a specific format. You normally don’t want to store it in this special format.

We create a converter which exchanges each uppercase character ‘B’ in a string with the string “-Z-”. The sample is not very useful, but it shows what can be done with converters.

Use Case

A string can contain any character. However when the string is shown on the UI there should be no ‘B’ visible. Instead of the ‘B’ we should show ‘-Z-’. This should only be done when the string is visible on the UI. When the string is stored in the db or some other place it should be stored with the ‘B’.

Implementation

I used JDev 11.1.1.7.0 for this sample, which is the oldest JDev version I have access to. The steps to create a converter should be almost equal in all versions, but I deliberately choose the oldest JDev I have so that other users with other version should have no problem migrating this sample to their version.

The final sample can be downloaded from GitHub at BlogConverterSample.

Model Project

We start by creating a fresh ADF Web Application. If you want a detailed description on how to do this, you can follow Writing Reproducible Test Cases: Why and How. For the model part I only use one DB table, the EMPLOYEES table. The resulting model project looks like

We don’t need to make any change to the generated project. This model project is only created to show that the converter works on data read from the DB table too.

ViewController Project

For users interested in more details about converters, please read the doc at http://docs.oracle.com/cd/E48682_01/web.1111/b31973/af_validate.htm#BABGIEDH. To start with the converter, we create a java class in the ViewController project and name it MyB2ZConverter.java. As package we choose ‘de.hahn.blog.convertersample.view.converter’

As the class will be a converter we have to implement the javax.faces.convert.Converter interface. For this you click on the green ‘+’ sign and can search for the right interface

This process will create the java class and two methods

These are the methods we have to implement for our use case. The first method ‘getAsObject’ is called when the data from the UI is send to the server for further processing. The ‘getAsString’ method is called when data from a storage (DB, bean property or pagedef variable) is going to be rendered to the UI.

As our use case is to exchange every “B” with the string “-Z-” we can implement the getAsString method easily by replacing every “B’ with “-Z-”. The method has three parameters, the current FacesContext which you can use to write messages, the UIComponent for which the converter is called and finally an Object representing the data which we want to convert. The result of the conversion must be a String. The resulting method look like

/** Method to get the string representation of hte object to use in the UI
* @param facesContext current facesContext
* @param uIComponent component which was used to deliver the data
* @param object data from storage to be converted
* @return sting to use for in the UI
*/
public String getAsString(FacesContext facesContext, UIComponent uIComponent, Object object) {
  if (object != null) {
    String ret = object.toString().replaceAll("B", "-Z-");
    return ret;
  } else {
    return null;
  }
}

After the check if the object to convert is null (in this case there is nothing to do), we use the String.replaceAll(…) method to search for ‘B’ and replace it with “-Z-”.

Keep in mind that the first parameter to the replaceAll method is a regular expression (see String.replaceAll(java.lang.String, java.lang.String)).

Now, if the data from the UI is send back to the model layer, it has to be converted back into the original format. So we have to do the conversion backwards by replacing all “-Z-” with “B” in the getAsObject(…) method:

/** Method which get the data from a uiComponent and should return it in the format we like to store in the DB (or elswhere)
* @param facesContext current facesContext
* @param uIComponent component which was used to deliver the data
* @param string data from the ui component
* @return object to use for further work (e.g. storage in the DB)
*/
public Object getAsObject(FacesContext facesContext, UIComponent uIComponent, String string) {
  if (string != null) {
    String ret = string.replaceAll("-Z-", "B");
    return ret;
  } else {
    return null;
  }
}

The result will be an Object which will be passed back to the model layer. If you don’t implement the getAsObject(…) method and just return the third parameter as resulting object, you would change every data in the back end to the new format. This may be your intention, but most often you don’t want to do this. It would mark every row of data dirty you have visited without any user interaction. This is because you pass different data back to the model than you read from it.

The last step to do is to register the custom converter in the faces-config.xml file of the ViewController project. Open the faces-config.xml file in JDev and select the ‘Converter’ tab

Click the green ‘+’ sign to get the an empty row in the converter section. Go to the property window and you see

Where we click on the ‘…’ button on the right end of the ‘Class’ field. We get the search for a class dialog where we look for the MyB2ZConverter class

Select the class and enter an ID fro the converter. This ID will be used in the UI to tell a component to use this converter.

Finally the converter section look like

UI Page

Now we can use the converter in a page or fragment. We start with a simple page where we define a inputText field and a button to submit the content of the field to see the converter working.

In the adfc-config.xml we add a JSPX page named ‘index’

And this page uses a quick layout as seen here

We add a title and the inputText field, a button to submit the data and two outputText fields to show what the converter has done to the data. The page layout looks like

or in code

If you like to copy the code use the following representation:

<?xml version='1.0' encoding='UTF-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html"
 xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
 <jsp:directive.page contentType="text/html;charset=UTF-8"/>
 <f:view>
 <af:document id="d1">
 <af:form id="f1">
 <af:panelStretchLayout topHeight="50px" id="psl1">
 <f:facet name="top">
 <af:outputText value="Converter Sample" id="ot1" inlineStyle="font-size:x-large;"/>
 </f:facet>
 <f:facet name="center">
 <af:panelGroupLayout layout="scroll" xmlns:af="http://xmlns.oracle.com/adf/faces/rich" id="pgl1">
 <af:inputText label="Enter String" id="it1" value="#{bindings.myInput1.inputValue}">
 <f:converter converterId="B2ZConverter"/>
 </af:inputText>
 <af:commandButton text="refresh" id="cb1"/>
 <af:outputText value="current data: #{bindings.myInput1.inputValue}" id="ot2"/>
 <af:outputText value="current data with converter: #{bindings.myInput1.inputValue}" id="ot3">
 <f:converter converterId="B2ZConverter"/>
 </af:outputText>
 <af:commandButton text="Converter with DB Data" id="cb2" action="emp"/>
 </af:panelGroupLayout>
 <!-- id="af_one_column_header_stretched" -->
 </f:facet>
 </af:panelStretchLayout>
 </af:form>
 </af:document>
 </f:view>
</jsp:root>

Hint: you might notice another component, a button which is later used to navigate to a second page. This is described later.

For the inputText field we need to store the data a user enters. For this we can either use a DB table, a bean property or a pagedef variable. We use a pagedef variable (more on see see Creating Variables and Attribute Bindings to Store Values Temporarily in the PageDef) which we bind to the value property of the inputText component (value=”#{bindings.myInput1.inputValue}”). The converter is setup by adding an f:converter tag like

<af:inputText label="Enter String" id="it1" value="#{bindings.myInput1.inputValue}">
  <f:converter converterId="B2ZConverter"/>
</af:inputText>

The converterId points to the ID defined in the faces-config.xml file. Running the page will show

Enter ‘Hello’ into the field and clicking outside the field (so that it looses the focus) will show

As we see, the two outputText fields don’t show anything as the data in not submitted jet. Clicking the ‘refresh’ button submits the data and the converter goes to action

Well, as the input did not have any ‘B’ nothing changes. So lets us add another word ‘Beta’ and click outside the inputText

As we did not submit the data to the server, we still see ‘Hello Beta’ and the outputText fields show ‘Hello’ both. Now click the ‘refresh’ button to get

The inputText has changed to the new format where the “B” is exchanged with the “-Z-”, however the outputtext ‘current data’ still shows the ‘Hello Beta’. The reason for this is that the data send to the binding layer was converted back using the getAsObject(…) method which exchanged the “-Z-” with “B”.

This implements the use case described at the beginning.

Now, to show that the same converter works with data from a DB table as well we add another two pages to the adfc-config.xml. One showing the employees in a read only table with a link on the employeeId which navigates to the employee details in a form.

The navigation to the second use case is done with the button mentioned earlier (‘Converter with DB data’)

Clicking on the button will show a table with employees where the EMail column was used to add the converter

The column tag looks like

<af:column sortProperty="#{bindings.EmployeesView1.hints.Email.name}" filterable="true" sortable="true"
    headerText="#{bindings.EmployeesView1.hints.Email.label}" id="c3">
  <af:outputText value="#{row.Email}" id="ot5">
    <f:converter converterId="B2ZConverter"/>
  </af:outputText>
</af:column>

Like with the inputText we just add a f:converter tag with the right ID “B2ZConverter”. With this use case we see why the getAsObject(…) method should undo the formatting. You don’t want to store the Email like this. You only want to show it this way, but not overwrite the correct Email fro the employee. You can check the DB data and see that the Email is still stored with the “B” and not the “-Z-”

To verify this we can click the link in the first column to goto the detail page of the selected employee

Again, we see the ‘Email’ in the new format and the original data ‘NO CONVERTER Email’ in the normal data. The tags used for this are

  <af:inputText value="#{bindings.Email.inputValue}" label="#{bindings.Email.hints.label}" required="#{bindings.Email.hints.mandatory}"
      columns="#{bindings.Email.hints.displayWidth}" maximumLength="#{bindings.Email.hints.precision}"
      shortDesc="#{bindings.Email.hints.tooltip}" id="it1">
    <f:validator binding="#{bindings.Email.validator}"/>
    <f:converter converterId="B2ZConverter"/>
  </af:inputText>
  <af:panelLabelAndMessage label="NO CONVERTER #{bindings.Email.hints.label}" id="plam1">
    <af:outputText value="#{bindings.Email.inputValue}" id="ot2"/>
  </af:panelLabelAndMessage>

When using the binding for the Email without the converter we see the data as it’s stored in the DB. Using the converter we see the converted data.

The sample was build with JDeveloper 11.1.1.17.0 using the HR DB schema. You can download the sample from GitHub BlogConverterSample.zip

JDeveloper: Navigation after Return from Bounded Task Flow

A question on the OTN JDeveloper and ADF ‘Space’ asked for a sample and/or tutorial on how to navigate after a bounded task flow, based on pages, returns from its work.
I setup a sample application to show how this works. The application is built using JDeveloper 11.1.1.7.0 and uses the HR DB schema. The sample can be loaded using the link provided at the end of the post.
Before we start let’s take a look at the running application:


Now that we have seen how the application works let’s check out how it’s implemented.

We start with an unbounded task flow (adfc-config.xml) which look like

Unbounded Task Flow: adfc-config.xml

Unbounded Task Flow: adfc-config.xml


We see that the application is built from two pages, a ‘Start’ page and an ‘End’ page. The ‘Start’ page can call a bounded task flow employee-btf. The start page holds one button ‘Start’ which calls the bounded task flow which is shown below.

Bounded Task Flow: employee-btf.xml

Bounded Task Flow: employee-btf.xml

This task flow shows a table of employees and allows to navigate to a detail page where the employee data can be changed. Depending on the result of the change, we navigate back to the start page (when the rollback button is clicked) or we navigate to the ‘end’ page if the changes are committed (using the commit button). The buttons are calling the navigation case named ‘start’ or ‘end’ which are task flow return cases which are looking like

Task Flow Return: Start

Task Flow Return: Start

or

Task Flow Return: End

Task Flow Return: End

As you see the return for ‘start’ calls a parent navigation ‘start’ which is implemented using a ‘wild card’ navigation ‘*’. Same is true for ‘end’ task flow return call. Once the navigation is given back to the parent task flow, it looks for a navigation with the name of the outcome ‘start’ or ‘end’ and executes the navigation.

The sample can be loaded from the ADF EMG Samples workspace BlogUBTBTFNavigation.zip

JDEV 11.1.2.1.0: Using router to conditionally set navigation target

Interesting question came up on OTN ADF froum.
You have one page (page2) which is called from two other pages (page1 and page3). The question is how to set up the navigation in page2 so that you have only on button (back) which gets you back to the page the user navigated from to page2?

There is more then one solution to this problem. In this blog entry I show the declarative solution, so no java code is used. Here is adfc-config.xml which shows the navigation:

Router Back Navigation

Router Back Navigation

This is a simple navigation where the user can navigate from Page1 or Page3 to Page2. Page2 uses only one navigation case ‘back’ and let the router decide where to go. To make this happen, the button which navigates from Page1 to Page2 needs to store a hint ‘1’ in pageFlowScope which the router can check to decide where to go to. For this I add a af:setPropertyListener to store the hint in pageFlowScope. You don’t need a bean to store the value as the storage for the value is set up automatically.

                        <af:commandButton text="Page 2" id="cb1" action="page12">
                            <af:setPropertyListener from="#{'1'}" to="#{pageFlowScope.backTarget}"
                                                    type="action"/>
                        </af:commandButton>

The same technique is used in Page3 to store the hint ‘3’ in pageFlowScope

                        <af:commandButton text="Page 2" id="cb1" action="page32">
                            <af:setPropertyListener to="#{pageFlowScope.backTarget}" type="action"
                                                    from="#{'3'}"/>
                        </af:commandButton>

Finally the magic is done in the router. Here the value which is stored in pageFlowScope variable ‘backTarget’ is checked and hte correct navigation target is used for the back navigation. Below is the source of the router, the design view is shown in the first picture.

  <router id="backRouter">
    <case id="__12">
      <expression>#{pageFlowScope.backTarget eq '1'}</expression>
      <outcome>page21</outcome>
    </case>
    <case id="__13">
      <expression>#{pageFlowScope.backTarget eq '3'}</expression>
      <outcome>page23</outcome>
    </case>
    <default-outcome>page21</default-outcome>
  </router> 

When you run the sample you start from Page1 and navigate to Page2 you see the output text which shows the content of the pageFlowScope variable, ‘1’ in this case.

Navigation from Page1 to Page2

Navigation from Page1 to Page2

If the user navigates from Page3 to Page2 the output looks like

Navigation Page3 to Page2

Navigation Page3 to Page2


the output text shows ’3′ in this case.

You can download the sample application which is build using JDeveloper 11.1.2.1.0 from here BlogRouterBackNavigation.zip.
Please rename the file to ‘.zip’ after downloading it!
The sample don’t use any db connection and should be runnable on older JDeveloper versions too.

JDeveloper: Don’t overlook warning signs in task flows or check your ‘Show Warning’ settings

This week I came across a hard to find error using task flows, which was caused by a simple reason.

I tired out Frank Nimphius samples #52 about using task flows which are not part of the application itself, but which are added from adf libraries to the application. First I tried the sample Frank provided in the Code Corner article, which worked as expected after changing the db connections to my environment.

Running Sample #52

Running Sample #52

Next I’d build my own task flow in an adf library and tired to use it in the parent application as reagion. All went OK till I run the application. Here is the picture

My application with Task Flow in Library

My application with Task Flow in Library

As you can see the region representing the task flow from the library is not shown or empty. The first step to analyze the error was to inspect the task flow in the library which looked like:

Rebuilded task flow in the adf library

Rebuilded task flow in the adf library

This looked OK for me and a further visual inspection of each of the other elements did not show any difference compared to Franks sample. Here are the images of the method call activity

Method call setdeptId

Method call setdeptId

and the navigation together with the navigation target

Navigation and target page

Navigation and target page

After some more checking I saw the hint which finally lead me to finding the error. In the image above you see a yellow or brownish frame around the ‘From Outcome’ field. I didn’t pay much attention to this at first, but after checking the ‘setdeptId’ method call outcome and the navigation targets ‘From Outcome’ I saw the difference.
The ‘setdeptId’ method call had ‘setdeptId’ set as ‘Fixed Outcome’ whereas the navigation was set to ‘goEmployees’. Once these two attributes are set to the same value the the color frame is gone.

Navigation target after correction

Navigation target after correction

Some questions still to answer:
(q1) Why don’t you get any error output in the log when running the application?
(q2) Why do you see no other error or warning in the design view in JDeveloper?

The answer to (q1) is easy once you found the reason for the error. As the method call activity (setting the department id and executing the query) did not throw an error, the navigation took place. As the outcome was set to not existing target, the region remains empty. A this could be desired behavior and no exception occurred no log output is generated.

Question (q2) took some more research. Lately I wrote a couple of blog entries with many pictures from the samples I used to show something. Some of of the images I used showed a small yellow attention sign when e.g. a page element on a task flow when the page is not created jet. As I took some of the images before the pages where ready and I didn’t want to show the warnings in my pictures, I turn the warnings off. This is not a problem as I later created the missing pages. But I forgot about this change with the result that the visual warning for the error I described here was hidden.
With the warning turned on again the error is easy to detect:

Task Flow in the library with 'Show Warnin' on

Task Flow in the library with 'Show Warnin' on

To turn the ‘Show Warning’ on or off you drop down the ‘Show’ list box in the diagram view of a task flow or page:

'Show Warnig'

'Show Warnig'

So, always remember to reset settings you only change for your convenience or make sure you never forget their impact.

Using diferent VOs for Master Detail Navigation (the Declatative Way)

A user on the OTN forum asked a question how to do a master detail like navigation where the master VO is not equal to the detail VO and no accessors or link is available between the tow VOs.

A use case for this scenario is e.g. you have a read only table as master which holds an attribute which is the foreign key to an other table (the master table has a FK to the detail you like to change). In the sample I’m talking about in this blog I used the HR schema, the employees table as master and the the department as detail. I show how to use the employees as read only table, select an employee to edit the department the employee is assigned to.

Here is the data model of the sample:

Data Model

Data Model


As you can see there are no view links defined which could be used to navigate from the employee to the related department.

I’ll do all this the declarative way, so I don’t use a bean or other Java code. I use a bounded task flow and start with a query panel with the read only employees table. Each row shows the id of the employee, the name and the department id. I add a button to the department id of each row and use this to navigate to the departments edit page. Here you see the running app, the query panel which I used to select employees records and the button which I added to the department is column.

Start Screen

Start Screen

I used a button here because of an error in this version (11.1.2) of jdev which prevent the table from selection the current row when you just hit a link in a row. Frank Nimphius provided a workaround for this here:JDeveloper 11.1.2 : Command Link in Table Column Work Around. A click on the ‘Department’ button for ‘Jannette King’ will navigate to Department ’80’ which is editable

Select a Department from the Table and Edit Department

Select a Department from the Table and Edit Department

The work flow is implemented as shown below:

Work Flow

Work Flow

As you see the whole work is done in bounded task flow which first presents the query panel together with the resulting employees table (read only). The column ‘Department ID’ shows the button I use to navigate to the editable departments page. As there is no view link, it’s not enough to select the employees row to mark it as current row. I have to extract the department id from the selected row and use this to search for the department before showing the departments edit page.

I store the department id in a page flow scope variable named ‘#{pageFlowScope.depKey}’. If you like you can store the value of the department id elsewhere e.g. in the variables iterator of the page binding. To extract and store the value I use a af:setPropertyListener which allows to react on the action of the button and transfer the value to page flow scope variable. Here is the code of the department id column:

                            <af:column sortProperty="#{bindings.EmployeesView1.hints.DepartmentId.name}"
                                       sortable="true"
                                       headerText="#{bindings.EmployeesView1.hints.DepartmentId.label}"
                                       id="resId1c4" width="114">
                                <af:outputText value="#{row.DepartmentId}" id="ot5">
                                    <af:convertNumber groupingUsed="false"
                                                      pattern="#{bindings.EmployeesView1.hints.DepartmentId.format}"/>
                                </af:outputText>
                                <af:commandButton text="ShowDepartment" id="cb2" action="showDep">
                                    <af:setPropertyListener from="#{row.DepartmentId}"
                                                            to="#{pageFlowScope.depKey}"
                                                            type="action"/>
                                </af:commandButton>
                            </af:column>

The button action navigates to he method call ‘SetCurrentRowWithKeyValue’ in the bounded task flow. This method I dragged from the data control palette from the DepatermetnsView1 operation onto the bounded task flow definition page

SetCurrentRowWithKeyValue from DepartmentsView1

SetCurrentRowWithKeyValue from DepartmentsView1

The method searches the department using the the value stored in the page flow scope variable. The dialog below opens automatically when you drop the method on the task flow and lets me enter the key value to search for:

setCurrentRowWithKeyValue  Edit Action Binding

setCurrentRowWithKeyValue Edit Action Binding

Here is the pagedef file for the method call:

PageDef of setCurrentRowWithKeyValue Method

PageDef of setCurrentRowWithKeyValue Method


After the search the current row is set in the DepaertmensView1 and I can navigate to to the edit page. That’s about it.

You can download the sample work space from here Sample Workspace blogmasterdetaildeclarative_v2-zip. You have to rename the file to ‘.zip’ after download!

Globale Navigation mit Menus in TemplatePages

In meinem aktuellen Projekt trat das Problem auf, dass eine Navigation über eine Menustruktur ermöglicht werden soll, die in allen Seiten der Anwendung zugänglich sein soll. Leider ist die Anwendung so groß, dass die UseCases gruppiert wurden und in eigene TaskFlows ausgelagert werden müssen (das entspricht wohl auch der ‘Best Practice’).
Leider stellen wir fest, dass innerhalb eines Taskflow (bounded) kein Zugriff auf Regeln des aufrufenden Taskflow (i.a. adfc-config.xml) möglich sind, da der Taskflow in sich abgeschlossen ist. Alle Seiten werden von einer TemplatePage abgeleitet, die das Menu für die globale Navigation als Facette in jede Seite einbaut. Damit braucht die Navigation nur einmal in dem Menufragment verdrahtet zu werden.
Nach der Suche nach Lösungen gab es aus meiner Sicht nur zwei Möglichkeiten:

  1. Eine Seite in der adfc-config.xml die das Menu für die globale Navigation enthält, die einzelnen Taskflows, die die Usecases darstellen werden immer in dieser Seite als Region eingebunden
  2. Die Navigation über das Menu muss in allen Seiten zugänglich gemacht werden, auch wenn eine Seite in einem Taskflow (gegebenenfalls geschachtelt) eingebaut ist.

Lösung 1 habe ich nicht weiter untersucht, da mir die Erfahrung im Umgang mit Regions fehlt. Lösung 2 sollte eigentlich die einfachere sein. Wir sind doch wohl nicht die einzigen Anwender mit der Anforderung einer global zugreifbarer Navigation über eine Menuleiste, die in einem Fragment eingebaut ist.

Leider hat die Suche nach einer Lösung wie in 2. beschrieben kein Resultat hervorgebracht. Nach einigem Probieren bin ich auf die im Folgendem beschriebene Lösung gekommen:

Jeder Menueintrag wird an eine Bean im Session Scope gebunden. Die Bean merkt sich nur welcher Menueintrag gewählt wurde (über einen an die Bean gebundenen ActionLisener pro Menueintrag). Weiterhin enthält die Bean eine Methode, die die Kennung des Menueintrag der als letzter gewählt wurde zurückliefert. Die Kennung dient dann später als Sprungziel.

Jeder Taskflow enthält eine Wildcard-Regel, die immer einen festen Outcome (z.B. fertigMenuauswahl) liefert, der auch genau über diesen Namen angesprungen werden kann.

Wildcrd Regel zur Menunavigation

Wildcard Regel zur Menunavigation

Bei der Auswahl eines Menueintrag muss jetzt nur nuch als Action der oben definierte Outcome geliefert werden (hier fertigMenuauswahl). Als XML sieht dies dann so aus:

<control-flow-rule>
<from-activity-id>*</from-activity-id>
<control-flow-case>
<from-outcome>fertigMenuauswahl</from-outcome>
<to-activity-id>menuauswahl</to-activity-id>
</control-flow-case>
</control-flow-rule>

Ein Menueintrag stellt sich im Propertyinspector so dar:

Propertyinspector eines Meuneintrag

Propertyinspector eines Meuneintrag

oder im Code:

<af:menuBar>
<af:menu text=”Stammdaten”>
<af:commandMenuItem text=”Standardsätze” actionListener=”#{MenuBean.menuauswahlStandardsaetze}”
action=”fertigMenuauswahl”/>
<af:commandMenuItem text=”Quellen”
actionListener=”#{MenuBean.menuauswahlQuellen}”
action=”fertigMenuauswahl”/> …
Der Code in der Bean ist:

public class MenuBean
{
String mMenuauswahl = null;

public void menuauswahlStandardsaetze(ActionEvent actionEvent)
{
    mMenuauswahl = "menustandardsaetze";
}
public String handleMenuAuswahl()
{
    return mMenuauswahl;
}
}

Als letztes Glied in der Kette fehl nur noch die eigentliche Durchführung der Navigation im unbounded Taskflow (adfc-config.xml). Dies geschieht wieder durch eine Wildcard-Regel, die mit der ID, die der Menuauswahl geliefert wird (fertigMenuauswahl) eine Methode in der Bean anspringt, die als Ergebnis das zuletzt ausgewählte Menu zurückliefert.

Ausschnitt der adfc-config.xml

Ausschnitt der adfc-config.xml

Der Methodencall wird so verdrahtet:

Propertyinspector handleMenuauswahl

Propertyinspector handleMenuauswahl

Damit können globale Navigationen über das Menu in allen Seiten, auch solchen in Taskflows, die auch noch geschachtelt sein können, durchgeführt werden.

Es ist allerdings noch zu berücksichtigen, dass in der Bean gegebenenfalls noch ein Rollback durchzuführen ist, damit in einem Taskflow gemachte Änderungen zurückgenommen werden, falls sie nicht expliziert gespeichert wurden.