TODOLIST
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));}