<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;
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();
}
})
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.