TODOLIST
效果如图
要求:
- 当输入 todo 项时,按回车键,todo list 增加一项未完成的 todo 记录。
- 当点击最顶部 input 旁边的 checkbox 开关,如果是选中状态,所有的 todo 项都需选中,反之亦然。
- 当点击某一项 todo 的 checkbox 时,如果是选中状态,该 todo 文字变灰,并且文字带删除中划线。
- 底部要实时记录当前还有多少项未完成的 todo list。
- 底部三个按钮可分别过滤出不同状态的todo list:「所有」、「未完成」、「已完成」
- 可删除当前 todo 项。
<!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></title>
<style>
/* code here */
.input-box,
.btn {
display: flex;
justify-content: center;
align-items: center;
}
.list {
margin: 20px 0;
}
.item-box {
display: flex;
justify-content: space-between;
padding: 5px 10px;
border-bottom: 1px solid gray;
}
.item {
outline: none;
}
.item-box input[type="checkbox"]:checked+label {
color: red;
text-decoration: line-through;
}
.item-close {
width: 22px;
height: 22px;
padding: 0 6px;
font-size: 16px;
box-sizing: border-box;
background: rgb(44, 131, 244);
color: white;
}
</style>
</head>
<body>
<div class="container">
<!-- code here -->
<div class="input-box">
<input type="checkbox" id="check" />
<input type="text" id="input" />
</div>
<div class="list">
</div>
<div class="btn">
<div id="noCompleteNum">0</div>
<div>项未完成</div>
<button onclick="allToDo()">所有</button>
<button onclick="noComplete()">未完成</button>
<button onclick="complete()">已完成</button>
</div>
</div>
<script>
// code here
// 初始化数据
const initData = function () {
const data = [
{
"id": 1,
"checked": false,
"text": "abcede"
},
{
"id": 2,
"checked": true,
"text": "ewrwerw"
},
{
"id": 3,
"checked": false,
"text": "fdsfsdfsdf"
},
{
"id": 4,
"checked": false,
"text": "dfgrrg"
}
];
return new Proxy(data.map(item => {
return new Proxy({
id: item.id,
checked: item.checked,
text: item.text
}, {
set(target, propKey, value, receiver) {
updateNoCompleteNum();
return Reflect.set(target, propKey, value, receiver);
}
})
}), {
set(target, propKey, value, receiver) {
updateNoCompleteNum();
return Reflect.set(target, propKey, value, receiver);
}
})
}
const data = initData();
const list = document.querySelector('.list');
const checkAll = document.querySelector('#check');
const input = document.querySelector("#input");
const addItem = function ({ text, id, checked }) {
const fragment = document.createDocumentFragment();
const box = document.createElement('div');
const item = document.createElement('input');
const label = document.createElement('label');
const del = document.createElement('div');
const left = document.createElement('div');
const right = document.createElement('div');
label.innerHTML = text;
label.for = 'input' + id;
item.id = 'input' + id;
item.type = 'checkbox';
item.checked = checked;
item.className = "item";
del.className = 'item-close';
del.innerText = 'X';
box.className = 'item-box';
box.id = id;
left.appendChild(item);
left.appendChild(label);
right.appendChild(del);
box.appendChild(left);
box.appendChild(right);
fragment.appendChild(box);
list.appendChild(fragment);
};
// 清空容器内容的函数
const clearList = function (list) {
while (list.firstChild) {
list.removeChild(list.firstChild);
}
}
// 渲染列表
const renderList = function (dataList) {
clearList(list);
dataList.forEach(item => {
addItem(item);
});
}
renderList(data);
// 利用proxy监听data数组
const updateNoCompleteNum = function () {
// 等页面更新完成之后data值更新完成获取
setTimeout(() => {
let noCompleteNum = data.filter(item => !item.checked).length;
const noCompleteNode = document.querySelector('#noCompleteNum');
noCompleteNode.innerText = noCompleteNum;
})
}
const allToDo = function () {
renderList(data);
}
const noComplete = function () {
const dataList = data.filter(item => !item.checked);
renderList(dataList);
}
const complete = function () {
const dataList = data.filter(item => item.checked);
renderList(dataList);
}
const resetData = function (checked) {
data.forEach(it => {
// let item = list.childNodes[it.id].children[0].children[0];
let item = Array.from(list.childNodes).filter(node => node.id == it.id)[0];
if (item) {
let node = item.querySelector(`#input${it.id}`);
node.checked = checked;
it.checked = checked;
}
});
}
const handleInput = function (e) {
if (e.key === 'Enter') {
const item = new Proxy({
id: data.length + 1,
checked: false,
text: input.value
}, {
set(target, propKey, value, receiver) {
updateNoCompleteNum();
return Reflect.set(target, propKey, value, receiver);
}
});
data.push(item);
addItem(item);
input.value = '';
}
};
const handleCheckAll = function (e) {
if (e.target.checked) {
// 全选
resetData(true);
} else {
// 全不选
resetData(false);
}
};
const handleListClick = function (e) {
if (e.target.className === 'item') {
const index = Number(e.target.id.slice(5));
data[index - 1].checked = e.target.checked;
} else if (e.target.className === 'item-close') {
const index = e.target.parentNode.parentNode.id;
data.splice(index - 1, 1);
const node = [...list.childNodes].filter(it => it.id == index)[0];
list.removeChild(node);
}
};
input.addEventListener('keydown', handleInput);
checkAll.addEventListener('click', handleCheckAll);
list.addEventListener('click', handleListClick);
const handleRemoveListener = function () {
list.removeEventListener('click', handleListClick);
checkAll.removeEventListener('click', handleCheckAll);
input.removeEventListener('keydown', handleInput);
}
document.addEventListener('unload', handleRemoveListener);
</script>
</body>
</html>
但是这里renderList是删除当前所有list的节点然后重新构建的,虽然有数据驱动的思想但是这样操作dom是非常消耗性能的,因此做如下变更,通过设置item.style.display
// 渲染列表
const initRender = function (dataList) {
dataList.forEach(item => {
addItem(item);
});
}
// 利用proxy监听data数组
const updateNoCompleteNum = function () {
// 等页面更新完成之后data值更新完成获取
setTimeout(() => {
let noCompleteNum = data.filter(item => !item.checked).length;
const noCompleteNode = document.querySelector('#noCompleteNum');
noCompleteNode.innerText = noCompleteNum;
})
}
initRender(data);
updateNoCompleteNum();
const getDisplayHash = function (key, val) {
const hash = {};
data.forEach(item => {
(item[key] === undefined || item[key] === val) && (hash[item.id] = 1);
});
return hash;
}
const renderList = function (hash) {
for (let i = 0; i < list.children.length; i++) {
let item = list.children[i];
if (hash[item.id]) {
item.style.display = '';
} else {
item.style.display = 'none'
}
}
}
const allToDo = function () {
renderList(getDisplayHash());
}
const noComplete = function () {
renderList(getDisplayHash('checked', false));
}
const complete = function () {
renderList(getDisplayHash('checked', true));
}