# Drag & Drop API 實作
###### tags: `實作` `javascript`
[實作頁面連結](https://allenw0815.github.io/mission/mission_30/index.html)

### Flowchart
1. HTML、CSS 一頁即可呈現
2. 了解 Drag & Drop API
3. 透過 JS 觸發拖曳事件
4. 最後比對陣列完成實作
> JS 流程
1.設定原始資料,這邊先複製一份原始資料,然後產生畫面
1. map 回傳陣列,將每一個數字回傳成為一個物件,帶有原本的值還有一個隨機數 ( 用來排序用 )
2. sort 將陣列透過剛剛的隨機數把陣列隨機打亂排列
3. forEach 針對每個傳入的物件依序去生成 HTML 結構,到這邊已實現每次進入的順序都不同
4. 接著加掛事件監聽
* 展開運算子
```javascript=
let number01 = [1, 2, 3]
let number02 = [...number01]
console.log(number02) // [1, 2, 3]
number01 == number02 // false
```
```javascript=
const number = ['08','18','27','33','47','52','66','79','87','99']
const createList = () =>{
[...number]
.map(num => ( { value : num, sort : Math.random()} ))
.sort((a, b) => a.sort - b.sort)
.forEach((item,index) => {
const listItem = document.createElement('li')
listItem.setAttribute('data-index', index) //透過這個index判斷拉出哪個拉進哪個
listItem.innerHTML =
`
<span class="number"> ${index+1}.</span>
<div class="draggable" draggable="true">
<p>Drag Number : ${item.value}</p>
<i class="fas fa-bars"></i>
</div>
`
listItems.push(listItem)
dragArea.appendChild(listItem)
});
addEventListeners()
}
```
2.接著一次將所需的節點加掛事件監聽,querySelectorAll 回傳的是 nodeList 需要用 forEach 方法來遍歷,最下方參考資料有所需要的事件說明。
```javascript=
const addEventListeners = () =>{
const draggables = document.querySelectorAll('.draggable') // 抓到右邊的<div>
const listItems = document.querySelectorAll('.drag-area li') // 抓到整個<li>
draggables.forEach(draggable =>{
draggable.addEventListener('dragstart',dragStart)
})
listItems.forEach(item =>{
item.addEventListener('dragenter',dragEnter)
item.addEventListener('dragleave',dragLeave)
item.addEventListener('dragover',dragOver)
item.addEventListener('drop',dragDrop)
})
}
```
3. 接著觸發各個事件的對應 function ( 這邊比較複雜 ),註解也有點饒舌,要慢慢看慢慢想。
```javascript=
function dragStart() {
// 一拖曳的時候會觸發
dragStartIndex = this.closest('li').getAttribute('data-index') //賦值 知道拖曳的目標物
}
function dragEnter() {
// 進入範圍時會觸發
this.classList.add('over') //將拉入的<li>添加樣式
}
function dragLeave() {
// 離開範圍時會觸發
this.classList.remove('over') //將拖離的<li>移除樣式
}
function dragOver(e) {
// 到目標物上時持續被觸發
e.preventDefault(); //見下方紀錄
}
function dragDrop() {
// 拖曳區塊被放置一個有效區域時觸發
const dragEndIndex = this.getAttribute('data-index') //定義要被交換的目標物
swapItems(dragStartIndex, dragEndIndex)
this.classList.remove('over') // 要移除樣式 不然沒有觸發 dragLeave 樣式會被保留
}
const swapItems = (fromIndex, toIndex) =>{
let firstItem = listItems[fromIndex].querySelector('.draggable') //被拖的那個<div>
let secondItem = listItems[toIndex].querySelector('.draggable') //要拉進的那個<div>
listItems[fromIndex].appendChild(secondItem) //被拖的<li>放進要被拉進去的<div>
listItems[toIndex].appendChild(firstItem) //被拉進去<li>要放被拖的<div>
}
```
4. 檢查數字大小順序,比對答案陣列與當前陣列的數字,並添加提示樣式
```javascript=
function checkOrder(){
const standerOrder = [] //正確順序
let ps = document.querySelectorAll('p') //取得所有<p>
ps.forEach(p => {
standerOrder.push((p.innerText).slice(-2)) //取得所有最後兩碼的文字 推入陣列中
})
standerOrder.sort().reverse(); //排序後反轉成由大到小
ps.forEach((p,index) => {
if((p.innerText).slice(-2) !== standerOrder[index]){
p.classList.remove('correct')
p.classList.add('wrong')
} else{
p.classList.remove('wrong')
p.classList.add('correct')
}
})
}
```
### 記錄
* 觀察事件
```javascript=
const dragStart = () => {
console.log('Event','dragStart');
}
const dragOver = () => {
console.log('Event','dragOver');
}
const drop = () => {
console.log('Event','drop');
}
const dragEnter = () => {
console.log('Event','dragEnter');
}
const dragLeave = () => {
console.log('Event','dragLeave');
}
```
* 按著移動時,觀察事件觸發的時機,再去指派要做什麼事情
> <font color="#f00">這邊發現一個重要的問題</font>
>
> 原本使用 arrow function 時候 this 的指向會變成 window 導致失效報錯,所以先改回一般寫法,再另外研究 this 問題。
```javascript=
function dragEnter() {
console.log('Event','dragEnter');
console.log(this);
}
```

---
* 抓取節點
> closest() 往上查找 只要找到符合條件的 就停止
> find() 往下查找 只要找到符合條件的 就停止
> parent() 只往上查找一層
> parents() 往上查找 不停止 找出所有符合條件的
---
* 這邊發現 dragover 事件會影響 drop 事件 (同時觸發時 drop 會失效),所以在 dragover 事件觸發 function 內取消預設執行的動作 (但不用 dropover 就無法實現功能)
```javascript=
function dragOver(e) {
e.preventDefault();
}
```
* 產生一個陣列長度為 10,並且內容為隨機數 ( 一行搞定 )
```javascript=
const setData = () => new Array(10).fill(0).map((e)=> e += Math.floor(Math.random()*100))
}
```
### 小記
這一實作比較難一點,因為比較多陌生的東西,有發現這種拖曳的功能有現成的函式庫可以使用,之後可以再作補充,讓移動可以更順暢,瀏覽起來不生硬。
### 參考
[MDN Drag & Drop API](https://developer.mozilla.org/zh-TW/docs/Web/API/HTML_Drag_and_Drop_API)
[YT 有示範lib使用](https://www.youtube.com/watch?v=z54suepKiIU)
[Array相關文章](https://osk2.medium.com/array-%E5%8F%AF%E8%83%BD%E8%B7%9F%E4%BD%A0%E6%83%B3%E7%9A%84%E4%B8%8D%E4%B8%80%E6%A8%A3-%E4%B8%8A-21f2031e4697)