jqGrid 是一款基于 jQuery 的表格插件,通过它开发者可以轻易实现前后端的数据交互(使用 ajax 异步通信),并将之以表格的形式展现在前台页面上。
想要在项目中使用 jqGrid,首先要导入 jqGrid 的几个 js 文件和 css 文件。具体如下:
<script language="javascript" src="${ctx}/js/jquery-3.1.1.min.js" >script>
<script language="javascript" src="${ctx}/js/jqGrid/js/jquery-ui.min.js" >script>
<script language="javascript" src="${ctx}/js/jqGrid/js/i18n/grid.locale-cn.js" >script>
<script language="javascript" src="${ctx}/js/jqGrid/js/jquery.jqGrid.js" >script>
<link rel="stylesheet" href="${ctx}/js/jqGrid/theme/jquery-ui.min.css" />
<link rel="stylesheet" href="${ctx}/js/jqGrid/theme/ui.jqgrid.css" />
当然,体贴如我是不会忘记附上下载地址的。
下载 jqGrid 插件请戳我!
下载 jqGrid 主题请戳我!
然后,我们再对这几个文件作简要的说明。
由于 jqGrid 是依赖与 jQuery 框架的,因此我们需要导入 jquery 文件。
如果我们想让 jqGrid 以中文的形式展示提示语,那么我们需要导入 grid.locale-cn.js 文件。它可以理解为汉化包,支持 jqGrid 的提示语以汉字的形式显示。
jqGrid 作为一款前端的表格插件,自然会有多种主题,而 jquery-ui.min.js、jquery-ui.min.css、ui.jqgrid.css 正是用于实现 jqGrid 个性化主题的文件。
最后也是最重要的,就是 jqGrid 的核心文件 jquery.jqGrid.js。当然,你也可以选择导入 jquery.jqGrid.min.js。这两者的区别在于,前者是完整版的,具有完整的注释,方便源码阅读,而后者是压缩版的,更适合用于生产环境。
导入上述几个文件后,标志着我们已经完成对 jqGrid 的安装,这时,就让我们以一个简单的示例,来看看 jqGrid 的效果。
首先,我们先来看看一个完整的 jqGrid 实例的前端代码。
HTML 部分:
<body>
<div class="content-wrap">
<table id="list">table>
<div id="pager" class="pager">div>
div>
body>
js 部分:
$(document).ready(function () {
$('#list').jqGrid({
url: ctx + '/student/query-list', // 获取数据的地址
datatype: 'json', // 从服务器端返回的数据类型,默认xml。可选类型:xml,local,json,jsonnp,script,xmlstring,jsonstring,clientside
mtype: 'POST', // 提交方式,默认为GET
height: 'auto', // 高度,表格高度。可为数值、百分比或'auto'
width: 1000, // 如果设置则按此设置为主,如果没有设置则按colModel中定义的宽度计算
colNames: ['姓名', '性别', '邮箱', '生日', '学历', '状态', '入学时间'], // 列显示名称,是一个数组对象
colModel: [
// name 表示列显示的名称;
// index 表示传到服务器端用来排序用的列名称;
// width 为列宽度;
// align 为对齐方式;
// sortable 表示是否可以排序
{name: 'name', index: 'name', width: '15%', align: 'center'},
{name: 'sex', index: 'sex', width: '20%', align: 'center'},
{name: 'email', index: 'email', width: '20%', align: 'center'},
{name: 'birth', index: 'birth', width: '20%', align: 'center'},
{name: 'education', index: 'education', width: '20%', align: 'center'},
{name: 'status', index: 'status', width: '20%', align: 'center'},
{name: 'entranceTime', index: 'entranceTime', width: '20%', align: 'center'}
],
rownumbers: true,// 显示左侧的序号
//altRows:true,// 设置为交替行表格,默认为false
//sortname:'entranceTime', // 排序列的名称,此参数会被传到后台
//sortorder:'asc', // 排序顺序,升序或者降序(ASC或DESC)
viewrecords: true, // 是否在翻页导航栏显示记录总数
rowNum: 5, // 每页显示记录数
rowList: [5, 10, 15], // 用于改变显示行数的下拉列表框的元素数组
pager: $('#pager') // 定义翻页用的导航栏
});
$('#list4').jqGrid('setLabel', 'rn', '序号'); // 为序号列命名
});
在这个代码示例中,我已经为每个属性添加了注释说明,所以不再另外讨论里面每个属性的作用。但是,需要注意的是,上述代码,只是 jqGrid 的一个简单示例,jqGrid 还有更多神奇的属性和方法,等待大家去挖掘。如果你有兴趣,不妨戳下面的链接研究研究。
英语渣渣请戳这里~
英语学霸请戳这里~
我相信,细心的读者一定会发现,在这个简单的示例中,我没有说明如何获取数据。关于这部分,请不要着急,我会在下文中进行详细地介绍。
那么,现在先让我们欣赏一下上述示例运行的效果吧!
传输数据是 jqGrid 中的重要环节。本文采用 springMVC 的方式进行前后端的数据交互,采用 JSON 作为数据传输的格式。
首先,我们介绍一下请求对象 JqGridPageReq。该对象的作用是接收前端 jqGrid 传来的参数,然后通过这些参数到数据库查询相应的数据。代码如下:
public class JqgridPageReq<T> {
/**
* 总页数
*/
private Integer total;
/**
* 当前页
*/
private Integer page;
/**
* 每页记录数
*/
private Integer rows;
private T qo;
/**
* 排序字段
*/
private String sidx;
/**
* 排序
*/
private String sord;
public Integer getTotal() {
return total;
}
public void setTotal(Integer total) {
this.total = total;
}
public Integer getPage() {
return page;
}
public void setPage(Integer page) {
this.page = page;
}
public Integer getRows() {
return rows;
}
public void setRows(Integer rows) {
this.rows = rows;
}
public T getQo() {
return qo;
}
public void setQo(T qo) {
this.qo = qo;
}
public String getSidx() {
return sidx;
}
public void setSidx(String sidx) {
this.sidx = sidx;
}
public String getSord() {
return sord;
}
public void setSord(String sord) {
this.sord = sord;
}
}
然后是响应对象 JqGridPageResp。该对象的作用是将结果集、页码、记录数等信息传输给前台,从而前台可以获取数据并且将其显示出来。
import java.util.List;
public class JqgridPageResp<T> {
/**
* 总页数
*/
private Integer total = 0;
/**
* 当前页
*/
private Integer page = 0;
/**
* 本次查询总记录数
*/
private Integer records = 0;
/**
* 结果集
*/
private List<T> rows;
public Integer getTotal() {
return total;
}
public void setTotal(Integer total) {
this.total = total;
}
public Integer getPage() {
return page;
}
public void setPage(Integer page) {
this.page = page;
}
public Integer getRecords() {
return records;
}
public void setRecords(Integer records) {
this.records = records;
}
public List<T> getRows() {
return rows;
}
public void setRows(List<T> rows) {
this.rows = rows;
}
}
尽管我们已经建立好了请求对象与响应对象,但是目前,我们的 springMVC 还无法识别它们。我们必须进行相关的配置,才能让 springMVC 实现请求参数到 POJO 的映射。
我们只需在配置文件 spring-servlet.xml 中添加如下配置:
<mvc:annotation-driven >
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
mvc:message-converters>
mvc:annotation-driven>
MappingJackson2HttpMessageConverter 可以自动完成 POJO 对象和 JSON 数据的转化。这样,当我们从前端传参给服务端时,若 JqgridPageReq 对象中含有与传入参数名字相同的成员变量,spring 将自动调用 JqgridPageReq 中对应的 set 方法为该变量赋值,从而实现 JSON 数据到 POJO 对象的映射。同理,当我们的服务端返回数据给前端时,spring 会将 POJO 对象转化成 JSON 格式后再返回响应。
了解这些原理后,我们就可以来看看 Controller 层中的具体实现:
@RequestMapping(value = "/query-list", method = RequestMethod.POST)
@ResponseBody
public Object queryStudentList(JqgridPageReq<Student> req, BaseQo qo) {
JqgridPageResp<Student> resp = new JqgridPageResp<Student>();
// 获得当前页码
qo.setPageNumber(req.getPage());
// 获得每页记录数
qo.setPageSize(req.getRows());
// 查询学生信息记录
PageList<Student> studentList = studentInfoService.queryStudentList(qo);
// 获取分页对象
Paginator paginator = studentList.getPaginator();
// 返回当前页码
resp.setPage(paginator.getPage());
// 返回总记录数
resp.setRecords(paginator.getTotalCount());
// 返回总页数
resp.setTotal(paginator.getTotalPages());
// 返回查询数据
resp.setRows(studentList);
return resp;
}
其中,函数参数 req 负责接收前端传来的参数,变量 resp 负责将服务器端处理后的数据返回给前端,@ResponseBody 负责告诉 springMVC 跳过视图解析,直接将 JSON 数据返回给前端。这样,我们就实现了 jqGrid 与服务端的数据交互。
jqGrid 作为一款表格插件,自然避免不了分页的问题。在上述的代码中,我们已经知道 jqGrid 会向服务端发送页码信息,然后服务端在处理后也会返回分页信息给前端。那么这个分页的实现,究竟是靠什么完成的呢?且看本部分的主角,PageBounds。
PageBounds 是大神 miemiedev 写的一个 Mybatis 分页控件。在使用 PageBounds 前,我们需要在项目的 pom.xml 文件里添加依赖:
<dependency>
<groupId>com.github.miemiedevgroupId>
<artifactId>mybatis-paginatorartifactId>
<version>1.2.8version>
dependency>
PageBounds 有以下构造方法:
new PageBounds(); // 默认构造函数不提供分页,返回 ArrayList
new PageBounds(int limit); // 取 TOPN 操作,返回 ArrayList
new PageBounds(Order... order); // 只排序不分页,返回 ArrayList
new PageBounds(int page, int limit); // 默认分页,返回 PageList
new PageBounds(int page, int limit, Order... order); // 分页加排序,返回PageList
new PageBounds(int page, int limit, List<Order> orders, boolean containsTotalCount); // 使用 containsTotalCount 来决定查不查询 totalCount,即返回 ArrayList 还是 PageList
这里需要说明的是,上面代码注释部分提到的 PageList 其实是 ArrayList 的子类,它与 ArrayList 的差别在于它包含了一个返回 Paginator 对象(存有分页信息)的方法。
public Paginator getPaginator() {
return this.paginator;
}
当我们要调用 PageBound 实现分页时,只需要在我们的 service 实现类中编写如下代码,即可实现。
public PageList<Student> queryStudentList(BaseQo qo) {
PageBounds pgs = new PageBounds(qo.getPageNumber(), qo.getPageSize());
return studentDao.queryStudentList(qo, pgs);
}
看到这里时,我相信有些读者已经一脸懵逼了。为什么我们只需要在 dao 接口中传入 PageBounds 类型参数,就可以实现分页?这不科学啊?印象中的分页,应该是需要经过很多代码处理的才对。关于这些问题,不慌,我们现在就来解决他们!
在以前,如果让你写一个分页的代码,你会怎么写?我的思路是这样的,从数据库中把符合查询条件的数据找出来,放在一个 List 中,然后通过页码、每页记录数从 List 中找出每一页的数据集 newList。这个思路的具体实现代码如下:
public List<Student> getInitPage(List<Student> list, int Page, int pageSize) {
List<Student> newList = new ArrayList<Diary>();
this.list = list; // 结果集
recordCount = list.size(); // 总记录数
this.pageSize = pageSize; // 每页记录数
this.maxPage = (recordCount % pageSize == 0) ? (recordCount / pageSize) : (recordCount/pageSize + 1); // 最大页数
try {
for (int i = (Page - 1) * pageSize; i <= Page * pageSize - 1; i++) {
try {
// 在list中,下标最大值为 recordCount-1
if (i >= recordCount) {
break;
}
} catch (Exception e) {
// ignore
}
newList.add(list.get(i));
}
} catch (Exception e) {
e.printStackTrace();
}
return newList;
}
而 PageBounds 的分页原理,其实跟这个是非常相似的。只不过,PageBounds 并不是把所有记录取出来再分页,而是根据页码和每页记录,通过在 SQL 查询语句上添加 limit 限制条件,从数据库中读取指定部分的记录。
看到这里,相信不少人都会恍然大悟。但是,又有一个新的问题产生了。我知道 PageBounds 的作用原理,但是它究竟是如何作用在 SQL 语句上的?为什么我们只是在 dao 层接口把它作为参数传入,它就可以对 SQL 产生作用?
这时,就轮到 interceptor 登场了!interceptor 的中文意思是拦截,看到拦截这个词,你是不是马上反应过来了?没错,PageBounds 就是把我们的 SQL 拦截下来,然后加上它的 limit 条件,再传给数据库。
不过,要使 PageBounds 具备拦截功能,我们还需要在 MyBatis 的配置文件 mybatis-conf.xml 中添加一些配置:
<configuration>
<plugins>
<plugin interceptor="com.github.miemiedev.mybatis.paginator.OffsetLimitInterceptor">
<property name="dialectClass" value="com.github.miemiedev.mybatis.paginator.dialect.MySQLDialect" />
plugin>
plugins>
<mappers>
<mapper resource="panda/student/project/dao/studentMapper.xml" />
mappers>
configuration>
这里需要说明的是,如果你用的是 Oracle 数据库,需要导入的是不同的包,也就是 dialectClass 对应的 value 要不同。最后,让我们瞅瞅,这个拦截器究竟长啥样。
package com.github.miemiedev.mybatis.paginator.dialect;
public class MySQLDialect extends Dialect {
public MySQLDialect() {
}
public boolean supportsLimitOffset() {
return true;
}
public boolean supportsLimit() {
return true;
}
public String getLimitString(String sql, int offset, String offsetPlaceholder, int limit, String limitPlaceholder) {
return offset > 0?sql + " limit " + offsetPlaceholder + "," + limitPlaceholder:sql + " limit " + limitPlaceholder;
}
}
看到 getLimitString 方法,你心中的疑问是不是都解开了。每当我们准备传送 SQL 给数据库时,MySQLDialect 会将它拦截下来,交给 getLimitString 方法处理后,再发给数据库。我们的分页查询,就这样实现了。