“This is the fourth day of my participation in the August Gwen Challenge.

preface

Today, we will use vue.js to implement a drag and drop across tables. Before developing this business, I also investigated many solutions on the Internet, but I don’t think they meet the current needs. So, the root develops a set of oneself again, convenient later maintenance.

What are the requirements? That is, multiple tables can be dragged to each other. That is, table items in table A can be dragged to table B, table items in table B can be dragged to table C, and they can be dragged to each other by single or multiple table items. Then, table D is restricted. Only one item can be dragged in at A time, and the password needs to be entered. If the password is correct, the dragged item will replace the item in Table D, and the replaced item in Table D will be put into table A, which can only be replaced, but not deleted.

Text is too boring, let’s put a GIF to see the effect.

The tables A, B, C and D mentioned above correspond to the tourist, operator, electrician and administrator below.

This graph is not static ~

In actual combat

Now that we know what we want to achieve, let’s get to work!

The first step

We need to determine which dependencies need to be installed for our requirement.

After initializing the project, install the above dependencies. You can see it in the package.json file:

  "dependencies": {
    "element-ui": "^ 2.15.5"."sortablejs": "^ 1.14.0"."vue": "^ 2.6.11." "
  },
Copy the code

The second step

To introduce ElementUI, see the ElementUI website for more details. Then we create a folder named DragTables under the directory SRC \ Components (assuming you have this folder). Inside the folder, we create another utils folder with the index.vue file. In the utils folder we create two more files: data.js and index.js.

That is, the file directory structure is:

- components
-- DragTables
--- utils
---- data.js
---- index.js
--- index.vue
Copy the code

The third step

The utils\data.js file is the data file, while utils\index.js is the utility function file.

Now let’s define the data.

// data.js
export default {
    / / visitors
    guestData: [{userId: "1".name: "a1".account: "w".jobTit: '1'.auth: '1'
    },
    {
        userId: "211".name: "a0".account: "w".jobTit: '1'.auth: '1'}]./ / administrator
    managerData: [{userId: "121".name: "a2".account: "w".jobTit: '1'.auth: '1'}]./ / electrician
    electricianData: [{userId: "12121".name: "a3".account: "w".jobTit: '1'.auth: '1'}]./ / operator
    operatorData: [{userId: "133e".name: "a4".account: "w".jobTit: '1'.auth: '1'}}]Copy the code

The userId must be unique.

Next, we define the utility function. Here we need a deep-copy method, which we define in the utils\index.js file.

// utils\index.js

/**
 * Deep copy
 * @param {Object} target* /
export function deepClone(target) {
    // Define a variable
    let result;
    // If it is an object that needs a deep copy
    if (typeof target === 'object') {
        // If it is an array
        if (Array.isArray(target)) {
            result = []; // Assign result to an array and iterate
            for (let i in target) {
                // Recursively clone each item in the array
                result.push(deepClone(target[i]));
            }
            // Determine if the current value is null; Directly assign to null
        } else if (target === null) {
            result = null;
            // If the current value is a RegExp object, assign it directly
        } else if (target.constructor === RegExp) {
            result = target;
        } else {
            // If the object is a normal object, the direct for in loop assigns all the values of the object recursively
            result = {};
            for (let i intarget) { result[i] = deepClone(target[i]); }}// If it is not an object, it is a primitive data type
    } else {
        result = target;
    }
    // Return the final result
    return result;
}
Copy the code

The fourth step

With the preparation done, we are ready to go to DragTables\index.vue for the most important combat. Let’s start by compartmenting the code.

1. UI page code

<template>
    <div
      class="main-box"
      v-loading="loadLoading"
      element-loading-text="Data loading"
      element-loading-spinner="el-icon-loading"
    >
      <div class="main-l">
<! Visitors - - - >
        <div class="top-name">
          <div class="top-box">
            <div class="top-count">Number of people: {{initStatus.newGuestList? guestData.length : newGuestList.length }}</div>
          </div>
          <p>tourists</p>
        </div>
        <div class="utable-box">
          <el-table
            ref="guestData"
            :data="guestData"
            :row-key="getUserId"
            @row-click="guestDataSelect"
            style="width: 100%; border: 1px solid #002368; margin-bottom: 20px"
            @selection-change="selectionGuestChange"
          >
            <el-table-column type="selection" width="55"> </el-table-column>
            <el-table-column
              prop="name"
              label="Name"
              align="center"
              show-overflow-tooltip
            ></el-table-column>
            <el-table-column
              prop="account"
              label="Account"
              align="center"
              show-overflow-tooltip
            >
            </el-table-column>
            <el-table-column
              prop="jobTit"
              label="Position"
              align="center"
              show-overflow-tooltip
              style="position: relative"
            >
              <template slot-scope="scope">
                <span>{{ scope.row.jobTit }}</span>
              </template>
            </el-table-column>
          </el-table>
        </div>
      </div>
      <div class="main-r">
<! -- Administrator -->
        <div class="top-name">
          <p>The administrator</p>
        </div>
        <el-table
          ref="managerData"
          :data="managerData"
          :row-key="getUserId"
          style="width: 100%; border: 1px solid #002368; margin-bottom: 10px"
        >
          <el-table-column
            prop="name"
            label="Name"
            align="center"
            show-overflow-tooltip
          >
          </el-table-column>
          <el-table-column
            prop="account"
            label="Account"
            align="center"
            show-overflow-tooltip
          >
          </el-table-column>
          <el-table-column
            prop="jobTit"
            label="Position"
            align="center"
            style="position: relative"
            show-overflow-tooltip
          >
            <template slot-scope="scope">
              <span>{{ scope.row.jobTit }}</span>
            </template>
          </el-table-column>
        </el-table>
<! -- Operator -->
        <div class="top-name">
          <div class="top-box">
            <div class="top-count">Toll: {{initStatus. NewOperatorList? operatorData.length : newOperatorList.length }}</div>
          </div>
          <p>The operator</p>
        </div>
        <div class="table-b">
          <div class="table-box">
            <el-table
              ref="operatorData"
              class="table-l"
              :data="operatorData"
              :row-key="getUserId"
              style="margin-bottom: 20px"
              @row-click="operatorDataSelect"
              @selection-change="selectionOperatorChange"
            >
              <el-table-column type="selection" width="55"> </el-table-column>
              <el-table-column
                prop="name"
                label="Name"
                align="center"
                show-overflow-tooltip
              >
              </el-table-column>
              <el-table-column
                prop="account"
                label="Account"
                align="center"
                show-overflow-tooltip
              >
              </el-table-column>
              <el-table-column
                prop="jobTit"
                label="Position"
                align="center"
                show-overflow-tooltip
              ></el-table-column>
              <el-table-column align="center" label="Operation">
                <template>
                   <el-button size="small" @click.stop="">The editor</el-button>
                </template>
              </el-table-column>
            </el-table>
          </div>
        </div>
<! - electrical -- -- >
        <div class="top-name">
          <div class="top-box">
            <div class="top-count">Toll: {{initStatus. NewElectricianList? electricianData.length : newElectricianList.length }}</div>
          </div>
          <p>The electrician</p>
        </div>
        <div class="table-b">
          <div class="table-box">
            <el-table
              ref="electricianData"
              :data="electricianData"
              :row-key="getUserId"
              @row-click="electricianDataSelect"
              class="table-l"
              @selection-change="selectionElectricianChange"
            >
              <el-table-column type="selection" width="55"> </el-table-column>
              <el-table-column
                prop="name"
                label="Name"
                align="center"
                show-overflow-tooltip
              >
              </el-table-column>
              <el-table-column
                prop="account"
                label="Account"
                align="center"
                show-overflow-tooltip
              >
              </el-table-column>
              <el-table-column
                prop="jobTit"
                label="Position"
                align="center"
                show-overflow-tooltip
              ></el-table-column>
              <el-table-column align="center" label="Operation">
                <template>
                    <el-button size="small" @click.stop="">The editor</el-button>
                </template>
              </el-table-column>
            </el-table>
          </div>
        </div>
      </div>
<! -- Password popup -->
      <el-dialog :visible.sync="passwordView"  width="30%">
        <el-form ref="form">
          <el-form-item>
            <el-input
              v-model="password"
              placeholder="Please enter your password"
              type="password"
              show-password
            ></el-input>
          </el-form-item>
        </el-form>
        <span slot="footer" class="dialog-footer">
          <el-button @click="passwordView = false">Take away</el-button>
          <el-button type="primary" @click="okChangeManager"
            >Determine < / el - button ></span>
      </el-dialog>
    </div>
</template>
Copy the code

It is divided into five parts: tourist form, administrator form, operator form, electrician form, password popover. The top left corner of each table dynamically displays the number of people in the table. Also, based on the GIF above, what if one table doesn’t match the rest of the table layout? For example, the electrician’s form and operator’s form are different from the tourist’s form and administrator’s form, with one more operation item. Here’s how I deal with it: I find the same part of them, that is, they all have name, account number and title. Electrician’s form, operator’s form is just one more operation item. So we can split it up into two tables, and we have a separate table for the operations. As long as the electrician’s form or operator’s form monitoring their corresponding data length can be synchronized. Why split it into two tables? This is because if you drag from the tourist table to the operator table, there is no operation on the tourist table (this is because the drag plugin used only copies the corresponding Node). B: That certainly won’t do!

2. Logical code

<script>
import Sortable from "sortablejs";
import { deepClone } from "./utils/index";
import tableData from "./utils/data";

export default {
  name: "DragTables".data: () = > ({
    passwordView: false.loadLoading: false.password: "".guestData: [].managerData: [].electricianData: [].operatorData: [].initStatus: {
      newGuestList: true.newManagerList: true.newOperatorList: true.newElectricianList: true,},fromItem: "".newGuestList: [].newManagerList: [].newOperatorList: [].newElectricianList: [].selectGuestList: [].selectOperatorList: [].selectElectricianList: [].managerOldIndex: 0,}).watch: {
    passwordView: "watchPasswordView",},created() {
    // Define static data
    this.obj = {
      newGuestList: ["guestData".0].newManagerList: ["managerData".3].newOperatorList: ["operatorData".1].newElectricianList: ["electricianData".2]};this.guestData = tableData.guestData;
    this.managerData = tableData.managerData;
    this.electricianData = tableData.electricianData;
    this.operatorData = tableData.operatorData;
  },
  mounted() {
    this.sortGuest();
    this.sortOperator();
    this.sortElectrician();
    this.sortManager();
  },
  methods: {
    // The password box is empty
    watchPasswordView(val) {
      if(! val) {this.password = ""; }},// Select the tourist
    guestDataSelect(row){ row.flag = ! row.flag;this.$refs.guestData.toggleRowSelection(row, row.flag);
    },
    // Select the operator
    operatorDataSelect(row){ row.flag = ! row.flag;this.$refs.operatorData.toggleRowSelection(row, row.flag);
    },
    // Select electrician
    electricianDataSelect(row){ row.flag = ! row.flag;this.$refs.electricianData.toggleRowSelection(row, row.flag);
    },
    // Make sure to drag to the administrator
    okChangeManager() {
      if (this.password.trim().length > 0) {
        const item = this[this.fromItem][this.managerOldIndex];
        if (item) {
          this.newManagerList = this.initStatus.newManagerList
            ? deepClone(this.managerData)
            : deepClone(this.newManagerList);
          this.newGuestList = this.initStatus.newGuestList
            ? deepClone(this.guestData)
            : deepClone(this.newGuestList);
          this.initStatus.newGuestList = false;
          this.initStatus.newManagerList = false;
          this.newManagerList = [item];
          this[this.fromItem].splice(this.managerOldIndex, 1);
          this.initStatus[this.fromItem] = false;

          if (this.managerData[0]) {
            const obj = deepClone(this.managerData[0]);
            this.newGuestList.push(obj);
            this.guestData.push(obj);
          }
          this.managerData = [item];
          switch (this.fromItem) {
            case "newGuestList":
              this.guestData = deepClone(this.newGuestList);
              break;
            case "newOperatorList":
              this.operatorData = deepClone(this.newOperatorList);
              break;
            case "newElectricianList":
              this.electricianData = deepClone(this.newElectricianList);
              break;
            default:
              break;
          }
          this.$message({
            message: "Drag succeeded!".type: "success"});this.password = "";
          this.passwordView = false;
        } else {
          this.$message({
            message: "Drag failed".type: "warning"});this.password = "";
          this.passwordView = false; }}else {
        this.$message({
          message: "Please enter your password".type: "warning"}); }},/ / get userId
    getUserId(row) {
      return row.userId;
    },
    // Encapsulate add data
    useAddNewData(evt, newData, oldData) {
      const item = this[this.fromItem][evt.oldIndex]; / / add item
      const loading = this.$loading({
        lock: true.text: "Loading".spinner: "el-icon-loading".background: "Rgba (0, 0, 0, 0.7)"});setTimeout(() = > {
        loading.close();
        this.$message({
          message: "Drag succeeded!".type: "success"}); },1000);
      this[newData] = this.initStatus[newData]
        ? deepClone(oldData)
        : deepClone(this[newData]);
      this.initStatus[newData] = false;
      oldData.push(item);
      this[newData].push(item);
      this[this.fromItem].splice(evt.oldIndex, 1);
      this.$refs[this.obj[newData][0]].$el
        .querySelectorAll(".el-table__body-wrapper > table > tbody") [0]
        .removeChild(evt.item);
    },
    // Encapsulates add (multiple) data
    useAddsNewData(evt, newData, oldData) {
      const arr = [];
      for (
        let index = 0;
        index < this[`selectThe ${this.fromItem.split("new") [1]}`].length;
        index++
      ) {
        const element = this[`selectThe ${this.fromItem.split("new") [1]}`][index];
        arr.push(element.userId);
      }
      const loading = this.$loading({
        lock: true.text: "Loading".spinner: "el-icon-loading".background: "Rgba (0, 0, 0, 0.7)"});setTimeout(() = > {
        loading.close();
        this.$message({
          message: "Batch drag successful!".type: "success"}); },1000);
      this[newData] = this.initStatus[newData]
        ? deepClone(oldData)
        : deepClone(this[newData]);
      this.initStatus[newData] = false;
      this[newData].push(... this[`selectThe ${this.fromItem.split("new") [1]}`]);
      this[this.obj[newData][0]].push( ... this[`selectThe ${this.fromItem.split("new") [1]}`]);this.useDel(
        this[`selectThe ${this.fromItem.split("new") [1]}`].this[this.obj[this.fromItem][0]]);this.$refs[this.obj[newData][0]].$el
        .querySelectorAll(".el-table__body-wrapper > table > tbody") [0]
        .removeChild(evt.item);
    },
    // Encapsulate initialization data
    useInitData(fromItem, oldData) {
      this.fromItem = fromItem;
      this[fromItem] = this.initStatus[fromItem]
        ? deepClone(oldData)
        : deepClone(this[fromItem]);
      this.initStatus[fromItem] = false;
    },
    // Batch delete (array)
    useDel(data, currentData) {
      for (let i = 0; i < data.length; i++) {
        const element = data[i];
        for (let j = 0; j < currentData.length; j++) {
          const item = currentData[j];
          if (item === element) {
            currentData.splice(j, 1); }}}},// Restore the initial state
    useReduction(i) {
      const arr = [
        {
          data: "guestData".sletData: "selectGuestList"}, {data: "operatorData".sletData: "selectOperatorList"}, {data: "electricianData".sletData: "selectElectricianList",},];this.$refs[arr[i].data].clearSelection();
      this[arr[i].sletData] = [];
    },
    // Listen for the visitor table selection
    selectionGuestChange(val) {
      this.selectGuestList = val;
    },
    // Listen for operator table selection
    selectionOperatorChange(val) {
      this.selectOperatorList = val;
    },
    // monitor electrician table selection
    selectionElectricianChange(val) {
      console.log(val);
      this.selectElectricianList = val;
    },
    // Drag tourists
    sortGuest() {
      const el = this.$refs.guestData.$el.querySelectorAll(
        ".el-table__body-wrapper > table> tbody") [0];
      Sortable.create(el, {
        ghostClass: "sortable-ghost".sort: false.animation: 150.group: {
          name: "person".pull: true.put: true,},setData: function (
          /** DataTransfer */ dataTransfer,
          /** HTMLElement*/ dragEl
        ) {
          dataTransfer.setData("Text", dragEl.textContent); // `dataTransfer` object of HTML5 DragEvent
        },
        onStart: () = > {
          this.useInitData("newGuestList".this.guestData); / / initialization
        },
        onAdd: (evt) = > {
          this.useReduction(0);
          if (this[`selectThe ${this.fromItem.split("new") [1]}`].length === 0) {
            this.useAddNewData(evt, "newGuestList".this.guestData);
          } else {
            this.useAddsNewData(evt, "newGuestList".this.guestData); }},onEnd: (ev) = > {
          if (this[`selectThe ${this.fromItem.split("new") [1]}`].length ! = =0) {
            this.useReduction(0);
            if (ev.to.outerText.indexOf("Administrator")! = = -1) {
              this.$nextTick(() = > {
                this.newGuestList = deepClone(this.guestData);
                const data = deepClone(this.guestData);
                this.guestData = data;
              });
            } else {
              const data = deepClone(this.guestData);
              this.newGuestList = deepClone(this.guestData);
              this.guestData = [];
              this.$nextTick(() = > {
                this.guestData = data; }); }}else {
            this.$nextTick(() = > {
              this.guestData = this.newGuestList; }); }}}); },// Drag operator
    sortOperator() {
      const el = this.$refs.operatorData.$el.querySelectorAll(
        ".el-table__body-wrapper > table> tbody") [0];
      Sortable.create(el, {
        ghostClass: "sortable-ghost".sort: false.animation: 150.group: {
          name: "person".pull: true.put: true,},setData: function (
          /** DataTransfer */ dataTransfer,
          /** HTMLElement*/ dragEl
        ) {
          dataTransfer.setData("Text", dragEl.textContent); // `dataTransfer` object of HTML5 DragEvent
        },
        onStart: () = > {
          this.useInitData("newOperatorList".this.operatorData); / / initialization
        },
        onAdd: (evt) = > {
          this.useReduction(1);
          if (this[`selectThe ${this.fromItem.split("new") [1]}`].length === 0) {
            this.useAddNewData(evt, "newOperatorList".this.operatorData);
          } else {
            this.useAddsNewData(evt, "newOperatorList".this.operatorData); }},onEnd: (ev) = > {
          if (this[`selectThe ${this.fromItem.split("new") [1]}`].length ! = =0) {
            this.useReduction(1);
            if (ev.to.outerText.indexOf("Administrator")! = = -1) {
              this.$nextTick(() = > {
                this.newOperatorList = deepClone(this.operatorData);
                const data = deepClone(this.operatorData);
                this.operatorData = data;
              });
            } else {
              const data = deepClone(this.operatorData);
              this.newOperatorList = deepClone(this.operatorData);
              this.operatorData = [];
              this.$nextTick(() = > {
                this.operatorData = data; }); }}else {
            this.$nextTick(() = > {
              this.operatorData = this.newOperatorList; }); }}}); },// Drag the electrician
    sortElectrician() {
      const el = this.$refs.electricianData.$el.querySelectorAll(
        ".el-table__body-wrapper > table> tbody") [0];
      Sortable.create(el, {
        ghostClass: "sortable-ghost".sort: false.animation: 150.group: {
          name: "person".pull: true.put: true,},setData: function (
          /** DataTransfer */ dataTransfer,
          /** HTMLElement*/ dragEl
        ) {
          dataTransfer.setData("Text", dragEl.textContent); // `dataTransfer` object of HTML5 DragEvent
        },
        onStart: () = > {
          this.useInitData("newElectricianList".this.electricianData); / / initialization
        },
        onAdd: (evt) = > {
          this.useReduction(2);
          if (this[`selectThe ${this.fromItem.split("new") [1]}`].length === 0) {
            this.useAddNewData(evt, "newElectricianList".this.electricianData);
          } else {
            this.useAddsNewData(
              evt,
              "newElectricianList".this.electricianData ); }},onEnd: (ev) = > {
          if (this[`selectThe ${this.fromItem.split("new") [1]}`].length ! = =0) {
            this.useReduction(2);
            if (ev.to.outerText.indexOf("Administrator")! = = -1) {
              this.$nextTick(() = > {
                this.newElectricianList = deepClone(this.electricianData);
                const data = deepClone(this.electricianData);
                this.electricianData = data;
              });
            } else {
              const data = deepClone(this.electricianData);
              this.newElectricianList = deepClone(this.electricianData);
              this.electricianData = [];
              this.$nextTick(() = > {
                this.electricianData = data; }); }}else {
            this.$nextTick(() = > {
              this.electricianData = this.newElectricianList; }); }}}); },// Drag the administrator
    sortManager() {
      const el = this.$refs.managerData.$el.querySelectorAll(
        ".el-table__body-wrapper > table > tbody") [0];
      Sortable.create(el, {
        ghostClass: "sortable-ghost".sort: false.animation: 150.group: {
          name: "person".pull: false.put: true,},setData: function (
          /** DataTransfer */ dataTransfer,
          /** HTMLElement*/ dragEl
        ) {
          dataTransfer.setData("Text", dragEl.textContent); // `dataTransfer` object of HTML5 DragEvent
        },
        onAdd: (evt) = > {
          console.log(evt)
          switch (this.fromItem) {
            case "newGuestList":
              {
                const data = deepClone(this.guestData);
                this.guestData = [];
                this.$nextTick(() = > {
                  this.guestData = data;
                });
              }
              break;
            case "newOperatorList":
              {
                const data = deepClone(this.operatorData);
                this.operatorData = [];
                this.$nextTick(() = > {
                  this.operatorData = data;
                });
              }
              break;
            case "newElectricianList":
              {
                const data = deepClone(this.electricianData);
                this.electricianData = [];
                this.$nextTick(() = > {
                  this.electricianData = data;
                });
              }
              break;
            default:
              break;
          }
          if (this[`selectThe ${this.fromItem.split("new") [1]}`].length < 2) {
            this.managerOldIndex = evt.oldIndex;
            this.passwordView = true;
          } else {
            this.$message({
              message: "Batch failure!".type: "warning"}); }this.$refs.managerData.$el
            .querySelectorAll(".el-table__body-wrapper > table > tbody") [0] .removeChild(evt.item); }}); ,}}};</script>
Copy the code

The drag-and-drop plugin we use here is SorTableJS, which is very powerful. I will briefly introduce its use here.

Sortable.create(el,{})
Copy the code

Here, we need to pass two parameters to the create method under Sortable. The first parameter is the el node, which defines each item that can be dragged, such as:

const el = this.$refs.guestData.$el.querySelectorAll(
  ".el-table__body-wrapper > table> tbody") [0];
Copy the code

This means the entry in the visitor table.

The second parameter is a configurable parameter that defines configuration properties and methods. Details parameters and the method can be reference to Chinese website: http://www.sortablejs.com/.

Next, we’ll look at the logical implementation in layers. The object returned by the data method is just some data that we see initialized, but we won’t elaborate on it here. Next comes the Watch property, which listens for the passwordView property of the object returned from the Data method with the corresponding watchPasswordView method.

We find it in the methods property below, which simply initialize (empty) the contents of the password box each time.

// The password box is empty
watchPasswordView(val) {
  if(! val) {this.password = ""; }},Copy the code

Then we go to the Created method, and we basically do two things. One is to define a static object (not defined in the data method, so we don’t do reactive processing, for performance optimization), and the other is to get table data.

created() {
// Define static data
this.obj = {
  newGuestList: ["guestData".0].newManagerList: ["managerData".3].newOperatorList: ["operatorData".1].newElectricianList: ["electricianData".2]};// Get table data
this.guestData = tableData.guestData;
this.managerData = tableData.managerData;
this.electricianData = tableData.electricianData;
this.operatorData = tableData.operatorData;
},
Copy the code

Then we go to the mounted method. We see that the drag-and-drop table methods are called in the mounted method. Why are they in the Mounted method? To use drag and drop, you must wait until the instance is mounted.

Finally, we will enter the methods property, where many methods are defined, but let’s start with the functional section.

mounted() {
    this.sortGuest();
    this.sortOperator();
    this.sortElectrician();
    this.sortManager();
}
Copy the code

These methods are mainly selected by clicking the corresponding table items.

// Select the tourist
guestDataSelect(row){ row.flag = ! row.flag;this.$refs.guestData.toggleRowSelection(row, row.flag);
},
// Select the operator
operatorDataSelect(row){ row.flag = ! row.flag;this.$refs.operatorData.toggleRowSelection(row, row.flag);
},
// Select electrician
electricianDataSelect(row){ row.flag = ! row.flag;this.$refs.electricianData.toggleRowSelection(row, row.flag);
}
Copy the code

Saves the selected table entry data.

// Listen for the visitor table selection
selectionGuestChange(val) {
  this.selectGuestList = val;
},
// Listen for operator table selection
selectionOperatorChange(val) {
  this.selectOperatorList = val;
},
// monitor electrician table selection
selectionElectricianChange(val) {
  this.selectElectricianList = val;
}
Copy the code

Define a unique key for each table entry.

/ / get userId
getUserId(row) {
  return row.userId;
},
Copy the code

Let’s get to the key part, which is drag. It doesn’t look like a lot of code, but this could be optimized, but to make it easier to tell, let’s do this. Sortable.create(el, {}); sortable.create (el, {}); There are onStart(), onAdd(), onEnd() methods.

// Drag tourists
sortGuest() {
  const el = this.$refs.guestData.$el.querySelectorAll(
    ".el-table__body-wrapper > table> tbody") [0];
  Sortable.create(el, {
    ghostClass: "sortable-ghost".sort: false.animation: 150.group: {
      name: "person".pull: true.put: true,},setData: function (
      /** DataTransfer */ dataTransfer,
      /** HTMLElement*/ dragEl
    ) {
      dataTransfer.setData("Text", dragEl.textContent); // `dataTransfer` object of HTML5 DragEvent
    },
    onStart: () = > {
      this.useInitData("newGuestList".this.guestData); / / initialization
    },
    onAdd: (evt) = > {
      this.useReduction(0);
      if (this[`selectThe ${this.fromItem.split("new") [1]}`].length === 0) {
        this.useAddNewData(evt, "newGuestList".this.guestData);
      } else {
        this.useAddsNewData(evt, "newGuestList".this.guestData); }},onEnd: (ev) = > {
      if (this[`selectThe ${this.fromItem.split("new") [1]}`].length ! = =0) {
        this.useReduction(0);
        if (ev.to.outerText.indexOf("Administrator")! = = -1) {
          this.$nextTick(() = > {
            this.newGuestList = deepClone(this.guestData);
            const data = deepClone(this.guestData);
            this.guestData = data;
          });
        } else {
          const data = deepClone(this.guestData);
          this.newGuestList = deepClone(this.guestData);
          this.guestData = [];
          this.$nextTick(() = > {
            this.guestData = data; }); }}else {
        this.$nextTick(() = > {
          this.guestData = this.newGuestList; }); }}}); },// Drag operator
sortOperator() {
  const el = this.$refs.operatorData.$el.querySelectorAll(
    ".el-table__body-wrapper > table> tbody") [0];
  Sortable.create(el, {
    ghostClass: "sortable-ghost".sort: false.animation: 150.group: {
      name: "person".pull: true.put: true,},setData: function (
      /** DataTransfer */ dataTransfer,
      /** HTMLElement*/ dragEl
    ) {
      dataTransfer.setData("Text", dragEl.textContent); // `dataTransfer` object of HTML5 DragEvent
    },
    onStart: () = > {
      this.useInitData("newOperatorList".this.operatorData); / / initialization
    },
    onAdd: (evt) = > {
      this.useReduction(1);
      if (this[`selectThe ${this.fromItem.split("new") [1]}`].length === 0) {
        this.useAddNewData(evt, "newOperatorList".this.operatorData);
      } else {
        this.useAddsNewData(evt, "newOperatorList".this.operatorData); }},onEnd: (ev) = > {
      if (this[`selectThe ${this.fromItem.split("new") [1]}`].length ! = =0) {
        this.useReduction(1);
        if (ev.to.outerText.indexOf("Administrator")! = = -1) {
          this.$nextTick(() = > {
            this.newOperatorList = deepClone(this.operatorData);
            const data = deepClone(this.operatorData);
            this.operatorData = data;
          });
        } else {
          const data = deepClone(this.operatorData);
          this.newOperatorList = deepClone(this.operatorData);
          this.operatorData = [];
          this.$nextTick(() = > {
            this.operatorData = data; }); }}else {
        this.$nextTick(() = > {
          this.operatorData = this.newOperatorList; }); }}}); },// Drag the electrician
sortElectrician() {
  const el = this.$refs.electricianData.$el.querySelectorAll(
    ".el-table__body-wrapper > table> tbody") [0];
  Sortable.create(el, {
    ghostClass: "sortable-ghost".sort: false.animation: 150.group: {
      name: "person".pull: true.put: true,},setData: function (
      /** DataTransfer */ dataTransfer,
      /** HTMLElement*/ dragEl
    ) {
      dataTransfer.setData("Text", dragEl.textContent); // `dataTransfer` object of HTML5 DragEvent
    },
    onStart: () = > {
      this.useInitData("newElectricianList".this.electricianData); / / initialization
    },
    onAdd: (evt) = > {
      this.useReduction(2);
      if (this[`selectThe ${this.fromItem.split("new") [1]}`].length === 0) {
        this.useAddNewData(evt, "newElectricianList".this.electricianData);
      } else {
        this.useAddsNewData(
          evt,
          "newElectricianList".this.electricianData ); }},onEnd: (ev) = > {
      if (this[`selectThe ${this.fromItem.split("new") [1]}`].length ! = =0) {
        this.useReduction(2);
        if (ev.to.outerText.indexOf("Administrator")! = = -1) {
          this.$nextTick(() = > {
            this.newElectricianList = deepClone(this.electricianData);
            const data = deepClone(this.electricianData);
            this.electricianData = data;
          });
        } else {
          const data = deepClone(this.electricianData);
          this.newElectricianList = deepClone(this.electricianData);
          this.electricianData = [];
          this.$nextTick(() = > {
            this.electricianData = data; }); }}else {
        this.$nextTick(() = > {
          this.electricianData = this.newElectricianList; }); }}}); },// Drag the administrator
sortManager() {
  const el = this.$refs.managerData.$el.querySelectorAll(
    ".el-table__body-wrapper > table > tbody") [0];
  Sortable.create(el, {
    ghostClass: "sortable-ghost".sort: false.animation: 150.group: {
      name: "person".pull: false.put: true,},setData: function (
      /** DataTransfer */ dataTransfer,
      /** HTMLElement*/ dragEl
    ) {
      dataTransfer.setData("Text", dragEl.textContent); // `dataTransfer` object of HTML5 DragEvent
    },
    onAdd: (evt) = > {
      console.log(evt)
      switch (this.fromItem) {
        case "newGuestList":
          {
            const data = deepClone(this.guestData);
            this.guestData = [];
            this.$nextTick(() = > {
              this.guestData = data;
            });
          }
          break;
        case "newOperatorList":
          {
            const data = deepClone(this.operatorData);
            this.operatorData = [];
            this.$nextTick(() = > {
              this.operatorData = data;
            });
          }
          break;
        case "newElectricianList":
          {
            const data = deepClone(this.electricianData);
            this.electricianData = [];
            this.$nextTick(() = > {
              this.electricianData = data;
            });
          }
          break;
        default:
          break;
      }
      if (this[`selectThe ${this.fromItem.split("new") [1]}`].length < 2) {
        this.managerOldIndex = evt.oldIndex;
        this.passwordView = true;
      } else {
        this.$message({
          message: "Batch failure!".type: "warning"}); }this.$refs.managerData.$el
        .querySelectorAll(".el-table__body-wrapper > table > tbody") [0] .removeChild(evt.item); }}); },Copy the code

Since onStart(), onAdd(), and onEnd() have similar logic to sortGuest() and other drag and drop methods, we’ll isolate sortGuest().

First, you can see that the onStart() method calls this.useinitData (), which means that when you start dragging and dropping, you call this.useinitData (), which takes a string as its first argument and an array as its second argument, which is the current table data. This method does two things: define an identifier that records the current table when the drag starts, and clone the current table’s data into a new array.

// Encapsulate initialization data
useInitData(fromItem, oldData) {
  this.fromItem = fromItem;
  this[fromItem] = this.initStatus[fromItem]
    ? deepClone(oldData)
    : deepClone(this[fromItem]);
  this.initStatus[fromItem] = false;
}
Copy the code

Next, we’ll examine the onAdd() method, which means that it fires when dragged to add to the current table. This method does two things, one is to call the useReduction method, and the other is to call different methods based on whether the old table item has selected data.

onAdd: (evt) = > {
    this.useReduction(0);
    if (this[`selectThe ${this.fromItem.split("new") [1]}`].length === 0) {
      this.useAddNewData(evt, "newGuestList".this.guestData);
    } else {
      this.useAddsNewData(evt, "newGuestList".this.guestData); }}Copy the code

The following is the useReduction method.

// Restore the initial state
useReduction(i) {
  const arr = [
    {
      data: "guestData".sletData: "selectGuestList"}, {data: "operatorData".sletData: "selectOperatorList"}, {data: "electricianData".sletData: "selectElectricianList",},];this.$refs[arr[i].data].clearSelection(); // Empty the check box
  this[arr[i].sletData] = []; // Empty the selection data
}
Copy the code

Next, look at the useAddNewData() method, which encapsulates a drag-and-add item method. The first argument is the first argument in the onAdd() method, the second argument is a string that identifies the new data, and the third argument is the table data object that is currently being added.

First, we fetch the table item we want to add, and then use the this.$loading() method to first load an animation. Update old and new data, add the current item to the current table, and delete data from the old table, using the removeChild method to remove page elements.

The useAddsNewData method does the same thing, but iterates over the selected data.

// Encapsulate add data
useAddNewData(evt, newData, oldData) {
  const item = this[this.fromItem][evt.oldIndex]; / / add item

  const loading = this.$loading({
    lock: true.text: "Loading".spinner: "el-icon-loading".background: "Rgba (0, 0, 0, 0.7)"});setTimeout(() = > {
    loading.close();
    this.$message({
      message: "Drag succeeded!".type: "success"}); },1000);

  this[newData] = this.initStatus[newData]
    ? deepClone(oldData)
    : deepClone(this[newData]);
  this.initStatus[newData] = false;
  oldData.push(item);
  this[newData].push(item);
  this[this.fromItem].splice(evt.oldIndex, 1);
  this.$refs[this.obj[newData][0]].$el
    .querySelectorAll(".el-table__body-wrapper > table > tbody") [0]
    .removeChild(evt.item);
},

// Encapsulates add (multiple) data
useAddsNewData(evt, newData, oldData) {
  const arr = [];
  for (
    let index = 0;
    index < this[`selectThe ${this.fromItem.split("new") [1]}`].length;
    index++
  ) {
    const element = this[`selectThe ${this.fromItem.split("new") [1]}`][index];
    arr.push(element.userId);
  }
  const loading = this.$loading(
    lock: true.text: "Loading".spinner: "el-icon-loading".background: "Rgba (0, 0, 0, 0.7)"});setTimeout(() = > {
    loading.close();
    this.$message({
      message: "Batch drag successful!".type: "success"}); },1000);
  this[newData] = this.initStatus[newData]
    ? deepClone(oldData)
    : deepClone(this[newData]);
  this.initStatus[newData] = false;
  this[newData].push(... this[`selectThe ${this.fromItem.split("new") [1]}`]);
  this[this.obj[newData][0]].push( ... this[`selectThe ${this.fromItem.split("new") [1]}`]);this.useDel(
    this[`selectThe ${this.fromItem.split("new") [1]}`].this[this.obj[this.fromItem][0]]);this.$refs[this.obj[newData][0]].$el
    .querySelectorAll(".el-table__body-wrapper > table > tbody") [0]
    .removeChild(evt.item);
},
Copy the code

One of the methods we use in the useAddsNewData method is the useDel method, which is used to batch delete the elements of the array, where the old data deletes specified items.

// Batch delete (array)
useDel(data, currentData) {
  for (let i = 0; i < data.length; i++) {
    const element = data[i];
    for (let j = 0; j < currentData.length; j++) {
      const item = currentData[j];
      if (item === element) {
        currentData.splice(j, 1); }}}},Copy the code

We also have the final method okChangeManager(), which is the outer layer of the method to determine whether the password is empty. I’m simplifying the logic here. Again, we need to get the added item, because the added item can only be one, so here we’ll look directly if conditions permit. We need to know that the added item is added to the administrator data table, the original data is moved to the visitor table, and the added item is removed from the original table data.

// Make sure to drag to the administrator
okChangeManager() {
  if (this.password.trim().length > 0) {
    const item = this[this.fromItem][this.managerOldIndex]; / / add item
    if (item) {
      this.newManagerList = this.initStatus.newManagerList
        ? deepClone(this.managerData)
        : deepClone(this.newManagerList);
      this.newGuestList = this.initStatus.newGuestList
        ? deepClone(this.guestData)
        : deepClone(this.newGuestList);
      this.initStatus.newGuestList = false;
      this.initStatus.newManagerList = false;
      this.newManagerList = [item];
      this[this.fromItem].splice(this.managerOldIndex, 1);
      this.initStatus[this.fromItem] = false;

      if (this.managerData[0]) {
        const obj = deepClone(this.managerData[0]);
        this.newGuestList.push(obj);
        this.guestData.push(obj);
      }
      this.managerData = [item];
      switch (this.fromItem) {
        case "newGuestList":
          this.guestData = deepClone(this.newGuestList);
          break;
        case "newOperatorList":
          this.operatorData = deepClone(this.newOperatorList);
          break;
        case "newElectricianList":
          this.electricianData = deepClone(this.newElectricianList);
          break;
        default:
          break;
      }
      this.$message({
        message: "Drag succeeded!".type: "success"});this.password = "";
      this.passwordView = false;
    } else {
      this.$message({
        message: "Drag failed".type: "warning"});this.password = "";
      this.passwordView = false; }}else {
    this.$message({
      message: "Please enter your password".type: "warning"}); }},Copy the code

3. Style code

<style scoped>
.top-name {
  padding: 10px;
  background: # 333;
  font-size: 14px;
  position: relative;
}
.top-name > p {
  color: #00a7ff;
  text-align: center;
  font-size: 16px;
}
.main-box {
  display: flex;
  justify-content: space-between;
}
.main-l..main-r {
  width: 48%;
  position: relative;
}
.table-b {
  position: relative;
}
.isdel {
  text-align: center;
  font-size: 18px;
  color: #fff;
}
.top-box {
  display: flex;
  height: 30px;
  justify-content: space-between;
  align-items: center;
}
.top-count {
  color: #fff;
  font-size: 14px;
  margin-left:10px ;
}
.utable-box {
  overflow: auto;
  height: 700px;
}
.table-box {
  overflow: auto;
  height: 230px;
  margin-bottom: 10px;
  border: 1px solid # 333;
}
</style>
Copy the code

Styles are not analyzed too much here.

conclusion

To see the full code and see it in action, visit the source address below:

https://github.com/maomincoding/drag-tables
Copy the code

about

Author: Vam’s Golden Bean Road.

CSDN blog star of the Year 2019, CSDN blog has reached millions of visitors. Nuggets blog post repeatedly pushed to the home page, the total page view has reached hundreds of thousands.

In addition, my public number: front-end experience robbed road, the public continues to update the latest front-end technology and related technical articles. Welcome to pay attention to my public number, let us together in front of the road experience rob it! Go!