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:
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
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:
Create a new entity object based on Employees call Employees
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
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
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:
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.
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.
Delete all of the fields except the FirstName, LastName and
Email, leaving the command buttons in place as well.
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.
Copy the downloaded textfield.jar
to the public_html
\WEB-INF\lib
directory of your ViewController project.
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:
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).
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.
Open the employeeSearch.jsp page in the visual editor.
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.
Set the Label Property of the PanelLabelAndMessage to match
the expression used in the LastName field: #{bindings.LastName.label}.
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.
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)
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.
In the Model project, select the application module and
choose Go To Application
Module Class
from the context menu.
Add the following imports and method to the
application module Impl:
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.
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.
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):
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).
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.
Select the new Bindings node and again using the content
menu choose Insert
inside bindings | methodAction.
The Action Binding editor will display.
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.
OK the dialog. The PageDef will now look something like this:
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.
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.
Set the id
property to ajaxPageDef.
Set the path
property to the location of your ajaxPageDef file, without the .xml
extension - in this example com.groundside.view.pageDefs.ajaxPageDef
OK the Insert page dialog.
Now select the pageMap node in the Structure window and
choose Insert inside
pageMap | page.
Another (slightly different) Insert Page dialog will appear.
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..
Set the usageId
property to ajaxPageDef.
OK the dialog. Your DataBindings.cpx file should now look
something like this, containing mappings for both the search page and
the Ajax transaction:
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.
In the ViewController project select the web.xml file and
select Properties
from the context menu
In the properties navigator select Filter Mappings and
press the Add...
button. The Create Web Application Filter Mapping dialog will pop up.
In this dialog set the Filter
Name property to adfBindings.
Select the Servlet
Name radio button and set that to Faces Servlet.
Check the Request
checkbox and OK the dialog. The properties dialog should now look like
this:
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.
With the ViewController project selected, use File | New from the menu to
create a new Java class, for example com.groundside.view.handler.AjaxHandler.
Create a private variable in the class called bindings, of type oracle.binding.BindingContainer.
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.
Open the
faces-config.xml file from the navigator. In the editor,
switch to the Overview tab at the base of the editor .
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.
Set the Name
property of the bean to ajaxHandler.
Set the Class
property to the name of the class you just created e.g. com.groundside.view.handler.AjaxHandler.
Leave the Scope
property as request and OK the dialog.
In the Overview panel, click the Sideways pointing arrow
next to Managed
Properties, and press the New button that
appears within that region.
In the Create Managed Property dialog that appears set the Name property to bindings.
Set the Class
property to oracle.binding.BindingContainer.
OK the dialog
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}.
OK the dialog.
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.
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.
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.
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;
Implement the new method as follows: (note line
numbers have been added for explanation purposes only):
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:
Create the PageDef file that contains the mappings to the
required service methods.
Map the Ajax PageDef file to the Ajax URL in the
DataBindings.cpx file.
Ensure that the ADF Bindings Filter is invoked on Ajax
conversations
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.