基于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 相关文章推荐
前淘宝前端开发工程师阿当的PPT中有JS技术理念问题
Jan 15 Javascript
js 刷新页面的代码小结 推荐
Apr 02 Javascript
利用jQuery插件扩展识别浏览器内核与外壳的类型和版本的实现代码
Oct 22 Javascript
实例解析jQuery中如何取消后续执行内容
Dec 01 Javascript
jquery replace方法去空格
May 08 jQuery
jQuery实现可编辑表格并生成json结果(实例代码)
Jul 19 jQuery
vue.js声明式渲染和条件与循环基础知识
Jul 31 Javascript
从零开始实现Vue简单的Toast插件
Dec 03 Javascript
细说Vue组件的服务器端渲染的过程
May 30 Javascript
JavaScript 预解析的4种实现方法解析
Sep 03 Javascript
基于javascript的无缝滚动动画实现2
Aug 07 Javascript
vue项目在线上服务器访问失败原因分析
Aug 14 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
基于php验证码函数的使用示例
2013/05/03 PHP
PHP统计二维数组元素个数的方法
2013/11/12 PHP
destoon实现调用当前栏目分类及子分类和三级分类的方法
2014/08/21 PHP
浅谈php自定义错误日志
2015/02/13 PHP
浅谈PHP的反射机制
2016/12/15 PHP
静态的动态续篇之来点XML
2006/08/15 Javascript
ie 处理 gif动画 的onload 事件的一个 bug
2007/04/12 Javascript
jquery ready()的几种实现方法小结
2010/06/18 Javascript
JavaScript高级程序设计(第3版)学习笔记7 js函数(上)
2012/10/11 Javascript
javascript面向对象包装类Class封装类库剖析
2013/01/24 Javascript
javascript event在FF和IE的兼容传参心得(绝对好用)
2014/07/10 Javascript
JavaScript获取伪元素(Pseudo-Element)属性的方法技巧
2015/03/13 Javascript
Javascript实现字数统计
2015/07/03 Javascript
javascript实现根据iphone屏幕方向调用不同样式表的方法
2015/07/13 Javascript
js控制div层的叠加简单方法
2016/10/15 Javascript
JS中如何实现复选框全选功能
2016/12/19 Javascript
JavaScript严格模式详解
2017/01/16 Javascript
js实现数组去重方法及效率?Ρ? target=
2017/02/14 Javascript
JQuery EasyUI 结合ztrIee的后台页面开发实例
2017/09/01 jQuery
详解Vue This$Store总结
2018/12/17 Javascript
详解vue的数据劫持以及操作数组的坑
2019/04/18 Javascript
[02:55]2018DOTA2国际邀请赛勇士令状不朽珍藏Ⅲ饰品一览
2018/08/01 DOTA
[01:00:53]OG vs IG 2018国际邀请赛小组赛BO2 第一场 8.18
2018/08/19 DOTA
jupyter notebook 调用环境中的Keras或者pytorch教程
2020/04/14 Python
Python 用__new__方法实现单例的操作
2020/12/11 Python
获取邓白氏信用报告:Dun & Bradstreet
2019/01/22 全球购物
Conforama西班牙:您的家具、装饰和电器商店
2020/02/21 全球购物
Vuori官网:运动服装的终级表现
2021/01/27 全球购物
毕业生求职简历的自我评价
2013/10/07 职场文书
实习生自我鉴定范文
2013/12/05 职场文书
《我要的是葫芦》教学反思
2014/02/23 职场文书
文化宣传方案
2014/03/13 职场文书
安全责任书模板
2014/07/22 职场文书
党员转正大会主持词
2015/07/02 职场文书
Django集成富文本编辑器summernote的实现步骤
2021/05/31 Python
Python基础数据类型tuple元组的概念与用法
2021/08/02 Python