Todo
功能实现
https://rencoo.github.io/appDemo/todo/index.html
---1.添加事项
---2.置顶事项
---3.删除事项
---4.用时排序
---5.删除所有
1class Todo{
2 constructor(id) {
3 this.container = document.getElementById(id);
4 this.todoContainer = this.container.querySelector('.todo-container');
5 this.delContainer = this.container.querySelector('.delete-container');
6
7 this.todoList = []; // 用于存放todo
8 this.todoDelList = []; // 用于存放删除的todo
9
10 let container = this.container,
11 addBtn = container.querySelector('.addBtn'),
12 input = container.querySelector('.todo-ctrl input'),
13 delBtn = container.querySelector('.delBtn'),
14 sortBtn = container.querySelector('.sortBtn');
15
16 /* 待办板块 */
17 // 点击按钮添加
18 addBtn.addEventListener('click', () => this.createTodo());
19 // 回车添加
20 input.addEventListener('keydown', evt => {
21 if(evt.key == 'Enter') {
22 this.createTodo();
23 }
24 })
25 // 点击todo文本,编辑todo
26 let todoContainer = this.todoContainer;
27 todoContainer.addEventListener('click', evt => {
28 let target = evt.target;
29 if(target.classList.contains('todo-task')) {
30 target.setAttribute('contenteditable', 'true');
31 }
32 target.focus(); // 点击后使文本框立即获得焦点
33 })
34 // 回车按钮完成编辑,并失焦
35 todoContainer.addEventListener('keydown', evt => {
36 let target = evt.target;
37 if(evt.key == 'Enter') {
38 if(target.classList.contains('todo-task')) {
39 let todoCell = target.parentNode,
40 idx = this.indexOfElement(todoCell),
41 task = target.value, // 获取task
42 todo = this.todoList[idx];
43 todo.task = task; // 修改todo
44 this.saveTodos() // 保存
45 target.blur(); // 失焦
46 evt.preventDefault(); // 阻止回车默认换行行为
47 }
48 }
49 })
50 // 点击时间进行置顶
51 todoContainer.addEventListener('click', evt => {
52 let target = evt.target;
53 let todoList = this.todoList;
54 if(target.classList.contains('todo-time')) {
55 let todoCell = target.parentNode,
56 idx = this.indexOfElement(todoCell),
57 todo = todoList[idx];
58 todoList.splice(idx, 1); // 在待办数组中删除todo
59 todoList.unshift(todo); // 在待办数组中置顶todo
60 this.saveTodos(); // 保存
61 todoCell.remove(); // 在待办页面删除todoCell
62 todoContainer.prepend(todoCell) // 在删除页面置顶todoCell
63 }
64 })
65 // 点击复选框移除事件(事件委托)
66 todoContainer.addEventListener('click', evt => {
67 let target = evt.target;
68 if(target.classList.contains('todo-input')) { // 判断目标元素;jQuery 可以直接指定响应事件的元素
69 let todoCell = target.parentNode,
70 idx = this.indexOfElement(todoCell), // 获取todo的idx
71 todo = this.todoList[idx]; // 获取todo数据
72 this.todoList.splice(idx, 1) // 从待办数组中删除todo
73 this.saveTodos(); // 保存
74 todoCell.remove(); // 在待办中移除todoCell
75
76 let time = this.getInterval(todo); // 获取删除和创建的时间差
77 todo.time = time;
78 this.todoDelList.push(todo); // 往删除数组中添加todo
79 this.saveTodos(); // 保存
80 this.insertTodo(delContainer, todo); // 在删除中显示todoCell
81 }
82 })
83
84 /* 删除板块 */
85 // 点击复选框移除单条(删除板块)
86 let delContainer = this.delContainer;
87 delContainer.addEventListener('click', evt => {
88 let target = evt.target;
89 if(target.classList.contains('todo-input')) {
90 let todoCell = target.parentNode,
91 idx = this.indexOfElement(todoCell);
92 this.todoDelList.splice(idx, 1);
93 this.saveTodos();
94 todoCell.remove();
95 }
96 })
97 // 清空删除板块
98 delBtn.addEventListener('click', evt => {
99 var r = confirm("确定要删除所有吗");
100 if (r==true) {
101 this.todoDelList.length = 0 // 清空todoDelList
102 delContainer.innerHTML = ''; // 删除板块清空todoCell
103 this.saveTodos(); // 保存
104 }
105 else{
106 return;
107 }
108 })
109 // 事项用时排序
110 sortBtn.addEventListener('click', evt => {
111 let todoDelList = this.todoDelList;
112 todoDelList.sort(desc); // 对数组进行排序
113 this.saveTodos(); // 保存
114 delContainer.innerHTML = '';
115 let len = todoDelList.length;
116 for(let i = 0; i < len; i++) {
117 let todo = todoDelList[i];
118 this.insertTodo(delContainer, todo);
119 }
120 })
121 // sort rule,完成事项用时的排序规则
122 let desc = function(a, b) {
123 let m = this.time_to_minute(a.time),
124 n = this.time_to_minute(b.time);
125 return m > n ? -1 : 1;
126 }.bind(this);
127
128 // 初始化,载入数据和渲染页面
129 this.initTodos();
130 }
131 // 创建todo
132 createTodo() {
133 let task = this.getInputValue(); // 获取文本框中的值
134 if(!task) { // 文本框是否为空
135 alert("请输入!");
136 return;
137 }
138 let currentTime = this.getCurrentTime(); // 获取当前时间
139 let todo = { // 创建todo对象
140 'task': task,
141 'time': currentTime
142 }
143 this.todoList.push(todo); // 待办数组添加todo
144 this.saveTodos(); // 保存数据
145 this.insertTodo(this.todoContainer, todo); // 在页面上添加todoCell
146 let input = this.container.querySelector('.todo-ctrl input');
147 input.value = '';
148 }
149 // 获取输入框的值
150 getInputValue() {
151 let input = this.container.querySelector('.todo-ctrl input'),
152 value = input.value;
153 return value;
154 }
155 // 插入todo;
156 insertTodo(box, todo) { // 参数:容器;todo;
157 let t = this.templateTodo(todo);
158 box.insertAdjacentHTML('beforeEnd', t);
159 }
160 // 模板todo
161 templateTodo(todo) {
162 let t = `
163 <div class="todo-cell">
164 <input class="todo-input" type="checkbox"/>
165 <span class="todo-task" contenteditable ="false">${todo.task}</span>
166 <span class="todo-time">${todo.time}>>Top</span>
167 </div>
168 `
169 return t;
170 }
171 // 获取当前时间
172 getCurrentTime() {
173 var d= new Date(),
174 addZero = this.addZero,
175 month = addZero(d.getMonth() + 1),
176 date = addZero(d.getDate()), // 几号;区别于getDay星期几
177 hour = addZero(d.getHours()),
178 minute = addZero(d.getMinutes());
179 var t = `${month}/${date} ${hour}:${minute}`;
180 return t;
181 }
182 addZero(t) {
183 t = t < 10 ? '0' + t : t;
184 return t;
185 }
186 // 获取元素的序号
187 indexOfElement(el) {
188 let parent = el.parentElement,
189 len = parent.children.length;
190 for (let i = 0; i < len; i++) {
191 let e = parent.children[i];
192 if (e === el) {
193 return i;
194 }
195 }
196 }
197 // todo的时长
198 getInterval(todo) { // 时间差是分钟级别的
199 let createdTime = todo.time, // 创建时刻
200 delTime = this.getCurrentTime(), // 删除时刻
201 interval = this.time_to_minute(delTime) - this.time_to_minute(createdTime), // 时间差
202 time = this.minute_to_time(interval); // 将分钟转化为标准格式
203 return time;
204 }
205 // 时间积累量(相对于月初)
206 time_to_minute(time) {
207 let t = time.split('/'), // '08', '17 16:34'
208 s= t[1].split(' '), // '17', '16:34'
209 d = s[1].split(':'), // '16', '34'
210 // month = t[0], // 默认事件都在同一个月内发生
211 date = s[0],
212 hour = d[0],
213 minute = d[1],
214 m = Number(date * 1440) + Number(hour * 60) + Number(minute);
215 return m;
216 }
217 // 积累量转化为时间
218 minute_to_time(m) {
219 let addZero = this.addZero;
220 let date = addZero(Math.floor(m / 1440)),
221 hour = addZero(Math.floor(m / 60) % 24),
222 minute = addZero(m % 60),
223 time = `00/${date} ${hour}:${minute}`;
224 return time;
225 }
226 // localStorage 网页存储数据
227 saveTodos() {
228 let m = JSON.stringify(this.todoList),
229 n = JSON.stringify(this.todoDelList);
230 localStorage.todoList = m;
231 localStorage.todoDelList = n;
232 }
233 // 载入数据
234 loadTodos() {
235 let m = localStorage.todoList,
236 n = localStorage.todoDelList,
237 obj ={
238 todoList: JSON.parse(m),
239 todoDelList: JSON.parse(n)
240 };
241 return obj;
242 }
243 // 初始化todo
244 initTodos() {
245 let obj = this.loadTodos();
246 this.todoList = obj.todoList;
247 let len = this.todoList.length;
248 for (let i = 0; i < len; i++) {
249 let todo = this.todoList[i];
250 this.insertTodo(this.todoContainer, todo);
251 }
252
253 this.todoDelList = obj.todoDelList;
254 let length = this.todoDelList.length;
255 for(let j = 0; j < length; j++) {
256 let todoDel = this.todoDelList[j];
257 this.insertTodo(this.delContainer, todoDel);
258 }
259 }
260}
261
262// 实例化对象
263const todo = new Todo('todoDemo');
200多行的小demo收获
由于demo存在两个类似的板块,因此很多方法都需要抽象出来,使得两者都能使用;
由于有了数据存储这个操作,因此每次进行删除、插入、置顶、排序等操作时,就先必须把数据库中(数组)的数据信息更新并保存(类似于告诉后端你更新了什么数据),再在页面上显示相关改动(相当于展示在用户面前的前端重新局部渲染)
利用localStorage存储数据(localStorage中只能存储字符串,所以我们要借助于JSON.stringify()和JSON.parse();)
处理数据:利用对象保存解析localStorage的多条数据信息,而不能使用数组;然后要使用数据的时候,再读取对象的属性(这里是todoList与todoDelList);
对处理数据有了初步认知,处理好数据是编程中十分重要的内容,利用合理的数据结构存储数据,能方便数据的管理与使用
localStorage存储数据不安全,每条数据的key-value都被清晰的记录在浏览器的面板上;
根据数组中元素的特性,可以自定义排序规则,再根据规则利用sort对数组进行排序;
完成事项用时(ex 00/00 01:08)的排序,可以分别对每个位置进行比较,一旦出现大小结果就直接返回,这样排序开销会比较低;排序规则如下
1// ex
2// 先将标准时间的各项数据提取出保存在对象中(处理数据)
3var times = [
4 {
5 date: 17,
6 h: 18,
7 m: 04
8 },
9 {
10 date: 18,
11 h: 18,
12 m: 04
13 },
14 {
15 date: 19,
16 h: 18,
17 m: 04
18 },
19 {
20 date: 19,
21 h: 20,
22 m: 04
23 },
24]
25// 制定比较规则
26var desc = function(a, b) {
27 if (a.date !== b.date) {
28 return a.date > b.date ? -1 : 1;
29 } else {
30 if (a.h !== b.h) {
31 return a.h > b.h ? -1 : 1;
32 } else {
33 if (a.m !== b.m) {
34 return a.m > b.m ? -1 : 1;
35 }
36 }
37 }
38}
39// 对时间数组中的对象进行排序
40times.sort(desc)
41
42// 结果如下,符合预期
43[{ date: 19, h: 20, m: 4 },{ date: 19, h: 18, m: 4 },{ date: 18, h: 18, m: 4 },{ date: 17, h: 18, m: 4 }]