级别: 中级 Adam Bermingham (adam_bermingham@ie.ibm.com), 软件工程师, IBM, Intel, Microsoft,HP
2007 年 6 月 04 日 现今的 Internet 应用程序需要用户具有越来越丰富的经验。当今十分流行并且成功的 Web 应用开发方法是使用 Ajax 样式模式设计 Web 应用。IBM Rational Application Developer 所包含的 Ajax 的特征之一是 Typeahead 组件。本文讲述了这一组件在不同领域的多功能性:服务器端动态结果过滤、CSS 定制和使用 JavaScript API 的 JavaScript 事件处理 。它使得 Web 开发者超越了大多数的使用案例,进入到更高级的 Typeahead 使用方法。
序言
 | |
请访问 Ajax 技术资源中心,这是有关 Ajax 编程模型信息的一站式中心,包括很多文档、教程、论坛、blog、wiki 和新闻。任何 Ajax 的新信息都能在这里找到。
|
|
IBM® Rational® Application Developer 中的 Enhanced JavaServer Faces™ (JSF™) Components 和 Standard JSF Components,提供了能够迅速开发丰富的、健壮的 Web 应用的高级功能。不但扩展了资源库,而且每个组件自身都是通用的、可重复使用的、(也许最重要的是)可自定义的。这种灵活性涉及到客户端的组件结构,和服务器端的业务逻辑。Enhanced Faces Typeahead 组件是一个很好的例子。本文将使用大量高级技术以讲述如何方便的定制与获得大部分 Typeahead 组件的输出。主要包括:
- 创建服务器端动态过滤器
- 使用 CSS 和标记属性改变外观
- 使用 Typeahead 事件和 JavaScript™ API
本文假设您已经熟悉了 Rational Application Developer 中的 JSF 组件工具。为了循序渐进地了解如何创建您的第一个 Typeahead 组件,请参阅资源 部分中的介绍性的文章。
实现一个服务器端的动态过滤器
Typeahead 组件最直接的使用案例包括一个简单的字典查询。例如,当您在具有下拉显示最流行搜索条的搜索框中输入字符时,最开始显示的都是用户输入过的字符(如图 1所示)。
图 1. 简单的 Typeahead
后台中发生的是:
- 在客户端,脚本(JavaScript编程语言)显示了已在输入栏中输入字符(发生了
keydown 事件)。
- Enhanced Faces JavaScript Library
backchannel 对象在后台将搜索词提交到服务器端。backchannel 封装了所有的功能和逻辑,包括了发送 XMLHttpRequest 到服务器端,或是通过通过隐藏 iFrame 提交信息。
- 在服务器端,响应是以 XML 的形式存在的。您能够在 JavaScript 浏览器控制台追踪
XMLHttpRequest 路径看到响应(和需求)。一个典型的 XML 响应(两种XML岛)的代码如列表1所示:
列表 1. 带有两条结果的 Typeahead XML 服务器端响应
<?xml version='1.0' encoding='ISO-8859-1'?>
<taresponse>
<suggestlist>
<suggestion>bd0772-Ja</suggestion>
<suggestion>ede651-Ja</suggestion>
</suggestlist>
</taresponse>
<url>/test2/myPage.faces?$$ajaxid=form1:typeahead1&$$ajaxmode=axpartial</url>
|
- 脚本中,
Callback 函数提供了 XMLHttpRequest。它作为响应中的参数被传递和调用。之后 Typeahead 脚本通过插入 HTML tables 和 divs 系统在 Document Object Model (DOM)中创建了推荐列表。
从用户的角度来看,下拉栏的应用是无缝地,且不需要重载页面。一旦弹出列表,您可以用您的鼠标或键盘从列表中选择选项。就像其他的标准下拉控件一样,实现了输入功能。
您常常需要引入更复杂的逻辑以管理后端搜索。以下的例子显示了过滤器如何应用于搜索过程中。
Bean 结构
本例中,结构如图 2所示。
图 2. 简单 bean 的结构
bean 包(typeaheadFilter.zip,可在 下载部分下载)结构被一分为二。bean 的数据结构在一边:
正如您所期望的,是您熟悉的模式:拥有许多客户的公司,握有大量订单。
另一边是搜索逻辑。绑定到 Typeahead 的 Map 是 SimpleSearch bean,它参照了 SampleFilter bean。过滤 bean 包括有关当前过滤器的状态信息。搜索 bean 能够获取过滤器 bean 的状态以决定在推荐列表中返回哪些结果。两边都需要被 Root bean 引用。
Root bean 创建了一个 Company 对象。Company 对象返回了 Customer 类型的四个对象:
- Wender's Washers
- Jackson's Juggernauts
- Pierre's Plumbing
- William's Web Development
每个 Customer 产生了30个 Order 类的对象。 对于每个 Order,ID String 都包含了一个16进制值(等于 0xf00000),后跟连字号,后跟客户名字的前两个字母(例如,ede651-Ja 或 bd0772-Ja)。虽然这些数字不保证唯一,但对于演示的目的已经足够了。这些是 Typeahead 会搜索的代码。
编写代码逻辑
扩展了 java.util.AbstractMap 类(本例中,如列表 2所示,SimpleSearch bean)的 bean 封装了 Typeahead 的搜索逻辑。 Typeahead 调用 Map bean 的 get 方法。 get 方法会返回下列之一:
- 包含了 Strings 的 ArrayList containing
- 简单的 String (在单个结果的情况下返回)
-
null
列表 2. SimpleSearch bean 中的搜索逻辑
public Object get(Object key) {
Customer[] customers = filter.root.company.getCustomers();
ArrayList matches = new ArrayList();
int len=0;
String s = (String)key;
System.out.println("in search, key: " + key);
for(int i=0; i<customers.length; i++)
{
// the following line filters out customers which don't match our filter
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
if (!customers[i].CustomerName.equalsIgnoreCase(filter.getFilter())) continue;
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
System.out.println("using customer: " + customers[i].CustomerName);
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
// if it gets to here we have a
Order[] orders = customers[i].getOrders();
for (int j=0; j<orders.length; j++){
String record = (String) orders[j].getOrderID();
if (record.length() > s.length()) {
String subrecord = record.substring(0,s.length());
if (subrecord.equalsIgnoreCase(s)==true) {
System.out.println("adding record to matches: " + record);
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
matches.add(len++,record);
}
}
}
}
return matches;
}
|
列表 2中,注意两条粗线,它改变了所有搜索顺序,唯一搜索的是 Customer Name 匹配过滤器的字符。因此,如果将过滤器设为 "Pierre's Plumbing",就会仅仅搜索相关联的客户订单。这是一个很简单的脚本,但是您可以应用相同的技术,例如:
- 在一定范围的日期中获取所有客户订单
- 基于环境变量的过滤
在 Rational Application Developer 中创建简单的验证应用
您可以在 Rational Application Developer 中创建显示以上过滤行为的应用。为创建一个新的应用并且导入 beans,执行如下步骤。
-
创建一个新的支持 Faces 的 Dynamic Web Project ,目标是您的开发环境中的服务器,如图 3 所示。 Typeahead 支持的服务器是 IBM® WebSphere® Application Server (V6.x or later), IBM® WebSphere® Portal Server (V5.1 or later),和 Apache Tomcat V5.5。
图 3. New Dynamic Web Project 对话框
-
为项目导入 beans。右击项目,并选择 Import > Archive File,然后进入储存 typeaheadFilter.zip 文件的文件夹。确保选择将文件导入项目中的源文件夹(默认是"src"),如图 4所示。
图 4. 导入 JavaBean 包
下一步,您需要创建 JSP™(JavaServer Pages™)页面,然后创建页面数据。
-
右击文件夹并且选择 New > Web Page,在 WebContent 文件夹中创建一个新的 Web 页面,如图 5所示。
图 5. 创建新的 JSP
为页面输入名称并且选择 Finish。
-
在 Page Data 视图中,创建一个新的 Faces Managed Bean。在 Add JavaBean 对话框中的 Class 部分,输入
com.ibm.devworks.typeaheadfilter.Root,如图 6所示。您可以使用右边的 helper 按钮快速实现。确保将 Scope设置为 session。
图 6. 添加一个 Faces Managed Bean, "Root"
现在,您需要为数据输入和输出增加一些组件。
-
从 Page Data 视图将过滤类的
filter 拖至页面,如图 7所示。从对话框中选择 Inputting data 。
图 7. 为 Filter 添加控制
-
从 Enhanced Faces Components 中(位于右面)将输入栏拖入页面中。(如果框架不存在,您需要从 Palette Customization 对话框显示它。)这一输入栏将成为您的 Typeahead 控件。本例(图 8)有一张表格和文本以识别栏并整齐的布置。
-
虽然这不是必需的,但是您可以从 Page Data 视图将 Company 结点拖至页面,用以显示网状 DataTable 中的数据内容。这可以在开发阶段帮助可视化数据。
图 8. Design 视图中使用了控件的 JSP
这种情况下您需要用 Typeahead 实现输入栏。
-
在输入栏的 Behavior 标签( Properties 视图中),选择 Enable typeahead option 检验栏实现 Typeahead 功能。注意在 Source 视图中您可以看到 hx:inputHelperTypeahead 标签已被加入 h:inputText 子标签。
-
在 Design 标签,从 Page Data 视图将搜索对象拖至紧邻输入栏的 Typeahead 帮助按钮,如图 9 所示。(注意:如果您需要提供包括 Map 的类型,可以使用 java.lang.Object)。
图 9. 拖拽搜索对象以绑定 Typeahead
现在可以在服务器上运行程序了,运行时如与 图 9类似。输入不同的客户名称并点击 Submit,您会发现(图 10) Typeahead 的结果会根据过滤器当前设置的 Customer 改变。您可以立即应用。
如果您投入更多的时间,您可以开发出更复杂的过滤器。逻辑上的下一步也是实现 Filter (Customer 输入) 。这对于具有大量客户十分有用。
图 10. 运行于服务器上 (注意仅仅返回 Wender 的 Orders )
本例使用 JavaBeans 技术,因为它十分简单。但是,您可以使用任意终端作为数据存储。这非常有用。例如,您可以为任务裁减 SQL 查询,并且使用 JDBC (Java™ Database Connectivity API) 连接数据库。
事件处理
Typeahead 的事件是 inProgress、oncomplete、onstart 和 onerror。 事件处理是 Typeahead 的 Quick Edit 视图所指定的。您可以利用 JavaScript 将它们连在一起。这时,关注于为 oncomplete事件增加一个 JavaScript 事件句柄。
 |
inProgress 和 onerror
Rational Application Developer v7.0 不支持 inProgress 和 onerror 事件,但是不久的更新版本中将会加以支持。看看这一部分!
|
|
一个简单的句柄
您需要使用标签将功能联系起来。您可以在 Quick Edit 视图中实现,如下步骤所示。
-
在视图左部分选择 oncomplete 。
-
在右部分,输入提示(
您处于 oncomplete 事件句柄中)。
列表 4中划线的代码应产生在 Source 视图。
列表 4. 一个简单的事件句柄
<script type="text/javascript">
function func_1(thisObj, thisEvent) {
//use 'thisObj' to refer directly to this component instead of keyword 'this'
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
//use 'thisEvent' to refer to the event generated instead of keyword 'event'
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
alert("You are in the oncomplete event handler");
}
</script>
.
.
.
<h:inputText id="text1" styleClass="inputText">
<hx:inputHelperTypeahead id="typeahead1" styleClass="inputText_Typeahead"
value="#{myRoot.filter.search}" oncomplete="return func_1(this, event);">
</hx:inputHelperTypeahead>
</h:inputText>
|
您可以在服务器端运行页面快速验证这些工作。当客户端收到推荐列表时,将会显示一个弹出信息框。在产生的评述中提到的 thisObj 参考了 Typeahead 关联的 DOM 输入栏。这对于获取维度、内容、id 等等十分有用。
Typeahead Behavior 对象
但是,更有用的是得到 JavaScript Typeahead Behavior 对象。Behavior 是 Extended Faces JavaScript 库中的概念,其功能可与 DOM 对象、事件和 Behavior 相关。这种情况下, DOM 对象是输入栏,事件是 onkeydown,行为类型是 typeahead,结果与列表 5 所示的代码相似。
列表 5. 得到 Typeahead 行为对象
var myTypeahead = hX_5.getBehaviorById(thisObj.id,"typeahead","onkeydown");
|
与对象相联允许您通过索引获取推荐字符。列表 6 展示了一个简单的句柄,它重复了推荐过程,添加到字符,之后提醒用户(图 11)。
列表 6. 获取、显示来自 Typeahead 的推荐列表的句柄
<script type="text/javascript">
function func_1(thisObj, thisEvent) {
var myTypeahead = hX_5.getBehaviorById(thisObj.id,"typeahead","onkeydown");
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
var i, s = "The suggestions are: \n";
for(i=0; i<myTypeahead.suggestionCells.length; i++)
{
s += (i+1) + ". " + myTypeahead.getSuggestion(i) + "\n";
}
alert(s);
}
</script>
|
图 11. Event 中访问推荐内容
用户所输入的内容
另一件事就是从输入栏中获取文本。在返回推荐列表后,会将第一个结果插入输入栏中。如果它与所输入的字符不同,输入栏的文本将会变为第一个推荐,在自动完成过程中高亮显示添加到输入栏中的推荐部分。 Typeahead 对象存储了用户输入的被称为 typedLength的字符长度。因此,您可以用代码从 Typeahead 中很容易的获取输入字符,如列表 7所示。
列表 7. 从 Typeahead Behavior 获取用户输入字符
var myTypeahead = hX_5.getBehaviorById(thisObj.id,"typeahead","onkeydown");
var typedSoFar = thisObj.value.substring(0,myTypeahead.typedLength);
|
改变外观
封装在 Rational Application Developer 中的 Enhanced JSF Components 最重要的特点之一就是它的高级自定义能力。这个部分中,您将学习到如何利用 Rational Application Developer 工具通过 CSS 和标签属性有效使用自定义能力。本文将会考察 Typeahead 三点不同的关系,并演示您如何为每个关系改变它的外观。包括了 JSP 代码片断和相应的 CSS 风格(可用的)。
非插入的下拉栏
Web 应用的 GUI (图形化用户接口)中的下拉栏常常是插入性的。 尤其是在您很可能不需要它的地方。大部分这样的脚本中, Typeahead 一般没用。例如,假设您正在输入您的名。一般来说不会有任何问题,因为名一般少于八个字符。当您不确定拼写的时候,您可以从 Typeahead 中获得帮助。之后,您就可以输入开始的几个字母并从列表中选择。另一个相似的情况为,Web 开发者不想使页面信息变得模糊,尤其是用户输入依赖于显示全部页面内容的情况。
一种减少 Typeahead 插入的方法是减少下拉栏的尺寸。有两种方法可以实现。第一,出现在下拉栏中的行数。这可以在 hx:inputHelperTypeahead 的 Properties 标签很容易的实现,只要在 Height of Suggestion Area中输入一个数字。您会注意到,还存在一个 Maximum number of suggestions displayed 区域(图 12)。这是用来限制从服务器端返回的结果数目。
发送 XML 结果列表的代码(列表 8)仅包括 n 个结果, n是返回的最大值。如果您设置的推荐列表高度数少于返回的数目,将会在下拉栏中自动显示垂直滚动条。以这种方式,不需要改变结果集的尺寸。
图 12. 限制下拉栏高度
列表 8. 结果尺寸的属性
<h:inputText id="text1" styleClass="inputText">
<hx:inputHelperTypeahead id="typeahead1" styleClass="inputText_Typeahead"
value="#{myRoot.filter.search}" oncomplete="return func_1(this, event);"
size="2" maxSuggestions="100"></hx:inputHelperTypeahead>
</h:inputText>
|
第二种方式是通过激活 autocomplete 模式完全忽略 Typeahead 下拉栏 。当启动了 autocomplete 模式,不会在页面上显示下拉栏,但是您的输入自动变为来自服务器端的第一个结果。这种方式下, Typeahead 控件不会占用额外的屏幕空间。这对于结果集很小的情况十分有用,有时您会需要有多种选择。
例如,您正从联系人列表中输入名字。一般来说,输入第一个名字的姓的前两个字母就会出现唯一结果。不必折中从结果集中选择的功能,但是,您也可以利用键盘的方位键循环选择结果集中的内容。
第三种减少插入性的方式是引入透明的下拉栏。如果下拉栏降低了不透明度,您的注意力就不会从输入栏转移。如果下拉栏足够透明,您仍然可以看到页面信息,在输入栏输入信息。图 13显示了这种例子。
图 13. 透明的 Typeahead
列表 9 显示了用以产生下拉区域透明效果的 Typeahead 类的 CSS 风格。适合于透明度的属性存在于 bold中。
列表 9. CSS中的透明度
.inputText_TypeAhead {
background-color: #ccddff;
border-width: 1px;
border-style: solid;
border-color: ThreeDDarkShadow;
width:300px;
-moz-opacity: .7;
opacity: .7
}
.inputText_TypeAhead-List {
background-color: transparent;
text-align: left;
vertical-align: middle;
height: auto;
font-family: sans-serif;
font-weight: 400;
font-size: 10pt;
border-collapse: collapse
}
.inputText_TypeAhead-Item {
background-color: transparent;
color: WindowText;
padding-left: 1pt;
padding-right: 1pt
}
|
使用长推荐列表的显示长度建议
如果您在输入栏中输入长字符往往是十分不方面的(例如,您需要输入很长的化合物名称或地址)。 Typeahead 可以在输入部分字符之后通过提供推荐列表的方式帮助您。对于平均长度为150个字符的数据集来说,这种功能是十分有效的,仅需前 <10 个字符就可以进行区分。
为了实现预定效果,有必要改变 Typeahead 的风格。虽然 Typeahead 能够处理限制文本,这是不需要的,尤其是当推荐限制多于一次的时候。取而代之的是,你可以通过 CSS 增加推荐区域的宽度 。默认情况下,推荐区域的宽度设为i输入栏的宽度。即使您 需要为长字符设置长输入栏,但是过长的输入栏不利于整齐或紧凑的设计。保证下拉栏不截断输入栏宽度的方式是在 hx:inputHelperTypeahead Properties 视图中清除 Match width of the suggestion area with the input field 检验框。在 Typeahead 标签设置 matchWidth=false 。
图 14. Matchwidth 检验框
下一步,您需要在 CSS 中指定宽度,使得 GUI 与图 15 相似。您可以使用任意 CSS 可接受的单位指定宽度。例如,y 设为400px。在基础 CSS 类中的 CSS width 属性是唯一一个需要改变的,如列表 10所示。
列表 10. CSS 中扩展宽度
.inputText_TypeAhead {
background-color: #ccddff;
border-width: 1px;
border-style: solid;
border-color: ThreeDDarkShadow;
width: 400px;
-moz-opacity: .7;
opacity: .7
}
}
|
图 15. 宽的下拉栏
现在您可以自由的控制 Typeahead 的宽度,或完全(通过指定准确的宽度)或相对的包含元素(通过指定百分比)。
新字符
对于输入不知道的字符,或是难于输入的字符(例如长的包括字母和数字的字符)是比较棘手的。例如,这种字符可以是部分数字或 ID 号。这种情况下,最好在输入过程中尽早取消推荐列表需求。视图中有两栏用以控制取消推荐列表的 Ajax 需求的情况:
- 一种是在您输入至少一种指定字符后取消延迟
- 一种是控制从最后一个用户的键盘按下事件后所过去的总时间
这些属性---startCharacters 和startDelay (如图 16所示)---的默认值为一个字符,500 毫秒。对于未知字符的情况,有必要将时间延迟减少为100ms。
图 16. 延迟属性
 |
减少延迟时间的警示 将延迟时间降低为0或接近零是不可预知的,因为键盘按下事件和 Ajax 需求是一种竞争关系。有可能使用先前的输入栏的值而取消了服务器需求。 |
|
但是,如同所有的 Ajax,这样极大的增加了服务器的流量。建议您有效结合两种属性以优化性能和 Typeahead 可用性间的平衡关系。反之,当您十分熟悉所输入的字符时,根据同样的原因增大属性能够带来更多好处。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| Sample beans for Typeahead filtering scenario | typeaheadFilter.zip | 4 KB | HTTP |
|---|
参考资料 学习
获得产品和技术
讨论
关于作者  | 
|  | 从 2004 年 10月开始,Adam Bermingham 工作在 IBM Dublin Software Lab,成为了一名软件工程师。他毕业于 Dublin 的 Trinity College,拥有计算机 B.A. (Mod) ,并且是一名 Sun 认证的 Java 程序员。他一直从事于 IBM 的 Enhanced JavaServer Faces Components 和 IBM LanguageWare 的运行时开发工作。 |
对本文的评价
|