How Vue-Element-Admin Implements Drag-and-Drop Table Functionality Using vuedraggable
Vue-Element-Admin implements drag-and-drop tables by wrapping the vuedraggable component around list data, binding arrays to the :list prop, enabling cross-list dragging with the group attribute, and synchronizing state through custom helper methods.
The PanJiaChen/vue-element-admin repository provides a production-ready implementation of drag-and-drop table functionality using vuedraggable in the DndList component. This pattern allows users to reorder items within a single list or move items between two separate lists while maintaining reactive state synchronization.
Core Implementation Pattern in Vue-Element-Admin
The implementation follows a structured component-based architecture that leverages vuedraggable (version 2.20.0 as specified in package.json) to handle the underlying Sortable.js logic.
Importing and Registering vuedraggable
The component begins by importing the draggable library and registering it locally. This encapsulation ensures the drag-and-drop functionality remains modular and reusable across the application.
import draggable from 'vuedraggable'
export default {
name: 'DndList',
components: { draggable },
props: {
list1: {
type: Array,
default: () => []
},
list2: {
type: Array,
default: () => []
}
}
}
Binding Data with the :list Prop
The vuedraggable component requires a reactive array bound to the :list prop. In src/components/DndList/index.vue, two separate draggable instances manage list1 and list2 respectively. This binding ensures that any reordering or movement automatically mutates the source arrays.
<draggable :list="list1" group="article" class="dragArea">
<!-- list1 items -->
</draggable>
<draggable :list="list2" group="article" class="dragArea">
<!-- list2 items -->
</draggable>
Enabling Cross-List Dragging with group
To allow items to move between the two lists, both draggable instances share the same group name (article). This configuration creates a shared drag context where elements can transition from list1 to list2 and vice versa while maintaining the reactive data flow.
Handling Firefox Compatibility with setData
The implementation includes a workaround for a known Firefox bug where the drag operation fails without data in the dataTransfer object. The setData method injects empty text data to satisfy the browser requirement.
methods: {
setData(dataTransfer) {
dataTransfer.setData('Text', '')
}
}
Custom Helper Methods for State Synchronization
Beyond basic dragging, the DndList component provides explicit control methods for programmatically moving items between lists. These helpers ensure data consistency when users prefer click-based interactions over drag gestures.
The deleteEle method removes an item from the left list (list1) and conditionally prepends it to the right list (list2) if not already present:
deleteEle(ele) {
const idx = this.list1.findIndex(item => item.id === ele.id)
if (idx !== -1) {
this.list1.splice(idx, 1)
}
if (!this.list2.some(item => item.id === ele.id)) {
this.list2.unshift(ele)
}
}
Conversely, the pushEle method moves items from the right list back to the left:
pushEle(ele) {
const idx = this.list2.findIndex(item => item.id === ele.id)
if (idx !== -1) {
this.list2.splice(idx, 1)
}
if (!this.list1.some(item => item.id === ele.id)) {
this.list1.push(ele)
}
}
Styling Drag States and Visual Feedback
The component uses scoped SCSS to provide visual cues during drag operations. It leverages Sortable.js's automatic class injection to style elements being dragged or targeted as drop zones.
.sortable-chosen {
background: #4ab7bd !important;
color: #fff;
}
.sortable-ghost {
opacity: 0.5;
background: #c8ebfb;
}
The .sortable-chosen class applies to the actively dragged element, while .sortable-ghost styles the placeholder indicating where the item will drop.
Complete Code Example
The following implementation demonstrates the full pattern used in src/components/DndList/index.vue:
<template>
<div class="dndList">
<div class="dndList-list">
<h3>Draggable List 1</h3>
<draggable
:list="list1"
group="article"
class="dragArea"
:set-data="setData"
>
<div
v-for="element in list1"
:key="element.id"
class="list-complete-item"
>
<div class="list-complete-item-handle">
{{ element.id }} {{ element.title }}
</div>
<div style="position:absolute;right:0px;">
<span
style="float: right;margin-top: -20px;margin-right:5px;"
@click="deleteEle(element)"
>
<i style="color:#ff4949" class="el-icon-delete"/>
</span>
</div>
</div>
</draggable>
</div>
<div class="dndList-list">
<h3>Draggable List 2</h3>
<draggable
:list="list2"
group="article"
class="dragArea"
:set-data="setData"
>
<div
v-for="element in list2"
:key="element.id"
class="list-complete-item"
>
<div
class="list-complete-item-handle2"
@click="pushEle(element)"
>
{{ element.id }} {{ element.title }}
</div>
</div>
</draggable>
</div>
</div>
</template>
<script>
import draggable from 'vuedraggable'
export default {
name: 'DndList',
components: { draggable },
props: {
list1: {
type: Array,
default: () => []
},
list2: {
type: Array,
default: () => []
}
},
methods: {
deleteEle(ele) {
const idx = this.list1.findIndex(item => item.id === ele.id)
if (idx !== -1) {
this.list1.splice(idx, 1)
}
if (!this.list2.some(item => item.id === ele.id)) {
this.list2.unshift(ele)
}
},
pushEle(ele) {
const idx = this.list2.findIndex(item => item.id === ele.id)
if (idx !== -1) {
this.list2.splice(idx, 1)
}
if (!this.list1.some(item => item.id === ele.id)) {
this.list1.push(ele)
}
},
setData(dataTransfer) {
dataTransfer.setData('Text', '')
}
}
}
</script>
Summary
- Vue-Element-Admin implements drag-and-drop table functionality using vuedraggable by wrapping the
<draggable>component around list data insrc/components/DndList/index.vue. - The
:listprop creates two-way binding with reactive arrays, while thegroupattribute enables cross-list dragging between multiple containers. - Custom helper methods (
deleteEleandpushEle) provide programmatic control for moving items between lists while preventing duplicates. - A
setDataworkaround ensures Firefox compatibility by populating thedataTransferobject during drag operations. - Scoped SCSS styles for
.sortable-chosenand.sortable-ghostprovide visual feedback during drag interactions.
Frequently Asked Questions
How does vuedraggable integrate with Vue-Element-Admin's table components?
Vuedraggable integrates as a wrapper component around standard list rendering logic. In src/components/DndList/index.vue, the <draggable> element replaces a static container, binding directly to the list1 and list2 arrays via the :list prop. This integration maintains Vue's reactivity system while adding Sortable.js drag capabilities without modifying the underlying Element UI table components.
What is the purpose of the group attribute in the DndList component?
The group attribute creates a shared drag context between multiple draggable containers. By assigning both lists the group name article, the configuration allows items to move from list1 to list2 and vice versa. Without this attribute, dragging would be restricted to reordering within a single list. This pattern supports complex workflows like task boards or permission assignment interfaces.
How does the implementation handle browser-specific bugs like the Firefox drag issue?
The implementation includes a setData method that addresses a Firefox-specific requirement where the drag operation fails if the dataTransfer object contains no data. The method injects an empty string via dataTransfer.setData('Text', '') during the drag start event. This workaround ensures consistent cross-browser behavior without affecting the visual presentation or data payload of the dragged items.
Can the drag-and-drop pattern be extended to more than two lists?
Yes, the pattern scales to multiple lists by adding additional <draggable> components with the same group attribute value. Each list binds to a distinct array prop (e.g., list3, list4), and the helper methods can be refactored to accept target list parameters dynamically. The existing architecture in DndList demonstrates the reactive principles required for multi-list implementations, though the current component specifically handles two lists for the common "todo/done" workflow pattern.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →