前言
待办事项是非常常见的习题,其中需求涵括了增、删、读、改资料,充分模拟未来在操纵资料时会碰上的各种情境与问题,可以说各种软件都是一种客制化的待办事项。随着助教写一次,未来写很多应用会轻松得多~
解题
第一步:制作界面
本步骤要完成的动作如下:
- 提供用户输入界面
- 使用 JavaScript 选取并监听界面动态
<!-- 提供用户输入界面 --><div class="todo"> <form data-todo-form class="todo__form"> <input name="userInput" type="text" placeholder="请输入事项" required /> <input type="submit" value="提交" /> </form> <ul data-todo-list></ul></div>
常常同学会直接 <input>
上绑监听,虽然并不会造成什么问题但助教会更倾向使用现有的 <form>
来让用户提交资料,原因是 HTML 原生就支持客户端的资料验证,可以更方便地去检核用户输入的资料对错,也可以更语义化地描述「这是一个表单,等待用户填写内容」,而非「这是画面上随便一个可以输入的栏位」。
<!-- 使用验证强制要求用户必须输入内容 --><input type="text" required />
接着就是使用 JavaScript 选取并监听界面动态(表单提交与事项清单点击)
// 使用 JavaScript 选取并监听界面动态const todoForm = document.querySelector('[data-todo-form]');const todoList = document.querySelector('[data-todo-list]');
// 表单当提交时……todoForm.addEventListener('submit', (e) => { // 取消浏览器默认行为(跳转页面) e.preventDefault();});
// 当事项清单被点击时……todoList.addEventListener('click', (e) => {});
第二步:显示待办事项
在界面架构好之后就需要于第一次网页加载时将 JavaScript 中的数据显示到画面上,因此先定义一笔待办事项资料(在此填充了假资料,方便同学理解):
// id = 事项独一无二的识别码// name = 事项的名称// isCompleted = 事项完成状态
let tasks = [ { id: '1', name: 'Learn HTML', isCompleted: false, }, { id: '2', name: 'Learn CSS', isCompleted: true, },];
之后就是考虑如何显示这笔资料,由于这是一個会频繁执行的动作,制作一个函数将功能包装起来,方便之后引用:
function renderTask(tasks) { // 抓取 task 资料,并用 map 跑过一轮,在每次迭代中把资料取出并放入 HTML 中回传 const tasksHTML = tasks.map( (task) => ` <li task-id=${task.id}> <input id=${task.id} data-task-toggle type="checkbox" ${task.isCompleted ? 'checked' : ''}> <label for=${task.id}>${task.name}</label> <button data-task-delete>删除</button> </li> `, ); todoList.innerHTML = tasksHTML.join('');}
renderTask(tasks);
可以特别注意这里使用了现有的数据作判断,当「isCompleted
为 true
」就为 input 添加打勾否则不打勾。
<input ${task.isCompleted ? "checked" : ""}>
第三步:新增待办事项
后续步骤就简单很多了,新增事项流程上就是:
- 检测表单被提交
- 将新增的事项名称丢给
addTask
函式处理 - 加入一笔新的资料给
tasks
- 呼叫
renderTask
重新渲染一次
如果不想要因为一个事项改动就触发整个待办事项重新渲染一次,可以作细微的操控去编辑 DOM,不过在本次解题中先采取最简单直观的方式 :更新完资料就整体刷新再显示。
// 01.检测表单被提交todoForm.addEventListener('submit', (e) => { e.preventDefault(); // 02.将提交的事项名称传入 addTask 函式 addTask(e.target.userInput.value); // 重置表单内容 e.target.reset();});
function addTask(taskName) { // 03.加入新的资料给 tasks,tasks 等于(旧的 tasks + 新事项) tasks = [ ...tasks, { id: Date.now().toString(), name: taskName, isCompleted: false, }, ]; // 04.重新渲染一次 renderTask(tasks);}
这里使用 Date.now() 来得出自 1970/01/01 00:00:00 UTC
起经过的毫秒数作为 ID 使用。
Date.now().toString();
第四步:删除待办事项
要删除事项,就势必要得知「点击下去的删除按钮是哪项事项」,再根据事项 ID 与目前 tasks
数据去交互比对,如果相同就移除这笔资料并重新渲染一次所有事项。
删除事项流程就是:
- 检测待办清单是否被点击
- 确认被点击的是删除按钮
- 取出
task-id
属性 - 传入
removeTask
函式中 - 在
removeTask
函式中比对tasks
中相同task-id
的事项 - 删除
tasks
中该笔事项 - 呼叫
renderTask
重新渲染一次
// 监听待办清单是否被点击todoList.addEventListener('click', (e) => { // 当被点击时,确认被点击的事项有 "data-task-delete" 属性 if (e.target.hasAttribute('data-task-delete')) { // 如果有,将其父元素上的 task-id 属性取出,传入 removeTasks 函式内 const taskId = e.target.parentElement.getAttribute('task-id'); removeTask(taskId); }});
这里助教使用了一个数组方法 `reduce` 来快速剔除不需要的数据(`curr.id === targetId`)。
```javascriptfunction removeTask(targetId) { // 比对 tasks 中相同 task-id 的事项, // 最终 tasks 等于(过滤掉删除事项的事项) tasks = tasks.reduce((prev, curr) => { if (curr.id === targetId) { return prev; } return [...prev, curr]; }, []); renderTask(tasks);}```
第五步:切换待办事项
切换事项状态的思路和删除事项非常相似,切换事项流程就是:
- 检测待办清单是否被点击
- 确认被点击的是复选框
- 取出
task-id
属性 - 传入
toggleTask
函数中 - 在
toggleTask
函数中比对tasks
中相同task-id
的事项 - 切换 tasks 中该笔事项的状态
// 监听待办清单是否被点击todoList.addEventListener('click', (e) => { // 当被点击时,确认被点击的事项有 "data-task-toggle" 属性 if (e.target.hasAttribute('data-task-toggle')) { // 如果有,将其父元素上的 task-id 属性取出,传入 toggleTask 函数内 const taskId = e.target.parentElement.getAttribute('task-id'); toggleTask(taskId); }});
function toggleTask(targetId) { // 跑过 tasks 中每件事项,如果 id 相同,翻转该事项的完成状态 tasks.forEach((task) => { if (task.id === targetId) { task.isCompleted = !task.isCompleted; } });}
结语
See the Pen Todo vanilla JS by Riceball (
@riecball) on CodePen.
以上是助教制作整个待办事项题目的思考历程,同学可以参考但不用奉为宗旨死背或直接复制粘贴,实际操作一次效果最佳~