基于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 相关文章推荐
prototype Element学习笔记(篇二)
Oct 26 Javascript
javascript 自动填写表单的实现方法
Apr 09 Javascript
关于jquery append() html时的小问题的解决方法
Dec 16 Javascript
jquery高效反选具体实现
May 05 Javascript
探讨在JQuery和Js中,如何让ajax执行完后再继续往下执行
Jul 09 Javascript
简单实用的反馈表单无刷新提交带验证
Nov 15 Javascript
探讨JavaScript语句的执行过程
Jan 28 Javascript
如何用JavaScript实现动态修改CSS样式表
May 20 Javascript
Web纯前端“旭日图”实现元素周期表
Mar 10 Javascript
Vue.js实现文章评论和回复评论功能
May 30 Javascript
关于JavaScript中的this指向问题总结篇
Jul 23 Javascript
vue实现员工信息录入功能
Jun 11 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
Session保存到数据库的php类分享
2011/10/24 PHP
PHP中全面阻止SQL注入式攻击分析小结
2012/01/30 PHP
解决cPanel无法安装php5.2.17
2014/06/22 PHP
PHP用户验证和标签推荐的简单使用
2016/10/31 PHP
kindeditor 加入七牛云上传的实例讲解
2017/11/12 PHP
PHP Cli 模式设置进程名称的方法
2019/06/12 PHP
laravel 自定义常量的两种方案
2019/10/14 PHP
laravel框架中视图的基本使用方法分析
2019/11/23 PHP
Javascript 原型和继承(Prototypes and Inheritance)
2009/04/01 Javascript
用javascript删除当前行,添加行(示例代码)
2013/11/25 Javascript
jquery控制display属性为none或block
2014/03/31 Javascript
jquery动态添加删除一行数据示例
2014/06/12 Javascript
12行javascript代码绘制一个八卦图
2015/04/02 Javascript
JS实现模拟风力的雪花飘落效果
2015/05/13 Javascript
使用jquery/js获取iframe父子级、同级获取元素的方法
2016/08/05 Javascript
jQuery层级选择器实例代码
2017/02/06 Javascript
webpack独立打包和缓存处理详解
2017/04/03 Javascript
Vue.js实现列表清单的操作方法
2017/11/15 Javascript
vue-cli3.0使用及部分配置详解
2018/08/29 Javascript
webpack4+express+mongodb+vue实现增删改查的示例
2018/11/08 Javascript
JavaScript实现的联动菜单特效示例
2019/07/08 Javascript
js实现漂亮的星空背景
2019/11/01 Javascript
vue 组件内获取actions的response方式
2019/11/08 Javascript
通过原生vue添加滚动加载更多功能
2019/11/21 Javascript
原生js实现移动小球(碰撞检测)
2020/12/17 Javascript
Python多线程结合队列下载百度音乐的方法
2015/07/27 Python
全面解析Python的While循环语句的使用方法
2015/10/13 Python
Python 字符串与二进制串的相互转换示例
2018/07/23 Python
centos6.8安装python3.7无法import _ssl的解决方法
2018/09/17 Python
基于html和CSS3制作酷炫的导航栏
2015/09/23 HTML / CSS
HTML5调用手机发短信和打电话功能
2020/04/29 HTML / CSS
人力资源管理专业毕业生自我评价
2013/09/21 职场文书
大二学生学习个人自我评价
2014/01/19 职场文书
家长会欢迎标语
2014/06/24 职场文书
Python Django获取URL中的数据详解
2021/11/01 Python
详解JAVA的控制语句
2021/11/11 Java/Android