todo 案例
- 原html
html
css
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Document</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认" />
</div>
<ul class="todo-main">
<li>
<label>
<input type="checkbox" />
<span>xxxxx</span>
</label>
<button class="btn btn-danger" style="display:none">删除</button>
</li>
<li>
<label>
<input type="checkbox" />
<span>yyyy</span>
</label>
<button class="btn btn-danger" style="display:none">删除</button>
</li>
</ul>
<div class="todo-footer">
<label>
<input type="checkbox" />
</label>
<span>
<span>已完成0</span> / 全部2
</span>
<button class="btn btn-danger">清除已完成任务</button>
</div>
</div>
</div>
</div>
</body>
</html>
/*base*/
body {
background: #fff;
}
.btn {
display: inline-block;
padding: 4px 12px;
margin-bottom: 0;
font-size: 14px;
line-height: 20px;
text-align: center;
vertical-align: middle;
cursor: pointer;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
border-radius: 4px;
}
.btn-danger {
color: #fff;
background-color: #da4f49;
border: 1px solid #bd362f;
}
.btn-danger:hover {
color: #fff;
background-color: #bd362f;
}
.btn:focus {
outline: none;
}
.todo-container {
width: 600px;
margin: 0 auto;
}
.todo-container .todo-wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
/*header*/
.todo-header input {
width: 560px;
height: 28px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 7px;
}
.todo-header input:focus {
outline: none;
border-color: rgba(82, 168, 236, 0.8);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}
/*main*/
.todo-main {
margin-left: 0px;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0px;
}
.todo-empty {
height: 40px;
line-height: 40px;
border: 1px solid #ddd;
border-radius: 2px;
padding-left: 5px;
margin-top: 10px;
}
/*item*/
li {
list-style: none;
height: 36px;
line-height: 36px;
padding: 0 5px;
border-bottom: 1px solid #ddd;
}
li label {
float: left;
cursor: pointer;
}
li label li input {
vertical-align: middle;
margin-right: 6px;
position: relative;
top: -1px;
}
li button {
float: right;
display: none;
margin-top: 3px;
}
li:before {
content: initial;
}
li:last-child {
border-bottom: none;
}
/*footer*/
.todo-footer {
height: 40px;
line-height: 40px;
padding-left: 6px;
margin-top: 5px;
}
.todo-footer label {
display: inline-block;
margin-right: 20px;
cursor: pointer;
}
.todo-footer label input {
position: relative;
top: -1px;
vertical-align: middle;
margin-right: 5px;
}
.todo-footer button {
float: right;
margin-top: 5px;
}
基础示例
html
css
js
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Document</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认" />
</div>
<ul class="todo-main">
</ul>
<div class="todo-footer">
<label>
<input type="checkbox" />
</label>
<span>
<span>已完成<span id="finish_num">0</span></span></span> / 全部<span id="all_num">0</span>
</span>
<button class="btn btn-danger">清除已完成任务</button>
</div>
</div>
</div>
</div>
<script src="./index.js"></script>
</body>
</html>
/*base*/
body {
background: #fff;
}
.btn {
display: inline-block;
padding: 4px 12px;
margin-bottom: 0;
font-size: 14px;
line-height: 20px;
text-align: center;
vertical-align: middle;
cursor: pointer;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
border-radius: 4px;
}
.btn-danger {
color: #fff;
background-color: #da4f49;
border: 1px solid #bd362f;
}
.btn-danger:hover {
color: #fff;
background-color: #bd362f;
}
.btn:focus {
outline: none;
}
.todo-container {
width: 600px;
margin: 0 auto;
}
.todo-container .todo-wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
/*header*/
.todo-header input {
width: 560px;
height: 28px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 7px;
}
.todo-header input:focus {
outline: none;
border-color: rgba(82, 168, 236, 0.8);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}
/*main*/
.todo-main {
margin-left: 0px;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0px;
}
.todo-empty {
height: 40px;
line-height: 40px;
border: 1px solid #ddd;
border-radius: 2px;
padding-left: 5px;
margin-top: 10px;
}
/*item*/
li {
list-style: none;
height: 36px;
line-height: 36px;
padding: 0 5px;
border-bottom: 1px solid #ddd;
}
li label {
float: left;
cursor: pointer;
}
li label li input {
vertical-align: middle;
margin-right: 6px;
position: relative;
top: -1px;
}
li button {
float: right;
display: none;
margin-top: 3px;
}
li:before {
content: initial;
}
li:last-child {
border-bottom: none;
}
/*footer*/
.todo-footer {
height: 40px;
line-height: 40px;
padding-left: 6px;
margin-top: 5px;
}
.todo-footer label {
display: inline-block;
margin-right: 20px;
cursor: pointer;
}
.todo-footer label input {
position: relative;
top: -1px;
vertical-align: middle;
margin-right: 5px;
}
.todo-footer button {
float: right;
margin-top: 5px;
}
const input = document.querySelector('.todo-header input')
const list = document.querySelector('.todo-main')
const finishNumSpan = document.querySelector('#finish_num')
const allNumSpan = document.querySelector('#all_num')
const todos = [
{ id: "001", name: "抽烟", done: true },
{ id: "002", name: "喝酒", done: true },
{ id: "003", name: "开车", done: false },
]
// 返回todo 已完成,和总共的数量
function updateTodoInfo() {
let finishNum = 0
todos.forEach(todo => {
if (todo.done) {
finishNum++
}
})
finishNumSpan.innerHTML = finishNum
allNumSpan.innerHTML = todos.length
}
window.onload = function () {
todos.forEach(todo => addTodo(todo))
}
// 添加任务
/*
@param {Object} todo: {
id: string;
name: string;
done: boolean;
}
*/
function addTodo(todo) {
const li = document.createElement('li')
const checked = todo.done ? 'checked' : ''
const display = todo.done ? 'inline-block' : 'none'
li.id = todo.id
li.innerHTML = `
<label>
<input id="change-${todo.id}" onchange="handleTodoChange(this)" type="checkbox" ${checked}/>
<span>${todo.name}</span>
</label>
<button id="remove-${todo.id}" onclick="handleTodoDelete(this)" class="btn btn-danger" style="display:${display}">删除</button>
`
list.appendChild(li)
updateTodoInfo()
}
function removeTodo(id) {
const li = document.getElementById(id)
console.log('li:', li)
li.parentNode.removeChild(li)
const index = todos.findIndex(todo => todo.id === id);
if (index !== -1) {
// 使用 splice() 方法删除找到的对象
todos.splice(index, 1);
}
updateTodoInfo()
}
function changeTodo(id, done) {
const removeID = "remove-" + id
const removeBotton = document.getElementById(removeID)
if (done) {
removeBotton.display = "inline-block"
} else {
removeBotton.display = "none"
}
todos.forEach(todo => {
if (todo.id === id) {
todo.done = done
}
})
updateTodoInfo()
}
// 按钮添加任务
input.addEventListener('keyup', function (e) {
if (e.key === 'Enter') {
const value = this.value.trim()
if (!value) return
todo = {
id: `${Date.now()}`,
name: value,
done: false
}
addTodo(todo)
this.value = ''
}
})
function handleTodoChange(target) {
console.log('target:', target)
const todoIDs = target.id.split('-')
const todoID = todoIDs[todoIDs.length - 1]
const checked = target.checked
changeTodo(todoID, checked)
}
function handleTodoDelete(target) {
console.log('target:', target)
console.log('target.parentNode:', target.parentNode)
const todoIDs = target.id.split('-')
const todoID = todoIDs[todoIDs.length - 1]
removeTodo(todoID)
}
完整示例
html
css
js
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Document</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认" />
</div>
<ul class="todo-main">
</ul>
<div class="todo-footer">
<label>
<input type="checkbox" />
</label>
<span>
<span>已完成<span id="finish_num">0</span></span></span> / 全部<span id="all_num">0</span>
</span>
<button class="btn btn-danger">清除已完成任务</button>
</div>
</div>
</div>
</div>
<script src="./index.js"></script>
</body>
</html>
/*base*/
body {
background: #fff;
}
.btn {
display: inline-block;
padding: 4px 12px;
margin-bottom: 0;
font-size: 14px;
line-height: 20px;
text-align: center;
vertical-align: middle;
cursor: pointer;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
border-radius: 4px;
}
.btn-danger {
color: #fff;
background-color: #da4f49;
border: 1px solid #bd362f;
}
.btn-danger:hover {
color: #fff;
background-color: #bd362f;
}
.btn:focus {
outline: none;
}
.todo-container {
width: 600px;
margin: 0 auto;
}
.todo-container .todo-wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
/*header*/
.todo-header input {
width: 560px;
height: 28px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 7px;
}
.todo-header input:focus {
outline: none;
border-color: rgba(82, 168, 236, 0.8);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}
/*main*/
.todo-main {
margin-left: 0px;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0px;
}
.todo-empty {
height: 40px;
line-height: 40px;
border: 1px solid #ddd;
border-radius: 2px;
padding-left: 5px;
margin-top: 10px;
}
/*item*/
li {
list-style: none;
height: 36px;
line-height: 36px;
padding: 0 5px;
border-bottom: 1px solid #ddd;
}
li label {
float: left;
cursor: pointer;
}
li label li input {
vertical-align: middle;
margin-right: 6px;
position: relative;
top: -1px;
}
li button {
float: right;
display: none;
margin-top: 3px;
}
li:before {
content: initial;
}
li:last-child {
border-bottom: none;
}
/*footer*/
.todo-footer {
height: 40px;
line-height: 40px;
padding-left: 6px;
margin-top: 5px;
}
.todo-footer label {
display: inline-block;
margin-right: 20px;
cursor: pointer;
}
.todo-footer label input {
position: relative;
top: -1px;
vertical-align: middle;
margin-right: 5px;
}
.todo-footer button {
float: right;
margin-top: 5px;
}
const input = document.querySelector('.todo-header input')
const list = document.querySelector('.todo-main')
const allCheckBox = document.querySelector('.todo-footer input')
const finishNumSpan = document.querySelector('#finish_num')
const allNumSpan = document.querySelector('#all_num')
const todos = [
{ id: "001", name: "抽烟", done: true },
{ id: "002", name: "喝酒", done: true },
{ id: "003", name: "开车", done: false },
]
// 返回todo 已完成,和总共的数量
function updateTodoInfo() {
let finishNum = 0
console.log('todos:', todos)
todos.forEach(todo => {
if (todo.done) {
finishNum++
}
})
console.log('finishNum:', finishNum)
finishNumSpan.innerHTML = finishNum
allNumSpan.innerHTML = todos.length
allCheckBox.checked = finishNum === todos.length
}
window.onload = function () {
todos.forEach(todo => addTodo(todo))
}
// 添加任务
/*
@param {Object} todo: {
id: string;
name: string;
done: boolean;
}
*/
function addTodo(todo) {
const li = document.createElement('li')
const checked = todo.done ? 'checked' : ''
const display = todo.done ? 'inline-block' : 'none'
li.id = todo.id
li.innerHTML = `
<label>
<input id="change-${todo.id}" onchange="handleTodoChange(this)" type="checkbox" ${checked}/>
<span>${todo.name}</span>
</label>
<button id="remove-${todo.id}" onclick="handleTodoDelete(this)" class="btn btn-danger" style="display:${display}">删除</button>
`
list.appendChild(li)
updateTodoInfo()
}
function removeTodo(id) {
const li = document.getElementById(id)
console.log('li:', li)
li.parentNode.removeChild(li)
const index = todos.findIndex(todo => todo.id === id);
if (index !== -1) {
// 使用 splice() 方法删除找到的对象
todos.splice(index, 1);
}
updateTodoInfo()
}
function changeTodo(id, done) {
console.log(`in changeTodo id:${id}, done:${done}`)
const removeID = "remove-" + id
const removeBotton = document.getElementById(removeID)
if (done) {
removeBotton.style = "display:inline-block"
} else {
removeBotton.style = "display:none"
}
const changeID = "change-" + id
const changeCheckBox = document.getElementById(changeID)
changeCheckBox.checked = done
todos.forEach(todo => {
if (todo.id === id) {
todo.done = done
}
})
updateTodoInfo()
}
// 按钮添加任务
input.addEventListener('keyup', function (e) {
if (e.key === 'Enter') {
const value = this.value.trim()
if (!value) return
todo = {
id: `${Date.now()}`,
name: value,
done: false
}
todos.push(todo)
addTodo(todo)
this.value = ''
}
})
allCheckBox.addEventListener('change', function (e) {
console.log('e.target:', e.target)
console.log('e.target:', e.target.checked) // todo 子项会造成联动,需提前保存其状态
checked = e.target.checked
todos.forEach(todo => {
if (todo.done !== checked){
changeTodo(todo.id, checked)
}
})
})
function handleTodoChange(target) {
console.log('target:', target)
const todoIDs = target.id.split('-')
const todoID = todoIDs[todoIDs.length - 1]
const checked = target.checked
changeTodo(todoID, checked)
}
function handleTodoDelete(target) {
console.log('target:', target)
console.log('target.parentNode:', target.parentNode)
const todoIDs = target.id.split('-')
const todoID = todoIDs[todoIDs.length - 1]
removeTodo(todoID)
}
本地保存示例
html
css
js
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Document</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认" />
</div>
<ul class="todo-main">
</ul>
<div class="todo-footer">
<label>
<input type="checkbox" />
</label>
<span>
<span>已完成<span id="finish_num">0</span></span></span> / 全部<span id="all_num">0</span>
</span>
<button class="btn btn-danger">清除已完成任务</button>
</div>
</div>
</div>
</div>
<script src="./index.js"></script>
</body>
</html>
/*base*/
body {
background: #fff;
}
.btn {
display: inline-block;
padding: 4px 12px;
margin-bottom: 0;
font-size: 14px;
line-height: 20px;
text-align: center;
vertical-align: middle;
cursor: pointer;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
border-radius: 4px;
}
.btn-danger {
color: #fff;
background-color: #da4f49;
border: 1px solid #bd362f;
}
.btn-danger:hover {
color: #fff;
background-color: #bd362f;
}
.btn:focus {
outline: none;
}
.todo-container {
width: 600px;
margin: 0 auto;
}
.todo-container .todo-wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
/*header*/
.todo-header input {
width: 560px;
height: 28px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 7px;
}
.todo-header input:focus {
outline: none;
border-color: rgba(82, 168, 236, 0.8);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}
/*main*/
.todo-main {
margin-left: 0px;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0px;
}
.todo-empty {
height: 40px;
line-height: 40px;
border: 1px solid #ddd;
border-radius: 2px;
padding-left: 5px;
margin-top: 10px;
}
/*item*/
li {
list-style: none;
height: 36px;
line-height: 36px;
padding: 0 5px;
border-bottom: 1px solid #ddd;
}
li label {
float: left;
cursor: pointer;
}
li label li input {
vertical-align: middle;
margin-right: 6px;
position: relative;
top: -1px;
}
li button {
float: right;
display: none;
margin-top: 3px;
}
li:before {
content: initial;
}
li:last-child {
border-bottom: none;
}
/*footer*/
.todo-footer {
height: 40px;
line-height: 40px;
padding-left: 6px;
margin-top: 5px;
}
.todo-footer label {
display: inline-block;
margin-right: 20px;
cursor: pointer;
}
.todo-footer label input {
position: relative;
top: -1px;
vertical-align: middle;
margin-right: 5px;
}
.todo-footer button {
float: right;
margin-top: 5px;
}
const input = document.querySelector('.todo-header input')
const list = document.querySelector('.todo-main')
const allCheckBox = document.querySelector('.todo-footer input')
const finishNumSpan = document.querySelector('#finish_num')
const allNumSpan = document.querySelector('#all_num')
const todos = JSON.parse(localStorage.getItem("todos")) || []
// 返回todo 已完成,和总共的数量
function updateTodoInfo() {
let finishNum = 0
console.log('todos:', todos)
todos.forEach(todo => {
if (todo.done) {
finishNum++
}
})
console.log('finishNum:', finishNum)
finishNumSpan.innerHTML = finishNum
allNumSpan.innerHTML = todos.length
allCheckBox.checked = finishNum === todos.length
localStorage.setItem("todos", JSON.stringify(todos))
}
window.onload = function () {
todos.forEach(todo => addTodo(todo))
}
// 添加任务
/*
@param {Object} todo: {
id: string;
name: string;
done: boolean;
}
*/
function addTodo(todo) {
const li = document.createElement('li')
const checked = todo.done ? 'checked' : ''
const display = todo.done ? 'inline-block' : 'none'
li.id = todo.id
li.innerHTML = `
<label>
<input id="change-${todo.id}" onchange="handleTodoChange(this)" type="checkbox" ${checked}/>
<span>${todo.name}</span>
</label>
<button id="remove-${todo.id}" onclick="handleTodoDelete(this)" class="btn btn-danger" style="display:${display}">删除</button>
`
list.appendChild(li)
updateTodoInfo()
}
function removeTodo(id) {
const li = document.getElementById(id)
console.log('li:', li)
li.parentNode.removeChild(li)
const index = todos.findIndex(todo => todo.id === id);
if (index !== -1) {
// 使用 splice() 方法删除找到的对象
todos.splice(index, 1);
}
updateTodoInfo()
}
function changeTodo(id, done) {
console.log(`in changeTodo id:${id}, done:${done}`)
const removeID = "remove-" + id
const removeBotton = document.getElementById(removeID)
if (done) {
removeBotton.style = "display:inline-block"
} else {
removeBotton.style = "display:none"
}
const changeID = "change-" + id
const changeCheckBox = document.getElementById(changeID)
changeCheckBox.checked = done
todos.forEach(todo => {
if (todo.id === id) {
todo.done = done
}
})
updateTodoInfo()
}
// 按钮添加任务
input.addEventListener('keyup', function (e) {
if (e.key === 'Enter') {
const value = this.value.trim()
if (!value) return
todo = {
id: `${Date.now()}`,
name: value,
done: false
}
todos.push(todo)
addTodo(todo)
this.value = ''
}
})
allCheckBox.addEventListener('change', function (e) {
console.log('e.target:', e.target)
console.log('e.target:', e.target.checked) // todo 子项会造成联动,需提前保存其状态
checked = e.target.checked
todos.forEach(todo => {
if (todo.done !== checked){
changeTodo(todo.id, checked)
}
})
})
function handleTodoChange(target) {
console.log('target:', target)
const todoIDs = target.id.split('-')
const todoID = todoIDs[todoIDs.length - 1]
const checked = target.checked
changeTodo(todoID, checked)
}
function handleTodoDelete(target) {
console.log('target:', target)
console.log('target.parentNode:', target.parentNode)
const todoIDs = target.id.split('-')
const todoID = todoIDs[todoIDs.length - 1]
removeTodo(todoID)
}
自定义事件示例
没必要,使用原生js 自定义事件处理比较麻烦
全局事件示例
同上
消息订阅和发布
html
css
js
js
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Document</title>
<link rel="stylesheet" href="index.css">
<script src="../../../../../lib/my/pubsub.js"></script>
</head>
<body>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认" />
</div>
<ul class="todo-main">
</ul>
<div class="todo-footer">
<label>
<input type="checkbox" />
</label>
<span>
<span>已完成<span id="finish_num">0</span></span></span> / 全部<span id="all_num">0</span>
</span>
<button class="btn btn-danger">清除已完成任务</button>
</div>
</div>
</div>
</div>
<script src="index.js"></script>
</body>
</html>
/*base*/
body {
background: #fff;
}
.btn {
display: inline-block;
padding: 4px 12px;
margin-bottom: 0;
font-size: 14px;
line-height: 20px;
text-align: center;
vertical-align: middle;
cursor: pointer;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
border-radius: 4px;
}
.btn-danger {
color: #fff;
background-color: #da4f49;
border: 1px solid #bd362f;
}
.btn-danger:hover {
color: #fff;
background-color: #bd362f;
}
.btn:focus {
outline: none;
}
.todo-container {
width: 600px;
margin: 0 auto;
}
.todo-container .todo-wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
/*header*/
.todo-header input {
width: 560px;
height: 28px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 7px;
}
.todo-header input:focus {
outline: none;
border-color: rgba(82, 168, 236, 0.8);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}
/*main*/
.todo-main {
margin-left: 0px;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0px;
}
.todo-empty {
height: 40px;
line-height: 40px;
border: 1px solid #ddd;
border-radius: 2px;
padding-left: 5px;
margin-top: 10px;
}
/*item*/
li {
list-style: none;
height: 36px;
line-height: 36px;
padding: 0 5px;
border-bottom: 1px solid #ddd;
}
li label {
float: left;
cursor: pointer;
}
li label li input {
vertical-align: middle;
margin-right: 6px;
position: relative;
top: -1px;
}
li button {
float: right;
display: none;
margin-top: 3px;
}
li:before {
content: initial;
}
li:last-child {
border-bottom: none;
}
/*footer*/
.todo-footer {
height: 40px;
line-height: 40px;
padding-left: 6px;
margin-top: 5px;
}
.todo-footer label {
display: inline-block;
margin-right: 20px;
cursor: pointer;
}
.todo-footer label input {
position: relative;
top: -1px;
vertical-align: middle;
margin-right: 5px;
}
.todo-footer button {
float: right;
margin-top: 5px;
}
// 非构建工具不支持这么使用, 只是为了代码提示加上,写完因屏蔽
// import '../../../../../lib/my/pubsub.js';
const input = document.querySelector('.todo-header input')
const list = document.querySelector('.todo-main')
const allCheckBox = document.querySelector('.todo-footer input')
const finishNumSpan = document.querySelector('#finish_num')
const allNumSpan = document.querySelector('#all_num')
const todos = JSON.parse(localStorage.getItem("todos")) || []
// 返回todo 已完成,和总共的数量
function updateTodoInfo() {
let finishNum = 0
console.log('todos:', todos)
todos.forEach(todo => {
if (todo.done) {
finishNum++
}
})
console.log('finishNum:', finishNum)
finishNumSpan.innerHTML = finishNum
allNumSpan.innerHTML = todos.length
allCheckBox.checked = finishNum === todos.length
localStorage.setItem("todos", JSON.stringify(todos))
}
window.onload = function () {
todos.forEach(todo => addTodo(todo))
}
// 添加任务
/*
@param {Object} todo: {
id: string;
name: string;
done: boolean;
}
*/
function addTodo(todo) {
const li = document.createElement('li')
const checked = todo.done ? 'checked' : ''
const display = todo.done ? 'inline-block' : 'none'
li.id = todo.id
li.innerHTML = `
<label>
<input id="change-${todo.id}" onchange="handleTodoChange(this)" type="checkbox" ${checked}/>
<span>${todo.name}</span>
</label>
<button id="remove-${todo.id}" onclick="handleTodoDelete(this)" class="btn btn-danger" style="display:${display}">删除</button>
`
list.appendChild(li)
updateTodoInfo()
}
function removeTodo(id) {
const li = document.getElementById(id)
console.log('li:', li)
li.parentNode.removeChild(li)
const index = todos.findIndex(todo => todo.id === id);
if (index !== -1) {
// 使用 splice() 方法删除找到的对象
todos.splice(index, 1);
}
updateTodoInfo()
}
function changeTodo(id, done) {
console.log(`in changeTodo id:${id}, done:${done}`)
const removeID = "remove-" + id
const removeBotton = document.getElementById(removeID)
if (done) {
removeBotton.style = "display:inline-block"
} else {
removeBotton.style = "display:none"
}
const changeID = "change-" + id
const changeCheckBox = document.getElementById(changeID)
changeCheckBox.checked = done
todos.forEach(todo => {
if (todo.id === id) {
todo.done = done
}
})
updateTodoInfo()
}
// 按钮添加任务
input.addEventListener('keyup', function (e) {
if (e.key === 'Enter') {
const value = this.value.trim()
if (!value) return
const todo = {
id: `${Date.now()}`,
name: value,
done: false
}
todos.push(todo)
addTodo(todo)
this.value = ''
}
})
allCheckBox.addEventListener('change', function (e) {
console.log('e.target:', e.target)
console.log('e.target:', e.target.checked) // todo 子项会造成联动,需提前保存其状态
checked = e.target.checked
todos.forEach(todo => {
if (todo.done !== checked){
changeTodo(todo.id, checked)
}
})
})
eventCenter.subscribe('todo-change', (data) => {
changeTodo(data.todoID, data.checked)
})
eventCenter.subscribe('todo-delete', (data) => {
removeTodo(data.todoID)
})
function handleTodoChange(target) {
console.log('target:', target)
const todoIDs = target.id.split('-')
const todoID = todoIDs[todoIDs.length - 1]
const checked = target.checked
eventCenter.publish('todo-change', {
todoID,
checked
})
}
function handleTodoDelete(target) {
const todoIDs = target.id.split('-')
const todoID = todoIDs[todoIDs.length - 1]
eventCenter.publish('todo-delete', {
todoID
})
}
const eventCenter = (function () {
/**
* @type {{[eventName: string]: Array<(param)=>void>}}
*/
const subscribers = {};
/**
* @description:
* @param {string} eventName
* @param {(param) => {}} callback
* @return {*}
*/
function subscribe(eventName, callback) {
if (!subscribers[eventName]) {
subscribers[eventName] = [];
}
subscribers[eventName].push(callback);
}
/**
* @description:
* @param {string} eventName
* @param {*} data
* @return {*}
*/
function publish(eventName, data) {
if (Array.isArray(subscribers[eventName])) {
subscribers[eventName].forEach(callback => callback(data));
}
}
function unsubscribe(eventName, callback) {
if (Array.isArray(subscribers[eventName])) {
subscribers[eventName] = subscribers[eventName].filter(cb => cb !== callback);
}
}
return {
subscribe,
publish,
unsubscribe
};
})();
编辑功能
html
css
js
js
js
js
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Document</title>
<link rel="stylesheet" href="index.css">
<script src="../../../../../lib/my/pubsub.js"></script>
</head>
<body>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认" />
</div>
<ul class="todo-main">
</ul>
<div class="todo-footer">
<label>
<input type="checkbox" />
</label>
<span>
<span>已完成<span id="finish_num">0</span></span></span> / 全部<span id="all_num">0</span>
</span>
</div>
</div>
</div>
</div>
<script src="todoCentor.js"></script>
<script src="elementCentor.js"></script>
<script src="index.js"></script>
</body>
</html>
/*base*/
body {
background: #fff;
}
.btn {
/* display: inline-block; */
padding: 4px 12px;
margin-bottom: 0;
font-size: 14px;
line-height: 20px;
text-align: center;
vertical-align: middle;
cursor: pointer;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
border-radius: 4px;
}
.btn-danger {
color: #fff;
background-color: #da4f49;
border: 1px solid #bd362f;
}
.btn-edit {
color: #fff;
background-color: skyblue;
border: 1px solid rgb(128, 202, 231);
}
.btn-danger:hover {
color: #fff;
background-color: #bd362f;
}
.btn:focus {
outline: none;
}
.todo-container {
width: 600px;
margin: 0 auto;
}
.todo-container .todo-wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
/*header*/
.todo-header input {
width: 560px;
height: 28px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 7px;
}
.todo-header input:focus {
outline: none;
border-color: rgba(82, 168, 236, 0.8);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}
/*main*/
.todo-main {
margin-left: 0px;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0px;
}
.todo-empty {
height: 40px;
line-height: 40px;
border: 1px solid #ddd;
border-radius: 2px;
padding-left: 5px;
margin-top: 10px;
}
/*item*/
li {
list-style: none;
height: 36px;
line-height: 36px;
padding: 0 5px;
border-bottom: 1px solid #ddd;
}
li label {
float: left;
cursor: pointer;
}
li label li input {
vertical-align: middle;
margin-right: 6px;
position: relative;
top: -1px;
}
li button {
float: right;
display: none;
margin-top: 3px;
}
li:before {
content: initial;
}
li:last-child {
border-bottom: none;
}
li:hover button {
display: block;
}
/*footer*/
.todo-footer {
height: 40px;
line-height: 40px;
padding-left: 6px;
margin-top: 5px;
}
.todo-footer label {
display: inline-block;
margin-right: 20px;
cursor: pointer;
}
.todo-footer label input {
position: relative;
top: -1px;
vertical-align: middle;
margin-right: 5px;
}
.todo-footer button {
float: right;
margin-top: 5px;
}
const todoCentor = (function () {
/**
* @description: 获取初始todo
* @returns {[{
* id: string;
* name: string;
* done: boolean;
* }]}
*/
function loadTodos() {
return JSON.parse(localStorage.getItem("todos")) || []
}
/**
* @description: 存储todos
* @param {[{
* id: string;
* name: string;
* done: boolean;
* }]} todos
*/
function saveTodos(todos) {
localStorage.setItem("todos", JSON.stringify(todos))
}
/**
* @description: 改变todos中todo值
* @param {[{
* id: string;
* name: string;
* done: boolean;
* }]} todos
* @param {{
* id: string;
* name: string;
* done: boolean;
* }} todos todo
*/
function addTodo(todos, todo) {
todos.push(todo)
}
/**
* @description:
* @param {[{
* id: string;
* name: string;
* done: boolean;
* }]} todos
* @param {string} todoID
* @param {boolean} done
* @return {*}
*/
function updateTodoDone(todos, todoID, done) {
todos.forEach(elem => {
if (elem.id === todoID) {
elem.done = done
}
})
}
/**
* @description:
* @param {[{
* id: string;
* name: string;
* done: boolean;
* }]} todos
* @param {string} todoID
* @param {string} name
* @return {*}
*/
function updateTodoName(todos, todoID, name) {
todos.forEach(elem => {
if (elem.id === todoID) {
elem.name = name
}
})
}
/**
* @description: 更新所有todo 的 done 状态
* @param {[{
* id: string;
* name: string;
* done: boolean;
* }]} todos
* @param {boolean} done
* @return {*}
*/
function updateTodosDone(todos, done) {
todos.forEach(elem => {
if (elem.done !== done) {
elem.done = done
}
})
}
/**
* @description: 删除指定todo id
* @param {[{
* id: string;
* name: string;
* done: boolean;
* }]} todos
* @param {string} id
*/
function removeTodo(todos, id) {
const index = todos.findIndex(todo => todo.id === id);
if (index !== -1) {
// 使用 splice() 方法删除找到的对象
todos.splice(index, 1);
}
return todos
}
return {
addTodo,
loadTodos,
saveTodos,
updateTodoDone,
updateTodoName,
updateTodosDone,
removeTodo,
}
})()
// 控制 todo dom 元素
const elementCenter = (function () {
const input = document.querySelector('.todo-header input')
const list = document.querySelector('.todo-main')
const allCheckBox = document.querySelector('.todo-footer input')
const finishNumSpan = document.querySelector('#finish_num')
const allNumSpan = document.querySelector('#all_num')
// 按钮添加任务
input.addEventListener('keyup', function (e) {
if (e.key === 'Enter') {
const value = this.value.trim()
if (!value) return
const todo = {
id: `${Date.now()}`,
name: value,
done: false
}
addTodo(todo)
eventCenter.publish('todo-add', todo)
this.value = ''
}
})
allCheckBox.addEventListener('change', function (e) {
console.log('e.target:', e.target)
console.log('e.target:', e.target.checked) // todo 子项会造成联动,需提前保存其状态
checked = e.target.checked
eventCenter.publish('update-todos-checked', checked)
})
/**
* @description:
* @param {[{
* id: string;
* name: string;
* done: boolean;
* }]} todos
* @return {*}
*/
function updateTodoInfos(todos) {
// 更新底部所有
const finishNum = todos.filter(todo => todo.done).length
finishNumSpan.innerHTML = finishNum
allNumSpan.innerHTML = todos.length
if (finishNum == todos.length) {
allCheckBox.checked = true
}else{
allCheckBox.checked = false
}
// 更新单个checked
todos.forEach(todo => {
const id = `change-${todo.id}`
document.getElementById(id).checked = todo.done
})
}
/**
* @description: 添加todo元素
* @param {{
* id: string;
* name: string;
* done: boolean;
* }} todo
* @return {*}
*/
function addTodo(todo) {
const li = document.createElement('li')
const checked = todo.done ? 'checked' : ''
li.id = todo.id
li.innerHTML = `
<label>
<input id="change-${todo.id}" onchange="handleTodoChecked(this)" type="checkbox" ${checked}/>
<span id="span-${todo.id}">${todo.name}</span>
<input id="inputedit-${todo.id}" type="text" value="${todo.name}" onkeyup="handleTodeFinishEdit(event, this)" style="display:none"/>
</label>
<button id="remove-${todo.id}" onclick="handleTodoDelete(this)" class="btn btn-danger">删除</button>
<button id="edit-${todo.id}" onclick="handleTodoEdit(this)" class="btn btn-edit">编辑</button>
`
list.appendChild(li)
}
/**
* @description:
* @param {[{
* id: string;
* name: string;
* done: boolean;
* }]} todos
* @return {*}
*/
function initTodos(todos) {
todos.forEach(todo => {
addTodo(todo)
})
}
/**
* @description:
* @param {string} todoID
*/
function removeTodo(todoID) {
const li = document.getElementById(todoID)
li.parentNode.removeChild(li)
}
return {
updateTodoInfos,
initTodos,
removeTodo,
}
})();
/**
* @description: 处理todo的change事件
* @param {HTMLElement} target
*/
function handleTodoChecked(target) {
console.log('target:', target)
const todoIDs = target.id.split('-')
const todoID = todoIDs[todoIDs.length - 1]
const checked = target.checked
eventCenter.publish('update-todo-checked', {
todoID,
checked
})
}
/**
* @description:
* @param {HTMLElement} target
* @return {*}
*/
function handleTodoDelete(target) {
const todoIDs = target.id.split('-')
const todoID = todoIDs[todoIDs.length - 1]
elementCenter.removeTodo(todoID)
eventCenter.publish('todo-delete', todoID)
}
/**
* @description:
* @param {HTMLElement} target
* @return {*}
*/
function handleTodoEdit(target) {
const todoIDs = target.id.split('-')
const todoID = todoIDs[todoIDs.length - 1]
target.style = 'display:none'
const spanID = `span-${todoID}`
document.getElementById(spanID).style = 'display:none'
const inputEditID = `inputedit-${todoID}`
document.getElementById(inputEditID).style = 'display:inline-block'
}
/**
* @description:
* @param {KeyboardEvent} event
* @param {HTMLElement} target
* @return {*}
*/
function handleTodeFinishEdit(event, target) {
if (event.key !== 'Enter') return
const todoIDs = target.id.split('-')
const todoID = todoIDs[todoIDs.length - 1]
const todoName = target.value
const spanID = `span-${todoID}`
document.getElementById(spanID).style = 'display:inline-block'
document.getElementById(spanID).innerHTML = todoName
target.style = 'display:none'
const editID = `edit-${todoID}`
document.getElementById(editID).style = ''
eventCenter.publish('update-todo-name', {
todoID,
todoName
})
}
const todos = todoCentor.loadTodos()
window.onload = function(){
elementCenter.initTodos(todos)
elementCenter.updateTodoInfos(todos)
}
// 改变todo的checked状态
eventCenter.subscribe('update-todo-checked', ({todoID, checked}) => {
todoCentor.updateTodoDone(todos, todoID, checked)
todoCentor.saveTodos(todos)
elementCenter.updateTodoInfos(todos)
})
eventCenter.subscribe('update-todo-name', ({todoID, todoName}) => {
todoCentor.updateTodoName(todos, todoID, todoName)
todoCentor.saveTodos(todos)
elementCenter.updateTodoInfos(todos)
})
// 改变所有todo的checked状态
eventCenter.subscribe('update-todos-checked', (checked) => {
todoCentor.updateTodosDone(todos, checked)
todoCentor.saveTodos(todos)
elementCenter.updateTodoInfos(todos)
})
// 删除todo
eventCenter.subscribe('todo-delete', (todoID) => {
todoCentor.removeTodo(todos, todoID)
todoCentor.saveTodos(todos)
elementCenter.updateTodoInfos(todos)
})
eventCenter.subscribe('todo-add', (todo) => {
todoCentor.addTodo(todos, todo)
todoCentor.saveTodos(todos)
elementCenter.updateTodoInfos(todos)
})
eventCenter.subscribe('todo-edit', (todo) => {
})
const eventCenter = (function () {
/**
* @type {{[eventName: string]: Array<(param)=>void>}}
*/
const subscribers = {};
/**
* @description:
* @param {string} eventName
* @param {(param) => {}} callback
* @return {*}
*/
function subscribe(eventName, callback) {
if (!subscribers[eventName]) {
subscribers[eventName] = [];
}
subscribers[eventName].push(callback);
}
/**
* @description:
* @param {string} eventName
* @param {*} data
* @return {*}
*/
function publish(eventName, data) {
if (Array.isArray(subscribers[eventName])) {
subscribers[eventName].forEach(callback => callback(data));
}
}
function unsubscribe(eventName, callback) {
if (Array.isArray(subscribers[eventName])) {
subscribers[eventName] = subscribers[eventName].filter(cb => cb !== callback);
}
}
return {
subscribe,
publish,
unsubscribe
};
})();