2024-09-29 課後作業-JavaScript 系列六:第7課 ── 資料序列化

課後作業-JavaScript 系列六:第7課 ── 資料序列化

07_js_localstorage.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JavaScript 系列六:第6課 ── 認識 data model 的優點</title>
<link rel="stylesheet" href="./07_js_localstorage.css">
</head>
<body>
<input type="text">
<button id="add_btn" onclick="add()">新增事項</button>
<button id="export_btn" onclick="export_todos()">匯出</button>
<button id="save_btn" onclick="save_todos()">儲存</button>
<div id="root">
</div>
<!--***** 切版start *****-->
<!-- <div class="wrap">
<h2>To Do List</h2>
<hr>
<div class="todo_input">
<input type="text" placeholder="新增待辦事項">
<button id="add_btn" onclick="add()">新增事項</button>
<button id="export_btn" onclick="export()">匯出</button>
<button id="save_btn" onclick="save_todos()">儲存</button>
<select>
<option value="normal">一般</option>
<option value="important">重要</option>
<option value="urgent">緊急</option>
</select>
</div>
<div class="todos_container">
<ul class="todoList">
<li>
<span></span> <span></span>
<button class="btn_li btn_on||btn_off">標示為已完成</button>
<button class="btn_li delBtn">刪除</button>
</li>

</ul>
</div>

</div> -->
<!--***** 切版end *****-->

<script src="./07_js_localstorage.js"></script>
</body>
</html>

07_js_localstorage.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
// todo 長這樣
// var todos = [
// {
// title: "倒垃圾",
// category: "normal",
// isCompleted: false
// },
// {
// title: "繳電話費",
// category: "important",
// isCompleted: false
// },
// {
// title: "採買本週食材",
// category: "urgent",
// isCompleted: false
// },
// ];


var todos = [];

function render_wrap(){
const body = document.querySelector('body');
const wrap = document.createElement('div');
wrap.className='wrap';
const h2 = document.createElement('h2');
h2.textContent="To Do List";
const hr = document.createElement('hr');


// 取得現有的 input 和 button 元素
const input_todo = document.querySelector('input');
const add_btn = document.querySelector('#add_btn');
const export_btn = document.querySelector('#export_btn');
const save_btn = document.querySelector('#save_btn');
// 新增事件急迫性
const select = document.createElement('select');
const option_normal = document.createElement('option');
option_normal.value = 'normal';
option_normal.textContent = '一般';
option_normal.selected = true;

const option_important = document.
createElement('option');
option_important.value = 'important';
option_important.textContent = '重要';

const option_urgent = document.createElement('option');
option_urgent.value = 'urgent';
option_urgent.textContent = '緊急';


// 取得現有的 root 容器
const root = document.querySelector('#root');

select.append(option_normal);
select.append(option_important);
select.append(option_urgent);
wrap.append(h2);
wrap.append(hr);
wrap.append(input_todo);
wrap.append(select);
wrap.append(add_btn);
wrap.append(export_btn);
wrap.append(save_btn);
wrap.append(root);

// 把 wrap 放到 body 裡面
body.append(wrap);


// 監聽 select 的變化
select.addEventListener('change', function () {
const selectedValue = select.value;
select.className = ''; // 先重置 class
if (selectedValue === 'important') {
select.className += ' important';
} else if (selectedValue === 'urgent') {
select.className += ' urgent';
}
});


// 監聽 input 元素的鍵盤事件
input_todo.addEventListener('keydown', function(event) {
// 檢查是否按下 Enter 鍵 (keyCode 13)
if (event.key === 'Enter') {
add(); // 執行 add 函式
}
});
}


function add()
{
// 請寫出此函式內容(更新 todos 陣列)
const input_todo = document.querySelector('input');
const select_category = document.querySelector('select');

let todo = input_todo.value.trim(); // 取得待辦事項的名稱
let category = select_category.value; // 取得選擇的分類
let isCompleted = false; //default值 未完成

if(todo !== ''){
// 將待辦事項以物件的形式加入 todos 陣列
todos.push({
title: todo,
category: category,
isCompleted: isCompleted
});
console.log(todos);
render();
}else {
input_todo.focus();
input_todo.placeholder = "新增待辦事項";
}
input_todo.value = '';
}

function render(){
console.log('render....');
const root = document.querySelector('#root');
root.textContent='';
const div_container = document.createElement('div');
div_container.className = 'todos_container';
const ul = document.createElement('ul');
ul.className = 'todoList';

todos.forEach(function(item, index){
const li = document.createElement('li');
const span = document.createElement('span');
const toggleBtn = document.createElement('button');
toggleBtn.className = item.isCompleted ? 'btn_li btn_on' : 'btn_li btn_off';
toggleBtn.textContent = item.isCompleted ? '改為待完成' : '標示已完成';
const delBtn = document.createElement('button');
delBtn.className = "btn_li delBtn";
delBtn.textContent = '刪除';

// 切換完成狀態
toggleBtn.onclick = () => {
item.isCompleted = !item.isCompleted; // 切換完成狀態
render(); // 重新渲染
}


// 設定刪除按鈕的點擊事件,刪除對應的項目
delBtn.onclick = () => {
todos = todos.filter((_, i) => i !== index); // 移除該項
render(); // 重新渲染
};

// 設定待辦事項的標題和分類
span.textContent = item.title;

// 如果已完成,標註「(已完成)」
if (item.isCompleted) {
const completedText = document.createElement('span');
completedText.textContent = ' (已完成)';
span.appendChild(completedText);
}

// 根據 category 設定 class 以改變樣式
li.className = item.category;

li.append(span);
li.append(toggleBtn);
li.append(delBtn);
ul.append(li);
});

div_container.append(ul);
root.append(div_container);
};

function export_todos() {
let export_msg = '';

todos.forEach(function(item, index) {
// 計算序號,從 1 開始
const number = index + 1;

// 根據 category 添加不同的格式
let formattedTitle = item.title;
if (item.category === 'important') {
formattedTitle = `*${item.title}*`; // 用 * 包裹
} else if (item.category === 'urgent') {
formattedTitle = `**${item.title}**`; // 用 ** 包裹
}


// 根據 isCompleted 添加狀態描述
const status = item.isCompleted ? '(已完成)' : '';

// 添加序號、格式化後的待辦事項、狀態描述,每行之間加上換行符
export_msg += `${number}. ${formattedTitle} ${status}\n`;
});

// 使用 alert 顯示匯出內容
alert(export_msg.trim()); // 使用 trim() 去除結尾的多餘空格
}

// 儲存 todos 到 localStorage
function save_todos() {
console.log('Saving todos...');
// 將 todos 陣列轉換成 JSON 字串
localStorage.setItem('todos', JSON.stringify(todos));
alert('儲存成功!');
}

// 從 localStorage 載入 todos
function load_todos() {
const savedTodos = localStorage.getItem('todos');
if (savedTodos) {
// 解析 JSON 字串並更新 todos 陣列
todos = JSON.parse(savedTodos);
render(); // 重新渲染待辦事項
}
}
window.onload=()=>{
render_wrap();
load_todos();
};

07_js_localstorage.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
@import url('https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&family=Playwrite+CU:wght@100..400&display=swap');
*{
padding: 0;
margin: 0;
}

.wrap{
margin: 30px auto;
width: 800px;
min-height: 600px;
background-color: #a9c5d5;
border-radius: 10px;
}
h2{
padding-top: 20px;
padding-left: 30px;
font-family: "Kalam", cursive;
font-weight: 400;
font-style: normal;
font-size: 35px;
color:#e8626b;
}


input{
width: 400px;
margin: 20px 0 20px 30px;
padding-left: 5px;
font-size: 20px;
line-height: 2;
border: 1px solid #889fad;
color:#000;
background-color: #fff;

}
input:hover, input:focus {
outline: none;
background-color: #eaeaa9;
}
select{
width: 100px;
height: 40px;
font-size: 20px;
line-height: 2;
margin-right: 20px;
text-align: center;
}
.important{
color: #ed7023;
}
.urgent{
color: #cf372c;
}

#add_btn , #export_btn ,#save_btn{
margin-left: 5px;
border: none;
font-size: 18px;
border-radius: 5px;
background-color: #000;
color: #fff;
padding: 10px;
}
#add_btn:hover, #save_btn:hover{
cursor: pointer;
background-color: #17507b;
}
#export_btn:hover{
cursor: pointer;
background-color: #b91919;
}
ul{
list-style-type: square;
min-height: 400px;
margin: 0 30px;
padding: 5px;
border-radius: 5px;
background-color: #8ca8ba;
box-shadow: inset 0 4px 8px rgba(0, 0, 0, 0.3);

}
li {
display: flex;
justify-content: space-between; /* 元素之間平均分布 */
align-items: center; /* 垂直置中對齊 */
margin: 10px 20px;
border-bottom: 1px dotted #28292b;
font-size: 18px;
color: #000;
}

span {
display: block;
word-wrap: break-word;
width: 70%;
margin-right: 10px;

}

.btn_li {
flex-shrink: 0;
width: 30%;
margin-left: 10px; /* 按鈕之間的間距 */
}


.btn_on{
color:#f00;
background-color: inherit;
}
.btn_off{
color: #fff;
background-color: #17507b;

}
.btn_on , .btn_off{
width:80px;
height: 50px;
border: none;

/* font-size: 15px; */
border-radius: 3px;
padding: 5px;
}
.btn_on:hover , .btn_off:hover{
cursor: pointer;
}

.delBtn{
width:80px;
height: 50px;
border: none;
font-size: 15px;
border-radius: 3px;
background-color: #040404;
color: #fff;
padding: 10px;
}

.delBtn:hover{
cursor: pointer;
background-color: #b91919;
}

作業取自: https://codelove.tw/@howtomakeaturn/post/NxN6yx
by 站長阿川