jQuery案例之ToDoList

jQuery案例之ToDoList

1. 需求

使用jQuery配合html与css完成以下界面

image-20230327234743866

2. 案例分析

2.1 说明

这里不纠结于html与css代码的搭建,着重处理其中的js需求

2.2 js事件原理

2.21 案例:案例介绍

1
2
3
4
// 1. 文本框里面输入内容,按下回车,就可以生成待办事项。
// 2. 点击待办事项复选框,就可以把当前数据添加到已完成事项里面。
// 3. 点击已完成事项复选框,就可以把当前数据添加到待办事项里面。
// 4. 但是本页面内容刷新页面不会丢失。

2.22 案例:toDoList 分析

1
2
3
4
5
// 1. 刷新页面不会丢失数据,因此需要用到本地存储 localStorage
// 2. 核心思路: 不管按下回车,还是点击复选框,都是把本地存储的数据加载到页面中,这样保证刷新关闭页面不会丢失数据
// 3. 存储的数据格式:var todolist = [{ title : ‘xxx’, done: false}]
// 4. 注意点1: 本地存储 localStorage 里面只能存储字符串格式 ,因此需要把对象转换为字符串 JSON.stringify(data)。
// 5. 注意点2: 获取本地存储数据,需要把里面的字符串转换为对象格式JSON.parse() 我们才能使用里面的数据。

2.23 案例:toDoList 按下回车把新数据添加到本地存储里面

1
2
3
4
5
6
// 1.切记: 页面中的数据,都要从本地存储里面获取,这样刷新页面不会丢失数据,所以先要把数据保存到本地存储里面。
// 2.利用事件对象.keyCode判断用户按下回车键(13)。
// 3.声明一个数组,保存数据。
// 4.先要读取本地存储原来的数据(声明函数 getData()),放到这个数组里面。
// 5.之后把最新从表单获取过来的数据,追加到数组里面。
// 6.最后把数组存储给本地存储 (声明函数 savaDate())

2.24 案例:toDoList 本地存储数据渲染加载到页面

1
2
3
4
// 1.因为后面也会经常渲染加载操作,所以声明一个函数 load,方便后面调用
// 2.先要读取本地存储数据。(数据不要忘记转换为对象格式)
// 3.之后遍历这个数据($.each()),有几条数据,就生成几个小li 添加到 ol 里面。
// 4.每次渲染之前,先把原先里面 ol 的内容清空,然后渲染加载最新的数据。

2.25 案例:toDoList 删除操作

1
2
3
4
5
6
7
// 1.点击里面的a链接,不是删除的li,而是删除本地存储对应的数据。
// 2.核心原理:先获取本地存储数据,删除对应的数据,保存给本地存储,重新渲染列表li
// 3.我们可以给链接自定义属性记录当前的索引号
// 4.根据这个索引号删除相关的数据----数组的splice(i, 1)方法
// 5.存储修改后的数据,然后存储给本地存储
// 6.重新渲染加载数据列表
// 7.因为a是动态创建的,我们使用on方法绑定事件

2.26 案例:toDoList 正在进行和已完成选项操作

1
2
3
4
5
6
7
// 1.当我们点击了小的复选框,修改本地存储数据,再重新渲染数据列表。
// 2.点击之后,获取本地存储数据。
// 3.修改对应数据属性 done 为当前复选框的checked状态。
// 4.之后保存数据到本地存储
// 5.重新渲染加载数据列表
// 6.load 加载函数里面,新增一个条件,如果当前数据的done为true 就是已经完成的,就把列表渲染加载到 ul 里面
// 7.如果当前数据的done 为false, 则是待办事项,就把列表渲染加载到 ol 里面

2.27 案例:toDoList 统计正在进行个数和已经完成个数

1
2
3
4
// 1.在我们load 函数里面操作
// 2.声明2个变量 :todoCount 待办个数 doneCount 已完成个数
// 3.当进行遍历本地存储数据的时候, 如果 数据done为 false, 则 todoCount++, 否则 doneCount++
// 4.最后修改相应的元素 text()

3. 页面结构

3.1 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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>todolist</title>
<link rel="stylesheet" href="style.css">
<script src="../jquery-3.6.0.js"></script>
<script src="todo.js"></script>
</head>

<body>
<header>
<section>
<label for="title">ToDolist</label>
<input type="text" id="title" placeholder="添加todo" required="required" autocomplete="off">
</section>
</header>
<section>
<h2>正在进行<span id="todocount">0</span></h2>
<ol id="todolist" class="demo-class">
<!-- <li>
erqwewer
<input type="checkbox" name="" id="">
<span class="remove">-</span>
</li> -->
</ol>
<h2>已经完成<span id="donecount">0</span></h2>
<ul id="donelist">
<!-- <li>
erqwewer
<input type="checkbox" name="" id="" checked>
<span class="remove">-</span>
</li> -->
</ul>
</section>
<footer>
<p>(请按下<strong>空格键</strong>输入内容,然后按下<strong>enter</strong>回车)</p>
copyright &copy; 2014 todolist.cn
</footer>
<a href="javascript:;" class="up" title="back"></a>
</body>

</html>

3.2 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
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
// out: style.css
* {
padding: 0;
margin: 0;
}

body,
html {
background-color: rgb(204, 204, 204);
scroll-behavior: smooth;
}

header {
background-color: #000;
padding: 20px 0;
width: 100%;
position: fixed;
top: 0;

section {
margin: 0 auto;
width: 50%;

label {
color: white;
line-height: 25px;
font-size: 25px;
font-weight: bolder;
}

input {
width: 50%;
height: 25px;
float: right;
padding-left: 15px;
border-radius: 15px;
border: none;
outline: none;
box-shadow: inset 0px 2px 5px black;
}

.big {
animation: cartoon .5s linear alternate;
}

@keyframes cartoon {
from {
transform: scale(1.3);
}

to {}
}

}
}

section {
margin: 0 auto;
width: 50%;
margin-top: 70px;

h2 {
padding: 10px 0;

span {
width: 20px;
height: 20px;
line-height: 20px;
text-align: center;
font-size: 15px;
border-radius: 50%;
background-color: aliceblue;
float: right;
}
}

ol {
list-style: none;

li {
width: 100%;
height: 40px;
border-radius: 5px;
line-height: 40px;
margin: 10px 0;
background-color: aliceblue;
border-left: 3px solid green;

input {
width: 20px;
height: 20px;
float: left;
margin: 10px 15px 0;
}

span {
width: 20px;
height: 20px;
border: 2px solid #6d6d6d;
text-align: center;
line-height: 20px;
border-radius: 50%;
float: right;
margin: 10px 10px 0;
cursor: pointer;
}
}
}

ul {
list-style: none;

li {
width: 100%;
height: 40px;
border-radius: 5px;
line-height: 40px;
margin: 10px 0;
background-color: rgb(167, 169, 171);
border-left: 5px solid #999;
opacity: 0.5;

input {
width: 20px;
height: 20px;
float: left;
margin: 10px 15px 0;
}

span {
width: 20px;
height: 20px;
border: 2px solid #6d6d6d;
text-align: center;
line-height: 20px;
border-radius: 50%;
float: right;
margin: 10px 10px 0;
cursor: pointer;
}
}
}
}

footer {
margin-top: 50px;
line-height: 30px;
text-align: center;

p {
text-align: center;
padding: 10px 0;
font-size: 15px;

strong {
font-size: 20px;
}
}
}

a{
text-decoration: none;
color: black;
background-color: white;
display: none;
width: 30px;
height: 30px;
border-radius: 50%;
border: 1px solid #000000;
text-align: center;
line-height: 30px;
font-weight: bolder;
position: fixed;
right: 5%;
bottom: 30%;
}

3.3 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
// 1. 文本框里面输入内容,按下回车,就可以生成待办事项。
// 2. 点击待办事项复选框,就可以把当前数据添加到已完成事项里面。
// 3. 点击已完成事项复选框,就可以把当前数据添加到待办事项里面。
// 4. 但是本页面内容刷新页面不会丢失。

$(function () {
// 每次打开页面或刷新时都会调用渲染加载本地存储函数loadData(),数据会一直呈现
loadData()

// 1、按下回车,把完整数据存储到本地存储里去
$('#title').on('keydown', function (e) {
// 键盘事件对象,e.keyCode===13代表按下回车,回车的阿斯卡值是13
if (e.keyCode === 13) {
if ($(this).val() === '') {
alert('内容不能为空')
} else {
// 1.1 先读取本地存储原来的数据(getData())
var localData = getData()

// 1.2 更新localData数据,追加新数据给localData数组对象
localData.push({
title: $(this).val(),
done: false
})

// 将追加的数组对象存储到本地
saveData(localData)

// 2. 渲染数据
loadData()

// 当文本框的值回车保存后要清零
$(this).val('')
}

}
})

// 3. 事件删除,用到事件委派
$('#todolist,#donelist').on('click', 'span', function () {
// 先获取本地数据
var localData = getData()
// 修改数据
// 获取当前点击的复选框所对应的任务的索引
var index = $(this).attr('id')
// 使用 splice() 方法将该任务从本地数据数组中删除
localData.splice(index, 1)
// 保存数据
saveData(localData)
// 重新渲染
loadData()
})

// 4. 正在进行和已完成选项操作
// 给未完成和已完成列表中的复选框添加点击事件
$('#todolist,#donelist').on('click', 'input', function () {
// 先获取本地数据
var localData = getData()
// 修改数据
// 获取当前点击的复选框所对应的任务的索引
var index = $(this).siblings('span').attr('id')
// 修改该任务的完成状态,将当前点击的复选框所对应的任务的完成状态修改为当前复选框的选中状态。
localData[index].done = $(this).prop('checked')
// 保存数据
saveData(localData)
// 重新渲染
loadData()
})

// 读取本地存储函数getData()
// 读取本地存储原来的数据,查看是否有数据
function getData() {
// 获取存储在本地的数据
var data = localStorage.getItem('todolist');
console.log(data);
// 如果存在数据
if (data !== null) {
// 获取本地存储的数据 我们需要把里面的字符串数据转换为 对象格式 JSON.parse()
// 将字符串数据转换为对象格式
return JSON.parse(data);
} else {
// 否则返回一个空数组
return []
}
}

// 保存本地存储数据函数saveData(Data)
function saveData(Data) {
// 本地存储里面只能存储字符串的数据格式 把我们的数组对象转换为字符串格式 JSON.stringify()
localStorage.setItem('todolist', JSON.stringify(Data))
console.log(Data);
}

// 渲染加载本地存储函数loadData()
function loadData(e) {
// 先读取取本地数据
var loadData = getData();

// 自定义代码
// 如果已加载的数据达到 15 条或更多
if (loadData.length >= 15) {
// 禁用 ID 为 title 的元素,并更改其 placeholder 属性
$('#title').attr({
'disabled': true,
'placeholder': '每日仅限添加15条内容'
})
} else {
// 否则启用 ID 为 title 的元素,并更改其 placeholder 属性
$('#title').attr({
'disabled': false,
'placeholder': '添加todo'
})
}

// 遍历之前先清空ol里的数据,不然不会出现渲染多次的bug
$('#todolist,#donelist').empty();

// 遍历添加数据(5. 统计完成个数)
var todocount = 0
var donecount = 0
// 遍历已加载的数据
$.each(loadData, function (index, element) {
// 如果复选框选中,ul添加数据,否则li添加数据
if (element.done) {
// 将已完成的任务添加到已完成列表中
$('#donelist').prepend('<li>' + (index + 1) + '. ' + element.title + '<input type="checkbox" name="" id="" checked><span class="remove" id=' + index + '>-</span></li>')
// 已完成计数器加 1
donecount++
} else {
// 将未完成的任务添加到未完成列表中
$('#todolist').prepend('<li>' + (index + 1) + '. ' + element.title + '<input type="checkbox" name="" id=""><span class="remove" id=' + index + '>-</span></li>')
// 未完成计数器加 1
todocount++
}
})

// 更新未完成和已完成计数器的值
$('#todocount').html(todocount)
$('#donecount').html(donecount)
}

// 自定义代码
// 当窗口滚动时
$(window).scroll(function () {
// 如果滚动距离达到 200
if ($(document).scrollTop() >= 200) {
// 显示“回到顶部”按钮
$('.up').fadeIn()
} else {
// 隐藏“回到顶部”按钮
$('.up').fadeOut()
}
// 当“回到顶部”按钮被点击时
$('.up').on('click', function () {
// 将页面滚动到顶部
$('body,html').scrollTop({
scrollTop: 0
})
})
})
// 当文档接收到按键事件时
$(document).on('keydown', function (e) {
// 如果按下的是空格键
if (e.keyCode === 32) {
// 将焦点放到 ID 为 title 的元素上,// 给 ID 为 title 的元素添加 big 类,// 将 ID 为 title 的元素的值设为空
$('#title').focus().addClass('big').val('')
}
})

})