DOM实战-js todo
1.需求:
实现一个如下页面:
- 最上面是输入框,后面是add按钮,输入文本点击add按钮,在下面就会出现一行,下面出现的每行最前面是两个按钮,然后后面是todo(要做的事)
- 第一个按钮是完成按钮,第二个按钮是删除按钮,点击完成按钮后这一行虽然不会消失,但是这一行会有一条横线在上面表示完成,点击删除按钮后这一行的数据就会消失不见
- 第三个按钮是修改,点击修改即可修改todo中的内容,修改完成后回车或按界面其他地方即可保存
- 两个按钮后面的第一个是todo,第二个是发布时间
2.实现代码:
HTML:
1 <!-- author: wyb --> 2 <!DOCTYPE html> 3 <html lang="en"> 4 <head> 5 <meta charset="UTF-8"> 6 <title>js todo</title> 7 <style> 8 *{ 9 margin: 0; 10 padding: 0; 11 } 12 .container{ 13 width: 60%; 14 margin: 0 auto; 15 } 16 .button{ 17 margin-right: 5px; 18 } 19 .complete{ 20 color: red; 21 text-decoration: line-through; 22 } 23 .pub-time{ 24 margin-left: 15px; 25 } 26 </style> 27 </head> 28 <body> 29 30 <div class="container"> 31 <!-- todo输入框 --> 32 <div class="todo-form"> 33 <input type="text" id="input"> 34 <button id="button-add">add</button> 35 </div> 36 <!-- todo列表 --> 37 <div id="todo-list"> 38 <!-- 示例todo --> 39 <div class="todo-cell"> 40 </div> 41 </div> 42 </div> 43 44 <script src="todo.js"></script> 45 </body> 46 </html>
JavaScript:
1 // 封装输出 2 var log = function() { 3 console.log.apply(console, arguments) 4 }; 5 6 // 字符串处理: 7 var todoTemplate = function (todo) { 8 // 下面是JavaScript中的字符串替换: 9 var t = `<div class="todo-cell"><button class="button-complete button">完成</button><button class="button-delete button">删除</button><button class="button-update button">编辑</button><span contenteditable='false' class="todo-label">${todo.task}</span><span class="pub-time">发布时间: ${todo.time}</span>`; 10 return t; 11 }; 12 13 // 插入新元素 14 var insertTodo = function (todo) { 15 // 获得todo-cell的HTML字符串: 16 var todoItem = todoTemplate(todo); 17 18 var todoList = document.querySelector("#todo-list"); 19 todoList.insertAdjacentHTML('beforeend', todoItem); 20 }; 21 22 // 开关一个元素的某个class 23 var toggleClass = function(element, className) { 24 if (element.classList.contains(className)) { 25 element.classList.remove(className) 26 } else { 27 element.classList.add(className) 28 } 29 }; 30 31 // 得到当前时间 32 var currentTime = function () { 33 var d = new Date(); 34 var year = d.getFullYear(); 35 var month = d.getMonth() + 1; 36 var day = d.getDate(); 37 var hour = d.getHours(); 38 var minute = d.getMinutes(); 39 var second = d.getSeconds(); 40 // 时间格式处理 41 if(minute <= 9){ 42 minute = "0" +minute 43 } 44 if(second <= 9){ 45 second = "0" +second 46 } 47 48 var timeString = `${year}/${month}/${day} ${hour}:${minute}:${second}`; 49 log("now time is: ", timeString); 50 return timeString 51 }; 52 53 // 返回自己在父元素中的下标 54 var indexOfElement = function(element) { 55 var parent = element.parentElement; 56 for (var i = 0; i < parent.children.length; i++) { 57 var e = parent.children[i]; 58 if (e === element) { 59 return i 60 } 61 } 62 }; 63 64 // 保存 todoList 65 var saveTodos = function() { 66 var s = JSON.stringify(todoArray); 67 localStorage.todoArray = s; 68 }; 69 70 var loadTodos = function() { 71 var s = localStorage.todoArray; 72 return JSON.parse(s); 73 }; 74 75 76 // 事件处理相关: 77 // 响应事件函数: 78 var bindEventAdd = function () { 79 var buttonAdd = document.getElementById("button-add"); 80 buttonAdd.addEventListener('click', function () { 81 log("button-add click"); 82 83 // 获得todo的值: 84 var task = document.getElementById("input").value; 85 // log(task); 86 // 获得todo对象 87 var todo = { 88 'task': task, 89 'time': currentTime(), 90 }; 91 // 将数据存入数组中 92 todoArray = loadTodos(); 93 todoArray.push(todo); 94 saveTodos(); 95 96 // 插入todo-list: 97 insertTodo(todo) 98 }); 99 }; 100 101 var bindEventEnter = function(){ 102 var todoList = document.querySelector("#todo-list"); 103 todoList.addEventListener('keydown', function (event) { 104 log('todo keydown: ', event, event.target); 105 var target = event.target; 106 if(event.key === 'Enter') { 107 log('按了回车'); 108 // 失去焦点 109 target.blur(); 110 // 阻止默认行为的发生, 也就是不插入回车 111 event.preventDefault(); 112 // 更新todo 113 var index = indexOfElement(target.parentElement); 114 log('update index: ', index); 115 // 把元素在 todoList 中更新 116 todoArray = loadTodos(); 117 todoArray[index-1].task = target.innerText; 118 saveTodos(); 119 } 120 }); 121 }; 122 123 var bindEventButton = function () { 124 // bindEventButton -> 复制todo所在div中的3个按钮的响应 125 var todoList = document.querySelector("#todo-list"); 126 todoList.addEventListener('click', function (event) { 127 log('click: ', event, event.target); 128 // 获得点击对象和其父元素(todo的div) 129 var target = event.target; 130 var todoDiv = target.parentElement; 131 132 // complete和delete和update的具体操作: 133 if(target.classList.contains('button-complete')) { 134 // 给 todo的div 开关一个状态 class 135 toggleClass(todoDiv, 'complete') 136 } else if (target.classList.contains('button-delete')) { 137 log('delete'); 138 var index = indexOfElement(todoDiv) - 1; 139 log(index); 140 // 删除父节点 141 todoDiv.remove(); 142 // 把元素从 todoArray 删除: 143 // delete todoArray[index] -> 不是完全删除,删除的数据变成了undefined依然留着数组中 144 todoArray = loadTodos(); 145 log("delete: ", todoArray[index]); 146 todoArray.splice(index, 1); 147 log(todoArray); 148 saveTodos(); 149 } 150 else if (target.classList.contains('button-update')) { 151 log('update'); 152 var cell = target.parentElement; 153 var span = cell.children[3]; 154 log("span is: ", span); 155 span.setAttribute("contenteditable", true); 156 // span.contentEditable = true // 同理 157 span.focus(); 158 } 159 }); 160 }; 161 162 var bindEventBlur = function() { 163 var todoList = document.querySelector('#todo-list'); 164 todoList.addEventListener('blur', function(event){ 165 log('todo blur: ', event, event.target); 166 var target = event.target; 167 if (target.classList.contains('todo-label')) { 168 log('update and save'); 169 // 让 span 不可编辑 170 target.setAttribute('contenteditable', 'false'); 171 // 更新todo 172 var index = indexOfElement(target.parentElement); 173 log('update index: ', index); 174 // 把元素在 todoList 中更新 175 todoArray = loadTodos(); 176 todoArray[index-1].task = target.innerText; 177 saveTodos() 178 } 179 }, true) 180 }; 181 182 183 // 绑定事件: 184 var bindEvents = function () { 185 // 添加todo 186 bindEventAdd(); 187 // 文本框输入todo 按回车保存 188 bindEventEnter(); 189 // 完成按钮和删除按钮和编辑按钮 190 bindEventButton(); 191 // 文本框失去焦点后保存todo 192 bindEventBlur() 193 }; 194 195 196 // 初始化todo: 197 var initTodos = function () { 198 var todoArray = loadTodos(); 199 for (var i = 0; i < todoArray.length; i++) { 200 var todo = todoArray[i]; 201 insertTodo(todo); 202 } 203 }; 204 205 // 存储数据 206 var todoArray = []; 207 // 程序主入口 208 var __main = function (){ 209 // 绑定事件: 210 bindEvents(); 211 212 // 程序加载后, 加载 todoArray 并且添加到页面中 213 initTodos(); 214 215 }; 216 217 __main(); 218 219 220 221 // 一些说明: 222 // 事件委托相关概念 223 // === 224 // 225 // 问题在于, todo都是运行的时候才添加的元素 226 // 对于这样的元素, 我们没办法实现绑定事件 227 // 我们可以把 click 事件绑定在事先存在的父元素上 228 // 通过父元素响应click事件 调用相应的事件响应函数 229 // 而事件响应函数会被传入一个参数, 就是事件本身 230 // 然后在运行的时候通过 event.target 属性(发起事件的元素,例如某个按钮) 231 // 来检查被点击的对象是否是需要的对象, 这个概念就是事件委托 232 233 // 与存储相关的问题: 234 // === 235 // localStorage 可以用来存储字符串数据, 在浏览器关闭后依然存在 236 // 存储方法如下: 237 // localStorage.name = 'wyb'; 238 // 关闭浏览器, 注释上一句代码 239 // 再次用同一个浏览器打开该项目, 仍然能获取到这个值 240 // log('关闭浏览器后: ', localStorage.name); 241 // localStorage删除数据: 242 // localStorage.removeItem("name"); 243 // 注意: 244 // 利用 localStorage 就可以 存储todo 245 // 但是 todo存在于array中 246 // 而 localStorage 只能存储 string 数据 247 // 所以没办法直接存储todo数据 248 // 249 // 可行的办法如下: 250 // 存储的时候把 array 转换为字符串 读取的时候把字符串转成 array 251 // 这个过程通常被称之为 序列化 和 反序列化 252 // 在 js 中, 序列化使用 JSON 格式 253 // 254 // var s = JSON.stringify([1, 2, 3, 4]); 255 // log('序列化后的字符串', typeof s, s); 256 // var a = JSON.parse(s); 257 // log('反序列化后的数组', typeof a, a); 258 // 输出结果: 259 // 序列化后的字符串 string [1,2,3,4] 260 // 反序列化后的数组 object Array(4) 261 // 262 // 使用 JSON 序列化后, 就可以把 todo存入浏览器的 localStorage 了 263 // 264 // 与时间相关的问题: JavaScript中的时间对象 -> Date对象 265 // === 266 // 常用用法如下: 267 /* 268 var d = new Date() 269 d.getFullYear() 270 年份, 2016 271 d.getMonth() 272 月份, 0-11 273 d.getDate() 274 日期, 1-31 275 d.getHours() 276 小时, 0-23 277 d.getMinutes() 278 分钟, 0-59 279 d.getSeconds() 280 秒数, 0-59 281 d.getMilliseconds() 282 毫秒, 0-999 283 d.getDay() 284 星期几, 0-6 285 */
3.实现效果:
(1)最开始界面
(2)输入信息点击add
(3)点击完成
(4)点击删除
(5)点击编辑
修改完成后回车后或点击其他页面即可
(6)刷新或关闭网页再次打开依然是之前保存的todo
too young too simple sometimes native!