一、效果展示
1、拖动前
2、拖动中
3、拖动后
4、撤销回到拖动前状态
二、需求分析
通过上篇我们知道,如果要实现拖拽,必须要有一个可以拖拽的标签,或者叫容器,比如上篇里面的tr就是一个拖拽的容器,那么如果要实现选择行的拖拽,那么博主的第一反应是将选中的行放到一个容器里面,比如放到一个div中,然后注册这个div的可拖拽,可是实际情况是,tr是在table里面的标签,如果将tr用div包起来,势必将table里面的样式打乱,这个界面就真的是乱掉了。很显然,这条路走不通。然后通过谷歌浏览器审核元素知道,用
Bootstrap
table生成的表格tr的父级元素实际上是tbody,于是在想是否可以注册tbody的拖拽,实践证明,此法可行。于是就此开干。
三、代码示例
cshtm的代码就不再重复,和上篇相同。我们重点来看看js代码。
var
i_statuindex =
0
;
var
arrdata =
[];
var
m_oTable =
null
; $(function () {
//
1.初始化表格
m_oTable =
new
TableInit(); m_oTable.Init();
//
2.初始化按钮事件
var
oButtonInit =
new
ButtonInit(); oButtonInit.Init();
//
3.日期控件的初始化
$(
"
.datetimepicker
"
).datetimepicker({ format:
'
yyyy-mm-dd hh:ii
'
, autoclose:
true
, todayBtn:
true
, }); });
//
表格相关事件和方法
var
TableInit =
function () {
var
oTableInit =
new
Object(); oTableInit.Init
=
function () { $(
'
#tb_order_left
'
).bootstrapTable({ url:
'
/api/OrderApi/get
'
, method:
'
get
'
, striped:
true
, cache:
false
, striped:
true
, pagination:
true
, height:
600
, uniqueId:
"
TO_ORDER_ID
"
, queryParams: oTableInit.queryParams, queryParamsType:
"
limit
"
, sidePagination:
"
server
"
, pageSize:
10
, pageList: [
10
,
25
,
50
,
100
], search:
true
, strictSearch:
true
, showColumns:
true
, showRefresh:
true
, minimumCountColumns:
2
, clickToSelect:
true
, columns: [{ checkbox:
true
}, { field:
'
ORDER_NO
'
, title:
'
订单号
'
}, { field:
'
BODY_NO
'
, title:
'
车身号
'
}, { field:
'
VIN
'
, title:
'
VIN码
'
}, { field:
'
TM_MODEL_MATERIAL_ID
'
, title:
'
整车编码
'
}, { field:
'
ORDER_TYPE
'
, title:
'
订单类型
'
}, { field:
'
ORDER_STATUS
'
, title:
'
订单状态
'
}, { field:
'
CREATE_DATE
'
, title:
'
订单导入时间
'
}, { field:
'
PLAN_DATE
'
, title:
'
订单计划上线日期
'
}, { field:
'
VMS_NO
'
, title:
'
VMS号
'
}, { field:
'
ENGIN_CODE
'
, title:
'
发动机号
'
}, { field:
'
TRANS_CODE
'
, title:
'
变速箱号
'
}, { field:
'
OFFLINE_DATE_ACT
'
, title:
'
实际下线日期
'
}, { field:
'
HOLD_RES
'
, title:
'
hold理由
'
}, { field:
'
SPC_FLAG
'
, title:
'
特殊标记
'
}, ], onLoadSuccess: function (data) { oTableInit.InitDrag();
if
(data.total >
0
) {
var
iheight = $(
'
#div_tableleft
'
).find(
"
.fixed-table-container
"
).height(); $(
'
#div_tableleft
'
).find(
"
.fixed-table-container
"
).height(iheight +
36
); } }, onCheckAll: function (rows) { $(
"
#tb_order_left tbody tr
"
).addClass(
"
selected
"
); }, onUncheckAll: function (rows) { $(
"
#tb_order_left tbody tr
"
).removeClass(
"
selected
"
); } }); $(
'
#tb_order_right
'
).bootstrapTable({ url:
'
/api/OrderApi/get
'
, method:
'
get
'
, toolbar:
'
#toolbar_right
'
, striped:
true
, cache:
false
, striped:
true
, pagination:
true
, height:
600
, queryParams: oTableInit.queryParamsRight, queryParamsType:
"
limit
"
,
//
ajaxOptions: { departmentname: "", statu: "" },
sidePagination:
"
server
"
, pageSize:
10
, pageList: [
10
,
25
,
50
,
100
], search:
true
, strictSearch:
true
, showRefresh:
true
, minimumCountColumns:
2
, columns: [ { field:
'
ORDER_NO
'
, title:
'
订单号
'
}, { field:
'
BODY_NO
'
, title:
'
车身号
'
}, { field:
'
VIN
'
, title:
'
VIN码
'
}, { field:
'
TM_MODEL_MATERIAL_ID
'
, title:
'
整车编码
'
}, { field:
'
ORDER_TYPE
'
, title:
'
订单类型
'
}, { field:
'
ORDER_STATUS
'
, title:
'
订单状态
'
}, { field:
'
CREATE_DATE
'
, title:
'
订单导入时间
'
}, { field:
'
PLAN_DATE
'
, title:
'
订单计划上线日期
'
}, { field:
'
VMS_NO
'
, title:
'
VMS号
'
}, { field:
'
ENGIN_CODE
'
, title:
'
发动机号
'
}, { field:
'
TRANS_CODE
'
, title:
'
变速箱号
'
}, { field:
'
OFFLINE_DATE_ACT
'
, title:
'
实际下线日期
'
}, { field:
'
HOLD_RES
'
, title:
'
hold理由
'
}, { field:
'
SPC_FLAG
'
, title:
'
特殊标记
'
}, ], onLoadSuccess: function (data) { oTableInit.InitDrop(); } }); }; oTableInit.InitDrag
=
function () { $(
'
#tb_order_left tbody
'
).draggable({ helper:
"
clone
"
, start: function (
event
, ui) {
var
old_left_data = JSON.stringify($(
'
#tb_order_left
'
).bootstrapTable(
"
getData
"
));
var
old_right_data = JSON.stringify($(
'
#tb_order_right
'
).bootstrapTable(
"
getData
"
));
var
odata = { index: ++
i_statuindex, left_data: old_left_data, right_data: old_right_data }; arrdata.push(odata); }, stop: function (
event
, ui) { } }); }; oTableInit.InitDrop
=
function () { $(
"
#div_tableright div[class=fixed-table-container]
"
).droppable({ drop: function (
event
, ui) {
var
arrtr = $(ui.helper[
0
]).find(
"
tr[class=selected]
"
);
if
(arrtr.length <=
0
) { alert(
"
请先选中要插单的行
"
);
return
; }
var
oTop = ui.helper[
0
].offsetTop;
var
iRowHeadHeight =
40
;
var
iRowHeight =
37
;
var
rowIndex =
0
;
if
(oTop <= iRowHeadHeight + iRowHeight /
2
) { rowIndex
=
0
; }
else
{ rowIndex
= Math.ceil((oTop - iRowHeadHeight) /
iRowHeight); }
for
(
var
i =
0
; i < arrtr.length; i++
) {
var
arrtd = $(arrtr[i]).find(
"
td
"
);
var
uniqueid = $(arrtr[i]).attr(
"
data-uniqueid
"
);
var
rowdata =
{ ORDER_NO: $(arrtd[
1
]).text(), BODY_NO: $(arrtd[
2
]).text(), VIN: $(arrtd[
3
]).text(), TM_MODEL_MATERIAL_ID: $(arrtd[
4
]).text(), ORDER_TYPE: $(arrtd[
5
]).text(), ORDER_STATUS: $(arrtd[
6
]).text(), CREATE_DATE: $(arrtd[
7
]).text() ==
"
-
"
?
null
: $(arrtd[
7
]).text(), PLAN_DATE: $(arrtd[
8
]).text() ==
"
-
"
?
null
: $(arrtd[
8
]).text(), VMS_NO: $(arrtd[
9
]).text(), ENGIN_CODE: $(arrtd[
10
]).text(), TRANS_CODE: $(arrtd[
11
]).text(), OFFLINE_DATE_ACT: $(arrtd[
12
]).text() ==
"
-
"
?
null
: $(arrtd[
12
]).text(), HOLD_RES: $(arrtd[
13
]).text(), SPC_FLAG: $(arrtd[
14
]).text(), TO_ORDER_ID: uniqueid }; $(
"
#tb_order_right
"
).bootstrapTable(
"
insertRow
"
, { index: rowIndex++
, row: rowdata }); $(
'
#tb_order_left
'
).bootstrapTable(
"
removeByUniqueId
"
, uniqueid); } oTableInit.InitDrag(); } }); }; oTableInit.queryParams
= function (
params
) {
//
配置参数
var
temp = {
//
这里的键的名字和控制器的变量名必须一直,这边改动,控制器也需要改成一样的
limit:
params
.limit,
//
页面大小
offset:
params
.offset,
//
页码
strBodyno: $(
"
#txt_search_bodynumber
"
).val(), strVin: $(
"
#txt_search_vinnumber
"
).val(), strOrderno: $(
"
#txt_search_ordernumber
"
).val(), strEngincode: $(
"
#txt_search_engin_code
"
).val(), strOrderstatus:
0
, strTranscode: $(
"
#txt_search_trans_code
"
).val(), strVms: $(
"
#txt_search_vms
"
).val(), strCarcode: $(
"
#txt_search_carcode
"
).val(), strImportStartdate: $(
"
#txt_search_import_startdate
"
).val(), strImportEnddate: $(
"
#txt_search_import_enddate
"
).val(), strSendStartdate: $(
"
#txt_search_send_startdate
"
).val(), strSendEnddate: $(
"
#txt_search_send_enddate
"
).val(), };
return
temp; }; oTableInit.queryParamsRight
= function (
params
) {
//
配置参数
var
temp = {
//
这里的键的名字和控制器的变量名必须一直,这边改动,控制器也需要改成一样的
limit:
params
.limit,
//
页面大小
offset:
params
.offset,
//
页码
strBodyno:
""
, strVin:
""
, strOrderno:
""
, strEngincode:
""
, strOrderstatus:
5
, strTranscode:
""
, strVms:
""
, strCarcode:
""
, strImportStartdate:
""
, strImportEnddate:
""
, strSendStartdate:
""
, strSendEnddate:
""
, };
return
temp; };
return
oTableInit; };
//
页面按钮初始化事件
var
ButtonInit =
function () {
var
oInit =
new
Object();
var
postdata =
{}; oInit.Init
=
function () {
//
查询点击事件
$(
"
#btn_query
"
).click(function () { $(
"
#tb_order_left
"
).bootstrapTable(
'
refresh
'
); });
//
重置点击事件
$(
"
#btn_reset
"
).click(function () { $(
"
.container-fluid
"
).find(
"
.form-control
"
).val(
""
); $(
"
#tb_order_left
"
).bootstrapTable(
'
refresh
'
); });
//
插单操作点击事件
$(
"
#btn_insertorder
"
).click(function () { });
//
撤销操作点击事件
$(
"
#btn_cancel
"
).click(function () {
if
(i_statuindex <=
0
) {
return
; }
for
(
var
i =
0
; i < arrdata.length; i++
) {
if
(arrdata[i].index !=
i_statuindex) {
continue
; }
var
arr_left_data =
eval(arrdata[i].left_data);
var
arr_right_data =
eval(arrdata[i].right_data); $(
'
#tb_order_left
'
).bootstrapTable(
'
removeAll
'
); $(
'
#tb_order_right
'
).bootstrapTable(
'
removeAll
'
); $(
'
#tb_order_left
'
).bootstrapTable(
'
append
'
, arr_left_data);
for
(
var
x =
0
; x < arr_right_data.length; x++
) { $(
"
#tb_order_right
"
).bootstrapTable(
"
insertRow
"
, { index: x, row: arr_right_data[x] }); }
//
$('#tb_order_right').bootstrapTable('append', arr_right_data);
//
append之后不能drop
break
; } i_statuindex
--
;
//
重新注册可拖拽
m_oTable.InitDrag();
//
m_oTable.InitDrop();
}); };
return
oInit; };
还是重点看看部分代码
1、注册左边可拖拽
$('#tb_order_left tbody').draggable({
helper: "clone",
start: function (event, ui) {
var old_left_data = JSON.stringify($('#tb_order_left').bootstrapTable("getData"));
var old_right_data = JSON.stringify($('#tb_order_right').bootstrapTable("getData"));
var odata = { index: ++i_statuindex, left_data: old_left_data, right_data: old_right_data };
arrdata.push(odata);
},
stop: function (event, ui) {
}
});
这里代码很简单,主要做了两件事:
(1)注册tbody的可拖拽,同样适用的JQuery UI的draggable事件。
(2)在开始拖拽前,保存两边表格的数据,用于还原的操作。
2、注册右边drop
$("#div_tableright div[class=fixed-table-container]").droppable({
drop: function (event, ui) {
var arrtr = $(ui.helper[0]).find("tr[class=selected]");
if (arrtr.length <= 0) {
alert("请先选中要插单的行");
return;
}
var oTop = ui.helper[0].offsetTop;
var iRowHeadHeight = 40;
var iRowHeight = 37;
var rowIndex = 0;
if (oTop <= iRowHeadHeight + iRowHeight / 2) {
rowIndex = 0;
}
else {
rowIndex = Math.ceil((oTop - iRowHeadHeight) / iRowHeight);
}
for (var i = 0; i < arrtr.length; i++) {
var arrtd = $(arrtr[i]).find("td");
var uniqueid = $(arrtr[i]).attr("data-uniqueid");
var rowdata = {
ORDER_NO: $(arrtd[1]).text(),
BODY_NO: $(arrtd[2]).text(),
VIN: $(arrtd[3]).text(),
TM_MODEL_MATERIAL_ID: $(arrtd[4]).text(),
ORDER_TYPE: $(arrtd[5]).text(),
ORDER_STATUS: $(arrtd[6]).text(),
CREATE_DATE: $(arrtd[7]).text() == "-" ? null : $(arrtd[7]).text(),
PLAN_DATE: $(arrtd[8]).text() == "-" ? null : $(arrtd[8]).text(),
VMS_NO: $(arrtd[9]).text(),
ENGIN_CODE: $(arrtd[10]).text(),
TRANS_CODE: $(arrtd[11]).text(),
OFFLINE_DATE_ACT: $(arrtd[12]).text() == "-" ? null : $(arrtd[12]).text(),
HOLD_RES: $(arrtd[13]).text(),
SPC_FLAG: $(arrtd[14]).text(),
TO_ORDER_ID: uniqueid
};
$("#tb_order_right").bootstrapTable("insertRow", { index: rowIndex++, row: rowdata });
$('#tb_order_left').bootstrapTable("removeByUniqueId", uniqueid);
}
oTableInit.InitDrag();
}
});
这里代码和之前有点变化
(1)注册#div_tableright div[class=fixed-table-container]标签的droppable,这个标签是Bootstrap Table表格初始化后自动生成的,为什么不直接注册表格#tb_order_right的droppable,是因为这个标签作用域太小,会导致拖过来的tbody捕捉不到drop事件。而注册表格外部的#div_tableright div[class=fixed-table-container]这个div标签可以解决问题。
(2)通过var arrtr = $(ui.helper[0]).find("tr[class=selected]");找到拖过来tbody里面选中的行,然后将数据取出依次插入右边表格,左边表格则依次删除行数据。
(3)这里有点麻烦的是找drop的位置,我们知道,要想将左边选中的行放到右边指定的位置,那么就得得到当前鼠标drop的位置,这里通过ui.helper[0].offsetTop属性来获得鼠标的Y轴位置,通过计算得到要插入的位置。
3、撤销操作
$("#btn_cancel").click(function () {
if (i_statuindex <= 0) {
return;
}
for (var i = 0; i < arrdata.length; i++) {
if (arrdata[i].index != i_statuindex) {
continue;
}
var arr_left_data = eval(arrdata[i].left_data);
var arr_right_data = eval(arrdata[i].right_data);
$('#tb_order_left').bootstrapTable('removeAll');
$('#tb_order_right').bootstrapTable('removeAll');
$('#tb_order_left').bootstrapTable('append', arr_left_data);
for (var x = 0; x < arr_right_data.length; x++) {
$("#tb_order_right").bootstrapTable("insertRow", { index: x, row: arr_right_data[x] });
}
//$('#tb_order_right').bootstrapTable('append', arr_right_data);//append之后不能drop
break;
}
i_statuindex--;
//重写注册可拖拽
m_oTable.InitDrag();
//m_oTable.InitDrop();
});
撤销操作和之前也基本相同。
四、总结
效果是完成了,美中不足的是每次拖过去的都是整个tbody,而不是选中的行,奈何多个选中的行无法用某一个容器包起来。暂时没找到合适的解决方案。先这样吧,等以后想到好的方案了再优化吧。
五、优化方案(02月6日)
1、注册drap的方法
oTableInit.InitDrag = function () {
$('#tb_order_left tbody').draggable({
helper: "clone",
start: function (event, ui) {
var old_left_data = JSON.stringify($('#tb_order_left').bootstrapTable("getData"));
var old_right_data = JSON.stringify($('#tb_order_right').bootstrapTable("getData"));
var odata = { index: ++i_statuindex, left_data: old_left_data, right_data: old_right_data };
arrdata.push(odata);
$(ui.helper[0]).find("tr[class!=selected]").remove();
},
stop: function (event, ui) {
}
});
};
增加了这一句$(ui.helper[0]).find("tr[class!=selected]").remove();这样在拖动的时候就看不到未选中的行了。
2、精准定位到右边表格指定位置:
oTableInit.InitDrop = function () {
$("#div_tableright div[class=fixed-table-container]").droppable({
drop: function (event, ui) {
var arrtr = $(ui.helper[0]).find("tr[class=selected]");
if (arrtr.length <= 0) {
toastr.warning('请先选中要插单的行');
return;
}
var oTop = ui.helper[0].offsetTop;
//因为表格每行的高度可能不一致,所以这里取插入行位置的办法是:取右边表格的行高依次累加,计算行索引。
var rowIndex = 0;
var bIsBottom = true;
var iRowHeadHeight = 40;
var addHeight = iRowHeadHeight;
var trs = $("#tb_order_right tbody tr");
for (var i = 0; i < trs.length; i++) {
addHeight += $(trs[i]).height();
if (addHeight > oTop) {
rowIndex = i;
bIsBottom = false;//这里参数用来定义拖动到右边表格最下面的情况,这时没有进入到此条件判断里面。
break;
}
}
if (bIsBottom) {
rowIndex = trs.length;
}
for (var i = 0; i < arrtr.length; i++) {
var arrtd = $(arrtr[i]).find("td");
var uniqueid = $(arrtr[i]).attr("data-uniqueid");
var rowdata = {
ORDER_NO: $(arrtd[1]).text(),
BODY_NO: $(arrtd[2]).text(),
VIN: $(arrtd[3]).text(),
TM_MODEL_MATERIAL_ID: $(arrtd[4]).text(),
ORDER_TYPE: $(arrtd[5]).text(),
ORDER_STATUS_NAME: $(arrtd[6]).text(),
CREATE_DATE: $(arrtd[7]).text() == "-" ? null : $(arrtd[7]).text(),
PLAN_DATE: $(arrtd[8]).text() == "-" ? null : $(arrtd[8]).text(),
VMS_NO: $(arrtd[9]).text(),
ENGIN_CODE: $(arrtd[10]).text(),
TRANS_CODE: $(arrtd[11]).text(),
OFFLINE_DATE_ACT: $(arrtd[12]).text() == "-" ? null : $(arrtd[12]).text(),
HOLD_RES: $(arrtd[13]).text(),
SPC_FLAG: $(arrtd[14]).text(),
TO_ORDER_ID: uniqueid,
ORDER_STATUS:0
};
$("#tb_order_right").bootstrapTable("insertRow", { index: rowIndex++, row: rowdata });
$('#tb_order_left').bootstrapTable("removeByUniqueId", uniqueid);
}
oTableInit.InitDrag();
}
});
};
因为每一行的行高不确定,是由行里面的数据动态撑出来的,所以这里也动态计算drop的位置。
至此,这个小的功能基本告一段落,基本的效果和待优化点也完成了。