TODO应用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title><!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>上课用品 8</title>
<style>
/* 完成的样式 */
.done {
color: gray;
text-decoration: line-through;
}
</style>
</head>
<body>
<div class="todo-main">
<!-- todo 输入框 -->
<div class="todo-form">
<input id='id-input-todo' type="text">
<button id='id-button-add' type="button">Add</button>
</div>
<!-- todo 列表 -->
<div id="id-div-container">
<!-- <div class='todo-cell'>
<button class='todo-done'>完成</button>
<button class='todo-delete'>删除</button>
<span contenteditable='true'>上课</span>
</div> -->
</div>
</div>
<script>
// 通过一个 Todo 应用, 学习下面这个概念
// 1, 什么是事件委托
// 2, 为什么需要事件委托
// 3, 如何实现事件委托
//
// 时间操作
// content editable (标签的可编辑属性)
// localStorage (本地存储) 和 JSON 格式
// 自己定义一个 log 函数
var log = function() {
console.log.apply(console, arguments)
}
var todoList = []
// 给 add button 绑定添加 todo 事件
var addButton = document.querySelector('#id-button-add')
addButton.addEventListener('click', function(){
// 获得 input.value
var todoInput = document.querySelector('#id-input-todo')
var task = todoInput.value
// 生成 todo 对象
var todo = {
'task': task,
'time': currentTime()
}
todoList.push(todo)
saveTodos()
insertTodo(todo)
})
var insertTodo = function(todo) {
// 添加到 container 中
var todoContainer = document.querySelector('#id-div-container')
var t = templateTodo(todo)
// 这个方法用来添加元素更加方便, 不需要 createElement
todoContainer.insertAdjacentHTML('beforeend', t);
}
var templateTodo = function(todo) {
var t = `
<div class='todo-cell'>
<button class='todo-done'>完成</button>
<button class='todo-delete'>删除</button>
<span contenteditable='true'>${todo.task}</span>
<span>${todo.time}</span>
</div>
`
return t
}
// 事件委托相关概念
// ===
//
// 问题在于, todo 都是运行的时候才添加的元素
// 对于这样的元素, 我们没办法事先绑定事件
// 我们可以把 click 事件绑定在事先存在的父元素上
// 然后在运行的时候检查被点击的对象(通过 event.target 属性)
// 是否是我们需要的对象, 这个概念就是事件委托
var todoContainer = document.querySelector('#id-div-container')
todoContainer.addEventListener('keydown', function(event){
log('container keydown', event, event.target)
var target = event.target
if(event.key === 'Enter') {
log('按了回车')
// 失去焦点
target.blur()
// 阻止默认行为的发生, 也就是不插入回车
event.preventDefault()
// 更新 todo
var index = indexOfElement(target)
log('update index', index)
// 把元素在 todoList 中更新
todoList[index].task = target.innerHTML
// todoList.splice(index, 1)
saveTodos()
}
})
// 通过 event.target 的 class 来检查点击的是什么
todoContainer.addEventListener('click', function(event){
log('container click', event, event.target)
var target = event.target
if(target.classList.contains('todo-done')) {
log('done')
// 给 todo div 开关一个状态 class
var todoDiv = target.parentElement
toggleClass(todoDiv, 'done')
} else if (target.classList.contains('todo-delete')) {
log('delete')
var todoDiv = target.parentElement
var index = indexOfElement(target)
log('delete index', index)
todoDiv.remove()
// 把元素从 todoList 中 remove 掉
// delete todoList[index]
todoList.splice(index, 1)
saveTodos()
}
})
// 保存 todoList
var saveTodos = function() {
var s = JSON.stringify(todoList)
localStorage.todoList = s
}
var loadTodos = function() {
var s = localStorage.todoList
return JSON.parse(s)
}
// 返回自己在父元素中的下标
var indexOfElement = function(element) {
var parent = element.parentElement
for (var i = 0; i < parent.children.length; i++) {
var e = parent.children[i]
if (e === element) {
return i
}
}
}
// 这个函数用来开关一个元素的某个 class
var toggleClass = function(element, className) {
if (element.classList.contains(className)) {
element.classList.remove(className)
} else {
element.classList.add(className)
}
}
// localStorage 可以用来存储字符串数据, 在浏览器关闭后依然存在
// 存储方法如下
// localStorage.name = 'gua'
// 关闭浏览器, 再次打开, 仍然能获取到这个值
log('关闭浏览器后', localStorage.name)
var todos = ['吃饭', '喝水', '上课']
//
// 利用 localStorage 就可以存储 todo
// 但是 todo 存在 array 中
// 而 localStorage 只能存储 string 数据
// 所以没办法直接存储
//
// 可行的办法如下
// 存储的时候把 array 转换为字符串
// 读取的时候把字符串转成 array
// 这个过程通常被称之为 序列化 和 反序列化
//
// 在 js 中, 序列化使用 JSON 格式
var s = JSON.stringify([1, 2, 3, 4])
log('序列化后的字符串', typeof s, s)
var a = JSON.parse(s)
log('反序列化后的数组', typeof a, a)
// 使用 JSON 序列化后, 就可以把 todo 存入浏览器的 localStorage 了
// {
// task: '',
// time: '',
// }
var currentTime = function() {
var d = new Date()
var month = d.getMonth() + 1
var date = d.getDate()
var hours = d.getHours()
var minutes = d.getMinutes()
var seconds = d.getSeconds()
var timeString = `${month}/${date} ${hours}:${minutes}:${seconds}`
return timeString
}
// 时间标准库
// ===
// 常用用法如下
/*
var d = new Date()
d.getFullYear()
年份, 2016
d.getMonth()
月份, 0-11
d.getDate()
日期, 1-31
d.getHours()
小时, 0-23
d.getMinutes()
分钟, 0-59
d.getSeconds()
秒数, 0-59
d.getMilliseconds()
毫秒, 0-999
d.getDay()
星期几, 0-6
*/
// 程序加载后, 加载 todoList 并且添加到页面中
todoList = loadTodos()
for (var i = 0; i < todoList.length; i++) {
var todo = todoList[i]
insertTodo(todo)
}
</script>
</body>
</html>
效果:
改版Todo应用程序
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>上课用品 8</title>
<style>
/* 完成的样式 */
.done {
color: gray;
text-decoration: line-through;
}
</style>
</head>
<body>
<div class="todo-main">
<!-- todo 输入框 -->
<div class="todo-form">
<input id='id-input-todo' type="text">
<button id='id-button-add' type="button">Add</button>
</div>
<!-- todo 列表 -->
<div id="id-div-container">
<!-- <div class='todo-cell'>
<button class='todo-done'>完成</button>
<button class='todo-delete'>删除</button>
<span contenteditable='true'>上课</span>
</div> -->
</div>
</div>
<script src='todo.js'></script>
</body>
</html>
todo.js
// 自己定义一个 log 函数
var log = function() {
console.log.apply(console, arguments)
}
// 给 add button 绑定添加 todo 事件
var bindEventAdd = function() {
log('bind add button')
var addButton = document.querySelector('#id-button-add')
log('add button', addButton)
addButton.addEventListener('click', function(){
log('click add')
// 获得 input.value
var todoInput = document.querySelector('#id-input-todo')
var task = todoInput.value
// 生成 todo 对象
var todo = {
'task': task,
'time': currentTime()
}
todoList.push(todo)
saveTodos()
insertTodo(todo)
})
}
var bindEventEnter = function() {
var todoContainer = document.querySelector('#id-div-container')
todoContainer.addEventListener('keydown', function(event){
log('container keydown', event, event.target)
var target = event.target
if(event.key === 'Enter') {
log('按了回车')
// 失去焦点
target.blur()
// 阻止默认行为的发生, 也就是不插入回车
event.preventDefault()
// 更新 todo
var index = indexOfElement(target.parentElement)
log('update index', index)
// 把元素在 todoList 中更新
todoList[index].task = target.innerHTML
// todoList.splice(index, 1)
saveTodos()
}
})
}
var bindEventButton = function() {
// 通过 event.target 的 class 来检查点击的是什么
var todoContainer = document.querySelector('#id-div-container')
todoContainer.addEventListener('click', function(event){
log('container click', event, event.target)
var target = event.target
if(target.classList.contains('todo-done')) {
log('done')
// 给 todo div 开关一个状态 class
var todoDiv = target.parentElement
toggleClass(todoDiv, 'done')
} else if (target.classList.contains('todo-delete')) {
log('delete')
var todoDiv = target.parentElement
var index = indexOfElement(target.parentElement)
log('delete index', index)
todoDiv.remove()
// 把元素从 todoList 中 remove 掉
// delete todoList[index]
todoList.splice(index, 1)
saveTodos()
} else if (target.classList.contains('todo-edit')) {
log('edit')
var cell = target.parentElement
var span = cell.children[3]
log('span is ', span)
span.setAttribute('contenteditable', 'true')
// span.contentEditable = true
span.focus()
}
})
}
var GuaEventType = {
blur: 'blur',
click: 'click',
}
// EventType.blur
// EventType.click
var bindEventBlur = function() {
log('bind event blur function')
var todoContainer = document.querySelector('#id-div-container')
todoContainer.addEventListener(GuaEventType.blur, function(event){
log('container blur', event, event.target)
var target = event.target
if (target.classList.contains('todo-label')) {
log('update and save')
// 让 span 不可编辑
target.setAttribute('contenteditable', 'false')
// 更新 todo
var index = indexOfElement(target.parentElement)
log('update index', index)
// 把元素在 todoList 中更新
todoList[index].task = target.innerHTML
// todoList.splice(index, 1)
saveTodos()
}
}, true)
}
var bindEvents = function() {
// 添加 todo
bindEventAdd()
// 文本框输入 todo 按回车保存
bindEventEnter()
// 完成按钮和删除按钮
bindEventButton()
// 文本框失去焦点后保存 todo
bindEventBlur()
}
var insertTodo = function(todo) {
// 添加到 container 中
var todoContainer = document.querySelector('#id-div-container')
var t = templateTodo(todo)
// 这个方法用来添加元素更加方便, 不需要 createElement
todoContainer.insertAdjacentHTML('beforeend', t);
}
var templateTodo = function(todo) {
var t = `
<div class='todo-cell'>
<button class='todo-done'>完成</button>
<button class='todo-delete'>删除</button>
<button class='todo-edit'>编辑</button>
<span class='todo-label' contenteditable='false'>${todo.task}</span>
<span>${todo.time}</span>
</div>
`
return t
}
// 保存 todoList
var saveTodos = function() {
var s = JSON.stringify(todoList)
localStorage.todoList = s
}
var loadTodos = function() {
var s = localStorage.todoList
return JSON.parse(s)
}
// 返回自己在父元素中的下标
var indexOfElement = function(element) {
var parent = element.parentElement
for (var i = 0; i < parent.children.length; i++) {
var e = parent.children[i]
if (e === element) {
return i
}
}
}
// 这个函数用来开关一个元素的某个 class
var toggleClass = function(element, className) {
if (element.classList.contains(className)) {
element.classList.remove(className)
} else {
element.classList.add(className)
}
}
var currentTime = function() {
var d = new Date()
var month = d.getMonth() + 1
var date = d.getDate()
var hours = d.getHours()
var minutes = d.getMinutes()
var seconds = d.getSeconds()
var timeString = `${month}/${date} ${hours}:${minutes}:${seconds}`
return timeString
}
var initTodos = function() {
todoList = loadTodos()
for (var i = 0; i < todoList.length; i++) {
var todo = todoList[i]
insertTodo(todo)
}
}
var todoList = []
var __main = function() {
// 绑定事件
bindEvents()
// 程序加载后, 加载 todoList 并且添加到页面中
initTodos()
}
__main()
参考:
事件委托
https://github.com/yonyouyc/blog/issues/25
https://segmentfault.com/a/1190000002613617
http://www.cnblogs.com/owenChen/archive/2013/02/18/2915521.html
http://blog.xieliqun.com/2016/08/12/event-delegate/#%E4%BA%8B%E4%BB%B6%E5%A7%94%E6%89%98
http://blog.xieliqun.com/2016/08/12/event-delegate/#%E4%BA%8B%E4%BB%B6%E5%A7%94%E6%89%98
时间操作
https://segmentfault.com/a/1190000000481753
https://www.cnblogs.com/tugenhua0707/p/3776808.html
本地化存储
https://segmentfault.com/a/1190000002701423
https://segmentfault.com/a/1190000003965297
https://segmentfault.com/a/1190000007506189#articleHeader5