--- tags: 六角學院_JavaScript 必修篇 - 前端修練全攻略 --- JS Todolist - 最終關卡 === <iframe height="300" style="width: 100%;" scrolling="no" title="todolist_v5" src="https://codepen.io/jason60810/embed/QWgLOWy?default-tab=html%2Cresult&editable=true" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true"> See the Pen <a href="https://codepen.io/jason60810/pen/QWgLOWy"> todolist_v5</a> by jason (<a href="https://codepen.io/jason60810">@jason60810</a>) on <a href="https://codepen.io">CodePen</a>. </iframe> 0.如何內嵌codepen --- 1. 點擊在 Fork 旁邊的 Embed 2. 點選 Inframe , 把code 貼到 HackMD 裡 1.先創建預設資料,並將操作的元素選取起來,再創建一個變數存取現在選取的完成狀態 --- ### JS 1. 創建 `data` 變數,並輸入資料(格式:陣列內包含多個物件) 2. 創建變數儲存要操作的元素 3. 創建 `filterStatus` 存取現在選取的完成狀態 ```javascript= // 資料 let data = [ { content: "為什麼不幫我發大絕?極靈~", finishStatus: "待完成" }, { content: "還敢下來啊冰鳥", finishStatus: "已完成" }, { content: "又舔、又舔嘴唇!!! ", finishStatus: "已完成" }, { content: "幸福好運到,人頭送夠夠~", finishStatus: "待完成" } ]; // 操作的元素 const list = document.querySelector(".list"); const listFooter = document.querySelector(".list_footer"); const tab = document.querySelector(".tab"); const tabFilter = document.querySelectorAll(".tab li"); const input = document.querySelector(".card input"); const btnAdd = document.querySelector(".btn_add"); // 存取現在選取的完成狀態 let filterStatus = "全部"; ``` 2.顯示資料 --- ### JS 1. 創建 `showData(data, condition)` 1. 創建空字串 `str` 2. 使用 `data.forEach` 將每個陣列裡的東西跑過一次 1. 創建 `checked` 來決定此資料的標籤是不是要有屬性`checked` 2. 將要新增的內容賦予創建變數`content` ,並利用 Template literals 更改成物件內容( 有屬性 `checked` 會被套用完成的樣式) 3. 創建變數`filterContent` 來儲存顯示的內容是篩選的還是全部 4. 將要新增的內容賦予 `str` 5. 使用 list.innerHTML 更改內容 ```javascript= // 顯示資料函式 function showData(data, condition) { let str = ""; data.forEach(function (item, index) { let checked = item.finishStatus === "已完成" ? "checked" : ""; let content = `<li> <label class="checkbox" for=""> <input type="checkbox" data-num="${index}" ${checked}/> <span>${item.content}</span> </label> <a href="#" class="delete" data-num="${index}"></a> </li>`; let filterContent = condition === item.finishStatus ? content : ""; str += condition === "全部" ? content : filterContent; }); list.innerHTML = str; } ``` 3.更改待完成項目數量和載入預設函式 --- ### JS 1. 創建 `chageUnFinishNum()` 1. 創建變數 `unFinishData` ,使用 `data.filter()` 篩選出是待完成的資料,並回傳至 `unFinishData` 2. 使用 `listFooter.innerHTML` 和 Template literals 更改待完成項目數量 2. 創建 `renderData(filterStatus)` 1. 呼叫 `chageUnFinishNum()` 更改待完成項目數量 2. 呼叫 `showData(data, condition)` 顯示資料 3. 先呼叫一次 `renderData(filterStatus)` 渲染畫面 ```javascript= // 更改待完成項目數量 function chageUnFinishNum() { let unFinishData = data.filter((item) => item.finishStatus === "待完成"); listFooter.innerHTML = `<p>${unFinishData.length} 個待完成項目</p><a href="#">清除已完成項目</a>`; } // 載入預設函式 function renderData(filterStatus) { chageUnFinishNum(); showData(data, filterStatus); } renderData(filterStatus); ``` 4.更改card_list的完成狀態函式和篩選邏輯 --- ### JS 1. 創建 `tabFilterChangeStatus(tabFilterStatus) ` 1. 創建變數 `tabFilterToArray` ,使用 `Array.apply(null, tabFilter)` 將 `querySelectorAll` 回傳的 `nodeList` ([不能使用陣列的方法像是 map 和 forEach](https://www.jstips.co/zh_tw/javascript/converting-a-node-list-to-an-array/)) 轉成 Array 2. 使用 `tabFilterToArray.forEach` 將每個陣列裡的東西跑過一次 1. 創建變數 `isActive` 儲存此標籤內容是否與 `tabFilterStatus` (tag 點擊的狀態) 相同,如果是則回傳 "active" ,否則回傳"" 2. 將此標籤的 class 屬性改成變數 `isActive` 2. 監聽 tab 點擊事件,如果有,執行以下 1. 呼叫 `tabFilterChangeStatus() ` 參數帶入 `e.target.textContent` (現在點擊的 tag 標籤內容) 2. `filterStatus ` 改成 `e.target.textContent` 3. 呼叫 `renderData(filterStatus)` 重新渲染畫面 ```javascript= // 更改card_list的完成狀態函式 function tabFilterChangeStatus(tabFilterStatus) { let tabFilterToArray = Array.apply(null, tabFilter); tabFilterToArray.forEach(function (item) { let isActive = item.textContent === tabFilterStatus ? "active" : ""; item.setAttribute("class", isActive); }); } // 篩選邏輯 tab.addEventListener("click", function (e) { tabFilterChangeStatus(e.target.textContent); filterStatus = e.target.textContent; renderData(filterStatus); }); ``` 5.新增邏輯(點擊加號) --- ### JS 1. 創建變數 `empytWarn` 2. 創建 `addItem()` 1. 如果輸入空白(! + 字串 = 布林值,空字串為 false) 1. 將屬性 placeholder 改成 `empytWarn` 2. 加上字串 "齁" , 然後 return 2. 將屬性 placeholder 改成 "請輸入代辦事項"(如果沒有輸入空白將改回原本的提示內容) 3. 將變數 `empytWarn` 改成原本的值(如果沒有輸入空白) 4. 創建空物件 `obj`,加入新屬性和其對應的值 5. 使用 `data.push(obj)` 將剛才的物件推入資料集中 6. 清空輸入欄 7. 重新渲染 3. 監聽 btnAdd 點擊事件,如果有,執行 `addItem()`,並取消 a 標籤的原始功能 4. 創建 `detectInputKey()` ,如果有按下 Enter 鍵,就呼叫 `addItem()` 5. 監聽 input 放開鍵盤事件,如果有,執行 `detectInputKey()` ```javascript= // 新增邏輯(點擊加號) let empytWarn = "這到底~這到底什麼輸入~"; function addItem() { if (!input.value.trim()) { input.setAttribute("placeholder", empytWarn); empytWarn += "齁"; return; } input.setAttribute("placeholder", "請輸入代辦事項"); empytWarn = "這到底~這到底什麼輸入~"; let obj = {}; obj.content = `${input.value}`; obj.finishStatus = "待完成"; data.push(obj); input.value = ""; renderData(filterStatus); } // 監聽輸入欄加號按鈕 btnAdd.addEventListener("click", addItem); // 取消a標籤功能 btnAdd.addEventListener("click", (e) => e.preventDefault()); // 新增邏輯(Enter鍵) function detectInputKey(e) { if (e.key !== "Enter") return; addItem(); } // 監聽輸入欄有沒有按Enter input.addEventListener("keyup", detectInputKey); ``` 6.如果該類沒有事項跳回全部頁面 --- ### JS 1. 創建 `isEmptyReturnAll()` 1. 創建變數 `finishData` ,儲存"完成的"資料 2. 創建變數 `unFinishData` ,儲存"待完成"資料 3. 如果現在的 tag 是"待完成",且沒有"待完成"資料,將`filterStatus` 改成"全部" 4. 如果現在的 tag 是"已完成",且沒有"已完成"資料,將`filterStatus` 改成"全部" 5. 呼叫 `tabFilterChangeStatus(filterStatus)` 改變現在顯示的 tag ```javascript= // 如果該類沒有事項跳回全部頁面 function isEmptyReturnAll() { let finishData = data.filter((item) => item.finishStatus === "已完成"); let unFinishData = data.filter((item) => item.finishStatus === "待完成"); filterStatus = filterStatus === "待完成" && unFinishData.length === 0 ? "全部" : filterStatus; filterStatus = filterStatus === "已完成" && finishData.length === 0 ? "全部" : filterStatus; tabFilterChangeStatus(filterStatus); } ``` 7.更改打勾狀態 --- ### JS 1. 監聽 list 點擊事件,如果有,執行以下 1. 如果點選的標籤是"INPUT" 2. 創建變數 `num` 儲存該標籤 data-num 屬性的內容 3. 利用 `num` 更改該資料的 `finishStatus ` 4. 如果此 tag 內的資料空了則呼叫 `isEmptyReturnAll()` 5. 重新渲染 ```javascript= // 更改打勾狀態 list.addEventListener("click", function (e) { if (e.target.nodeName === "INPUT") { let num = e.target.getAttribute("data-num"); data[num].finishStatus = data[num].finishStatus === "已完成" ? "待完成" : "已完成"; isEmptyReturnAll(); renderData(filterStatus); } }); ``` 8.刪除個別代辦事項和刪除全部代辦事項 --- ### JS 1. 監聽 list 點擊事件,如果有,執行以下 1. 如果點選的標籤為"A" 1. 取消原本的功能 2. 創建變數 `num` 儲存該標籤 data-num 屬性的內容 3. 利用 `data.splice(num, 1)` 將該資料刪除 4. 如果此 tag 內的資料空了則呼叫 `isEmptyReturnAll()` 5. 重新渲染 2. 監聽 listFooter 點擊事件,如果有,執行以下 1. 如果點選的標籤為"A" 1. 取消原本的功能 2. 將 data 內完成的代辦事項刪除 3. 如果此 tag 內的資料空了則呼叫 `isEmptyReturnAll()` 4. 重新渲染 ```javascript= //刪除個別代辦事項 list.addEventListener("click", function (e) { if (e.target.nodeName === "A") { e.preventDefault(); let num = e.target.getAttribute("data-num"); data.splice(num, 1); isEmptyReturnAll(); renderData(filterStatus); } }); //刪除全部完成的代辦事項 listFooter.addEventListener("click", function (e) { if (e.target.nodeName === "A") { e.preventDefault(); data = data.filter((item) => item.finishStatus === "待完成"); isEmptyReturnAll(); renderData(filterStatus); } }); ```