基于JQuery的列表拖动排序实现代码


Posted in Javascript onOctober 01, 2013

要求

拖动排序,从名字就不难想像,就是按住一行数据拖到想要的排序位置,保存新的排序队列。

思路

首先给列表行建立锚点,绑定mousedown和mouseup事件,当鼠标移动到想要插入的位置时,将对象行移动到目标行,然后对其经过的所有行进行排序处理。

思路很简单,但这里面仍然有几个问题要注意

1、移动到什么位置可以视作要插入到目标行的位置。
2、移动出了顶端和底端时,判断为第一和最后。
3、向上移动和向下移动的处理

解决

关于事件

Javascript里鼠标按下和放开事件为onmousedown,onmouseup,JQuery里是mousedown,mouseup,所以,这里使用mousedown和mouseup

首先,要知道界面有多少行,每一行有多高,因为要判断鼠标的移动距离

var tbodyHeight=setting.frame.outerHeight();  //setting.frame,父对象
var lineNum=$("."+setting.dgLine).length;  //setting.dgLine,每一行的classname
var lineHeight=Math.ceil(tbodyHeight/lineNum);

之所有要取lineNum(行数),除了计算行高外,还有个目的是要使用index(),通过序列索引值来进行移动行到目标位置

当mousedown事件触发后,就要开始计算鼠标移动的距离,用于判断该行到底要移动到什么位置

dgid=$(this).attr(setting.id);  //移动行的ID,setting.id,是每一行用来标记ID的名称
thisIndex=$("#"+setting.linePre+dgid).index(); //该行的索引,setting.linePre,每一行ID关辍
thisLineTop=$("#"+setting.linePre+dgid).offset().top; //该行的top值
topDistance=thisIndex*lineHeight;  //该行距离第一行顶端的距离
downDistance=(lineNum-thisIndex-1)*lineHeight; //该行距离最后一行底端的距离

dgid主要是用来区分每一行的标识,一般的列表都是程序循环输出来的,如果没有这样一个ID,就分不出哪行是哪行,所以,在HTML上,需要定义一个存放ID的家伙。程序通过attr就是来取这个值,保证每一行都有自己唯一的值。

thisLineTop,主要是用来和鼠标移动位置进行高度计算,然后根据行高、索引值来判断是移动到哪一行了。还有个作用是用来确定是否按在了移动锚点上,如果有值,说明是,那后面的mouseup就是成立的,如果没有值,说明没有按到锚点上,mouseup不执行任何操作。为什么要这样做呢?因为不管在页面的什么位置点鼠标,都会触发mouseup事件,如果没有一个判断,就会不停的执行,那就会产生一些问题。

topDistance和downDistance,用来判断鼠标有没有移出列表的范围,如果移除了,也就是鼠标移动的距离大于topDistance或downDistance,那就可以判断为需要移动到第一行或最后一行。

mousedown事件主要做的,就是这几件事情,当然,为了效果,还可以增加一些东西

$("#"+setting.linePre+dgid).css('background',setting.lineHighlight); //高亮移动行
var left=e.pageX+20;
var top=e.pageY;
dg_tips(left,top);  //创建一个提示层
$('body').css('cursor','move'); //更改页面的鼠标手势
$("body").disableSelection(); //禁止按下鼠标后移动鼠标时选中页面元素
setting.frame.mousemove(function(e){  //让提示层跟随鼠标移动
$("#dgf").css({"left":e.pageX+setting.tipsOffsetLeft+'px',"top":e.pageY+'px'});
});

这些的目的,只是让操作起来更有效果,比如高亮行,就是要让用户知道,他们操作的是哪一行。提示层的作用也一样。

关于禁止选中,.disableSelection();这是jQuery_UI里带的方法,如果你有使用jQuery_UI,那可以直接使用它,如果没有使用,可以这样做

$('body').each(function() {           
 $(this).attr('unselectable', 'on').css({
  '-moz-user-select':'none',
  '-webkit-user-select':'none',
  'user-select':'none'
 }).each(function() {
  this.onselectstart = function() { return false; };
 });
});

取消禁止选择

$('body').each(function() {           
 $(this).attr('unselectable', '').css({
  '-moz-user-select':'',
  '-webkit-user-select':'',
  'user-select':''
 });
});

考虑到通用性,所以后面的代码里,不会使用.disableSelection();

好了,下面是mouseup事件。这里mouseup事件是绑定在body上的,因为mouseup如果只是绑定在锚点上,那当鼠标移出锚点的时候,再松开鼠标,会发现,这个mouseup事件不执行了,因为它会认为是别的对象的mouseup。所以,最保险的方法是用$('body').mouseup。这样基本上就不会有问题。

mouseup触发后,首先就要判断thisLineTop是不是有值,防止无意义的事件执行。跟着判断鼠标移动的距离是正还是负,也就是向上移动还是向下移动。

var moveDistance=e.pageY-thisLineTop;

根据不同的方向作不同的处理

if(moveDistance<0){
 if(thisIndex!=0){
  moveDistance=Math.abs(moveDistance);  //为负数的时候,取一下绝对值
  if(moveDistance>lineHeight/2){ //判断移动距离是否超过行高的1/2
   if(moveDistance>topDistance){ //如果移动距离大于该行到顶边的距离
    focusIndex=0;
   }else{
    focusIndex=thisIndex-Math.ceil(moveDistance/lineHeight);
   }
   $("."+setting.dgLine).eq(focusIndex).before($("#"+setting.linePre+dgid));//将该行插入目标位置
  }
 }
}else{
 if(thisIndex!=lineNum-1){
  if(moveDistance>lineHeight/2+lineHeight){
   if(moveDistance>downDistance){
    focusIndex=lineNum-1;
   }else{
    focusIndex=thisIndex+Math.ceil(moveDistance/lineHeight)-1;
   }
   $("."+setting.dgLine).eq(focusIndex).after($("#"+setting.linePre+dgid));
  }
 }
}

之所以判断移动距离是否超过行高的1/2,是因为如果只移动一小点,可以视作不移动。在计算目标索引值的时候,使用了Math.ceil,最进位,而当移动距离大于0的时候,取了进位还要-1,因为是向下嘛。

向上移动和向下移动使用了不同的插入方法,before和after,可以试着想一下为什么要使用before和after。

移动完了,还得把按下鼠标时使用的效果给去除掉

$("#dgf").remove();//移除提示层
$("#"+setting.linePre+dgid).css('background','');//将高亮的行变为普通
dgid='';//将移动行的ID值空
thisLineTop=0;//将移动行的Top值至0
$('body').css('cursor','default');//更改鼠标手势为默认

基本上的情况就是这样,主要问题就是在处理移动和判断在哪里插入的问题上。其它的都非常简单。

下面给出完整的封装程序,包括更新数据部分

/*
* 
*  DragList.js
*  @author fuweiyi <fuwy@foxmail.com>
*  
*/
(function($){
 $.fn.DragList=function(setting){
  var _setting = {
   frame : $(this),
   dgLine : 'DLL',
   dgButton : 'DLB',
   id : 'action-id',
   linePre : 'list_',
   lineHighlight : '#ffffcc',
   tipsOpacity : 80,
   tipsOffsetLeft : 20,
   tipsOffsetTop : 0,
   JSONUrl : '',
   JSONData : {},
   maskLoaddingIcon : '',
   maskBackgroundColor : '#999',
   maskOpacity : 30,
   maskColor : '#000',
   maskLoadIcon:'',
  };
  var setting = $.extend(_setting,setting); 
  var dgid='',thisIndex,thisLineTop=0,topDistance,downDistance;
  var tbodyHeight=setting.frame.outerHeight();
  var lineNum=$("."+setting.dgLine).length;
  var lineHeight=Math.ceil(tbodyHeight/lineNum);
  $("."+setting.dgButton).mousedown(function(e){
   dgid=$(this).attr(setting.id);
   thisIndex=$("#"+setting.linePre+dgid).index();
   var left=e.pageX+20;
   var top=e.pageY;
   thisLineTop=$("#"+setting.linePre+dgid).offset().top;
   topDistance=thisIndex*lineHeight;
   downDistance=(lineNum-thisIndex-1)*lineHeight;
   $("#"+setting.linePre+dgid).css('background',setting.lineHighlight);
   dg_tips(left,top);
   $('body').css('cursor','move');
   unselect();
   setting.frame.mousemove(function(e){
    $("#dgf").css({"left":e.pageX+setting.tipsOffsetLeft+'px',"top":e.pageY+'px'});
   });
  });
  $('body').mouseup(function(e){
   if(thisLineTop>0){
    var moveDistance=e.pageY-thisLineTop;
    if(moveDistance<0){
     if(thisIndex!=0){
      moveDistance=Math.abs(moveDistance);
      if(moveDistance>lineHeight/2){
       if(moveDistance>topDistance){
        focusIndex=0;
       }else{
        focusIndex=thisIndex-Math.ceil(moveDistance/lineHeight);
       }
       $("."+setting.dgLine).eq(focusIndex).before($("#"+setting.linePre+dgid));
       dg_update(thisIndex,focusIndex);
      }
     }
    }else{
     if(thisIndex!=lineNum-1){
      if(moveDistance>lineHeight/2+lineHeight){
       if(moveDistance>downDistance){
        focusIndex=lineNum-1;
       }else{
        focusIndex=thisIndex+Math.ceil(moveDistance/lineHeight)-1;
       }
       $("."+setting.dgLine).eq(focusIndex).after($("#"+setting.linePre+dgid));
       dg_update(thisIndex,focusIndex);
      }
     }
    }
    $("#dgf").remove();
    $("#"+setting.linePre+dgid).css('background','');
    dgid='';
    thisLineTop=0;
    $('body').css('cursor','default');
    onselect();
   }
  });
  function dg_update(thisIndex,focusIndex){
   dg_mask();
   var start=thisIndex<focusIndex?thisIndex:focusIndex;
   var end=thisIndex<focusIndex?focusIndex:thisIndex;
   var ids='',vals='';
   for(var i=start;i<=end;i++){
    ids+=i==start?$("."+setting.dgLine).eq(i).attr(setting.id):','+$("."+setting.dgLine).eq(i).attr(setting.id);
    vals+=i==start?i:','+i;
   }
   $.getJSON(setting.JSONUrl,{'do':'changeorders','ids':ids,'vals':vals},function(d){
    $("#dg_mask").remove(); 
   });
  }
  function dg_mask(){
   var W=setting.frame.outerWidth();
   var H=setting.frame.outerHeight();
   var top=setting.frame.offset().top;
   var left=setting.frame.offset().left;
   var mask="<div id='dg_mask'><img src='"+setting.maskLoadIcon+"' alt='' /> 正在使劲的保存...</div>";
   $('body').append(mask);
   $("#dg_mask").css({"background":"#999","position":'absolute','width':W+'px','height':H+'px','line-height':H+'px','top':top+'px','left':left+'px','filter':'alpha(opacity='+setting.maskOpacity+')','moz-opacity':setting.maskOpacity/100,'opacity':setting.maskOpacity/100,'text-align':'center','color':'#000'});
  }
  function dg_tips(left,top){
   var floatdiv="<div id='dgf' style='padding:5px 10px;border:1px solid #444;background-color:#fff;filter:alpha(opacity="+setting.tipsOpacity+");moz-opacity:"+setting.tipsOpacity/100+";opacity:"+setting.tipsOpacity/100+";position:absolute;left:"+left+"px;top:"+top+"px;'>移动一条记录</div>";
   $('body').append(floatdiv);
  }
  function unselect(){
   $('body').each(function() {           
    $(this).attr('unselectable', 'on').css({
     '-moz-user-select':'none',
     '-webkit-user-select':'none',
     'user-select':'none'
    }).each(function() {
     this.onselectstart = function() { return false; };
    });
   });
  }
  function onselect(){
   $('body').each(function() {           
    $(this).attr('unselectable', '').css({
     '-moz-user-select':'',
     '-webkit-user-select':'',
     'user-select':''
    });
   });
  }
 }
})(jQuery);

使用

<table cellpadding="5" cellspacing="0" class="listtable" id="listtable">
 <thead>
  <tr>
   <td class="td50">拖动</td>
   <td class="td220">名称</td>
  </tr>
 </thead>
 <tbody id="drag_table">
  <!--{loop $lists $k $list}-->
  <tr id="list_{$list['id']}" action-id="{$list['id']}" class="drag_line">
   <td><div class="drag_orders" action-id="{$list['id']}"><img src="{SYS_URL}views/admin/images/move.png" alt="" /></div></div></td>
   <td>这里是一行</td>
  </tr>
  <!--{/loop}-->
 </tbody>
</table>
<script type="text/javascript">
$("#drag_table").DragList({
 dgLine:'drag_line',
 dgButton:'drag_orders',
 id:'action-id',
 linePre:'list_',
 JSONUrl:'{_ADMIN}?controller=contents&method=focus_form',
 maskLoadIcon:'{SYS_URL}views/admin/images/loading.gif'
});
</script>

参数主要是dgLine,dgButton,id,linePre和JSONUrl,通过看HTML代码,应该不难理解。

关于拖动排序就是这么多了,当然还可以把效果做得更漂亮些,提示更清楚点,有助于用户体验

Javascript 相关文章推荐
JavaScript 给汉字排序实例代码
Jun 28 Javascript
jquery1.4 教程二 ajax方法的改进
Feb 25 Javascript
jQuery结合PHP+MySQL实现二级联动下拉列表[实例]
Nov 15 Javascript
jquery validate poshytip 自定义样式
Nov 26 Javascript
javascript实现获取服务器时间
May 19 Javascript
JS实现的3D拖拽翻页效果代码
Oct 31 Javascript
AngularJS 使用 UI Router 实现表单向导
Jan 29 Javascript
Angular下H5上传图片的方法(可多张上传)
Jan 09 Javascript
jquery事件与绑定事件
Mar 16 Javascript
vue实现表格增删改查效果的实例代码
Jul 18 Javascript
layui+jquery支持IE8的表格分页方法
Sep 28 jQuery
vue 判断页面是首次进入还是再次刷新的实例
Nov 05 Javascript
Javascript 颜色渐变效果的实现代码
Oct 01 #Javascript
JavaScript的事件绑定(方便不支持js的时候)
Oct 01 #Javascript
javascript不可用的问题探究
Oct 01 #Javascript
JavaScript DOM 编程艺术(第2版)读书笔记(JavaScript的最佳实践)
Oct 01 #Javascript
js有序数组的连接问题
Oct 01 #Javascript
jquery更换文章内容与改变字体大小代码
Sep 30 #Javascript
jquery配合css简单实现返回顶部效果
Sep 30 #Javascript
You might like
三国漫画《火凤燎原》宣布动画化PV放出 预计2020年播出
2020/03/08 国漫
利用PHP实现开心消消乐的算法示例
2017/10/12 PHP
php5.6.x到php7.0.x特性小结
2019/08/17 PHP
浅谈JavaScript数据类型
2015/03/03 Javascript
jQuery对指定元素中指定字符串进行替换的方法
2015/03/17 Javascript
jquery获取当前元素索引值用法实例
2015/06/10 Javascript
jQuery常用且重要方法汇总
2015/07/13 Javascript
深入解析JavaScript编程中的this关键字使用
2015/11/09 Javascript
IScroll5 中文API参数说明和调用方法
2016/05/21 Javascript
深入浅出讲解ES6的解构
2016/08/03 Javascript
AngularJS自定义服务与fliter的混合使用
2016/11/24 Javascript
使用微信SDK自定义分享的方法
2019/07/03 Javascript
JavaScript 实现同时选取多个时间段的方法
2019/10/17 Javascript
vue下axios拦截器token刷新机制的实例代码
2020/01/17 Javascript
js编写简易的计算器
2020/07/29 Javascript
详解vue-router的导航钩子(导航守卫)
2020/11/02 Javascript
[38:32]完美世界DOTA2联赛循环赛 Forest vs DM 第二场 11.06
2020/11/06 DOTA
Python实现excel转sqlite的方法
2017/07/17 Python
python分布式环境下的限流器的示例
2017/10/26 Python
python爬虫使用cookie登录详解
2017/12/27 Python
Python控制Firefox方法总结
2019/06/03 Python
python自动化测试之如何解析excel文件
2019/06/27 Python
python 浅谈serial与stm32通信的编码问题
2019/12/18 Python
python中np是做什么的
2020/07/21 Python
python中_del_还原数据的方法
2020/12/09 Python
CSS3制作日历实现代码
2012/01/21 HTML / CSS
CSS3实现点击放大的动画实例代码
2017/02/27 HTML / CSS
美国第二大连锁药店:Rite Aid
2019/04/03 全球购物
酒店管理专业毕业生推荐信
2013/11/10 职场文书
市场营销专科应届生求职信
2013/11/24 职场文书
不打扫卫生检讨书
2014/02/12 职场文书
幼儿发展评估方案
2014/06/11 职场文书
卖房授权委托书样本
2014/10/05 职场文书
教师党员批评与自我批评发言稿
2014/10/15 职场文书
地球一小时活动总结
2015/02/27 职场文书
Z-Order加速Hudi大规模数据集方案分析
2022/03/31 Servers