<div class="container">
  <h1>TODO LIST</h1>
  <div class="card input">
    <input class="txt" type="text" placeholder="請輸入待辦事項" />
    <a href="#" class="btn_add">+</a>
  </div>
  <div class="card card_list">
    <ul class="tab" id="tab">
      <li class="active" data-tab="all">全部</li>
      <li data-tab="wait">待完成</li>
      <li data-tab="done">已完成</li>
    </ul>
    <div class="cart_content">
      <ul class="list">
<!--         <li>
          <label class="checkbox" for="">
            <input type="checkbox" />
            <span>把冰箱發霉的檸檬拿去丟</span>
          </label>
          <a href="#" class="delete"></a>
        </li>
        <li>
          <label class="checkbox" for="">
            <input type="checkbox" />
            <span>把冰箱發霉的檸檬拿去丟</span>
          </label>
          <a href="#" class="delete"></a>
        </li>
        <li>
          <label class="checkbox" for="">
            <input type="checkbox" />
            <span>把冰箱發霉的檸檬拿去丟</span>
          </label>
          <a href="#" class="delete"></a>
        </li>
        <li>
          <label class="checkbox" for="">
            <input type="checkbox" />
            <span>把冰箱發霉的檸檬拿去丟</span>
          </label>
          <a href="#" class="delete"></a>
        </li> -->
      </ul>
      <div class="list_footer">
        <p class="itemCount">0 個項目</p>
        <a href="#" class="clearAll">清除已完成項目</a>
      </div>
    </div>
  </div>
</div>
@import url("https://fonts.googleapis.com/earlyaccess/notosanstc.css");
@import url("https://fonts.googleapis.com/css?family=Baloo+Tamma+2:600, 700");

* {
  box-sizing: border-box;
}
html {
  font-size: 16px;
  -webkit-tap-highlight-color: transparent;
}
$default: #ffd370;
$dark: #333;
$gray: #9f9a91;
$light: #efefef;
body {
  min-height: 100vh;
  background: #eee;
  display: flex;
  color: $dark;
  background-image: linear-gradient(
    174deg,
    $default 2%,
    $default 46%,
    #ffffff 46%,
    #ffffff 100%,
    #e8e8e8 100%
  );
  letter-spacing: 0.07em;
}

h1 {
  text-align: center;
  font-size: 3rem;
  margin-bottom: 1.5rem;
  font-family: "Baloo Tamma 2";
  letter-spacing: 0.5rem;
  font-weight: bold;
  @media (max-width: 575px) {
    font-size: 2rem;
    margin-bottom: 1rem;
  }
}
.container {
  margin: 3rem auto 1.5rem auto;
  padding: 0 12px;
  width: 500px;
  @media (max-width: 575px) {
    margin-top: 1.5rem;
  }
}
.card {
  margin-bottom: 0.5rem;
  max-width: 100%;
  padding: 1rem;
  border-radius: 10px;
  background: #fff;
  box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.15);
}
input[type="text"] {
  width: 100%;
  border: 0;
  outline: 0;
  font-size: 1rem;
  padding-right: 1rem;
  color: $dark;
  &::placeholder {
    color: $gray;
  }
}
.input {
  padding: 4px 4px 4px 1rem;
  display: flex;
}
.btn_add {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: 10px;
  background: $dark;
  color: #fff;
  font-size: 2rem;
  text-decoration: none;
}

// card_list
.card_list {
  padding: 0;
}

.tab {
  display: flex;
  text-align: center;
  color: $gray;
  font-size: 14px;
  li {
    padding: 1rem;
    width: 100%;
    border-bottom: 2px solid $light;
    &.active {
      border-bottom: 2px solid $dark;
      color: $dark;
      font-weight: bold;
    }
  }
}
.cart_content {
  padding: 0.5rem 1rem 1rem 1rem;
  @media (max-width: 575px) {
    padding: 0.5rem 1rem 0.5rem 0.5rem;
  }
}
.list {
  li {
    position: relative;
    padding-right: 2rem;
    @media (max-width: 575px) {
      padding-right: 0;
    }
    a.delete {
      position: absolute;
      opacity: 0;
      right: 0;
      top: 50%;
      transform: translateY(-50%);
      text-decoration: none;
      color: $dark;
      display: block;
      width: 1rem;
      height: 1rem;
      background: #000;
      background-image: url("https://i.imgur.com/7Q4RjFx.jpg");
      background-size: contain;
      @media (max-width: 575px) {
        opacity: 1;
        width: 12px;
        height: 12px;
      }
    }
    &:hover a.delete {
      opacity: 1;
    }
  }
}

.list_footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1.5rem 2rem 1rem 0.5rem;
  font-size: 14px;
  a {
    color: $gray;
    text-decoration: none;
  }
  @media (max-width: 575px) {
    padding: 1.5rem 0 1rem 0.5rem;
  }
}

.checkbox {
  position: relative;
  user-select: none;
  width: 100%;
  display: block;
  padding-left: 44px;
  cursor: pointer;
  span {
    display: block;
    padding: 1rem 0;
    border-bottom: 1px solid #eee;
    line-height: 1.5;
    @media (max-width: 575px) {
      padding-right: 1rem;
    }
  }
  input {
    position: absolute;
    top: 0;
    left: 0;
    opacity: 0;
    cursor: pointer;
    display: block;
    height: 100%;
    width: 100%;
    margin: 0;
  }
  span::before {
    content: "";
    position: absolute;
    left: 0.5rem;
    top: 50%;
    transform: translateY(-50%) scale(1);
    height: 20px;
    width: 20px;
    border-radius: 5px;
    border: 1px solid $dark;
    pointer-events: none;
    transition: 0.3s ease;
  }
  span::after {
    content: "";
    position: absolute;
    left: 14px;
    top: 27%;
    transform: rotate(45deg);
    height: 15px;
    width: 0.5rem;
    border-radius: 1px;
    border-bottom: 3px solid $default;
    border-right: 3px solid $default;
    pointer-events: none;
    opacity: 0;
    transition: 0.3s ease;
  }
  input:checked {
    ~ span {
      color: $gray;
      text-decoration: line-through;
    }
    ~ span::before {
      border-color: transparent;
      transform: translateY(-50%) scale(0);
    }
    ~ span::after {
      opacity: 1;
    }
  }
}
View Compiled
const txt = document.querySelector('.txt');
const addBtn = document.querySelector('.btn_add');
const list = document.querySelector('.list');

// 宣告一個全域變數並賦予值為空陣列
// 會加入按鈕新增的物件
let todo =[];

// 1. 新增代辦事項

// 註冊監聽點擊按鈕事件:
addBtn.addEventListener('click', addList);
function addList(){  
  // 監聽函式內要先組物件,物件內包含:
    // input 欄位的值(記得先取出)
    // id (未來會需要透過 id 比對資料)
    // 紀錄完成狀態(ckeckbox 有沒有打勾勾)
  let todoData ={
    content : txt.value,
    id : new Date().getTime(),
    // 建立 Date() 物件後使用 getTime() 取得時間 
    // getTime() 會取出 (由 1970年1月1日零時零分計起到目前時間)
    
    checked:"",
    // 紀錄完成狀態(ckeckbox 有沒有打勾勾)
  };
  
  
  // 防呆,確認 input 欄位有值: 如果 todo 陣列中的 txt 不是空的話,才會新增
  // 有值的情況下,將剛組好的物件推到全域變數(記得宣告一個全域變數並賦予值為空陣列)
  if(txt.value !== ""){
    // 新增最新的會在最上面,所以使用 unshift
    // 每點擊按鈕,會將點擊物件資料 todoData 傳入空陣列 todo
    todo.unshift(todoData);
    // 檢查: console.log(todo);
    
    // 清空輸入框
    txt.value = "";
  }else{
    alert('請輸入代辦事項')
  };
  
  // 呼叫渲染
  // 使用 todo 陣列中的物件跑迴圈
  
  updateDone();
  // 取代:render(todo);
};


// 2. 渲染畫面
// 加入參數,用參數使用 foreach 迴圈
function render(arr){
  
  // 新增空字串
  let str = "";
  
  // forEach 迴圈
  // 使用參數,之後在監聽按鈕時,使用 todo 陣列跑迴圈
  arr.forEach(item => {
    // (todo 陣列)將輸入框內容加入空字串
    // todo 物件
  //  自訂標籤加入ID dataset
  str +=`<li data-id="${item.id}">
          <label class="checkbox" for="">
          
            <!-- checkbox 會有 checked 的屬性,確保是否勾起狀態 -->
            <input type="checkbox" ${item.checked}>
            <span>${item.content}</span>
          </label>
          <a href="#" class="delete"></a>
        </li>`
  });
  // 將 html 內容加入 list
  list.innerHTML = str;
}


// 3. 刪除單筆/切換打勾
// 監聽 list 中的 打叉icon <a href="#" class="delete"></a>
list.addEventListener('click', (e)=>{
  // 檢查: console.log(e.target);
  
  // 因為抓不到 li 的 dataset 所設定的 id
  // 所以要使用 closest 找到 ID
  // 檢查: console.log('使用 closest 找到 ID', e.target.closest('li').dataset.id);
  console.log('這是啥', e.target.classList.value)
  //取出來的 id 會是字串型別記得幫它轉型成數字型別
  let dataID = parseInt(e.target.closest('li').dataset.id);
      
  // 刪除:確認點擊到的是打叉按鈕
  if(e.target.classList.contains('delete')){
    // 檢查: console.log('抓到 delete')
    
    // 取消 a 標籤的預設行為
    e.preventDefault();
    
    // 找出點擊到的該筆資料的索引值
    // 如果 todo 陣列中的 id = dataID 時,回報索引值
    // 檢查: console.log('這是ID', todo.findIndex((item) => item.id === dataID));
    let dataIndex = todo.findIndex((item) => item.id === dataID);
    
    // 刪除該筆資料
    todo.splice(dataIndex, 1);
  }else{
    // 如果點擊的不是叉叉icon 時,在這個 li 中一律都是切換打勾功能
    // todoData 內的 id 是否等於點擊到的 id (dataID)
    todo.forEach((item) =>{
      if(item.id === dataID){
        // 如果是該 ID
        // 接著確認 todo 陣列中的 id ,是哪個狀態      
        if(item.checked === ""){
          item.checked = "checked";
        }else{
          item.checked = "";
        }
      }
    })
  }
  updateDone();
  // 取代:render(todo);
})


// 4. 切換 tab
const tabList = document.querySelector('#tab');

// 取出 tab 所有 li 
const tabAll = document.querySelectorAll('#tab li');

// 監聽是否點擊到全部/待完成/已完成區塊
tabList.addEventListener('click', (e)=>{
  
  // 取出埋藏的 tab 狀態(在 HTML 透過 data-"自定義屬性" 埋藏 tab 的狀態)
  // tab 顯示狀態 = 透過 e.target 將 dataset 埋入的 tab 取出
  datasetTab = e.target.dataset.tab;
  // 檢查: console.log('datasettab', datasetTab)
 
  // 點擊 tabList 的某個區塊,要先清除 li 中所有 active ,然後再點擊的地方加上 class
  // 有三個 li 都要清除
  tabAll.forEach((item)=>{
    item.classList.remove('active');
  })
  
  // 有被點到的重新加上 active
  e.target.classList.add('active');
  
  // 切換tab,重新顯示狀態列
  updateDone();
})


// 5. 更新代辦清單:修改完成狀態/計算項目
// 計算項目
const itemCount = document.querySelector('.itemCount');
  

// 建立一個新函式處理各種狀態
// !!!!!!這個函式要取代上面原本寫的 render(todo); !!!!!
// updateDone(); 會計算狀態列跟計算項目,所以取代點擊監聽中的render(todo);,才會每點擊一個項目就更新一次
function updateDone(){
  let updateData = [];
  
  // 找到 active 狀態的 tab
  const tabActive = document.querySelector('.tab .active');
  
  // 透過 tab 狀態去判斷是否完成
  if(tabActive.dataset.tab === 'done'){
    // 篩選
     updateData = todo.filter(function(item){
      return item.checked === "checked";
    })
    itemCount.textContent = `${updateData.length} 個已完成項目`
  }else if(tabActive.dataset.tab === 'wait'){
    updateData = todo.filter((item)=>item.checked === "")
    itemCount.textContent = `${updateData.length} 個待完成項目`
  }else{
    updateData = todo;
    itemCount.textContent = `${updateData.length} 個項目`
  }
  render(updateData); 
}


// 6. 清除已完成項目按鈕
const clearAll = document.querySelector('.clearAll');
clearAll.addEventListener('click', (e)=>{
  e.preventDefault();
  
  if(confirm("確認是否清除全部已完成項目") === true){
    todo = todo.filter((item)=>item.checked !== "checked");
    itemCount.textContent = `${todo.length} 個待完成項目`;
    updateDone();
  }
})


// 優化:鍵盤事件 Enter 也能新增待辦事項
txt.addEventListener('keyup', (e)=>{
  if(e.key === 'Enter'){
    addList();
  }
})

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.