之前曾有读者提到如何根据数据库码表生成下拉列表,当时我们推荐使用jquery.ajaxComboBox。但遗憾的是它的服务器端代码是PHP,日前我们已经将其迁移到了Grails。本来是打算把它做成Grails插件分发出来的。可在移植的过程中发现其接口设计带有明显的数据库特征,不太符合Grails的DomainClass风格,于是就打消了这个念头,仅止于完成一个TagLib和一个演示例程。本文将介绍例程的实现和使用,以供大家参考。本例程中使用两个Domain Class作为示例:Role,User,二者是一对多的关系。
例程很简单,就是演示了移植后jquery.ajaxComboBox的基本使用。跟其官网的例子不同,传入的不是数据库名,而是DomainClass。这个ComboBox有以下特点:
- 可设置Domain Class以及Domain Class中的字段;
- 如果查询结果相同,可使用子查询来辅助区别;
- 对查询结果进行排序设置;
- 对查询结果进行分页显示,并可自行设置每页显示的记录个数以及页码个数;
- 可设置初始值;
- 使用组ComboBox。
例程组成如下:
- jquery-1.4.2.min.js:JQuery JS文件;
- jquery.ajaxComboBox.3.5.min.js:jquery.ajaxComboBox的JS文件;
- jquery.ajaxComboBox.css:jquery.ajaxComboBox的CSS文件;
- jquery.ajaxComboBox自带的若干图片;
- jqac.DomainController.groovy:根据参数执行查询,返回结果。
- jqac.JqacTagLib.groovy:自定义的标签库,负责生成ComboBox和显示查询数据。
上述组成的前四项,除了对jquery.ajaxComboBox.3.5.min.js做了Ajax Post路径以及图片路径的更改外,其他均采用了原作者提供的文件。
首先看看TagLib:名字空间为jqac,其属性名主要来源于PHP接受的参数,但名字略有变化。对于无法通过原来参数传入的参数,使用了URL查询字符串的方式传入。部分代码如下:
static namespace= "jqac"
def domain= { attrs, body ->
def jsJQAC= """
jQuery(document).ready(function(\$){
\$('#${attrs.id}').ajaxComboBox(
${createURL(attrs.domain, attrs.subset)},
{
'lang' : 'en', //提示信息的语言,可选择en(英文),ja(日文),es(西班牙文)
'sub_info' : ${attrs.subInfo?:false},
'sub_as' : ${mapSubSetToSubAs(attrs.subset)},
'show_field' :"${attrs.showField?:''}",
'hide_field' :"${attrs.hideField?:''}",
'per_page' : ${attrs.pageSize?:10},
'navi_num' : ${attrs.naviNum?:5},
'navi_simple' : ${attrs.naviNum?:true},
'primary_key':"${attrs.primaryKey?:'id'}",
'field':"${attrs.field?:'name'}",
'order_field':"${attrs.orderField?:'name'}",
'order_by':"${attrs.orderBy?:'ASC'}",
'package' : ${attrs.package?:false},
'mini' : ${attrs.mini?:false},
'select_only':${attrs.selectOnly?:false},
'init_val' : [${attrs.initVal}],
'db_table':'${attrs.domain}'
}
);
});
"""
out << "<div id='$attrs.id' style='$attrs.style'></div>"
out << "<script type='text/javascript'>"<< jsJQAC << "</script>"
}
......
注意上述代码中的db_table参数,前面已经说过jquery.ajaxComboBox是直接面向数据库的设计。这里向标签使用者隐藏了这个参数,改而用domain属性。同时,在Controller里面也不是直接去拼凑SQL,而是借用了GORM的基础设施。关于代码中的其他参数,请参见后面的用法介绍。
在GSP中可以这样使用这个TagLib:
<jqac:domain id="role-id" style="width:260px;" domain="jqac.Role"/>
上述代码会生成一个id为role-id_1的input框,单击右边的,效果如下:

下面看看数据的打包——jqac.DomainController.groovy,如下是部分代码:
def q_word = params.q_word //input框中输入的内容;
def page_num =params.page_num //每页显示的记录个数,缺省值为10;
def per_page =params.per_page //每页显示的页码个数,缺省值为5;
def field = params.field //查询字段;
def order_field = params.order_field //排序字段,缺省值为name;
def order_by =params.order_by //排序策略,缺省值为ASC;
def domainname = params.domain //要查询的Domain Class;
def fieldInBF = field[0].toUpperCase() + field[1..-1]
//注入grailsApplication,使用其getClassForName方法获得Domain Class
def domain= grailsApplication.getClassForName(domainname)
//注意这里的技巧
def domains= domain."findAllBy${fieldInBF}Like"("%$q_word%",
[max: per_page,
offset:offset,
sort:order_field,
order:order_by])
def result=[:] //result是存放结果的Map;
domains.each{
result[..]=...
....
}
render result as JSON
效果图如下:

有的时候需要为ComboBox赋初始值,这个初始值可以是任意的字符串或者从Domcain Class中选择出来的。对于这样的需求,采用下述代码实现:
def results=[] //存放结果的List
def domainname = params.db_table //Domain Class
def q_word = params.q_word //查询条件
def field = params.field //初始值要显示的字段,缺省值为name
def primary_key = params.primary_key //查询字段,缺省值为id
def fieldInBF = primary_key[0].toUpperCase() + primary_key[1..-1]
def domain= grailsApplication.getClassForName(domainname)
if(!q_word.equals('') && !q_word.equals('null')){
def domains
if(primary_key.equals('id'))
domains= domain."findBy${fieldInBF}"(Long.parseLong(q_word))
else
domains= domain."findBy${fieldInBF}"(q_word)
domains.each{
results<<it."$field"
}
}
render results as JSON
好了,至此您应该已经了解这个小工具了,下面说明若干用法。
使用分页参数
对于大规模的数据,如果不分页,那势必得等到数据加载完全之后,界面才能有响应。这不仅对于用户体验来讲是件糟糕的事情,而且浪费了内存和带宽。这个jQuery的插件对此提供了分页支持。这也是我们推荐它的主要原因。分页涉及如下两个参数:
- pageSize:每页显示的记录数,缺省值为10;
- naviNum:显示的页码数,缺省值为5。
用法:
<jqac:domain id="role" style="width:260px" domain="jqac.Role" pageSize="2" naviNum="4" />
效果如下:

查询结果排序
排序涉及如下两个参数:
- orderField:排序字段,缺省为name字段;
- orderBy:排序属性,缺省为ASC。
用法:
<jqac:domain id="role" style="width:260px" domain="jqac.Role" orderField="id" orderBy="DESC" />
效果如下:

显示附加信息
如果ComboBox中显示的是某个部门的人员名称,出现同名的情况该如何区分同名的人员呢?此时可以使用附加信息加以区别。附加信息及如下参数:
- subInfo:将其值设置为"true",缺省会显示id以及name的附加信息,必须;
- subSet:设置需要显示的附加信息以及字段名的显示;
- showField:设置附加信息要显示的字段,如果字段为多个,可用逗号分隔,与subSet一同使用;
- hideField:设置附加信息要隐藏的字段,如果字段为多个,可用逗号分隔,与subSet一同使用;
用法组合可以是:subInfo、subInfo + subSet、subInfo + subSet + showField、subInfo + subSet + hideField、subInfo + subSet + showField + hideField。
如下是一种用法示例:
<jqac:domain id="subinfo" style="width:260px" domain="jqac.Role"
subInfo="true"
subset="[id:'ID', name:'Name', version:'Version']"
/>
效果如下:

ComboBox组
要使用ComboBox组,将package设置为true即可,缺省值为false,即不使用ComboBox组。
用法示例:
<jqac:domain id="role-id" style="width:260px" domain="jqac.Role" package="true" />
这里需要注意,生成的inpu框的id会根据ComboBox的个数来递增,形如:role-id_1,role-id_2...
效果如下:

初始化
有的时候需要给input框设置初始值,这个时候可以使用initVal参数,解释如下:
- initVal:input的初始值,可以是任意的字符串,也可以是Domain Class的某个字段的值,如果使用了package=true,initVal则可以设置为用逗号分隔的多个值;
- selectOnly:如果设置成true,表示input中的内容来自于ComboBox,该属性可单独使用,缺省值为false。如果input的初始值是从Domain Class获得,需要这个属性为true。否则,input的初始值将是initVal的值;
- primaryKey:如果input的初始值是从Domain Class获得,这个属性是指定input的初始值来自于Domain Class的哪个字段。
使用组Combobox,input的初始值来自于jqac.Role的name字段,用法如下:
<jqac:domain id="inival3" style="width:260px" domain="jqac.Role" primaryKey="name" selectOnly="true" initVal="'admin','guest'" package="true" />
效果如下:

如果initVal设置的是id字段的值,用法如下:
<jqac:domain id="inival3" style="width:260px" domain="jqac.Role" primaryKey="id" selectOnly="true" initVal="5,3" package="true" />
效果如下:

提交选择内容
上面讲述的是ComboBox中查询结果显示的各种用法,下面重点说说选择内容后,如何提交。这就需要注意两个参数:
- selectOnly,为true时,表面意思是input中的内容必须是由从ComboBox中选择,隐含意思是会生成一个隐藏的input框,它的name属性与页面显示的input的name相同,用于存放primaryKey指定的字段内容(通过FireBug即可知道这一点)。
- primaryKey,这个属性跟数据表中的primaryKey不一样。它用于设置页面POST时哪个字段的值被提交,即将field指向的字段以及primaryKey指向的字段以[field,primaryKey]的形式提交。该属性的缺省值为id。
这里需要注意,如果selectOnly设置为false,将不会生成隐藏的input框,那么primaryKey中设置的字段内容将不会被提交。
下面以User的新增和修改为例。在User的新增页面中可以选择Role,页面代码为
<jqac:domain id="roleid" style="width:260px" domain="jqac.Role" selectOnly="true" primaryKey="id" />
上述代码生成的HTML代码如下图:
注意上图中的两个input的name属性均为roleid_1。在页面提交后,Controller中通过params.roleid_1就能获得这两个input的值,形如:[admin,1]。要获得id,使用params.roleid_1[1]即可。比如:
def userInstance = new User(params) userInstance.role=Role.get(Long.parseLong(params.roleid_1[1])) userInstance.save()
在User的修改页面中,需要显示这个用户所属的Role,见如下代码:
<jqac:domain id="roleid" style="width:260px" domain="jqac.Role"
selectOnly="true"
initVal="${userInstance?.role?.id}"
primaryKey="id"/>
如果使用组ComboBox,比如新增Role的时候,可以选择多个User,页面代码:
<jqac:domain id="userid" style="width:260px" domain="jqac.User" selectOnly="true" primaryKey="id" package="true" />
由于这里可以使用多个ComboBox,这就需要记录ComboBox的个数。由于原先的jquery.ajaxComboBox没有这个功能,我对这个js稍作改动,添加了一个名为packagenum的隐藏input框,用于记录ComboBox的个数。具体代码参见jquery.ajaxComboBox.3.5.min.js中注释为“add by huwh”的内容。
对于上述代码,页面提交后,Controller中的处理如下:
def roleInstance = new Role(params)
if(params.packagenum){
Long.parseLong(params.packagenum).times{
if(!params."userid_${it+1}"[1].equals(""))
roleInstance.addToUsers(User.get(Long.parseLong(params."userid_${it+1}"[1])))
}
}
roleInstance.save()
由于Role的User可能是多个,那么在Role的修改页面,同样需要使用组ComboBox,并且需要赋初值。
<jqac:domain id="userid" style="width:260px" domain="jqac.User"
selectOnly="true"
initVal="${userids}"
package="true"
primaryKey="id"/>
这里需要注意userids是经过预处理的,为该Role的多个User的id字符串,形如:2,4,5。
洋洋洒洒写了这么多,感谢您认真读到这里。以上就是关于这个例程的基本介绍,因为后来没有打算作为插件发布,因此代码并没有经过严格的测试,也没有测试代码。有兴趣的读者可以下载下来自行修改使用,本例程的所有代码均在Apache许可证V2下发布。

最新评论
9 周 6 小时之前
9 周 1 天之前
11 周 5 天之前
11 周 5 天之前
17 周 2 天之前
17 周 4 天之前
19 周 3 天之前
20 周 1 天之前
20 周 2 天之前
20 周 5 天之前