前言
最近实现了一个拖拽的需求,需要支持嵌套列表、多选、两个列表互相拖动。现在市面上最火的拖拽库应该就是sortablejs了,在github上也是有30.3K的star。但是sortablejs有一个最大的不便就是需要开发者去处理源数据,所以他们团队封装了基于vue的Vue.Draggable和基于react的react-sortablejs。这两个库不需要开发者自己处理源数据,而且使用也比较简单,但是Vue.Draggable不支持多选(react版本的我没看)。由于项目需求需要支持多选,所以最后还是使用了sortablejs,开始了我的踩坑之旅。
如何使用sortablejs以及嵌套列表如何实现
创建一个sortablejs实例: new sortablejs(el, options);
el必须为可拖动元素的直接父节点,如果想要拖动elementui的组件,比如el-tabs,比如下图就需要将el-tabs__nav实例化。
嵌套列表: 嵌套列表就是像el-tree那种,有多个层级,每个层级都可以拖动,那么每个层级都需要去new sortablejs。
建议直接递归组件,在递归组件里去实例化。
如何使用多选
sortablejs时单独导出的多选插件,需要挂载多选插件。 官方文档地址: github.com/SortableJS/… 建议直接在main.js里挂载多选插件,这样在任何地方都可以直接使用多选,通过在配置项中使用multiDrag:true开启。
import { Sortable, MultiDrag } from 'sortablejs';
Sortable.mount(new MultiDrag());
在处理多选时要特别注意,在onEnd事件的event中,有item、items两个值,item为开始拖动时所拖动的那个元素,items是多选的所有元素。但是触发多选的动作必须是点击这些元素,点击后sortablejs才认为这是你选中的元素(所以这里最好搭配el-checkbox单选框使用)。所以在处理源数据时不要只去处理items里的数据,因为当用户不点击元素直接拖动某个元素时,items是空的,所以一定要同时处理item和items。
拖拽后B列表有两个重复的Dom
出现这个问题的前提是你在AB列表拖动时,将一个元素从A拖动到B后,修改了源数据,在B列表添加了一个源数据,但是此时拖动过来的dom也还在B列表,就会导致有两个一模一样的节点。 解决: 直接在B列表的onAdd方法中拿到item后remove sortablejs拖动过来的这个dom。记住同时处理item和items。
// 多选
evt.items.forEach((i: any) => {
i.remove();
});
// 单选
evt.item.remove();
clone模式的坑
clone模式的定义是:当从A列表拖动元素到B列表时,A列表仍然会有一个元素在原位置,sortabllejs是复制一个dom作为被拖拽的元素。包括其官方文档也是这样说的。官网文档地址:sortablejs.com/options
背景及问题:
clone模式下,一般把元素从A列表拖到B列表后,一般是需要将A列表中的元素禁用掉的,假如A列表元素全都有单选框,那么就给el-checkbox添加disabled属性即可。但是当你这么做后你会发现给拖动之后给原来的A列表中的节点添加disabled后是禁用不了的。
分析:
按照clone模式的定义,原来的dom应该是不变的,被拖动的应该是一个副本,但是其实sortablejs反过来了,它将复制的元素放在了原来的地方,把原来的dom当作被拖动的节点。就导致了当把一个节点拖到B列表后,原来的响应式节点就跑到了B列表,此时给A列表的那个被复制的节点加上disabled是不会作用的。官网的clone模式的例子就有这个问题,可以用浏览器调试一下。 官方例子:sortablejs.github.io/Sortable/
首先找到选中左侧列表中item2,右键存储为全局变量。
此时控制台能看到输出的节点是左侧的item2. 接下来拖动这个节点到右侧列表。
在控制台按方向键上,打印刚才的全局变量,此时可以看到item2已经是右侧列表中的元素了。所以这个例子与官网说的“移动的为该元素的副本”相违背。
解决:
参考下这篇文章:juejin.cn/post/743881… 他遇到的问题和我一模一样。
在A列表插入响应式节点,然后再移除复制的那个没有响应式的节点。
onEnd: function (/**Event*/ evt) {
const { target, clone, item } = evt
target.insertBefore(item, clone)
target.removeChild(clone)
}
clone模式下多个列表互相拖动的问题
一般互相拖动是从一个列表拖到另一个列表,这种情况没什么问题。但是当想从多个列表拖到一个列表时就会有问题了。如下图,左边有A、B、C三个列表,这三个group的name都是一样的,需要支持拖动右边这个列表。
现在会有个问题是当选择了多个列表里的项目后,拖动到右侧列表后,之前选中的几个元素,会移动到拖动的起始的列表里。造成这个现象的原因是sortablejs会把clone出的多个元素放到拖动的起始group里。
所以如果是要用clone模式并且支持列表互相拖动,那么一定要记住只能两个列表互相拖动。
如果想要支持多个列表拖动到另一个列表,那么不支持多选只支持单选就可以避免这个问题。
删除sortablejs实例后的问题
背景:
如图这种只有单独一个菜单的,它也是一个sortablejs实例里的元素。如果不用clone模式,用标准的拖动的话,那么将元素拖动之后需要将这个源数据删除,所以我删除了这个元素,而且我包括外层的div也一起删了。也就是这个sortablejs实例我也删除了。
问题:
删除之后,导致了一个问题,右侧新增的数据无法再拖动元素到其子级。
排查:
1.首先排查是否右侧新增数据后没有去new 新的sortablejs实例,但是经过排查,确认每次都new了一个新的实例。而且奇怪的是只有拖动单独一级这种才会出现这个问题。
2.所以我先暂时去掉了删除数据源的方法,去掉后发现怎么拖都可以,这就定位到了问题,和删除了sortablejs实例有关。
解决:
单独处理一级这种情况,不删除数据源,而是用一个参数记录此项目是否被拖走了,并在界面上给一个提示。
总结:
千万不能删除sortablejs实例。