基于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之Partial Application学习
Jan 10 Javascript
用javascript替换URL中的参数值示例代码
Jan 27 Javascript
jQuery插件实现控制网页元素动态居中显示
Mar 24 Javascript
Jquery Mobile 自定义按钮图标
Nov 18 Javascript
微信小程序 wxapp地图 map详解
Oct 31 Javascript
滚动条的监听与内容随着滚动条动态加载的实现
Feb 08 Javascript
js实现3D图片展示效果
Mar 09 Javascript
微信小程序商品详情页的底部弹出框效果
Nov 16 Javascript
vue使用高德地图根据坐标定位点的实现代码
Aug 22 Javascript
js获取浏览器地址(获取第1个斜杠后的内容)
Sep 03 Javascript
layui插件表单验证提交触发提交的例子
Sep 09 Javascript
laravel实现中文和英语互相切换的例子
Sep 30 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
一个取得文件扩展名的函数
2006/10/09 PHP
第七章 php自定义函数实现代码
2011/12/30 PHP
解析WordPress中控制用户登陆和判断用户登陆的PHP函数
2016/03/01 PHP
PHP使用token防止表单重复提交的方法
2016/04/07 PHP
PHP5.5.15+Apache2.4.10+MySQL5.6.20配置方法分享
2016/05/06 PHP
详解PHP用substr函数截取字符串中的某部分
2016/12/03 PHP
激活 ActiveX 控件
2006/10/09 Javascript
玩转jQuery按钮 请告诉我你最喜欢哪些?
2012/01/08 Javascript
jquery拖动插件(jquery.drag)使用介绍
2013/06/18 Javascript
js数组中如何随机取出一个值
2014/06/13 Javascript
js调试系列 断点与动态调试[基础篇]
2014/06/18 Javascript
js选择并转移导航菜单示例代码
2014/08/19 Javascript
jQuery实现表格行上移下移和置顶的方法
2015/05/22 Javascript
详解Javascript继承的实现
2016/03/25 Javascript
值得分享的轻量级Bootstrap Table表格插件
2016/05/30 Javascript
React中使用UEditor百度富文本的方法
2018/08/22 Javascript
如何使用Jquery动态生成二级选项列表
2020/02/06 jQuery
webpack.DefinePlugin与cross-env区别详解
2020/02/23 Javascript
JS倒计时两种实现方式代码实例
2020/07/27 Javascript
python计算圆周长、面积、球体体积并画出圆
2014/04/08 Python
浅要分析Python程序与C程序的结合使用
2015/04/07 Python
Python实现删除文件但保留指定文件
2015/06/21 Python
python opencv实现切变换 不裁减图片
2018/07/26 Python
python读出当前时间精度到秒的代码
2019/07/05 Python
Python对Tornado请求与响应的数据处理
2020/02/12 Python
python matplotlib包图像配色方案分享
2020/03/14 Python
Selenium webdriver添加cookie实现过程详解
2020/08/12 Python
python super()函数的基本使用
2020/09/10 Python
Python之Sklearn使用入门教程
2021/02/19 Python
HTML5 canvas基本绘图之图形变换
2016/06/27 HTML / CSS
如何用canvas实现在线签名的示例代码
2018/07/10 HTML / CSS
美国在线工具商店:Acme Tools
2018/06/26 全球购物
社团成立邀请函
2014/01/08 职场文书
验房委托书
2014/08/30 职场文书
公司授权委托书范文
2014/09/21 职场文书
让生命充满爱观后感
2015/06/08 职场文书