想先介绍一下要做这个功能的原因过程。
之前做过一个项目实现的功能是页面左边是树,右边是列表。树作为文件夹目录,列表显示文件夹下的文件。而且树可以隐藏,只显示文件列表。做了一个frameset,来实现这个功能。
后来的项目也用到了这个功能,但是要实现文件的移动。经过百度找到了介绍可拖拽的方法(感谢他们的分享),但是在我这个项目中拖拽的时候无法跨frame拖拽,后来改为一个页面中不同的div实现了。
上面是废话可以略过。
===================================分割线==================================
网上的分享都是两个列表的拖拽,原理都是一样的,但是有些效果需要自己实现,比如鼠标进入离开事件等。看图:
拖拽前:
拖拽中:
拖拽后:
思路:
(1)拖拽的时候携带文件的id
(2)拖拽到目标位置的时候鼠标松开时获取节点id
(3)在drop事件中获取到文件id与节点id,异步更新文件与节点的关系
(4)更新完文件与节点关系后刷新列表与节点
一、拖拽源使用draggable方法
$("#tableId tbody").draggable({}); 要拖拽的列表。jquery选择器中定义拖拽元素,拖拽多条,使用tbody,单条使用tr,也可以拖拽一个td。我要实现的是拖拽多条所以用的是tbody。自定义的helper显示拖拽的内容。
function initTableDrag() {
$("#sysMenuPageListTable tbody").draggable({
//helper: "clone",
helper: function(event) {
var selectedTr = $(event.currentTarget).find("tr[class='selected']");
if (selectedTr && selectedTr.length > 0) {
var html = "<div>";
for(var i = 0; i < selectedTr.length; i++) {
var fileName = $(selectedTr[i]).find("a")[0].innerText;
var fileId = $(selectedTr[i]).find("input").val();
html += "<div style='color:#0075E3; background-color:#F3F7F9;padding:5px; margin:2px;'>";
html += "<input type='hidden' value='"+fileId+"'/>"+fileName+"</div>";
}
html += "</div>";
return $(html);
} else {
alert("请选择要移动的数据!");
return $( "<div style='font-color:#0075E3;'>请选择要移动的数据!</div>" );
}
return $( "<div class='ui-widget-header'>I'm a custom helper</div>" );
},
containment: "body", // 移动范围
cursor: "move", cursorAt: { top: 20, left: -8 }, // 鼠标位置
delay: 300, // 延迟拖拽
distance: 5, // 5 像素后,拖拽才开始
snap: false, // 对齐到元素
opacity: 1, // 透明度
start: function (event, ui) {
//var arrtd = $(ui.helper[0]).find("td");
//var input = $(arrtd[1]).children()[0];
//if(input) {
//console.log(input.value);
//}
},
drag: function(event, ui) {
//var t = ui.helper[0].offsetTop;
//var l = ui.helper[0].offsetLeft;
},
stop: function (event, ui) {
//var arrtd = $(ui.helper[0]).find("td");
//alert($(arrtd[1]).children()[0].value);
}
});
}
重点说明一下:
helper中包含业务运算的主键,上文提到的文件id,放到了hidden里,drop的时候可以获取到。
containmen限定拖拽范围,不加可以拖出窗口
cursorAt定义了鼠标与helper的位置。要使鼠标在helper的外面,目的是为了拖拽到tree上的时候响应tree上添加的mouseover事件,获取到节点id。
二、拖拽目标使用droppable方法
(1)在介绍droppable之前,先说一下,ztree是没有mouseover事件的,只能自己绑定。
onAsyncSuccess: function(event, treeId, treeNode, msg){
// 添加 mouseover事件
var childs = treeNode.children;
var treeObj = $.fn.zTree.getZTreeObj("sysMenuTree");
var nodes_array = treeObj.transformToArray(childs);
if(nodes_array){
for(var i = 0; i < nodes_array.length; i++){
$("#"+ nodes_array[i].tId + "_a").bind("mouseover", function () {
$(this).addClass("tmpTargetNode_inner");
});
$("#"+ nodes_array[i].tId + "_a").bind("mouseout", function () {
$(this).removeClass("tmpTargetNode_inner");
});
}
}
// 此回调函数可逐级异步展开子节点
if (treeNode.code.split(".").length == 1) {
if(treeNode.isParent == true && treeNode.children && treeNode.children.length > 0) {
var children = treeNode.children;
for(var i = 0; i < children.length; i++) {
if(i==0){//默认选中第一个节点
zTree.selectNode(children[i]);//选中
}
openNode(children[i].code);
}
}
}
}
添加的mouseover与mouseout事件放到了onAsyncSuccess方法里,添加的类tmpTargetNode_inner是我自己在ztree里找的
(2)$("#treeId").droppable({});
$("#sysMenuTree").droppable({
tolerance: "touch",
drop: function (event, ui) {
if (confirm("是否确认移动?")) {
var divs = $(ui.helper[0]).children();
var fileIds = "";
if (divs && divs.length > 0) {
for (var i = 0; i < divs.length; i++) {
var fileId = $(divs[i]).find("input").val();
fileIds += fileId + ",";
}
}
var relaId = moveNodeId;
$.ajax({
type: "post",
url : "${ctx}/document/moveSysFileOnMenu.do",
data : {fileId : fileIds, relaId : relaId},
dataType : "json",
success : function(data){
if(data.status == "SUCCESS") {
alert("移动成功!")
reloadTable();
refreshMenuCode(moveNodeCode);
return;
} else if (data.status == "CURRENT") {
alert("文件已在当前目录,无须移动.");
return;
} else {
alert("移动失败!");
return;
}
},
error : function(XMLHttpRequest, textStatus, errorThrown){
}
});
}
}
});
这里重点说一下参数tolerance。
一开始是没有这个参数的,拖拽的时候总是有拖拽不触发drop事件的时候,现象是文件名特别长的时候总是没反应,文件名短的时候可以成功拖拽。后来发现只有拖拽到目录名字中间或更靠左的时候才行,但我想实现鼠标一到目录边上就触发drop,然后看了很久源码才发现可以添加参数tolerance。(下面两张绿底图片是jquery-ui.js的源码。)
tolerance默认是intersect,总共有四个值。fit、intersect、pointer、touch
注释中提到了edge,根据单词touch的意思和简单的代码注释,于是我选择了touch。一试果然可以。而默认的intersect意思是过半才可以。
变量moveNodeId是定义了一个全局变量,使用ztree的onMouseUp事件获取节点id,以便拖拽到位的时候可以获取到目标节点。
onMouseUp: function(event, treeId, treeNode){
if(treeNode){
moveNodeId = treeNode.id;
moveNodeCode = treeNode.code;
}
},
droppable中的ajax请求是业务实现,最终实现了bootstraptable到ztree的拖拽功能。