Wednesday, August 5, 2009

Cascading LOV's in Oracle ADF 11g Update 1


I have developed and posting sample application for Cascading LOV's, you can download - CascadingLOV.zip. Cascading LOV's in 11 are simple, you just need to define View Criteria on dependent LOV component and specify Bind variable. Usage for Bind variable should be specified as Required:


While you will define Bind variable, don't forget to set Display Hint for this variable to Hide. This will allow to hide Bind variable in LOV component and not to render it on UI:


Finally, when you will define LOV for View object attribute, in View Accessor select defined View Criteria and specify value for Bind variable, this will ensure cascading relationship:


In ViewController, don't forget to set AutoSubmit = true for parent LOV's and PartialTriggers for dependent LOV's, you can set EL expression for Disabled property. You will need to create Value Change Listener methods for parent LOV's as well. Those methods will clear values in dependent LOV components:


On runtime, it works pretty nice, here is an example parent LOV and two cascading LOV components. Third LOV is dependent on second and first:


When value in first LOV is selected, second LOV becomes enabled:


Value in second LOV is selected, third LOV becomes enabled:


Finally, value in third LOV is selected:


If parent LOV value is cleared, all dependent LOV's are cleared also, it wasn't working before:

Tuesday, August 4, 2009

LOV Search Edit

Sample application described in this post contains high practical value, it implements real use case. Defined use case - based on value selected in List-Of-Values (LOV) component, perform query in database and display results in form. Form with results is updated, when value in LOV is changed.

You can download developed application - LOVSearchEdit.zip and run it in JDeveloper 11g TP3, additionally you need to have Oracle XE database with HR schema.

Model part of sample application contains Locations Entity object and two View objects - LocationsView and CountriesLovView. First View object is based on Entity object and second View object is read-only, is used in LOV component.


View object - LocationsView is used to query database and return results displayed in form. In other words, this View object perform Search action. LocationsView contains WHERE clause with bind variable, it retrieves Locations by specified Country Id:


In View layer of our application, Search action is initialized by LOV component for Countries. I have created this LOV component as unbounded and not related to Model. I achieved this, by declaring countryIdVar variable in Page Definition, and using this variable as Base Data Source for LOV component.


You can create unbounded LOV component in JSPX page, by dropping empty af:inputListOfValues from ADF Faces Common Components palette. As next step, right-click on newly created component and select from menu - Bind to ADF Control. In Edit List Binding dialog you should choose variables as Base Data Source and in List Data Source choose View object that you want to use as list data provider. For example, in my application this dialog looks like this:


To execute Search action, I'm using ExecuteWithParams action defined in Page Definition and associated with LocationView. Input parameter for bind variable is retrieved directly from attribute wired with LOV component Base Data Source variable.


Defined ExecuteWithParams action is invoked from countryIdReturnPopupListener created for Countries LOV:


And finally last step, in order to refresh form with displayed Search results, you need to put AutoSubmit=true for Countries LOV component. And change PartialTriggers property of form with results to point to Countries LOV Id.

And yes, all this stuff really works. When page is opened and there still no value in LOV is selected, empty form is not displayed:


Let's select any value in LOV component:


Search results are retrieved and form with data is opened. User can navigate through results and update locations from selected country:

Monday, August 3, 2009

Ajax Transactions Using ADF and JavaServer Faces

Introduction

Ajax (Asynchronous JavaScript and XML) combined with the component based approach of JavaServer Faces (JSF) offers the exciting prospect of highly interactive components that can be used to build applications with almost desktop levels of interactivity.

In this article we're going to look at how an existing Ajax component can be wired up to use data supplied via the Oracle Application Development Framework (ADF) abstraction. This task is slightly complicated because, by definition, Ajax components will be needing access to ADF bindings outside of the normal request-response cycle.

This example uses an existing Ajax control available from java.net which provides "Google Suggest" style auto-suggest functionality. As you type letters into a field, a pop up list of the first few matching hits appears. You can then select one of these to auto-complete the field. Here's a screen-shot of the sample application in action:
Running Sample
The page itself provides a query-by-example screen for Employees. The last name field is instrumented with auto-suggest.

This article assumes that you are familiar with ADF as a framework and specifically with some of the key aspects of ADF data binding such as the editing of PageDef files. If this is not the case more information on ADF Data binding and how it works can be found in the ADF Developer's Guide.

Setup

For simplicity we're using ADF Business Components as the business service for this example. The method that the Ajax component will be wired to in this case is exposed through the ADF Business Components application module. However, the technique is applicable to any method exposed via ADF Model bindings such as those exposed though an EJB session beans or web services. Be aware, however, that the bound method will be called with every keystroke in the Ajax component. Always consider if the dataset is small enough to cache a JSF managed bean rather than fetching from the model every time.

1. Create a New Application

  1. Create a new Application based on the template Web Application [JSF, ADF BC]

2. Define the Business Service

Using the standard Oracle HR demo schema for this example. In the Model project:

  1. Create a new entity object based on Employees call Employees
  2. Create a new view object based on that entity object called AllEmployees. This view object will be used to create the query-by-example form and results table
  3. Create a new read-only view object based on the following query. This will be used to support the Ajax callback method.

SELECT DISTINCT
EMPLOYEES.LAST_NAME
FROM EMPLOYEES
WHERE upper( EMPLOYEES.LAST_NAME) like :searchPrefix
ORDER BY EMPLOYEES.LAST_NAME ASC

Define the bind variable searchPrefix as a String and call this view object UniqueEmployeeNames

  1. Create an application module EmployeeService, exposing AllEmployees as AllEmployees1 and UniqueEmployeeNames as UniqueEmployeeNames1 through the DataModel screen.

3. Create the Basic Page

Switch to the ViewController project and carry out the following steps:

  1. Create a new JSF Page employeeSearch.jsp, select the Do Not Automatically Expose UI Components in a Managed Bean option. We'll manually create a bean for the Ajax callback code later. Select the ADF Faces Tag Libraries as well as the Sun RI ones.
  2. With the new page in the editor, open the Data Control Palette and drag and drop the AllEmployees1 collection onto the page as an ADF Search Form.
  3. Delete all of the fields except the FirstName, LastName and Email, leaving the command buttons in place as well.
  4. Drag and drop the AllEmployees1 collection again, dropping it beneath the search form as an ADF Read-Only table. Trim down the number of columns displayed in the table if you wish.

At this stage we have a fully functional Query-By-Example screen. Run the page and press the Find buttton, enter "King" into the LastName field and press Execute and with the default HR dataset, two rows will appear.

Adding Auto-Suggest

The JSF component that we'll be using for this example is open source and available from java.net here. The component as supplied will work at runtime with JDeveloper and OC4J, but unfortunately for design time purposes, it uses some proprietary hooks into Sun's JSF development environment which are not available to JDeveloper. To solve this problem we have packaged a version of the component without these Sun specific references for the purposes of this illustration only. This is enough to demonstrate the techniques discussed in this article which are generic and extend to any Ajax component that needs access to ADF managed data.

1. Install the component

  1. Close down JDeveloper
  2. Download the following jar file containing the component: http://otn.oracle.com/products/jdev/tips/mills/AjaxAutoSuggest/textfield.jar
  3. The full version of this component can be found at https://blueprints.dev.java.net/bpcatalog/distDrops.html. You should review that page and the associated licence agreement before proceeding
  4. Copy the downloaded textfield.jar to the public_html \WEB-INF\lib directory of your ViewController project.
  5. Restart JDeveloper , bring up the project properties and display the JSF Tag Libraries. It should now look like the following illustration, note that the ajaxTags 0.03 tag library has appeared:
    Screenshot of project properties
  6. At this point the Component Palette should also have gained an extra page labeled AjaxTags. (If it has not, save all, exit and restart JDeveloper and it should appear).
  7. Bring up the project properties again and select the Libraries option in the navigator. Use the Add Jar/Directory... button to add the public_html\WEB-INF\lib\textfield.jar as a library.

2. Add the component to the employeeSearch screen

Next we need to replace the existing LastName field with the auto-suggest component.

  1. Open the employeeSearch.jsp page in the visual editor.
  2. From the ADF Faces Core component palette drag a PanelLabelAndMessage component and drop it after the existing InputText that contains the LastName binding. The Ajax component that we're using does not provide properties for setting a label, so we use a PanelLabelAndMessage as a container for it to provide a place to provide the field label and to ensure that everything lines up within the PanelForm component.
  3. Set the Label Property of the PanelLabelAndMessage to match the expression used in the LastName field: #{bindings.LastName.label}.
  4. Switch the component palette to the new AjaxTags page and drop a CompletionField onto the new PanelLabelAndMessage. A dialog titled Insert CompletionField will pop up. enter the following expression into the CompletionMethod*: field: #{ajaxHandler.searchNameComplete} and OK the dialog. We'll implement that method shortly.
  5. With the new CompletionField selected, bring up the property inspector and set the Id property of the comnponent to lastName and the Value property to #{bindings.LastName.inputValue}. (Copy this from the LastName field above)
  6. You can now delete the old InputText containing the LastName binding.

Important Note:

You may also find that the CompletionField component that we are using as an illustration, does not render correctly in the visual editor, however it will render correctly at runtime. This is a problem with this specific component not JDeveloper.

3. Implement the service method for returning the auto-complete list

The next task is to create the method within ADF Business Components (or your EJB Session Bean) that returns a list of suggested values for the field based on an input prefix that will be supplied from the Ajax component.

  1. In the Model project, select the application module and choose Go To Application Module Class from the context menu.
  2. Add the following imports and method to the application module Impl:

import java.util.ArrayList;
import java.util.List;

...

public List autoCompleteFindUniqueNames(String searchString){
UniqueEmployeeNamesImpl hits = this.getUniqueEmployeeNames1();
hits.setNamedWhereClauseParam("searchPrefix",searchString.toUpperCase()+ "%");
hits.setRangeSize(5);
hits.executeQuery();
ArrayList resultset = new ArrayList((int)hits.getEstimatedRowCount());

for (Row row:hits.getAllRowsInRange()){
resultset.add((String)row.getAttribute("LastName"));
}

return resultset;
}

This method is only a simple example that re-executes the UniqueEmployeeNames query (view object) with the prefix value passed from the Ajax control. Note that the method returns a list of strings containing the LastName attribute from the matching rows and that (in this example) the query is limited to only fetching the first five matches to return to the control.
The
getUniqueEmployeeNames1() method was automatically created when that veiw object was exposed through the application module's data model screen.

  1. Compile and save the application module Impl file and bring up the application module properties. Select the Client Interface entry in the tree and shuttle the new autoCompleteFindUniqueNames(String) method into the Selected pane of the shuttle control. OK the dialog and save the workspace.

4. Wiring the Ajax component to the service method

Now comes the interesting part where we need to implement the autocomplete event to wire the event raised by the Ajax component, to the service method we've just created. To do this we'll need several steps to create both a DataBinding defintion (PageDef file) and a managed bean to hold the event code.

4.1 Defining the ajaxPageDef

Ajax components will generally make GET requests to a different URL from the normal URL that a JSF page would post to. As a result, it's going to need it's own binding definition in the form of a PageDef file that will tell the framework what bindings are required for the Ajax transaction.

  1. Create a new PageDef file-ajaxPageDef.xml in the src... \pageDefs directory as follows. (You may find it easier to copy an existing PageDef such as the one created for the search page using File | Save As, and then adapt that):



version="10.1.3.36.73" id="ajaxSearchPageDef"
Package="com.groundside.view.pageDefs">

Note the id is set to match the name of the pagedef file and the package should match the actual location (amend based on your project).

  1. With this file open in the ediitor, select ajaxPageDef in the Structure window and from the context menu choose Insert inside ajaxPageDef | bindings from the context menu.
  2. Select the new Bindings node and again using the content menu choose Insert inside bindings | methodAction. The Action Binding editor will display.
  3. In the Action Binding Editor, select your application module data control (EmployeeServiceDataControl in this case) and choose the autocCompleteFindUniqueNames method in the Select an Action list.
    Screen shot of action binding creation
    OK the dialog. The PageDef will now look something like this:



version="10.1.3.36.73" id="ajaxPageDef"
Package="com.groundside.view.pageDefs">


InstanceName="EmployeeServiceDataControl.dataProvider"
DataControl="EmployeeServiceDataControl"
MethodName="autoCompleteFindUniqueNames"
RequiresUpdateModel="true" Action="999"
ReturnName="EmployeeServiceDataControl.methodResults.¬
EmployeeServiceDataControl_dataProvider_autoCompleteFindUniqueNames_result">



  1. Next edit the ViewController project DataBindings.cpx file. In this file we need to map the URL that the Ajax component will be using to the new PageDef file. This will ensure that ADF will have access to this bound method call when the Ajax request is sent.
  2. In the Structure window for the DataBindings.cpx, select the pageDefinitionUsages node and from the context menu choose Insert inside pageDefinitionUsages | page. The Insert page dialog will appear.
  3. Set the id property to ajaxPageDef.
  4. Set the path property to the location of your ajaxPageDef file, without the .xml extension - in this example com.groundside.view.pageDefs.ajaxPageDef
  5. OK the Insert page dialog.
  6. Now select the pageMap node in the Structure window and choose Insert inside pageMap | page. Another (slightly different) Insert Page dialog will appear.
  7. Set the path property to /faces/ajax-autocomplete. This is the URL that the component sends messages to. Different components will use different URLs so you will need to adapt the path used based on the component you have..
  8. Set the usageId property to ajaxPageDef.
  9. OK the dialog. Your DataBindings.cpx file should now look something like this, containing mappings for both the search page and the Ajax transaction:



version="10.1.3.36.73" id="DataBindings" SeparateXMLFiles="false"
Package="com.groundside.view" ClientType="Generic">






path="com.groundside.view.pageDefs.employeeSearchPageDef"/>

path="com.groundside.view.pageDefs.ajaxPageDef"/>



Package="com.groundside.model"
FactoryClass="oracle.adf.model.bc4j.DataControlFactoryImpl"
SupportsTransactions="true" SupportsFindMode="true"
SupportsRangesize="true" SupportsResetState="true"
SupportsSortCollection="true"
Configuration="EmployeeServiceLocal" syncMode="Immediate"
xmlns="http://xmlns.oracle.com/adfm/datacontrol"/>

4.2 Enable the ADF bindings filter on Ajax conversations

The binding information used by the Ajax component has now been set up, however, we need to make a small adjustment in the web.xml to ensure that the ADF Binding Filter is invoked for Ajax requests.

  1. In the ViewController project select the web.xml file and select Properties from the context menu
  2. In the properties navigator select Filter Mappings and press the Add... button. The Create Web Application Filter Mapping dialog will pop up.
  3. In this dialog set the Filter Name property to adfBindings.
  4. Select the Servlet Name radio button and set that to Faces Servlet.
  5. Check the Request checkbox and OK the dialog. The properties dialog should now look like this:
    Screen shot of web.xml properties
    OK the dialog.

4.3 Create the managed bean to hold the Ajax Event

Recall that in step 2-iv, we set up the textfield-jsf component with a binding reference to #{ajaxHandler.searchNameComplete}. We now need to create that managed bean and the searchNameComplete method that will call through to the application module autoCompleteFindUniqueNames method.

  1. With the ViewController project selected, use File | New from the menu to create a new Java class, for example com.groundside.view.handler.AjaxHandler.
  2. Create a private variable in the class called bindings, of type oracle.binding.BindingContainer.
  3. Right mouse click on the new variable and choose Generate Accessors... Check the box next to bindings in the Generate Accessors dialog and OK. This will generate getBindings() and setBindings() methods in the class. Save and compile the new class.
  4. Open the faces-config.xml file from the navigator. In the editor, switch to the Overview tab at the base of the editor .
  5. With the Managed Beans option selected in the list on the left hand side of the Overview panel, press the New button on the right hand side. The Create Managed Bean dialog will appear.
  6. Set the Name property of the bean to ajaxHandler.
  7. Set the Class property to the name of the class you just created e.g. com.groundside.view.handler.AjaxHandler.
  8. Leave the Scope property as request and OK the dialog.
  9. In the Overview panel, click the Sideways pointing arrow next to Managed Properties, and press the New button that appears within that region.
  10. In the Create Managed Property dialog that appears set the Name property to bindings.
  11. Set the Class property to oracle.binding.BindingContainer. OK the dialog
  12. Select the new bindings property and press the Edit button in the Managed Properties section. In the managed-property Properties dialog that appears, set the Value property to #{bindings}.
    Setting the bindings property up
    OK the dialog.
  13. Switch to Source view of the faces-config.xml. The ajaxHandler managed bean should now be defined like this:


ajaxHandler
com.groundside.view.handler.AjaxHandler
request

bindings
oracle.binding.BindingContainer
#{bindings}


The expression #{bindings} in the managed property will inject the binding container prepared by ADF into the managed bean when it is created via the setBindings() accessor method generated in 4.3-iii. This will provide access to the bound method.

  1. Save the faces-config file.

4.4 Create the Ajax callback method

Finally we need to implement the method that gets called as the user types into the auto-complete component. Like the URL we created the mapping for in 4.1-xi, this is specific to the component being used.

  1. Reopen the AjaxHandler class and add the following method signature:

public void searchNameComplete(FacesContext context,
String prefix,
CompletionResult result) {
}

The binding we defined in step 2-iv calls this method, passing the characters that the user has typed via the prefix parameter. We will use this to populate the CompletionResult object that the component also passes in.

  1. Add the following imports to support the code we're about to add (or use the code assistance features of the IDE to do this for you).

    import com.sun.j2ee.blueprints.bpcatalog.ajax.components.CompletionResult;
    import java.util.List;
    import java.util.Map;
    import javax.faces.context.FacesContext;
    import oracle.binding.BindingContainer;
    import oracle.binding.OperationBinding;
  2. Implement the new method as follows: (note line numbers have been added for explanation purposes only):

01: public void searchNameComplete(FacesContext context,
String prefix,
CompletionResult result) {
02: if (prefix != null && prefix.length() > 0) {
03: BindingContainer bc = getBindings();
04: if (bindings != null) {
05: OperationBinding ob = bc.getOperationBinding("autoCompleteFindUniqueNames");
06: Map params = ob.getParamsMap();
07: params.put("searchString", prefix);
08: List resultset = (List)ob.execute();
09: result.addItems(resultset);
10: }
11: }
12: }

Explanation of the method code

  • Line 02 checks to see if the user has typed a value yet. It's not worth calling the service method if the passed prefix is empty.
  • Line 03 gets a reference to the BindingContainer that ADF has injected when the managed bean is created. This provides access to the bindings defined in the ajaxPageDef file.
  • Line 04 ensures that the binding has been set up.
  • Line 05 gets a reference to the search method binding.
  • Line 06 gets the collection of arguments to pass to the search method.
  • Line 07 sets the searchString argument on the search method to the value passed in as the prefix from the Ajax control.
  • Line 08 invokes the search method on the application module.
  • Line 09 takes the string ArrayList passed back from the service method call and puts it into the CompletionResult object. The textfield-jsf control will then use that list to build the completion list that it presents..