Ajax Transactions Using ADF and JavaServer Faces

Written By Duncan Mills, Oracle Corporation
June, 2006

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<String> 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):
<?xml version="1.0" encoding="UTF-8" ?>
<pageDefinition xmlns="http://xmlns.oracle.com/adfm/uimodel"
                version="10.1.3.36.73" id="ajaxSearchPageDef"
                Package="com.groundside.view.pageDefs">
</pageDefinition>

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:
<?xml version="1.0" encoding="UTF-8" ?>
<pageDefinition xmlns="http://xmlns.oracle.com/adfm/uimodel"
                version="10.1.3.36.73" id="ajaxPageDef"
                Package="com.groundside.view.pageDefs">
  <bindings>
    <methodAction id="autocompleteFindUniqueNames"
                  InstanceName="EmployeeServiceDataControl.dataProvider"
                  DataControl="EmployeeServiceDataControl"
                  MethodName="autoCompleteFindUniqueNames"
                  RequiresUpdateModel="true" Action="999"
                  ReturnName="EmployeeServiceDataControl.methodResults.¬
                              EmployeeServiceDataControl_dataProvider_autoCompleteFindUniqueNames_result">

      <NamedData NDName="searchString" NDType="java.lang.String"/>
    </methodAction>
  </bindings>
</pageDefinition>
  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:
<?xml version="1.0" encoding="UTF-8" ?>
<Application xmlns="http://xmlns.oracle.com/adfm/application"
             version="10.1.3.36.73" id="DataBindings" SeparateXMLFiles="false"
             Package="com.groundside.view" ClientType="Generic">
  <pageMap>
    <page path="/employeeSearch.jsp" usageId="employeeSearchPageDef"/>
    <page path="/faces/ajax-autocomplete" usageId="ajaxPageDef"/>
  </pageMap>
  <pageDefinitionUsages>
    <page id="employeeSearchPageDef"
          path="com.groundside.view.pageDefs.employeeSearchPageDef"/>
    <page id="ajaxPageDef"
          path="com.groundside.view.pageDefs.ajaxPageDef"/>
  </pageDefinitionUsages>
  <dataControlUsages>
    <BC4JDataControl id="EmployeeServiceDataControl"
                     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"/>
  </dataControlUsages>
</Application>

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:
<managed-bean>
  <managed-bean-name>ajaxHandler</managed-bean-name>
  <managed-bean-class>com.groundside.view.handler.AjaxHandler</managed-bean-class>
  <managed-bean-scope>request</managed-bean-scope>
  <managed-property>
    <property-name>bindings</property-name>
    <property-class>oracle.binding.BindingContainer</property-class>
    <value>#{bindings}</value>
  </managed-property>
</managed-bean>
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<String> 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..

Summary

Gaining access to ADF bound methods from within an Ajax events may seem fairly complicated but breaks down into the following basic tasks:

  1. Create the PageDef file that contains the mappings to the required service methods.
  2. Map the Ajax PageDef file to the Ajax URL in the DataBindings.cpx file.
  3. Ensure that the ADF Bindings Filter is invoked on Ajax conversations
  4. Provide access to the bindings object in the managed bean containing the Ajax code.

These four tasks are going to be similar for any Ajax components. The variable factors will be the URL that the component submits it's requests to and the implementation of the actual event handler itself.
The complete workspace containing a worked example from this article can be downloaded from here. Download this sample and create a database connection to the HR schema called "hr" before making and running.

drmills v1.3 27-Jun-2006


false ,,,,,,,,,,,,,,,