Created
May 30, 2021 14:30
-
-
Save skysan87/ae7842ea8354bb7537fb206ed5cee45b to your computer and use it in GitHub Desktop.
[JavaScript][Vue] リストにあるタスクを特定の位置にDrag&Dropするサンプル
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!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