基于Gridster的用户个性化导航栏布局

          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基本的用法大家可以网上查到,下面说说遇到的几个问题:

  1. 关闭网格——动态元素的事件绑定
    $('ul').on("click",".close",function(){
    		   gridster.remove_widget( $(this).parents("li"));
    		});
     由于li标签及其子元素都是通过jQuery动态加载的,所以普通的事件绑定方法是无效的,这里必须使用on函数,这个函数是重载的,注意:要实现事件的动态绑定,这里的三个参数缺一不可,由于ul是本身就存在的,所以把事件绑定在ul上,原理可以百度上搜。
  2. 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看作两个域。
  3. 位置上传——同步模式     之前上传位置时使用的是$.post方法,该方法默认使用异步方式上传。我将上传函数绑定在window的unload事件上,将读取数据绑定在$(document).ready()上。这里出现过两个问题,若使用ie浏览器,从数据库更新情况来看根本没有上传数据,而使用chrome浏览器,关闭选项卡再重新访问时没有问题,得到的布局是上一次设置的,但是在刷新页面时,的确上传了数据,可是下载的布局数据有问题。基于这两种情况,做出假设:可能由于上传和下载两个动作相邻太近,在刷新后的页面获取位置数据时,刚才上传的数据还未完全写入,所以获得的不是最新数据(这个不确定是不是这样,只是推测,用的是MySql数据库),故下载的网格位置出错。解决办法是将post()方法改为$.ajax()方法,并设置async参数为false,即采用同步方式,当浏览器完成数据上传时才再次发出页面访问请求,这样下载的数据就是更新过的了。从实践结果来看,这种方法的确能正确上传并下载数据,这似乎与我的假设是一致的。
  4. 位置还原——动作排序     上面几个问题解决了,仍然不能正确还原布局,得到的坐标数据虽然是正确的,但必须要排序。原因是这样:以一开始的图片为例,若我们把折八百的那个网格关闭,下面的爱奇艺网格并不会停留在原来的位置,而是会上浮。所以说若返回的数据爱奇艺排在折八百之前,即使位置数据是准确的,在添加网格时还是会出错。解决方法很简单,网格的坐标数据是一个结构体,所有网格的坐标数据存储在数组中,我们只要先按row排序,再按col排序,然后按排列后的序列顺序插入网格就可以还原布局了,排序不用自己写,使用api:Gridster.sort_by_row_and_col_asc(data)即可。
        二.存在问题

          由于采用同步模式,只用数据上传完毕时网页才会关闭,尽管时间很短,但从实践来看耗费的时间已经是可感知的了,这样可能使用户体验不好。试验过的一个解决方法是修改服务器端程序,具体方法是这样:在数据库中新建一个更新状态表,包括用户id和更新状态state两个字段,当上传数据写回数据完毕时将state设为true,每次用户访问页面时要下载数据,下载之前先检查state是否为true,若为false则一直等待,此时前台可以在网格的容器内部显示“正在下载”等等。当state为true则服务器返回数据,然后前台页面会正确加载数据。这样就可以使用ajax的异步模式了,用户离开页面时页面可以即刻关闭,而不必等待上传完成。此方案在chrome浏览器中可行,但在ie中仍然有问题,因为关闭或刷新页面时仍然无法上传数据,原因还不知道。

猜你喜欢

转载自blog.csdn.net/u012334071/article/details/45145975