VWP+JPA+AJAX实现自动完成(autocomplete)文本框
Netbeans6.0里面的Visual Web Pack是一个基于JSF的可视化网页开发工具包,VWP使用的是Woodstock的JSF组件,这些组件使用起来非常方便,但通过大量的实践项目表 明,在VWP里面使用AJAX技术并不容易。虽然DynamicFaces组件库提供了AJAX Zone和AJAX Transaction 的组件,但是我发现,在Netbeans 6.0正式版以后的版本里面都无法正常使用这两个组件,原因是这些组件是基于一个旧版本的DOJO,而Netbeans6.0里面使用的是新版本的 DOJO,因此不再兼容。jMaki框架支持JSF,但Dojo的有些组件的某些用 于绑定manageBean的一些属性却不能再使用,例如dojo.combobox不再支持selected属性,使得JSF里面使用combobox 非常困难。在希望兼容性问题快点得到解决的同时,本文尝试使用另外一种方案来实现JSF,特别是VWP里面的AJAX。本文将实现一个 autocomplete文本框,其中数据通过JPA从数据库中获取,用户可以在文本框里输入某些关键字,从而快速地找出自己希望得到的结果。
软件需求:
1、JDK1.5以上
2、Netbeans 6.0以上包含JavaEE支持及glassfish v2的版本
实验步骤:
1、建立数据库:在Netbeans里面的"服务"窗口中,展开"数据库"-"jdbc:derby://localhost:1527/sample"节点,右键单击,选择"执行命令...",执行以下命令:
create table APP.PLACE(id int not null primary key, keyword varchar(100), name varchar(100));
insert into APP.PLACE values(1,'SCUT','South China University of Technology');
insert into APP.PLACE values(2,'GDUT','Guangdong University of Technology');
insert into APP.PLACE values(3,'SCNU','South China Normal University');
insert into APP.PLACE values(4,'SCAU','South China Agricultural University');
insert into APP.PLACE values(5,'GXU','Guangxi University');
insert into APP.PLACE values(6,'GZU','Guangzhou University');
insert into APP.PLACE values(7,'RUC','Renmin University of China');
insert into APP.PLACE values(8,'FDU','Fudan University');
insert into APP.PLACE values(9,'FZU','Fuzhou University');
insert into APP.PLACE values(10,'GDUB','Guangdong University of Business Studies');
2、建立Web项目,命名为ComboboxDemoJSF,选择框架的时候注意选择Visual Web JSF。
3、 建立持久化单元,具体步骤请参照上一篇"在jMaki中对表格进行查询和删除"。在选择Table的前,请选择PLACE,并把实体类的包定义为 entities(可事先在源包中建立)。建立完毕后,在项目窗口的"配置文件"节点中点击persistence.xml,去掉"在 ComboBoxDemoJSF模块中包括所有实体类"前面的勾,把entities.Place添加到"包括实体类"当中。
4、在Place.java中添加NamedQuery:
@NamedQuery(name = "Place.findAll", query = "SELECT p FROM Place p")
5、本Demo将使用一个文本框和一个列表框模拟Combobox,由于我们要实现列表框值动态改变而不刷新页面,因此需要自定义一个列表框选项的类FilteredOptions来替代列表框默认使用的com.sun.webui.jsf.model.DefaultOptionsList。以下是FilteredOptions.java的代码(请在comboboxdemojsf包中建立):
package comboboxdemojsf;
import com.sun.webui.jsf.model.Option;
import com.sun.webui.jsf.model.OptionsList;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class FilteredOptions extends OptionsList {
Option[] options = new Option[0]; //returned filtered options, built from props
HashMap<String, String> map; //loaded props
public FilteredOptions(String bundleName) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("ComboBoxDemoJSFPU");
EntityManager em = emf.createEntityManager();
List<entities.Place> list = em.createNamedQuery("Place.findAll").getResultList();
map = new HashMap<String, String>();
try {
for (entities.Place object : list) {
final String key = object.getKeyword();
final String name = object.getName();
map.put(key, name);
}
} catch (Exception e) {
e.printStackTrace();
}
filter(null);//initial data creation
}
public boolean filter(String filter) {
if (filter != null) {
filter = filter.toLowerCase();
}
//create new collection
List filteredValues = new ArrayList();
for (Object o : map.keySet()) {
try {
String key = (String) o;
String value = new String(((String) map.get(key)).getBytes("utf-8"), "utf-8");
if (value != null && (filter == null || filter.length() == 0 || key.toLowerCase().indexOf(filter) >= 0)) {
filteredValues.add(value);
}
} catch (UnsupportedEncodingException ex) {
Logger.getLogger(FilteredOptions.class.getName()).log(Level.SEVERE, null, ex);
}
}
Collections.sort(filteredValues);
//create new option list
options = new Option[filteredValues.size()];
int counter = 0;
for (Object o : filteredValues) {
options[counter++] = new Option((String) o);
}
return true;
}
public Option[] getOptions() {
return options;
}
private void next() {
throw new UnsupportedOperationException("Not yet implemented");
}
}
这个类使用一个HashMap来存储 "关键字-值" 对,例如SCUT对应South China University of Technology。其中,构造函数首先从持久化单元ComboBoxDemoJSFPU中取出所有Place对象,然后把keyword和name属性对应到HashMap里面。
另外,filter是处理列表框值的方法,其中的参数会接受用户输入的关键字,然后通过判断关键字,取出相应的名字值列表,并重新构造options属性(该属性覆盖父类OptionsList的options属性),这样,在JSF页面中就可以通过绑定这个属性来实现动态地改变列表框的值了。
6、设计页面:在设计页面前,先生成项目,因为FilteredOptions需要先编译好,设计页面的时候使用该类才能正常显示页面设计。打开Page1的java选项卡,在类Page1中添加以下代码:
private String filter = "";
public void setFilter(String value) {
this.filter = value;
listbox1DefaultOptions.filter(filter);
}
public String getFilter() {
return filter;
}
在页面中,我们将把这个属性绑定到文本框的text属性中,在JSF生命周期中执行set方法时,将会获取用户输入值,然后调用filter方法,改变列表框的值,而在执行get方法时,将会将用户在列表框里选择的值填充到文本框中。
7、从组件面板中拖一个文本框和一个列表框到Page1的页面设计窗口中,将文本框的id改为tf, 打开jsp选项卡,将<webuijsf:textField>和<webuijsf:listbox>标签用以下代码替换:
<webuijsf:textField binding="#{Page1.tf}" columns="47" id="tf" onKeyUp="document.getElementById('form1:listbox1').refresh('form1:tf');"
style="position: absolute; left: 144px; top: 93px;
width: 120px; height: 24px" text="#{Page1.filter}"/>
<webuijsf:listbox binding="#{Page1.listbox1}" id="listbox1" items="#{Page1.listbox1DefaultOptions.options}" onChange="document.getElementById('form1:tf').setProps( {value: document.getElementById('form1:listbox1').getSelectedValue() } );"
rows="10" style="position: absolute; left: 144px; top: 112px; width: 120px; height: 48px"/>
注意红色部分的两个客户端javascript事件,这两个事件将异步处理数据。
其中文本框的onKeyUp事件在用户每输一个字符的时候被触发,refresh(id1,id2...)方法做这两件事情:
1.异步提交id1,id2,...的值(AJAX方式提交),在这里,将会提交文本框的值;
2.刷新调用这个方法的对象的值,在这里,将会异步刷新document.getElementById('form1:listbox1')的值(AJAX方式)。
而列表框的onChange事件在用户改变选择的时候被触发,setProps方法可以设置所有组件的值(可以设计多个组件的值),在这里,设置的是文本框的值。而getSelectedValue方法可以获取用户在列表框里选择的值。
另外,注意到文本框的text属性绑定到filter属性,而列表框的items属性绑定到listbox1DefaultOptions属性。
8、改变 listbox1DefaultOptions的类型:打开java选项卡,展开"Managed Component Definition"节点,找到listbox1DefaultOptions的定义代码,用以下代码覆盖:
private FilteredOptions listbox1DefaultOptions = new FilteredOptions(SOURCE);
public FilteredOptions getListbox1DefaultOptions() {
return listbox1DefaultOptions;
}
public void setListbox1DefaultOptions(FilteredOptions dol) {
this.listbox1DefaultOptions = dol;
}
注意:通过jsp代码修改属性绑定可能会出错,如果出错,请在设计窗口或导航窗口右击组件,选择"属性绑定",然后进行设置绑定。
9、部署项目,运行,结果如下:
参考资料:
1.Dmitry Kushner的博客:
Creating autocomplete entry field with Woodstock
http://blogs.sun.com/dmitry/entry/creating_autocomplete_entry_field_with
2.Netbeans wiki: http://wiki.netbeans.org/VwAjaxSupport
Author:Yuanxin Li projectair@163.com, Yuan-Xin.Li@sun.com
发表于 Sun Functional 校园大使 [NetBeans] ( 五月 08, 2008 10:24 上午 ) Permalink | 评论[0]
