Developer Tools
JDeveloper
Written By
Duncan Mills, Oracle Corporation
June, 2006
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:
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.
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.
Using the standard Oracle HR demo schema for this example. In the Model project:
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
Switch to the ViewController project and carry out the following steps:
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.
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.
Next we need to replace the existing LastName field with the auto-suggest component.
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.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.
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.
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.
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.
<?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).
<?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>
<?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>
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.
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.
<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>
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.
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.
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;
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: }
Gaining access to ADF bound methods from within an Ajax events may seem fairly complicated but breaks down into the following basic tasks:
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