在本篇中,我们将向你展示怎样使用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()方法
public void encodeEnd(
FacesContext context,
UIComponent component) throws IOException
{
String valueString = _getValueAsString(context, component);
String clientId = component.getClientId(context);
Map attrs = component.getAttributes();
String title = (String)attrs.get(TITLE_ATTR);
String onchange = (String)attrs.get(ONCHANGE_ATTR);
MethodBinding doSuggest = (MethodBinding)attrs.get(DO_SUGGEST_ATTR);
ResponseWriter out = context.getResponseWriter();
out.startElement("div", component);
if (title != null)
out.writeAttribute("title", title, TITLE_ATTR);
// value="[converted-value]" onchange="[onchange]" />
out.startElement("input", component);
out.writeAttribute("id", clientId, null);
out.writeAttribut