• Bootstrap教程- Table 表格行实现多行同时拖拽效果
  • Belive 发表于 2016/2/22 16:20:00 | 分类标签: bootstrap教程 表格拖拽
  • 一、效果展示

    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的位置。

    至此,这个小的功能基本告一段落,基本的效果和待优化点也完成了。

  • 请您注意

    ·自觉遵守:爱国、守法、自律、真实、文明的原则

    ·尊重网上道德,遵守《全国人大常委会关于维护互联网安全的决定》及中华人民共和国其他各项有关法律法规

    ·严禁发表危害国家安全,破坏民族团结、国家宗教政策和社会稳定,含侮辱、诽谤、教唆、淫秽等内容的作品

    ·承担一切因您的行为而直接或间接导致的民事或刑事法律责任

    ·您在编程中国社区新闻评论发表的作品,本网站有权在网站内保留、转载、引用或者删除

    ·参与本评论即表明您已经阅读并接受上述条款

  • 感谢本文作者
  • 作者头像
  • 昵称:Belive
  • 加入时间:2013/6/13 0:00:00
  • TA的签名
  • 这家伙很懒,虾米都没写
  • +进入TA的空间
  • 以下内容也很赞哦
分享按钮