开发人员工具
Application Development Framework
第 3 章 —“应该具备”需求:创建一个大众化应用程序2009 年 5 月发表 在本章中,您将了解如何构建 应该具备 需求。我们将向应用程序添加一些额外特性,包括在用户接受我们的条款和条件后记录用户 IP 地址以及增强搜索应用程序的功能。 我们的需求可以总结为以下几点:
将搜索条件转换为大写并删除运货单编号中的连字符。在数据库中,数据存储的格式通常与用户输入的格式不同。通常,人名以大写形式存储在数据库中,然而,我们允许客户以大小写混合的名称进行搜索。 Oracle ADF 业务组件提供了一系列机制,用以在搜索条件(我们在 Oracle ADF 业务组件中称其为 绑定变量)到达数据库前对其进行拦截。最简单的机制是修改视图对象查询以进行转换。例如,考虑我们原有视图对象中的已修改查询,如下所示: 还有一种修改绑定变量的机制,即,底层 ViewObjectImpl 中的 bindParametersForCollection() 方法,它将充当阻塞点方法,用于在针对视图对象应用绑定变量前查看并修改视图对象绑定变量。由于我们之前有一个简单的解决方案,因此我们将继续使用该方法。稍后,我们将介绍 bindParametersForCollection() 解决方案,因为在我们需要将搜索条件记录到数据库时会证明该解决方案很有用。 如返回多条记录,隐藏结果。该要求非常奇怪;您可能以为我们希望显示页面中视图对象的所有结果。在本示例中,基础表没有基于搜索条件允许重复记录的主键或唯一键。如果显示多条记录,则会有一个隐私要求,客户不应能够看到其他客户的数据。作为 Oracle ADF 编程人员,我们不总是拥有修复底层数据问题的权限或控制力。我们只需使用现有的条件进行工作。 通过查看底层数据,可以看出尚未考虑对这些需求:
在我们的搜索字段中,如果输入 ACME-JOE-DOE-DDD4433,系统实际上将返回两条记录(上表中红色高亮显示)— 一个显示包裹运输中,另一个显示已运抵。这种情况下,我们的安全性约束(如果返回多条记录,用户不应看到结果)会产生问题。我们发现了一个隐藏要求,即,针对相同的 Lodged With、First Name、Last Name、Waybill # 和 Sender 的搜索,我们应返回最新记录。 那么,我们如何解决这个问题并确保仅显示特定包裹的最新状态记录?同样,由于 SQL 查询的强大功能,我们可以使用子查询修改视图对象,从而仅返回每个包裹的最新记录,如下所示: 如果对 WLYP-CHRIS-MUIR-ABC1234 的搜索返回两条记录,我们不能对该数据进行任何整理操作,所以当我们的 Web 页面上出现这种情况时,我们只能不显示结果,而请客户给我们打电话进行咨询。 对于 SearchPage.jspx,在视图对象中将其从 Component Palette 拖到我们的页面中,结果是 Oracle JDeveloper 在后台为我们创建了一个包含多个绑定的页面定义文件: 为进行调试,我们将在结果字段前放置一个 outputText,以文本形式输出返回记录的数量:
<af:outputText
value="Numbers of rows: #{bindings.ParcelsView1Iterator.estimatedRowCount}"/>
您将看到现在运行 Web 页面,结果字段将不会在初始时显示:
在用户接受条款和条件后仅启用搜索字段。返回到我们的查询屏幕原始的故事板,我们将看到我们希望选择 Accept 或 Reject 按钮都可以显示用户必须同意的条款和条件。在向屏幕进行输入时,查询条件域和 Enquiry 按钮只有在用户按下 Accept 按钮后才会启用。如果用户按下 Reject 按钮,这些域将保持只读状态。用户按下任一按钮后,按钮即变为不可用,这样用户将无法更改其选择,除非启动一个 新会话。 注意上一段对会话的强调:对于单一会话,我们将强制执行该规则,即用户接受或拒绝整个会话的条款和条件。如果接受,在用户离开前都将接受整个会话的条款和条件。然而,返回时,将再次提示用户接受或拒绝条款和条件。这是现在许多站点的典型要求。 记录用户的 IP 地址以及用户接受还是拒绝条款和条件。除了用户接受或拒绝条款和条件以外,记录用户的 IP 地址及其所选择的按钮也是必要的。这是出于稍后进行审核的目的:如果客户投诉侵犯其隐私,我们可以进行追溯并说明特定的 IP 地址接受或拒绝了我们的隐私免责声明中包含的条款和条件。 啊,我们差点错过了一个需求。我们还需要记录用户是包裹的发件人还是收件人、IP 地址以及用户接受还是拒绝了条款和条件。为此,我们希望在条款和条件文本前显示一个弹出列表,显式强制用户选择并指定用户所属类型:即,包裹的发件人还是收件人。 为满足以上需求,我们必须实施以下操作:
在屏幕上加上我们的条款和条件的批注,并添加 Accept 和 Reject 按钮。这是最简单的要求,结果如下: 以下为新增的代码: <af:outputText value="Do you accept the terms and conditions?"/> <af:panelGroupLayout layout="horizontal"> <af:commandButton text="Accept"/> <af:spacer height="5" width="5"/> <af:commandButton text="Reject"/> </af:panelGroupLayout> 创建一个按钮映射到的会话级 bean。接下来,当用户按 Accept 或 Reject 按钮时,我们需要将结果存储在会话 bean 中。我们使用 ViewController 项目创建一个 Java 类 view.beans.EnquiryBean,如下所示:
package view.beans;
import javax.faces.event.ActionEvent;
public class EnquiryBean {
String termsAndConditions = "unset";
public void setTermsAndConditions(String termsAndConditions) {
this.termsAndConditions = termsAndConditions;
}
public String getTermsAndConditions() {
return termsAndConditions;
}
// Intended for the "Accept" button for the terms and conditions
public void acceptTermsAndConditions(ActionEvent event) {
setTermsAndConditions("accepted");
}
// Intended for the "Reject" button for the terms and conditions
public void rejectTermsAndConditions(ActionEvent event) {
setTermsAndConditions("rejected");
}
}
注意,我们具有实例属性 termsAndConditions,因此对该类进行实例化后,该属性的值为“unset”,这表示用户尚未接受或拒绝条款和条件。在此之下,接下来的两个方法提供了用于读取和写入该属性的 getter 或 setter 访问器。
此外,我们提供了两个 ActionEvent 方法,映射到各自命令按钮的 actionListener 属性。acceptTermsAndConditions 按钮将 EnquiryBean termsAndConditions 属性设置为“accepted”,rejectTermsAndConditions 按钮将 termsAndConditions 属性设置为“rejected”。 在我们修改 commandButtons 以调用这两个方法之前,需要将 EnquiryBean 配置为 Oracle ADF Controller 的一个会话 bean。我们可以通过打开 Application Navigator 中的 ViewController adfc-config.xml 文件来完成此操作:
<af:commandButton text="Accept"
actionListener="#{enquiryBean.acceptTermsAndCondition}"/>
<af:spacer height="5" width="5"/>
<af:commandButton text="Reject"
actionListener="#{enquiryBean.rejectTermsAndConditions}"/>
按下 Accept 按钮以后,启用搜索字段和 Enquiry 按钮。要在按下 Accept 或 Reject 按钮后禁用这两个按钮,再次修改含有已禁用 EL 表达式的 commandButtons,如下所示:
<af:commandButton text="Accept"
actionListener="#{enquiryBean.acceptTermsAndConditions}"
disabled="#{enquiryBean.termsAndConditions != 'unset'}"/>
<af:spacer height="5" width="5"/>
<af:commandButton text="Reject"
actionListener="#{enquiryBean.rejectTermsAndConditions}"
disabled="#{enquiryBean.termsAndConditions != 'unset'}"/>
为禁用 enquiry 域,我们将以下已禁用 EL 表达式添加到每个域和 Enquiry 按钮:
<af:inputText value="#{bindings.pLodgedWith.inputValue}"
label="#{bindings.pLodgedWith.hints.label}"
required="#{bindings.pLodgedWith.hints.mandatory}"
columns="#{bindings.pLodgedWith.hints.displayWidth}"
maximumLength="#{bindings.pLodgedWith.hints.precision}"
shortDesc="#{bindings.pLodgedWith.hints.tooltip}"
rendered="true"
disabled="#{enquiryBean.termsAndConditions != 'accepted'}">
现在进入 Web 页面,我们看到 Accept 和 Reject 按钮已启用,但是 enquiry 域和 Enquiry 按钮已禁用:
添加发件人/收件人弹出列表。故事板显示我们还必须记录用户是包裹的发件人还是收件人。这将为我们提供使用我们系统的用户的有价值信息。 现在,可以将值“sender”和“recipient”硬编码到 Oracle ADF Faces RC 弹出列表绑定中,但是以我们的经验,我们知道老板经常在这之后对要求进行扩展。例如,当老板认识到我们的呼叫中心也将使用该应用程序时,肯定要添加值“call center”。理想情况下,相比对这些值进行硬编码,我们更倾向于从数据库表中获取这些值。在这个阶段,我们不能接触或更改数据库,但是在 Oracle JDeveloper 11g中,我们可以使用静态视图对象设置 Oracle ADF 业务组件结构以接受该数据(就好像它是来自数据库一样)。
在 Model 项目下,我们通过选择“Rows populated at design time (Static List)”选项创建了一个新的视图对象 EnquirySourceView。 获取该视图对象后,将通过应用程序模块将其公开,现在,我们的 ViewController 的 SearchPage 可以使用它。
为此,我们将手动创建自己的列表绑定。首先,我们切换到 SearchPage 的绑定页面,并按 Bindings 部分下的加号按钮: 绑定页面中的结果列表条目名为“Description”,这并不理想。我们将选择 Description 绑定,并通过 Property Inspector 将其重命名为“EnquirySourceList”:
<list IterBinding="EnquirySourceView1Iterator"
DTSupportsMRU="true"
StaticList="false"
ListIter="EnquirySourceView1Iterator"
id="EnquirySourceList"
NullValueFlag="start"
ListOperMode="navigation">
<AttrNames>
<Item Value="Description"/>
</AttrNames>
</list>
(注:Oracle JDeveloper 11g build 5188 在创建列表绑定时会产生一些错误,因此以上的 XML 值可能会有所不同。可以相应地调整本例中的条目)。
我们创建绑定后,在 Web 页面中从呈现 Insert Select One Choice 的 Component Palette 对话框中拖拽一个 Select One Choice,如下所示。我们希望从 EnquirySourceList 绑定中检索 Select One Choice 的值,因此我们使用以下 EL 表达式:
<af:selectOneChoice label="Who are you?">
<f:selectItems value="#{bindings.EnquirySourceList.items}"/>
</af:selectOneChoice>
运行我们的页面,我们将看到以下内容:
存储该值的最佳位置实际上是在 EnquiryBean 中。我们将添加以下实例变量和访问器:
String enquirySourceIndex;
public void setEnquirySourceIndex(String enquirySourceIndex) {
this.enquirySourceIndex = enquirySourceIndex;
}
public String getEnquirySourceIndex() {
return enquirySourceIndex;
}
然后,我们可以在 selectOneChoice 值属性中使用它:
<af:selectOneChoice
label="Who are you?"
value="#{enquiryBean.enquirySourceIndex}"
required="true"
disabled="#{enquiryBean.termsAndConditions != 'unset'}">
<f:selectItems value="#{bindings.EnquirySourceList.items}"/>
</af:selectOneChoice>
注意,我们还添加了 Disabled 属性以将该域设置为只读 — 当用户接受条款和条件后。此外,我们还通过所需属性将该域设置为必填域。
本节接下来的内容中,除了用户的 IP 地址和接受或拒绝条款和条件,我们还需要存储 Select One Choice 值。 由于我们刚刚创建了绑定,此操作将相对简单。在 EnquiryBean 中,我们将添加以下方法以通过绑定检索 Select One Choice 值,如下所示:
private String getEnquirySourceCode() {
DCBindingContainer dcBinding =
(DCBindingContainer)BindingContext.getCurrent().getCurrentBindingsEntry();
DCControlBinding listControlBinding =
dcBinding.findCtrlBinding("EnquirySourceList");
FacesCtrlListBinding listBinding = (FacesCtrlListBinding)listControlBinding;
Row row =
listBinding.getRowAtRangeIndex(Integer.valueOf(enquirySourceIndex));
String enquiryListCode = row.getAttribute("Code").toString();
return enquiryListCode;
}
注意我们如何首先在粗体显示的行中检索 EnquirySourceList 绑定,该绑定是映射到虚拟迭代器变量(我们已创建用来存储所选的 Select One Choice 值)的属性值绑定。默认情况下,Select One Choice 存储用户在弹出列表中选择的选项的索引值。因此,我们将提取 EnquirySourceList 绑定并使其在该行中检索索引值,然后检索该已检索行的 Code 属性值。
在 bean 中添加代码以捕获用户的 IP 地址。在这个阶段,我们已经在屏幕上添加了 Accept 和 Reject 按钮,还添加了发件人/收件人弹出列表。此外,我们在后台具有会话 bean,用其中的方法来处理用户按钮选择,最后,还添加了函数 getEnquirySourceCode() 来检索发件人/收件人弹出列表的值。 在 acceptTermsAndConditions 和 rejectTermsAndConditions 方法中,我们希望收集用户的详细信息,即用户 IP 地址。这可以在 EnquiryBean 会话 bean 中通过以下代码轻松完成:
public String getRemoteAddr() {
String remoteAddr =
((HttpServletRequest)FacesContext.getCurrentInstance(). (cont next line)
getExternalContext().getRequest()).getRemoteAddr();
return remoteAddr;
}
向数据库写入用户的 IP 地址和选择。最后这个任务实际上又回到了我们的第一个“应该具备”需求,即:记录用户的 IP 地址以及他们是接受还是拒绝了条款和条件(还有用户是发件人还是收件人)。在这个阶段,我们希望实施当前要求;其解决方案将与下一个要求结合。 与以上其他问题一样,解决此问题的最简单解决方法是将其分解为若干可解决的部分。该特定问题分为以下几个部分:
总结在本章中,我们添加了代码以满足我们应用程序的“应该具备”需求,包括记录用户的 IP 地址、用户是否接受我们的条款和条件以及增强搜索功能。在 下一章中,我们将添加功能来记录用户输入的所有搜索条件以及返回记录的数量。 |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||