---
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);
}
});
```