Vue.js-based draggable sortable table component

Vue Table Sortable component

  • This is a draggable sortable table component based on Vue.js.
    • html
      • The component receives two props: dataSource and keyWords, where dataSource is an array, keyWords is a string, and the default value is ‘index’.
    • js
    • The parent component uses
    • Summarize

This is a Vue.js-based draggable sortable table component.

html

<template>
  <!-- Use slots, pass customRow, newDataSource as slot properties to slot content -->
  <slot name="sort" :customRow="customRow" :dataSource="newDataSource"></slot>
</template>

The

component receives two props: dataSource and keyWords, where dataSource is an array, keyWords is a string, and the default value is ‘index’.

js

<script lang="ts">
import {<!-- --> ref, computed, defineComponent } from 'vue'

export default defineComponent({<!-- -->
  props:{<!-- -->
    // Receive dataSource and keyWords attributes passed in from outside
    dataSource: {<!-- -->type: Array, default: []},
    keyWords: {<!-- -->type: String, default: 'index'}
  },
  emits: ['update:dataSource','sortConfirm'], // Define the event dispatched to the parent component
  setup(props, {<!-- -->emit}){<!-- -->
    const newDataSource = computed({<!-- -->
      get() {<!-- -->
        return props. dataSource
      },
      set(value) {<!-- -->
        return emit('update:dataSource', value) // When the new data source changes, dispatch the update:dataSource event to pass the new data source to the parent component
      }
    })

    const dragItem = ref<any>(); // current drag item
    const dragIndex = ref<any>(); // Index of the current drag item
    const targetArr = ref<any>([]); // Array to store target row

    // Method to customize row content
    const customRow = (record:any, index:number) => {<!-- -->
      return {<!-- -->
        style: {<!-- -->
          cursor: 'move'
        },
        // mouseover
        onMouseenter: (event:any) => {<!-- -->
          let ev = event || window.event;
          if(ev & amp; & amp; ev. target){<!-- -->
            ev.target.draggable = true; // set the current line to be draggable
          }
        },
        // start dragging
        onDragstart: (event: Event|undefined) => {<!-- -->
          let ev = event || window.event;
          ev & amp; & amp; ev.stopPropagation(); // prevent event bubbling
          dragItem.value = record; // Set the current dragged item
          dragIndex.value = index; // Set the index of the current drag item
        },
        // dragging
        onDragover: (event: any) => {<!-- -->
          let ev = event || window.event;
          ev.preventDefault(); // prevent default drag behavior
          if(index == dragIndex.value) return; // If the index of the target row is the same as the current row, do nothing

          let nowLine = ev?.target.closest('tr.ant-table-row'); // Get the closest parent element with class 'tr.ant-table-row'

          if(!targetArr.value.includes(nowLine)){<!-- -->
            targetArr.value.push(nowLine); //Add the target line to the target line array
          }

          if (index > dragIndex.value) {<!-- --> // If the target row index is greater than the current row index
            if (!nowLine.classList.contains('afterLine')) {<!-- --> // if the target line does not have afterLine class name
              targetArr.value.forEach((ele:any) => {<!-- --> // Remove beferLine and afterLine class names of other target rows
                ele.classList.remove('beferLine')
                ele.classList.remove('afterLine')
              });
              nowLine.classList.add('afterLine') // Add afterLine class name to the target line
            }
          } else {<!-- --> // if the target row index is less than the current row index
            if (!nowLine.classList.contains('beferLine')) {<!-- --> // if the target line does not have the beferLine class name
              targetArr.value.forEach((ele:any) => {<!-- --> // Remove beferLine and afterLine class names of other target rows
                ele.classList.remove('beferLine')
                ele.classList.remove('afterLine')
              });
              nowLine.classList.add('beferLine') // add beferLine class name to the target line
            }
          }
        },
        // place the dragged item
        onDrop: (event: Event|undefined) => {<!-- -->
          let ev = event || window.event;
          ev & amp; & amp; ev.stopPropagation(); // prevent event bubbling

          emit('sortConfirm',{<!-- --> // dispatch the sortConfirm event to pass the sort confirmation information to the parent component
            id: newDataSource.value[dragIndex.value]?.id, // the id of the current drag item
            toId: newDataSource.value[index]?.id // id of the target row
          })

          newDataSource.value.splice(dragIndex.value,1); // remove the current drag item from the data source
          newDataSource.value.splice(index,0,dragItem.value); // Insert the current drag item into the target row position
          
          targetArr.value.forEach((ele:any) => {<!-- --> // Remove the beferLine and afterLine class names of all elements in targetArr
            ele.classList.remove('beferLine')
            ele.classList.remove('afterLine')
          })
          targetArr.value = []; // Clear target row array
        }
      }
    }

    return {<!-- -->
      customRow,
      newDataSource
    }
  }
})
</script>

Parent component uses

<template>
  <div>
    <!-- Use child components and bind the dataSource attribute through v-model -->
    <CustomComponent v-model="dataSource"></CustomComponent>
  </div>
</template>

<script>
import CustomComponent from '@/path/to/CustomComponent.vue';

export default {<!-- -->
  components: {<!-- -->
    CustomComponent
  },
  data() {<!-- -->
    return {<!-- -->
      dataSource: [
        // Initialize the data source
        {<!-- --> id: 1, name: 'Item 1' },
        {<!-- --> id: 2, name: 'Item 2' },
        {<!-- --> id: 3, name: 'Item 3' },
        //...
      ]
    };
  }
};
</script>

In the parent component, use the label to introduce the child component CustomComponent, and bind the dataSource attribute of the parent component to the v-model of the child component through the v-model directive. In the data option, initialize the dataSource array as the initial data, and you can modify and supplement data items according to actual needs.

In this way, in the parent component, you can access the data source in the child component by accessing this.dataSource, manipulate it and update it in real time

Summary

This component uses Vue 3’s ref, computed and defineComponent, and the setup hook function.

In the setup hook function, we define the computed property newDataSource to monitor changes in props.dataSource, and dispatch an event named update:dataSource to pass a new data source to the parent component when a change occurs.

At the same time, we also define several responsive variables dragItem, dragIndex and targetArr, which are used to store the current dragged item, the index of the current dragged item and the array of the target row respectively.

Among them, customRow is a method for customizing row content. It returns an object that contains handlers for events such as mouseover, start dragging, dragging, and dropping dragged items.

Finally, in the return value of the component, we return customRow and newDataSource as reactive data for use in templates. antd for component library