Skip to content

Instantly share code, notes, and snippets.

@skysan87
Created May 30, 2021 14:30
Show Gist options
  • Select an option

  • Save skysan87/ae7842ea8354bb7537fb206ed5cee45b to your computer and use it in GitHub Desktop.

Select an option

Save skysan87/ae7842ea8354bb7537fb206ed5cee45b to your computer and use it in GitHub Desktop.
[JavaScript][Vue] リストにあるタスクを特定の位置にDrag&Dropするサンプル
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://unpkg.com/vue@next"></script>
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">
<title>Document</title>
</head>
<body>
<div id="app">
<div id="trello" class="flex m-2">
<!-- NOTE: widthだとflexboxで幅が調整されるので、min-width -->
<div
v-for="(category,index) in displayCategories"
:key="index"
style="min-width: 400px;"
>
<div class="bg-gray-200 m-2 p-2 text-sm select-none">
{{ category.name }}
<!-- NOTE: dragした要素が元の場所に戻るので、dragover.preventとする -->
<div
v-for="(task,index) in category.tasks"
:key="index"
class="m-2 bg-white p-2 select-none"
:class="{ active: targetTask.id === task.id }"
@dragstart="dragTask(task)"
@dragover.prevent="dragOverTask(task)"
@dragend="dragEndHandler"
draggable="true"
>
{{ task.name }}
</div>
</div>
</div>
<div style="min-width: 400px;"
class="bg-gray-200 m-2 p-2 text-sm select-none"
>
<div
v-for="(time,index) in timetable"
:key="index"
class="bg-gray-200 text-sm select-none flex items-center"
>
<div style="width: 18px; text-align: center;">{{ time.label }}</div>
<div
class="bg-blue-100 border-b-2 border-l-2 border-r-2 border-blue-300"
:class="{ 'border-t-2': index === 0, 'bg-blue-500': time.isEnter }"
style="min-width: 400px; height: 40px;"
@dragenter="dragEnter(time.id)"
@dragover.prevent
@dragleave="dragLeave(time.id)"
@drop.prevent="drop(time.id)"
>
<div
class="w-full h-full bg-purple-400 select-none"
v-if="time.task !== null"
@dragstart="dragTask(time.task)"
@dragover.prevent
@dragend="dragEndHandler"
draggable="true"
>
<div>{{ time.task.name }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
<script>
const app = Vue.createApp({
data() {
return {
targetTask: '',
categories: [],
tasks: [],
timetable: [],
}
},
computed: {
displayCategories() {
let categories = []
let tasks = ""
this.categories.map(category => {
tasks = this.tasks.filter(task => (task.category_id === category.id && task.timeId === ''))
categories.push({
id: category.id,
name: category.name,
tasks
})
})
return categories
}
},
methods: {
dragEndHandler() {
this.targetTask = ''
event.dataTransfer.dropEffect = 'move'
event.dataTransfer.effectAllowed = 'move'
event.stopPropagation(); // バブリングキャンセル
},
dragTask(task) {
this.targetTask = task
event.dataTransfer.dropEffect = 'move'
event.dataTransfer.effectAllowed = 'move'
},
dragOverTask(overTask) {
event.dataTransfer.dropEffect = 'move'
event.dataTransfer.effectAllowed = 'move'
// タスクの並び替え
if (overTask.id !== this.targetTask.id) {
let deleteIndex = this.tasks.findIndex(task => task.id === this.targetTask.id)
let addIndex = this.tasks.findIndex(task => task.id === overTask.id)
this.tasks.splice(deleteIndex, 1)
this.targetTask.category_id = overTask.category_id
this.tasks.splice(addIndex, 0, this.targetTask)
}
},
dragEnter(timeId) {
const time = this.timetable.find(t => t.id === timeId)
time.isEnter = true
},
dragLeave(timeId) {
const time = this.timetable.find(t => t.id === timeId)
time.isEnter = false
},
/**
* @param {String} timeId 挿入先のtimeId
*/
drop(timeId) {
const time = this.timetable.find(t => t.id === timeId)
time.isEnter = false
if (this.targetTask !== '') {
// ドラッグ元のタスクを削除
this.timetable.forEach(t => {
if (t.task !== null && t.task.id === this.targetTask.id) {
t.task = null
}
})
// すでに登録されている場合は上書き
const distTask = this.tasks.find(t => t.timeId === timeId)
if (!!distTask) distTask.timeId = '' // リストに表示される
const task = this.tasks.find(task => task.id === this.targetTask.id)
task.timeId = timeId
time.task = task
} else {
time.task = null
}
},
creattTestData() {
const alphabet = 'abcd'.split('')
const category_count = 1
for (let i = 0; i < category_count; i++) {
this.categories.push({
id: i + 1,
name: `テスト${alphabet[i]}`,
collapsed: false,
})
}
for (let i = 0; i < 10; i++) {
this.tasks.push({
id: i + 1,
category_id: category_count - (i % category_count),
name: `テスト${i + 1}`,
timeId: ''
})
}
for (let i = 1; i <= 12; i++) {
this.timetable.push({
id: i,
label: i,
isEnter: false,
task: null
})
}
}
},
mounted() {
this.creattTestData()
}
}).mount('#app')
</script>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment