---
# System prepended metadata

title: JS Todolist - 最終關卡
tags: [六角學院_JavaScript 必修篇 - 前端修練全攻略]

---

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