在本篇中,我们将向你展示怎样使用mabon来创建一个简单而强有力的输入组件,它具有类似于google suggest所提供的内置的建议功能。为了使web开发者更为容易地使用我们的jdj inputsuggest组件,我们借助于weblets开源工程来把外部资源,例如图标和javascript库,绑定到一个java档案文件(jar)中―由它来描述我们的jsf组件绑定。
一、创建支持ajax的jsf htmlinputsuggest组件
这个jsf ajax输入建议方案共包括四个类,见图1。
图1.类图:构建输入建议组件所需要的类
这些类分别是:
•htmlinputsuggest―屏幕生成器特定的子类。
•htmlrenderer―这是一个超类,它提供一些便利的方法来实现资源编码。
•htmlinputsuggestrenderer―是你的新定制的屏幕生成器,它负责把标注生成到客户端屏幕上,包括需要的资源,例如javascript库和式样表等。
•htmlinputsuggesttag是标签处理器。
在我们的输入建议解决方案中,我们实现了一个javascript库―inputsuggest.js―它包含利用mabon从web开发者的支持bean中检索数据的功能。在本文中,我们将详细讨论inputsuggest.js文件和htmlinputsuggestrenderer―它们都受mabon的影响并且提供了输入域(这些输入域都具有输入探测(type-ahead)和建议列表功能)。
二、输入建议javascript库
既然我们使用mabon,因此不需要担心从支持bean中取回数据的问题。我们可以把这项任务交给mabon来完成。然而,我们关心的是,如何处理xmlhttprequest对象返回的数据,如何填充实际的建议列表以及如何处理用户交互。这个inputsuggest.js库中包含了大量的函数,用来处理键盘导航和鼠标交互。篇幅所限,在此我们将集中分析对该jsf htmlinputsuggest组件有重大影响的函数。
(一)dokeypress函数
显示于列表1中的dokeypress函数负责处理键击事件并检查是否用户按下了tab键。在正常情况下,这个tab键将移出输入域并激发blur事件。对于本文中的输入建议解决方案来说,一次tab键击也可以用于从建议列表中选择一个活动行。为此,我们需要跟踪tab键,从建议列表中选择一行,把值添加到输入域,或者,如果没有列表数据可用的话,离开该输入域。如果发生控件导航,那么将激活doblur()函数并关闭建议列表。
列表1―dokeypress函数
projsf.jdj.dokeypress = function(event){
var input = (event.srcelement || event.target);
var inputid = input.id;
var div = document.getelementbyid(inputid + "$suggest");
var divstyle = (div.currentstyle || div.style);
if (event.keycode == 9 && divstyle.display == "block")
{
div.style.display = "none";
var activerow = projsf.jdj._findactiverow(div);
input.value = activerow.innerhtml;
return false; //取消按tab键离开输入域
}
return true; //继续:按tab键离开输入域,它将调用doblur()
}
|
列表2―dokeyup函数
projsf.jdj.dokeyup = function(event){
var input = (event.srcelement || event.target);
var inputid = input.id;
var div = document.getelementbyid(inputid + "$suggest");
if (event.keycode == 9)//tab键
{ return false; }
else if ((div.style.display == "block" || div.childnodes.length > 0) &&
(event.keycode == 40 || event.keycode == 38))
{
if (div.style.display == "none")
{ div.style.display = "block"; }
else {
var activerow = projsf.jdj._findactiverow(div);
switch (event.keycode) {
case 40: /向下箭头
if (activerow.nextsibling)
{
activerow.classname = "htmlinputsuggestrow";
activerow = activerow.nextsibling;
activerow.classname = "htmlinputsuggestactiverow";
}
break;
case 38: /向上箭头
if (activerow.previoussibling)
{
activerow.classname = "htmlinputsuggestrow";
activerow = activerow.previoussibling;
activerow.classname = "htmlinputsuggestactiverow";
}
break;
}
input.value = activerow.innerhtml;
}
return false;
}
if (event.keycode != 8)//不是一个backspace键
{
input.blur();
input.focus();
}
if (input.value.length <= 2)
div.style.display = "none";
}
|
列表3―dochange函数
projsf.jdj.dochange = function(event,dosuggesturl){
var input = (event.srcelement || event.target);
var inputid = input.id;
var context = { _inputid: inputid };
net.java.dev.mabon.send({ url: dosuggesturl,
args: [input.value],
callback: function(result) {
projsf.jdj._callback.call(context,result);} });
return true;
}
|
列表4:_callback函数
projsf.jdj._callback = function(results){
var inputid = this._inputid;
var input = document.getelementbyid(inputid);
var div = document.getelementbyid(inputid + "$suggest");
if (results.length <= 1) {
div.style.display = "none";
return;
}
//从上下文中得到输入域id
var input = document.getelementbyid(inputid);
div.style.width = input.offsetwidth;
while (div.firstchild) {
div.removechild(div.firstchild);
}
for (var i=0; i < results.length; i++) {
var row = document.createelement("div");
var span = document.createelement("span");
var text = document.createtextnode(results[i]);
row.classname = "htmlinputsuggestrow";
row.appendchild(text);
row.onmouseover = new function("event",
"projsf.jdj._domouseover(event ||
window.event)");
row.onclick = new function("event",
"projsf.jdj._domouseclick(event ||
window.event)");
div.appendchild(row);
}
div.firstchild.classname = "htmlinputsuggestactiverow";
div.style.display = "block";
window.settimeout("projsf.jdj._selecttext('" + inputid + "', " +
"'" + input.value + "', " +
"'" + results[0] + "')",
200);
}
|
列表5:_selecttext函数
projsf.jdj._selecttext=function(inputid,initialvalue,suggestion){
var input = document.getelementbyid(inputid);
if (input.value != initialvalue)
return;
if (input.value == suggestion) return;
if (input.createtextrange)//ie特定的
{
var selectionstart = input.value.length;
input.value = suggestion;
var range = input.createtextrange();
range.movestart("character", selectionstart);
range.moveend("character", input.value.length);
range.select();
}
else //dom兼容的
{
var selectionstart = input.value.length;
input.value = suggestion;
input.selectionstart = selectionstart;
input.selectionend = input.value.length;
}
}
|
列表6.htmlinputsuggestrenderer的encodebegin()方法
package com.apress.projsf.jdj.render.html;
import ...//(省略)
/**
*htmlinputsuggestrenderer用自动建议的行为生成一个传统的htmlinputtext域
*.
*/
public class htmlinputsuggestrenderer extends htmlrenderer{
//...
public static string title_attr = "title";
public static string do_suggest_attr = "dosuggest";
public void encodebegin(
facescontext context,
uicomponent component) throws ioexception
{
writescriptresource(context,
"weblet://org.dojotoolkit.browserio/dojo.js");
writescriptresource(context,
"weblet://net.java.dev.mabon/mabon.js");
writescriptresource(context,
"weblet://com.apress.projsf.jdj/inputsuggest.js");
writestyleresource(context,
"weblet://com.apress.projsf.jdj/inputsuggest.css");
}
|
列表7.htmlinputsuggestrenderer的encodeend()方法
列表8.一个使用jsf httpinputsuggest组件的jsp页面
列表9:支持bean的value属性
package com.apress.projsf.jdj.application;
import java.util.arraylist;
import java.util.list;
/**
*backingbean是一个inputsuggest.jspx文档的支持bean。
*/
public class backingbean{
public void setvalue(object value) { _value = value; }
public object getvalue() { return _value; }
|
列表10.支持bean的dosuggest()方法
public string[] dosuggest(string initialvalue) {
list suggestions = new arraylist();
for (int i=0; i < _master_list.length; i++) {
if (_master_list[i].startswith(initialvalue))
suggestions.add(_master_list[i]);
}
return suggestions.toarray(new string[0]);
}
private object _value;
static private final string[] _master_list = new string[]
{
"pro jsf and ajax",
"pro ajax",
"pro jsp 2",
"pro jakarta",
"pro j2ee 1.4"
};
}
|
一个javascript工具函数。我们可以使用这种mabon协议来发送目标url和需要的任何参数,然后异步地从托管bean中接收数据。
(七)mabon和json
正如你所知,xmlhttprequest提供了两种响应类型―responsetext和responsexml―它们可以用于取回数据。你可能会问:我何时该使用哪一种响应类型?其实,这个问题的答案依赖于是否由你自己控制响应的语法。
responsexml类型返回一个完整的dom对象(它提供多种方式来遍历这棵dom树),从而允许你查找需要的信息并把所作变化应用到当前文档中。当你的组件有可能影响到周围的元素而且你不能控制响应时(例如,当你与一个web服务进行通讯时),这是相当有用的。
对于本例中的输入建议组件,你的确要控制响应并且你只想从你的组件中取回数据,而不是修改整个页面的dom结构。responsetext类型返回普通的文本,这允许你利用json语法用于响应。为了在组件中利用ajax技术,json是一种极其有用的数据交换格式,因为它可以轻易地使用eval()函数进行分析。
eval()函数仅使用一个参数(一个javascript代码字符串),并一次性分析和执行这个字符串而不是分析处理每一部分。这种方法要比任何其它类型的分析(例如xml dom分析方法)快得多。
这正是为什么mabon实现json的原因―你能够控制响应,而且json具有语法简单和分析速度快的特点。
(八)encodeend()方法
真正的工作是在encodeend()方法中完成的,见列表7。在encodeend()方法中,我们从htmlinputsuggest组件得到属性的map。这个组件的属性之一是dosuggest属性。通过这个属性,我们能够得到methodbinding(如果有的话),并且从这个methodbinding对象,我们能够得到实际的由web开发者所定义的methodbinding表达式(例如,#{backingbean.dosuggest})。然后,我们从表达式中修整#{}并且用类似mabon:/协议的语法来连接字符串的余下部分。最后,mabonviewhandler将识别这个字符串并返回一个资源url―它将被写向客户端(例如,/context-root/mabon-servlet-mapping/backingbean.dosuggest)。
三、使用输入建议组件
创建一套ajax方案并不是一项简单的任务,尽管有若干使得这类工作更为容易些的ajax工具包可用(例如dojo toolkit,www.dojotoolkit.org)。相比之下,jsf提供的是一种更为简单的编程模型和一种为大量开发者所熟悉的工具:jsp和java。为了完整地结束本文中所提供的ajax解决方案,让我们分析一下你如何在一个jsf应用程序中使用这个输入建议组件,由列表8所示。
这个页面包含一个htmlinputsuggest组件(),它把value属性设置为一个绑定表达式的值。这个表达式指向一个支持bean的value属性。dosuggest属性包含一个绑定表达式的方法―这个表达式指向在同一个支持bean上的dosuggest()方法。你可以在列表9中观察这个支持bean。
value属性仅是一个普通的javabean属性。但是,显示于列表10中的dosuggest()方法却值得引起你的注意。这个方法使用由用户输入的初始值,该值是从dochange()函数(见列表3)中经由mabon传递给它的。然后,dosuggest()方法根据用户在客户端输入的初始值返回一个经过过滤的数组。值得注意的是,返回的值遵循支持的json语法。
这个htmlinputsuggest组件的最后结果显示于图2中。
图2.在一个浏览器中生成htmlinputsuggest组件
四、结论
从本文中,我们希望你已经理解了如何使用mabon来实现你的jsf组件以支持基于ajax技术的数据回取,以及怎样通过利用weblets工程把你的jsf组件需要的外部资源打包到与你的java类相同的文档中。
最后,既然你已经知道怎样利用jsf和ajax技术创建可重用的丰富互联网应用程序,那么我们非常希望你能够利用你在本系列文章中所学的技术来创建自己的定制组件以构建丰富互联网应用程序(ria)。