jQuary实现最简单的待办事项列表(移动和PC端均可使用)
前言:ToDoList是一款无需注册即可使用,数据存储在用户浏览器的H5本地数据库里,是最简单、最安全的代办事项列表应用!
- 原工具网站链接:ToDoList官网
- 我们实现后的效果:MyToDoList
PC端效果:
1、实现流程
- 导入移动端所需要的CSS初始化样式
- 导入
flexible.js
适配文件、jQuery.js
插件 - 布局方式:采用
flexible.js + rem
适配布局 - 书写html结构
- 书写css样式,并设置jQuery需要的属性
- 书写jQuery代码实现具体功能,主要创建对象并实例化调用
2、实现思路
- 文档注册键盘事件,当按下的是
Enter
键就创建li节点添加到正在进行ol中 - 给正在进行中的li节点中的复选框添加点击事件,点击后克隆节点添加到已经完成中,然后删除正进行中的节点
- 给已经完成中的li节点中的复选框添加点击事件,点击后克隆节点添加到正在进行中,然后删除已经完成中的节点
- 给文档中所有li的右边的删除按钮a添加点击事件,点击后判断是哪一个元素,然后删除对应的li
- 给文档中最底下的clear,即a标签添加点击事件,点击后删除文档中的所有li
- 在文档创建的时候将其写入本地数据路,并添加对应的唯一标记,来后期识别
- 当点击复选框后根据唯一标记判断是哪一个li,并将其checked属性值更改为
true
和false
来辨别是已经完成的还是正在进行中的 - 当点击li中右边的删除按钮后,将其本地中的数据拿出来转换为json对象删除对应的数据,然后再转换为字符串将本地中的数据替换
- 当点击清除按钮后,将其本地中的数据拿出来转换为json对象,然后清空所有数据,再将其空数组转换为字符串替换本地数据。
- 在文档加载的时候将其本地加载进来进行转换,根据标记创建对应的节点,找到对应的css样式即可
3、所遇Bug
-
无法动态获取创建的节点
**解决:**使用jQuery中的on方法注册事件,并且使用事件委托动态获取创建的元素。
-
获取到对应的节点无法解决事件委托和冒泡
**解决:**根据对应的元素自己所拥有的唯一属性来辨别是否是该元素
-
无法将数据存入本地
**解决:**如果本地数据为空,则使用数组套json对象的方式创建数组json对象,并将json数组对象转换为json字符串存入本地;否则只将该次的json对象追加到本地的数组中
-
更改本地数据后找不到对应的元素
**解决:**节点创建时都给一个唯一的ID标记,并将该ID标记存入本地,每次触发的事件的时候根据该ID值判断是否是该元素的父亲,并进行操作
-
加载本地数据不知道如何归类
**解决:**根据每次更改的checked属性来辨别是未完成的还是已经完成的,并调用创建元素节点的函数,通过判断将其添加到对应的地方
4、源码奉上
- index.html(结构)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1.0,
maximum-scale=1.0,minimum-scale=1.0" ">
<title>ToDoList</title>
<!-- 导入jQuery插件 -->
<script src="./jQuery/jquery-3.5.1.min.js "></script>
<!-- 导入flexible插件 -->
<script src="./js/index.js "></script>
<!-- 移动端布局样式初始化的插件 -->
<link rel="stylesheet " href="./css/normalize.css ">
<link rel="stylesheet " href="css/style.css ">
<script src="./js/main.js "></script>
<script src="./js/cleateE.js "></script>
<script src="./js/data.js "></script>
</head>
<body>
<!-- 页头开始了 -->
<header>
<form action=" ">
<label for="title ">ToDoList</label>
<input type="text " id="title " class="title " placeholder="添加ToDo " required>
</form>
</header>
<!-- 页头结束了 -->
<section>
<h2 class="one ">
正在进行
<span>0</span>
</h2>
<ol></ol>
<h2 class="two ">
已经完成
<span>0</span>
</h2>
<ul></ul>
</section>
<!-- 页脚开始了 -->
<footer>
Copyright © 2014 todolist.cn
<a href="javascript:; ">clear</a>
</footer>
</body>
</html>
-
normalize.css(移动端初始化样式插件)
-
index.js(移动端适配插件)
-
jquery-3.5.1.min.js(jQuery插件)
-
style.scc(元素样式文件)
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* 当设备宽度大于750px时 */
@media screen and (min-width:750px) {
/* html采用75px的字体大小 */
html {
font-size: 75px !important;
}
}
body {
background: #CDCDCD;
}
header {
height: .666667rem;
background: rgba(47, 47, 47, 0.98);
}
header form {
width: 10rem;
margin: 0 auto;
display: flex;
align-items: center;
}
header label {
flex: 1;
width: 1.333333rem;
height: .666667rem;
line-height: .666667rem;
color: #ffffff;
font-size: .32rem;
cursor: pointer;
}
header input {
flex: 1;
width: 4.826667rem;
height: .346667rem;
padding-left: .2rem;
border: none;
border-radius: .066667rem;
box-shadow: 0 1px rgba(255, 255, 255, 0.24), 0 1px 6px rgba(0, 0, 0, 0.45) inset;
font-size: .16rem;
margin-right: 10px;
/* 点击表单后,表单边框消失 */
outline: none;
}
section,
footer {
min-width: 320px;
max-width: 750px;
width: 10rem;
margin: 0 auto;
font-family: Arial, Helvetica;
background: #CDCDCD;
}
section h2 {
height: .413333rem;
margin: .266667rem 0;
font-size: .333333rem;
}
section span {
float: right;
width: .266667rem;
height: .266667rem;
line-height: .266667rem;
text-align: center;
border-radius: 50%;
background: #E6E6FA;
font-size: .186667rem;
color: #666666;
margin-right: .133333rem;
}
ul,
ol {
list-style: none;
}
footer {
color: #666666;
font-size: .186667rem;
text-align: center;
}
footer a {
text-decoration: none;
color: #999999;
}
.li {
height: .426667rem;
background: #ffffff;
margin-bottom: .133333rem;
border-radius: .04rem;
border-left: .066667rem solid #629A9C;
box-shadow: 0 .013333rem .026667rem rgba(0, 0, 0, 0.07);
/* 弹性布局 */
display: flex;
/* 侧轴居中 */
align-items: center;
/* 两边贴边,再平分剩余空间 */
justify-content: space-around;
/* 相对定位 */
position: relative;
cursor: pointer;
}
.li input {
position: absolute;
width: .293333rem;
height: .293333rem;
left: .2rem;
}
.li p {
display: inline-block;
width: 85%;
height: 100%;
line-height: .426667rem;
font-size: .186667rem;
}
.li a {
position: absolute;
width: .346667rem;
height: .32rem;
line-height: .14rem;
text-align: center;
right: .106667rem;
border-radius: 50%;
border: .08rem double #ffffff;
background: #cccccc;
color: #ffffff;
}
.finish {
opacity: 0.5;
border-left: .066667rem solid #999999;
}
- main.js(js主入口文件)
$(function() {
// 实例化数据对象
var data = new Data();
// 实例化创建元素的对象
var create = new cleateE();
// 将数据加载进来
data.load();
//存储输入的文本值
this.text = null;
// 索引
this.num = 0;
// 给文档绑定键盘事件
$(document).keydown(function(event) {
if (event.which == 13 && $(".title").val() != "") {
// 获取输入的值
this.text = $(".title").val();
// 将文本框清空
$(".title").val("");
// 调用创建li节点的函数,并将索引作为参数传递
var n = create.creatE("ol", this.text, this.num);
// 索引递增
this.num++;
// 调用写入本地的函数,并将li的索引作为参数传递
data.storage(n, this.text);
}
})
// 将ol和ul中的a委托给文档对象的点击事件去执行
$(document).on("click", $("ol li a,ul li a"), function(event) {
// 如果点击的这个元素有href属性
if ($(event.target).prop("href") != undefined) {
// 获取该元素父亲的tag属性
var index = $(event.target).parent("li").attr("tag");
// 删除该节点的父亲
$(event.target).parent("li").remove();
// 调用删除本地对应数据的函数,并将触发该事件的元素对象的父亲作为参数传递
data.removeD(index);
}
})
// 【已完成】:使用事件委托,将li中的input委托给ul去执行
$("ol").on("click", $("input"), function(event) {
// 如果所点的这个元素有checked属性,并且为true
if ($(event.target).prop("checked") != undefined && $(event.target).prop("checked")) {
// 标记
var a = 1;
// 调用修改数据的函数,并将事件对象作为参数传递
data.amendV($(event.target).parent("li"), a);
// 则克隆这个元素的父亲li,并添加到ul中
$("ul").append($(event.target).parent("li").clone().addClass("finish"));
// 删除ol中的li节点
$(event.target).parent(".li").remove();
}
})
// 【未完成】:使用事件委托,将li中的input委托给ul去执行
$("ul").on("click", $("input"), function(event) {
// 如果所点的这个元素有checked属性,并且为false
if ($(event.target).prop("checked") != undefined && !$(event.target).prop("checked")) {
// 标记
var a = 0;
data.amendV($(event.target).parent("li"), a);
// 则克隆这个元素的父亲li,并添加到ol中
$("ol").append($(event.target).parent("li").clone().removeClass("finish"));
// 删除ul中的li节点
$(event.target).parent(".li").remove();
}
})
// 页脚的a添加点击事件
$("footer a").on("click", function() {
// 删除ul和ol中所有的li
$("ol li,ul li").remove();
data.clear();
})
// 计时器实时更新li的个数
setInterval(() => {
// 并将其文本添加到各自的span中
$(".one span").text($("ol li").length);
$(".two span").text($("ul li").length);
}, 1);
});
- cleateE.js(创建节点文件)
function cleateE() {
// 创建li节点
this.creatE = function(isol, value, num) {
// 创建li标签,并指定类名,并设置自定义属性
var li = $("<li></li>").addClass("li").attr("tag", num);
// 创建input标签,并添加到li标签中
li.append($("<input type='checkbox'>"));
// 创建a标签,并设置文本值,添加到li中
li.append($("<a></a>").text("-"));
// 如果为ol,则添加到ol中
if (isol == "ol") {
// 创建p标签,并设置文本值,添加到li中
li.append($("<p></p>").text(value));
// 将li添加到ol中
$("ol").append(li);
} else {
// 否则就是添加到ul中
// 创建p标签,并设置文本值,添加到li中
li.append($("<p></p>").text(value));
// 否则将li添加到ul中
$("ul").append(li);
}
// 返回创建的这个li的索引
return num;
}
}
- data.js(存入本地文件)
function Data() {
// 写入本地数据
this.storage = function(n, text) {
// 如果获取本地的数据不为空
if (window.localStorage.getItem("todo") != null) {
// 则获取本地数据,将其转换为json对象数组
var obj = JSON.parse(window.localStorage.getItem("todo"));
// 创建一个json对象,将其添加到另一个json对象数组中
obj.push({ "title": text, "done": false, "tag": n });
// 再将json对象数组转换为字符串写入到本地
window.localStorage.setItem("todo", JSON.stringify(obj))
} else {
//创建jeson对象,并装入数组
var data = [{ "title": text, "done": false, "tag": n }];
//将jeson对象转换为字符串写入本地
window.localStorage.setItem("todo", JSON.stringify(data))
}
}
// 修改本地数据
this.amendV = function(target, isFinish) {
// 则获取本地数据,将其转换为json对象数组
var obj = JSON.parse(window.localStorage.getItem("todo"));
// 如果是完成
if (isFinish) {
// 遍历json的数组,拿到每个json对象
for (var i = 0; i < obj.length; i++) {
// 如果点击的这个元素的tag属性 == 存储的json对象的tag,那么它们就是一个
if (target.attr("tag") == obj[i]["tag"]) {
// 更改存储的done属性,说明复选框被选中
obj[i]["done"] = true;
}
}
} else {
// 遍历json的数组,拿到每个json对象
for (var i = 0; i < obj.length; i++) {
// 如果点击的这个元素的tag属性 == 存储的json对象的tag,那么它们就是一个
if (target.attr("tag") == obj[i]["tag"]) {
// 更改存储的done属性,说明复选框被选中
obj[i]["done"] = false;
}
}
}
// 再将json对象数组转换为字符串写入到本地
window.localStorage.setItem("todo", JSON.stringify(obj))
}
// 删除本地数据
this.removeD = function(index) {
// 获取本地数据,将其转换为json对象数组
var obj = JSON.parse(window.localStorage.getItem("todo"));
// 遍历json数组对象
for (var i = 0; i < obj.length; i++) {
if (index == obj[i]["tag"]) {
// 那么就根据数组中的索引删除该元素
var value = obj.splice(i, 1);
}
}
// 再将json对象数组转换为字符串写入到本地
window.localStorage.setItem("todo", JSON.stringify(obj))
}
// 清空本地数据
this.clear = function() {
// 获取本地数据,将其转换为json对象数组
var obj = JSON.parse(window.localStorage.getItem("todo"));
// 遍历数组
for (var i = 0; i < obj.length; i++) {
obj.splice(i, 1)
i--;
}
// 再将json对象数组转换为字符串写入到本地
window.localStorage.setItem("todo", JSON.stringify(obj))
}
// 加载本地数据
this.load = function() {
// 实例化创建元素的对象
var create = new cleateE();
// 则获取本地数据,将其转换为json对象数组
var obj = JSON.parse(window.localStorage.getItem("todo"));
// 如果获取到
if (obj != null) {
// 就遍历对象
for (var i = 0; i < obj.length; i++) {
// 拿到文本值
var value = obj[i]["title"];
// 获取标记
var tag = obj[i]["tag"];
// 如果状态时true,则是已经完成的
if (obj[i]["done"]) {
// 那么就将它添加到ul中
create.creatE("ul", value, tag);
// 并设置复选框的样式
$("li input").addClass("finish").prop("checked", true);
} else {
// 否则就添加到ol中
create.creatE("ol", value, tag);
}
}
}
}
}
移动端效果: