使用 ADF 和 JavaServer Faces 的 Ajax 事务

作者:Duncan Mills,Oracle Corporation
2006 年 4 月

简介

Ajax(异步 JavaScript 和 XML)与基于组件的 JavaServer Faces (JSF) 方法的结合将带来令人激动的前景:高互动组件,通过它可以构建具桌面级互动性的应用程序。

在本文中,我们将讨论如何连接现有的 Ajax 组件以使用由 Oracle 应用程序开发框架 (ADF) 抽象提供的数据。这一任务有点复杂,因为依据定义,Ajax 组件需要访问普通请求响应周期之外的 ADF 绑定。

本示例使用了来自 java.net 的一个现成 Ajax 控件,它提供了类似于 "Google Suggest" 的自动提示功能。当在域中输入文字时,将弹出一个列表,其中提供了几个点击率排名前列的匹配项。然后您可以选择一项来自动填充该域。下面是该示例应用程序运行时的屏幕截图:
运行示例
该页本身提供了针对 Employees 的按例查询屏幕。姓氏域具备自动提示功能。

本文假定您熟悉 ADF 框架以及 ADF 数据绑定的几个要点,如 PageDef 文件的编辑。如果您未达到这一要求,请访问 ADF 开发人员指南,了解 ADF 数据绑定的详细信息和工作原理。

设置

为求简便,在本例中我们将使用 ADF 业务组件作为业务服务。其中,该 Ajax 组件将连接到的方法是通过 ADF 业务组件应用模块公开的。但这一做法适用于由 ADF 模型绑定公开的任何方法,如由 EJB 会话 bean 或 web 服务公开的方法。但要注意,Ajax 组件中的每一次按键都将调用绑定的方法。始终考虑数据集是否足够小,能够缓存由 JSF 管理的 bean 而不用每次都从模型中获取。

1. 创建一个新应用程序

  1. 基于模板 Web Application [JSF, ADF BC]创建一个新应用程序

2. 定义业务服务

在本例中使用标准 Oracle HR 演示模式。在 Model 项目中:

  1. 基于 Employees 新建一个实体对象 Employees
  2. 基于该实体对象新建一个视图对象 AllEmployees。该视图对象将用于创建按例查询表单和结果表。
  3. 基于下列查询新建一个只读视图对象。它将用于支持 Ajax 回调方法。
SELECT DISTINCT
EMPLOYEES.LAST_NAME
FROM EMPLOYEES
WHERE upper( EMPLOYEES.LAST_NAME) like :searchPrefix
ORDER BY EMPLOYEES.LAST_NAME ASC
 

将绑定变量 searchPrefix 定义为字符串,并调用这个视图对象 UniqueEmployeeNames

  1. 创建一个应用模块 EmployeeService,通过 DataModel 屏幕将 AllEmployees 公开为 AllEmployees1,UniqueEmployeeNames 公开为 UniqueEmployeeNames1

3. 创建基本页面

转换到 ViewController 项目并执行下列步骤:

  1. 新建一个 JSF 页面 employeeSearch.jsp,选择 Do Not Automatically Expose UI Components in a Managed Bean 选项。稍后我们将手动创建一个用于 Ajax 回调代码的 bean。选择 ADF Faces Tag Libraries 和 Sun RI。
  2. 对于编辑器中的新页面,打开 Data Control Palette,然后将 AllEmployees1 集合拖放到该页面上,作为 ADF Search Form。
  3. 删除除 FirstName、LastName 和 Email 外的所有域,保持命令按钮原位不动。
  4. 再次拖放 AllEmployees1 集合,将其置于搜索表单的下面,作为 ADF 只读表。您可以按需删减表中的列。

到这里我们已经成功创建一个功能齐全的 Query-By-Example 页面。运行该页面并点击 Find 按钮,在 LastName 域中输入 "King" 并点击 Execute,如使用的是默认的 HR 数据集,将显示两行。

添加自动提示

本例中要使用的 JSF 组件是开放源的,可以通过 java.net 获取。该组件可结合 JDeveloper 和 OC4J 在运行时期间正常工作,但在设计时期间不能正常工作,因为它使用一些专用的钩子连入 Sun 的 JSF 开发环境,而 JDeveloper 中没有这些钩子。为解决这一问题,我们针对本示例专门打包了一个不含这些 Sun 特定引用的组件版本。这已足够展示本文要讨论的技术,这些技术是通用的,可用于任何需要访问由 ADF 管理的数据的 Ajax 组件。

1. 安装组件

  1. 关闭 JDeveloper
  2. 下载包含该组件的 jar 文件:http://otn.oracle.com/products/jdev/tips/mills/AjaxAutoSuggest/textfield.jar
  3. 可在 https://blueprints.dev.java.net/bpcatalog/distDrops.html 找到该组件的全版本。在继续下一步前先查看这一页面和相关许可协议。
  4. 将下载的 textfield.jar 复制到您 ViewController 项目的 public_html \WEB-INF\lib 目录。
  5. 重启 JDeveloper,调出项目属性并显示 JSF Tag Libraries。外观如下所示,注意会出现 ajaxTags 0.03 标记库:
    项目属性屏幕截图
  6. 这时 Component Palette 中也应增添了一个标题为 AjaxTags 的页面。(如果没有,请保存所有工作,再关闭并重新打开 JDeveloper,该页面将出现)。
  7. 再次调出项目属性并选择导航器中的 Libraries 选择。使用 Add Jar/Directory... 按钮添加 public_html\WEB-INF\lib\textfield.jar 作为库。

2. 将组件添加到 employeeSearch 屏幕

下面我们将使用自动提示组件更换现有的 LastName 域。

  1. 在可视化编辑器中打开 employeeSearch.jsp 页面。
  2. 从 ADF Faces Core 组件选项板中将 PanelLabelAndMessage 组件拖放到现有的含 LastName 绑定的 InputText 后。我们使用的 Ajax 组件没有提供用于设置标题的属性项,所以我们使用 PanelLabelAndMessage 作为它的容器,通过容器来提供域标题,并保证在 PanelForm 组件中有序排列各项。
  3. 设置 PanelLabelAndMessage 的 Label Property 以匹配 LastName 域中的表达式: #{bindings.LastName.label}
  4. 从组件选项板切换到新 AjaxTags 页,将 CompletionField 拖放到新 PanelLabelAndMessage 上。随后将弹出 Insert CompletionField 对话框,将下列表达式输入到 CompletionMethod* 中:field: #{ajaxHandler.searchNameComplete},然后点击 OK。稍后我们将实施这一方法。
  5. 选择新的 CompletionField 后,调出属性查看器,设置该组件的 Id 属性为 lastNameValue 属性为 #{bindings.LastName.inputValue}。(从上面的 LastName 域中复制这一内容)
  6. 现在您可以删除包含 LastName 绑定的旧 InputText。

重要注意事项:

您会发现我们所使用的用于演示目的的 CompletionField 组件不能在可视化编辑器中正常显示,但是它可在运行时期间正常显示。这是该组件而不是 JDeveloper 的问题。

3. 实施返回自动填充列表的服务方法

下面我们要在 ADF 业务组件(或您的 EJB 会话 Bean)中创建一个方法,该方法将根据由 Ajax 组件提供的输入前缀返回域提示值列表。

  1. 在 Model 项目中,选择应用程序模块,然后从上下文菜单中选择 Go To Application Module Class
  2. 添加下列导入代码和方法到应用程序模块 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;

这一方法只是一个简单示例,它使用 Ajax 控件传递的前缀值来重新执行 UniqueEmployeeNames 查询(视图对象)。请注意,该方法从匹配行返回含 LastName 属性的字符串列表,且该查询(在本中例中)只限取前五个匹配项返回到该控件。
在通过应用程序模块的数据模型屏幕公开视图对象后,将自动创建 getUniqueEmployeeNames1()方法

  1. 编辑应用程序模块 Impl 文件然后保存,调出应用程序模块属性。在树中选择 Client Interface 项,将新的 autocompleteFindUniqueNames(String) 方法加入到 Selected 窗格中。点击 OK 关闭对话框并保存工作区。

4. 将 Ajax 组件连接到服务方法

下面我们要实施自动填充事件,将 Ajax 组件产生的事件与我们刚创建的服务方法关联。要完成这一工作,我们需要先用几步创建一个 DataBinding 定义(PageDef 文件)和一个保存事件代码的管理 bean。

4.1 定义 ajaxPageDef

通常,Ajax 组件将向一个 URL 发出 GET 请求,而该 URL 区别于 JSF 页以 POST 方式请求的普通 URL。因此,它需要自己的 PageDef 文件形式的绑定定义,用于通知框架 Ajax 事务所要求的绑定。

  1. 在 src...\pageDefs 目录中创建一个新的 PageDef 文件 ajaxPageDef.xml,如下所示。利用现有 PageDef (如为为搜索页面创建的 PageDef)将更为简便,方法是使用 File | Save As 先创建一个副本,然后再进行修改:
<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>

请注意, id 设置为匹配 pagedef 文件的名称, package 应该匹配实际位置 (根据您的项目修改)。

  1. 在编辑器中打开该文件,在 Structure 窗口中选择 ajaxPageDef,在上下文菜单中选择 Insert inside ajaxPageDef | bindings
  2. 选择新 Bindings 节点,再在上下文菜单中选择 Insert inside bindings | methodAction。随后将显示 Action Binding 编辑器。
  3. 在 Action Binding 编辑器中,选择您的应用程序模块数据控件(本例中为 EmployeeServiceDataControl),然后在 Select an Action 列表中选择 autoCompleteFindUniqueNames 方法。
    动作绑定创建过程屏幕截图
    点击 OK 关闭对话框。PageDef 将如下所示:
<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. 接下来,编辑 ViewController 项目 DataBindings.cpx 文件。在本文件中,我们需要将 Ajax 组件要用到的 URL 映射到这一新的 PageDef 文件。这样可以保证发送 Ajax 请求后,ADF 可以使用这一绑定方法调用。
  2. 在 DataBindings.cpx 的 Structure 窗口中,选择 pageDefinitionUsages 节点并从上下文菜单中选择 Insert inside pageDefinitionUsages | 页面。随后将出现 Insert Page 对话框。
  3. id 属性设置为 ajaxPageDef
  4. path 属性设置为您的 ajaxPageDef 文件所在的位置,不需要 .xml 扩展名,在本例中 path 值为 com.groundside.view.pageDefs.ajaxPageDef
  5. 点击 OK 关闭 Insert Page 对话框。
  6. 在 Structure 窗口中选择 pageMap 节点,然后选择 Insert inside pageMap | 页面。然后将显示另一个(稍有不同)Insert Page 对话框。
  7. path 属性设置为 /faces/ajax-autocomplete。这就是组件会向其发送消息的 URL。不同的组件将使用不同的 URL,所以您需要根据所用组件更改路径。
  8. usageId 属性设置为 ajaxPageDef
  9. 点击 OK 关闭对话框。您的 DataBindings.cpx 文件应该包含搜索页和 Ajax 事务的映射,如下所示:
<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 对 Ajax 会话启用 ADF 绑定过滤器

Ajax 组件使用的绑定信息已经配置完成,但我们需要稍稍改动 web.xml 以保证针对 Ajax 请求调用 ADF Binding Filter。

  1. 在 ViewController 项目中,选择 web.xml 文件,然后从上下文菜单中选择 Properties
  2. 在属性导航器中,选择 Filter Mappings 并按下 Add... 按钮。这将弹出 Create Web Application Filter Mapping 对话框。
  3. 在对话框中将 Filter Name 属性设置为 adfBindings
  4. 选择 Servlet Name 单选按钮,将其设置为 Faces Servlet
  5. 勾选 Request 复选框,然后点击 OK。属性对话框如下所示:
     web.xml 属性屏幕截图
    点击 OK 关闭对话框。

4.3 创建存储 Ajax 事件的管理 bean

在 2-iv 步中,我们将 textfield-jsf 组件设为绑定引用 #{ajaxHandler.searchNameComplete}。现在我们要创建该管理 bean 和 searchNameComplete 方法,searchNameComplete 方法将调用应用模块 autoCompleteFindUniqueNames 方法。

  1. 选择 ViewController 项目,从菜单中使用 File | New 新建一个 Java 类,例如 com.groundside.view.handler.AjaxHandler
  2. 在类中创建一个私有变量 bindings,类型为 oracle.binding.BindingContainer
  3. 右键单击该新变量,选择 Generate Accessors...在 Generate Accessors 对话框中勾选 bindings 旁边的方框,然后点击 OK。这将在类中生成 getBindings() 和 setBindings()。保存并编译新类。
  4. 从导航器中打开 faces-config.xml 文件。在编辑器中,切换到底部的 Overview 选项卡。
  5. 选择 Overview 面板左侧列表中的 Managed Beans 选项,按下右侧的 New 。随后将出现 Create Managed Bean 对话框。
  6. 将 bean 的 Name 属性设为 ajaxHandler。
  7. Class 属性设为刚创建的类的名称,例如 com.groundside.view.handler.AjaxHandler。
  8. 保留 Scope 属性为 request,点击 OK。
  9. 在 Overview 面板中,选择 Managed Properties 旁的 Sideways 箭头,然后按下该区域中出现的 New 按钮。
  10. 在出现的 Create Managed Property 对话框中,将 Name 属性设为 bindings
  11. Class 属性设为 oracle.binding.BindingContainer。点击 OK 关闭对话框。
  12. 选择新 bindings 属性,在 Managed Properties 中按下 Edit 按钮。在出现的 managed-property Properties 对话框中,将 Value 属性设为 #{bindings}
    bindings 属性设置
    点击 OK 关闭对话框。
  13. 切换到 faces-config.xml 的 Source 视图中,ajaxHandler 管理 bean 应定义如下:
<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>name</property-name>
<property-class>oracle.binding.BindingContainer</property-class>
<value>#{bindings}</value>
</managed-property>
</managed-bean>
在通过 4.3-iii.步中生成的 setBindings()accessor 方法创建管理 bean 后,管理属性中的表达式 #{bindings} 将把由 ADF 准备的绑定容器注入到该管理 bean 中。这样就可以使用该绑定方法了。
  1. 保存 faces-config 文件。

4.4 创建 Ajax 回调方法

最后我们将实现一个当用户在自动填充组件键入内容时要调用的方法。与我们在 4.1-xi 中为其创建映射的 URL 类似,这一方法特定于要使用的组件。

  1. 重新打开 AjaxHandler 类,加入下列方法签名:
public void emailValidator(FacesContext context,
String prefix,
CompletionResult result) {

}

我们在 2-iv 步中定义的绑定将调用这一方法,通过 prefix 参数传递用户输入的字符。我们将使用它来填充组件也会传入的 CompletionResult 对象。

  1. 添加以下 import 语句来支持我们将添加的代码(或使用 IDE 的代码支持特性来完成这一工作)。

    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. 新方法的实现如下所示:(注意:所添加行号仅用于说明目的):
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:  }

方法代码说明

  • 02 行 检查用户是否已经输入值。如传递的前缀为空时,不值得调用服务方法。
  • 03 行 当创建管理 bean 后,引用一个 ADF 已经注入的 BindingContainer。这提供了到 ajaxPageDef 文件中定义的绑定的访问。
  • 04 行  确保配置了绑定。
  • 05 行 引用搜索方法绑定。
  • 06 行 获取传递到搜索方法的一系列参数。
  • 07 行 将搜索方法上的 searchString 参数设为作为前缀从 Ajax 控件传入的值。
  • 08 行 调用应用程序模块上的搜索方法。
  • 09 行 获得从服务方法调用中传回的字符串 ArrayList 并将其置入 CompletionResult 对象。然后 textfield-jsf 控件将使用该列表来构建其需要提供的填充列表。

总结

从 Ajax 事件访问 ADF 方法看起来很复杂,但可将其分解为下面几个简单的基本任务:

  1. 创建 PageDef 文件,其中包含了到所需服务方法的映射。
  2. 在 DataBindings.cpx 文件中,将 Ajax PageDef 文件映射到 Ajax URL。
  3. 保证在 Ajax 会话上调用 ADF Bindings Filter。
  4. 提供到含 Ajax 代码的管理 bean 中的绑定对象的访问。

对于任何 Ajax 组件,以上四个任务类似。不同之处在于组件向其提交请求的 URL 和实际事件处理程序本身的实施。
您可从此处下载包含本文中所用的可正常运行的示例的整个工作区。下载这一示例,在编译和运行前先创建到 HR 模式 "hr" 的数据库连接。

drmills v1.2 2006 年 4 月 20 日

Left Curve
热门下载
Right Curve