Gridster.js是一款Duckboard设计的开源JS框架,主要用来实现用户可操控的网格布局,官网(www.gridster.net)上有Demo。这两天工作需要研究了一下Gridster.js结合ajax保存用户的布局数据,使用户离开网页后再次进入能展现出用户上一次设置的网格布局。
一.前端设计
界面效果大致如上图所示(不是专业做这个的,比较难看,重点主要在功能的实现上),实现的功能包括:按住绿色的部分可以拖拉导航网格,点击关闭可以移除相应的网格,按住右下角可以进行放缩,最后,当用户离开页面(包括关闭面板,关闭浏览器,刷新页面)时自动上传位置信息,当用户下次浏览时自动恢复布局。下面给出js代码:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>测试</title>
<link href="style.css" rel="stylesheet" type="text/css" />
<link rel="stylesheet" type="text/css" href="jquery.gridster.css">
<script src="jquery-1.11.2.js" type="text/javascript"></script>
<script src="jquery.gridster.js" type="text/javascript" charset="utf-8"></script>
<script>
$(document).ready(function(){
//初始化Gridster对象
gridster =new Gridster($(".gridster ul"),{ //通过jquery选择DOM实现gridster
widget_base_dimensions: [100, 120], //模块的宽高 [宽,高]
widget_margins: [5, 5], //模块的间距 [上下,左右]
draggable: {
handle: '.drag' //模块内定义拖动的元素
},
//设置resize句柄
resize:{
enabled: 'true',//允许放缩
handle:'.resize'//html标签的css类名,按住此标签可以对网格进行放缩
},
//设置serialize()方法的返回值
serialize_params: function($w, wgd) {//$w为要输出位置的网格对象(li),wgd为该网格对象的坐标对象,包括col,row,size //_x,size_y四个成员
return { col: wgd.col,
row: wgd.row,
size_x: wgd.size_x,
size_y: wgd.size_y,
url:$w.find("a").attr("href"), //这里对官网的样例进行了扩展,不仅上传坐标信息,还上传了url和title
title:$w.find("a").html()
}
},
});
$.ajaxSetup({cache:false});
//页面加载完后获取上一次用户的布局
//这里localhost不能和127.0.0.1混用,若浏览器的请求地址必须与这里的完全一致,全部为localhost或全部为127.0.0.1
$.get("http://localhost:8080/ui/positionStore.jsp",
function(data,status){
gridster.remove_all_widgets();
//注意:1.Gridster首字母必须大写;2.先按row排序,row相同的再按列排序,按排序后的顺序插入data可以保证位置不会偏移
data = Gridster.sort_by_row_and_col_asc(data);
alert("排序后的widget:\n"+JSON.stringify(data));
for(var i=0;i<=data.length;i++){
var li="<li><div style='width:100%;height:20%'>"
+ "<div class='drag' style='width:60%;background-color:#00FF00;float:left'>拖拽</div>"
+ "<div class='close' style='width:40%;background-color:#FF0000;float:left'>关闭</div></div>"
+ "<div style='width:100%;height='80%';background-color:#33FFFF;overflow:hidden'> "
+ "<a href='"+data[i].url+"'>"+data[i].title+"</a>"
+ "</div></li>";
gridster.add_widget(li,data[i].size_x,data[i].size_y,data[i].col,data[i].row);
}
}
);
//关闭widget
//注意:一定要用on(event,selector,function)方法,该方法使用委托机制
$('ul').on("click",".close",function(){
gridster.remove_widget( $(this).parents("li"));
});
//上传布局,若使用$(window).unload()配合$.post,在chrome浏览器中,刷新时可能无法从数据库取得最新位置信息,可在数据库中增加一张更新状态表
//在IE浏览器中则连数据都无法上传,结合两种浏览器的情况,必须使用$.ajax()并将async设置为false
$(window).on("unload",function(){
alert("即将上传");
var s = gridster.serialize();
/* $.post("http://localhost:8080/ui/positionStore.jsp",
JSON.stringify(s),
function(data,status){
alert("上传结果Status: " + status);
}); */
$.ajax({
type: "POST",
url: "http://localhost:8080/ui/positionStore.jsp",
data: JSON.stringify(s),
success: function(data,status){
alert("上传结果Status: " + status);
},
//必须设置为同步模式,即回调函数执行完毕后才向下执行(unload页面)
async:false
});
});
/* $(window).on("unload",function(){
while(1!=isUploaded) ;
alert("上传完毕");
}); */
});
</script>
</head>
<body>
<div id="container">
<div id="top" >
<form id="form">
<span class="bg s_ipt_wr iptfocus quickdelete-wrap">
<input name="wd" class="s_ipt" id="kw" maxLength="255" autocomplete="off" value=""/>
</span>
<span class="bg s_btn_wr">
<input class="bg s_btn" id="su" type="submit" value="搜索"/>
</span>
</form>
</div>
<div id="bottom" class="gridster" style="border:1px solid #e0e0e0;">
<ul >
</ul>
<!--<button id="upload">上传位置</button>-->
</div>
</div>
</body>
</html>
gridster基本的用法大家可以网上查到,下面说说遇到的几个问题:
- 关闭网格——动态元素的事件绑定
由于li标签及其子元素都是通过jQuery动态加载的,所以普通的事件绑定方法是无效的,这里必须使用on函数,这个函数是重载的,注意:要实现事件的动态绑定,这里的三个参数缺一不可,由于ul是本身就存在的,所以把事件绑定在ul上,原理可以百度上搜。$('ul').on("click",".close",function(){ gridster.remove_widget( $(this).parents("li")); });
- ajax的请求地址——localhost与127.0.0.1 大家都知道访问本机服务器的url有2种,一种是用公网地址访问,这种方式会把请求发到路由器进行路由再返回本机(印象中是这样),还有一种使用localhost和127.0.0.1,这种方式数据包不会离开本地,以前一直以为这两种方式没有区别,但这里却出现了问题。代码中使用的是localhost方式,但是当我在浏览器中使用127.0.0.1访问时可以向服务发送数据,却不能接受数据,故不能展示上一次设置的网格布局,浏览器的地址栏必须使用与代码中一致的方式,都是localhost才可以,具体原因是因为ajax不能跨域访问,即ajax中请求的url必须是你自己站点的,这里ajax似乎把localhost和127.0.0.1看作两个域。
- 位置上传——同步模式 之前上传位置时使用的是$.post方法,该方法默认使用异步方式上传。我将上传函数绑定在window的unload事件上,将读取数据绑定在$(document).ready()上。这里出现过两个问题,若使用ie浏览器,从数据库更新情况来看根本没有上传数据,而使用chrome浏览器,关闭选项卡再重新访问时没有问题,得到的布局是上一次设置的,但是在刷新页面时,的确上传了数据,可是下载的布局数据有问题。基于这两种情况,做出假设:可能由于上传和下载两个动作相邻太近,在刷新后的页面获取位置数据时,刚才上传的数据还未完全写入,所以获得的不是最新数据(这个不确定是不是这样,只是推测,用的是MySql数据库),故下载的网格位置出错。解决办法是将post()方法改为$.ajax()方法,并设置async参数为false,即采用同步方式,当浏览器完成数据上传时才再次发出页面访问请求,这样下载的数据就是更新过的了。从实践结果来看,这种方法的确能正确上传并下载数据,这似乎与我的假设是一致的。
- 位置还原——动作排序 上面几个问题解决了,仍然不能正确还原布局,得到的坐标数据虽然是正确的,但必须要排序。原因是这样:以一开始的图片为例,若我们把折八百的那个网格关闭,下面的爱奇艺网格并不会停留在原来的位置,而是会上浮。所以说若返回的数据爱奇艺排在折八百之前,即使位置数据是准确的,在添加网格时还是会出错。解决方法很简单,网格的坐标数据是一个结构体,所有网格的坐标数据存储在数组中,我们只要先按row排序,再按col排序,然后按排列后的序列顺序插入网格就可以还原布局了,排序不用自己写,使用api:Gridster.sort_by_row_and_col_asc(data)即可。
由于采用同步模式,只用数据上传完毕时网页才会关闭,尽管时间很短,但从实践来看耗费的时间已经是可感知的了,这样可能使用户体验不好。试验过的一个解决方法是修改服务器端程序,具体方法是这样:在数据库中新建一个更新状态表,包括用户id和更新状态state两个字段,当上传数据写回数据完毕时将state设为true,每次用户访问页面时要下载数据,下载之前先检查state是否为true,若为false则一直等待,此时前台可以在网格的容器内部显示“正在下载”等等。当state为true则服务器返回数据,然后前台页面会正确加载数据。这样就可以使用ajax的异步模式了,用户离开页面时页面可以即刻关闭,而不必等待上传完成。此方案在chrome浏览器中可行,但在ie中仍然有问题,因为关闭或刷新页面时仍然无法上传数据,原因还不知道。