Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。使用 Spring 可插入的 MVC 架构,从而在使用Spring进行WEB开发时,可以选择使用Spring的SpringMVC框架或集成其他MVC开发框架,如Struts1,Struts2等。
————————————————————————————————————————–
上一章我们实现了用户登录的功能,登录后可以查看所有在线用户,这样才可以选择与谁聊天,因此本章实现在线用户列表的功能。用户可能存在很多,需要对用户列表做分页展示,列表分页是一个共用性比较强的功能,因此我们把分页作为一个通用的模块开发。
既然为了开发一个通用的分页功能,所以我们定义一个使用泛型的数据包装类PageModel,主要包括数据总量,当前偏移量,页容量和当前页的数据总量,如下所示:

[code lang=”java”]
public class PageModel<T> {
private Class<T> clazz;
/**
* 数据
*/
private List<T> data;
/**
* 偏移量
*/
private int offset = 0;
/**
* 页容量
*/
private int limit = 10;
/**
* 总量
*/
private int total = 0;
/**
* 查询条件
*/
private Map<String, String> params;
}
[/code]

其中多了定义了两个属性,clazz保存泛型类型class用于获取分页链接,params用于报错查询条件集合。
在MasterController.index中返回测试数据,并在master.vm中显示数据,具体代码如下:

[code lang=”html”]

<tbody>
#foreach($item in $userPageModel.getData())

<tr>

<td>$item.username</td>

<td>$item.lastHeartBeatString</td>

<td>
#if($item.status == 1)
在线
#else
离线
#end
</td>

<td>
#if($item.username != $username && $item.status == 1)
<input type="button" value="聊天" onclick="chat(‘$!{username}’, ‘$!{item.username}’)"/>
#elseif($item.username != $username)
不能聊天
#end
</td>

</tr>

#end
</tbody>

[/code]

[code lang=”java”]
//MasterController.index方法更新如下
public String index(HttpServletRequest request, HttpServletResponse response) {
Object usernameObj = request.getSession().getAttribute("username");
if(usernameObj != null) {
PageModel<UserStatusVo> pageModel = userBo.listUser(1, 10);
request.setAttribute("userPageModel", pageModel);
}
return "master";
}
//UserBo新增listUser方法
public PageModel<UserStatusVo> listUser(int page, int limit) {
PageModel<UserStatusVo> pageModel = new PageModel<UserStatusVo>();
pageModel.setClazz(UserStatusVo.class);
pageModel.setLimit(limit);
int offset = (page-1) * limit;
pageModel.setOffset(offset);
pageModel.setTotal(Integer.MAX_VALUE);
List<UserStatusVo> userList = new ArrayList<UserStatusVo>();
for(int i=offset; i<offset+limit; i++ ) {
UserStatusVo user = new UserStatusVo();
user.setLastHeartBeat(new Date());
user.setStatus(1);
user.setUsername("username" + i);
userList.add(user);
}
pageModel.setData(userList);
return pageModel;
}
[/code]

数据展示出来了,我们看下分页功能实现,首先要能正确显示是否有链接,比如如果当前页是首页,那么首页应该仅显示文字,如果当前页不是首页,首页应该显示为链接。所以我们在PageModel中实现“当前是否为首页”,”获取首页链接地址”,”是否有上一页”,”获取上一页链接地址”,”是否有下一页”,”获取下一页链接地址”,”是否为最后一页”,“获取最后一页链接地址”。

[code lang=”java”]
public int getCurPage() {
if (offset == 0) {
return 1;
}

if (offset / limit == 0) {
return 1;
}

if (offset >= total) {
if (total % limit == 0) {
return total / limit;
} else {
return total / limit + 1;
}
}

return offset / limit + 1;
}

public int getTotalPage() {
if (total % limit == 0) {
return total / limit;
} else {
return total / limit + 1;
}
}

public boolean isFirst() {
return getCurPage() <= 1; } public String getFirstUrl() { return getUrl(1); } public boolean hasPre() { return getCurPage() > 1;
}

public String getPreUrl() {
int prePage = 1;

if (hasPre()) {
prePage = getCurPage() – 1;
}

return getUrl(prePage);
}

public boolean hasNext() {
return getCurPage() < getTotalPage(); } public String getNextUrl() { int nextPage = getTotalPage(); if (hasNext()) { nextPage = getCurPage() + 1; } return getUrl(nextPage); } public String getLastUrl() { return getUrl(getTotalPage()); } public boolean isLast() { return getCurPage() >= getTotalPage();
}

public String getUrl(int page) {
String baseURL = getBaseUrl();
String url = baseURL + "&page=" + page;
return url;
}

public String getBaseUrl() {
String baseURL = BaseURLUtil.getBaseURL(clazz);
baseURL = baseURL + "&limit=" + limit;
if (this.params != null) {
for (String key : this.params.keySet()) {
baseURL = baseURL + "&" + key + "=" + this.params.get(key);
}
}
return baseURL;
}
public class BaseURLUtil {

private static Map<Class, String> urlMap = new HashMap<Class, String>();

static {
urlMap.put(UserStatusVo.class, "/user/userList?1=1");
}

public static String getBaseURL(Class clazz) {
return urlMap.get(clazz);
}
}
[/code]

上面的功能开发中,也抽取了几个公用的功能方法“获取当前页”,“获取总页数”,“获取基础链接(不包括页参数)”和“根据页号获取链接”。
velocity页面模板变更如下:

[code lang=”html”]

<div id="userListPagination">

<div>
#if(${userPageModel.isFirst()})
首页
#else
<a href="javascript:void(0);" url="/imlearntest${userPageModel.getFirstUrl()}">首页</a>
#end
#if(${userPageModel.hasPre()})
<a href="javascript:void(0);" url="/imlearntest${userPageModel.getPreUrl()}">上一页</a>
#else
上一页
#end
<input value="$!{userPageModel.getCurPage()}" style="width:20px;height:15px" url="/imlearntest${userPageModel.getBaseUrl()}"/>
#if(${userPageModel.hasNext()})
<a href="javascript:void(0);" url="/imlearntest${userPageModel.getNextUrl()}">下一页</a>
#else
下一页
#end
#if(${userPageModel.isLast()})
尾页
#else
<a href="javascript:void(0);" url="/imlearntest${userPageModel.getLastUrl()}">尾页</a>
#end
</div>

</div>

[/code]

使用页面刷新的实现比较复杂,所以我们采用局部刷新的方式,所以修改a标签的href为javascript:void(0);不执行跳转,把跳转的链接作为a标签的url属性写入页面。为了实现a标签局部刷新和改变页面自动跳转,我们开发了几个js方法。

[code lang=”javascript”]
/**
* 分页页面内容获取,该函数只能注册到分页的a标签上,ajax成功后调用回调方法callback
*/
function clickGoto(callback, $this) {
var url = $this.attr("url");
$.ajax({
type: "GET",
url: url,
success: function(result){
callback(result);
},
error: function(result) {
alert(result);
}
});
}
/**
* 分页页面内容获取,该函数只能注册到分页的input.text标签上,ajax成功后调用回调方法callback
*/
function changeGoto(callback) {
var url = $this.attr("url");
var page = $this.val();
url = url + "&page="+page;
$.ajax({
type: "GET",
url: url,
success: function(result){
callback(result);
},
error: function(result) {
alert(result);
}
});
}
/**
* 把result的内容替换userListDiv的内容
*/
function userListPagination(result) {
$("#userListDiv").html(result);
}
/**
* userListPagination中的a标签注册点击事件响应
*/
$("#userListPagination a").click(function(){
var $this = $(this);
clickGoto(userListPagination, $this);
});
/**
* userListPagination中的input标签注册内容变更事件响应
*/
$("#userListPagination input").change(function(){
var $this = $(this);
changeGoto(userListPagination, $this);
});
[/code]

其中changeGoto和clickGoto函数可复用,所以这两个方法定义到pagination.js中,页面引入jquery.js和pagination.js。
<script type=”text/javascript” src=”/imlearntest/js/jquery-1.12.0.min.js”></script>
<script type=”text/javascript” src=”/imlearntest/js/pagination.js”></script>
实现链接/user/userList,在com.sunhaojie.imlearntest.controller.UserController添加方法userList,具体代码如下:

[code lang=”java”]
@RequestMapping("userList")
public String userList(@ModelAttribute("page") int page, @ModelAttribute("limit") int limit,
HttpServletRequest request, HttpServletResponse response) {
if (page <= 0) {
page = 1;
}

PageModel<UserStatusVo> pageModel = userBo.listUser(page , limit);

request.setAttribute("pageModel", pageModel);
return "user/userList";
}
[/code]

因为用户列表是局部刷新,所以把用户列表和分页做为单独的模板保存,在userList方法中返回用户列表。具体模板如下:

[code lang=”html”]
<!– /WEB-INF/vm/user/userList.vm –>

<table border="1">

<thead>

<tr>

<th width="30%">用户</th>

<th width="30%">最后登录时间</th>

<th width="20%">状态</th>

<th width="20%">操作</th>

</tr>

</thead>

<tbody>
#foreach($item in $pageModel.getData())

<tr>

<td>$item.username</td>

<td>$item.lastHeartBeatString</td>

<td>
#if($item.status == 1)
在线
#else
离线
#end
</td>

<td>
#if($item.username != $username && $item.status == 1)
<input type="button" value="聊天" onclick="chat(‘$!{username}’, ‘$!{item.username}’)"/>
#elseif($item.username != $username)
<input type="button" value="聊天记录"/>
#end
</td>

</tr>

#end
</tbody>

</table>

<div id="userListPagination">
#parse("/WEB-INF/vm/pagination.vm")
</div>

<script type="text/javascript">
/**
* 更新用户分页列表
*/
function userListPagination(result) {
$("#userListDiv").html(result);
}

$("#userListPagination a").click(function(){
var $this = $(this);
clickGoto(userListPagination, $this);
});
$("#userListPagination input").change(function(){
var $this = $(this);
changeGoto(userListPagination, $this);
});
</script>
<!– /WEB-INF/vm/pagination.vm –>

<div>
#if(${pageModel.isFirst()})
首页
#else
<a href="javascript:void(0);" url="/imlearntest${pageModel.getFirstUrl()}">首页</a>
#end
#if(${pageModel.hasPre()})
<a href="javascript:void(0);" url="/imlearntest${pageModel.getPreUrl()}">上一页</a>
#else
上一页
#end
<input value="${pageModel.getCurPage()}" style="width:20px;height:15px" url="/imlearntest${pageModel.getBaseUrl()}"/>
#if(${pageModel.hasNext()})
<a href="javascript:void(0);" url="/imlearntest${pageModel.getNextUrl()}">下一页</a>
#else
下一页
#end
#if(${pageModel.isLast()})
尾页
#else
<a href="javascript:void(0);" url="/imlearntest${pageModel.getLastUrl()}">尾页</a>
#end
</div>

[/code]

访问前端页面,发现分页功能可以实现了,现在我们需要把UserBo.listUser访问真实的数据,代码如下:

[code lang=”java”]
public PageModel<UserStatusVo> listUser(int page, int limit) {
int offset = (page-1) * limit;
List<UserStatusVo> userStatusList = new ArrayList<UserStatusVo>();
int i = 0;
for (String username : userStatus.keySet()) {
if (i >= offset && i < offset + limit) {
userStatusList.add(userStatus.get(username));
}
i++;
}

PageModel<UserStatusVo> pageModel = new PageModel<UserStatusVo>();
pageModel.setClazz(UserStatusVo.class);
pageModel.setData(userStatusList);
pageModel.setLimit(limit);
pageModel.setOffset(offset);
pageModel.setTotal(userStatus.size());

return pageModel;
}
[/code]

因为我们使用的HashMap存储的在线用户,所以获取分页用户列表时每次都要从头开始变量,实际工作中应该避免这种实现方式。

小练习:在本地完成用户列表功能