todolist案例 (ajax.js+node.js)
实现的效果
- 添加任务
- 显示全部的任务
- 删除任务
- 显示未完成的任务
- 点击复选框改变任务状态
server node.js
环境搭建
为todo数据库添加账号
使用mongo命令进入mongodb数据库
使用use admin命令进入到admin数据中
使用db.auth(‘root’, ‘root’)命令登录数据库
使用use todo命令切换到todo数据库
使用db.createUser({user: ‘itcast’, pwd: ‘itcast’, roles: [‘readWrite’]})创建todo数据库账号
使用exit命令退出mongodo数据库
** 使用npm下载一切需要的包**
app.js
// 引入express框架
const express = require('express');
// 路径处理模块
const path = require('path');
// 导入mongoose模块
const mongoose = require('mongoose');
// 导入bodyParser模块
const bodyParser = require('body-parser');
// 创建web服务器
const app = express();
// 静态资源访问服务功能
app.use(express.static(path.join(__dirname, 'public')));
// 处理post请求参数
app.use(bodyParser.json());
// app.use(bodyParser.urlencoded({extended: false}));
// 数据库连接
mongoose.connect('mongodb://itcast:itcast@localhost:27017/todo', {useNewUrlParser: true })
// 导入todo路由案例
const todoRouter = require('./route/todo')
// 当客户端的请求路径以/todo开头时
app.use('/todo', todoRouter);
// 获取用户列表信息
app.get('/users', (req, res) => {
res.send('当前是获取用户列表信息的路由');
});
// 获取某一个用户具体信息的路由
app.get('/users/:id', (req, res) => {
// 获取客户端传递过来的用户id
const id = req.params.id;
res.send(`当前我们是在获取id为${id}用户信息`);
});
// 删除某一个用户
app.delete('/users/:id', (req, res) => {
// 获取客户端传递过来的用户id
const id = req.params.id;
res.send(`当前我们是在删除id为${id}用户信息`);
});
// 修改某一个用户的信息
app.put('/users/:id', (req, res) => {
// 获取客户端传递过来的用户id
const id = req.params.id;
res.send(`当前我们是在修改id为${id}用户信息`);
});
app.get('/xml', (req, res) => {
res.header('content-type', 'text/xml');
res.send('<message><title>消息标题</title><content>消息内容</content></message>')
});
// 监听端口
app.listen(3000);
// 控制台提示输出
console.log('服务器启动成功');
todo.js
// 引入express框架
const express = require('express');
// 工具库
const _ = require('lodash');
// 对象校验
const Joi = require('joi');
// 创建todo案例路由
const todoRouter = express.Router();
// 导入todo集合构造函数
const Task = require('../model/task');
// 获取任务列表
todoRouter.get('/task', async (req, res) => {
const task = await Task.find();
// 响应
res.send(task);
});
// 添加任务
todoRouter.post('/addTask', async (req, res) => {
// 接收客户端传递过来的任务名称
const { title } = req.body;
// 验证规则
const schema = {
title: Joi.string().required().min(2).max(30)
};
// 验证客户端传递过来的请求参数
const { error } = Joi.validate(req.body, schema);
// 验证失败
if (error) {
// 将错误信息响应给客户端
return res.status(400).send({message: error.details[0].message})
}
// 创建任务实例
const task = new Task({title: title, completed: false});
// 执行插入操作
await task.save();
// 响应
setTimeout(() => {
res.send(task);
}, 2000)
});
// 删除任务
todoRouter.get('/deleteTask', async (req, res) => {
// 要删除的任务id
const { _id } = req.query;
// 验证规则
const schema = {
_id: Joi.string().required().regex(/^[0-9a-fA-F]{24}$/)
}
// 验证客户端传递过来的请求参数
const { error } = Joi.validate(req.query, schema);
// 验证失败
if (error) {
// 将错误信息响应给客户端
return res.status(400).send({message: error.details[0].message})
}
// 删除任务
const task = await Task.findOneAndDelete({_id: _id});
// 响应
res.send(task);
});
// 清除已完成任务
todoRouter.get('/clearTask', async (req, res) => {
// 执行清空操作
const result = await Task.deleteMany({completed: true});
// 返回清空数据
res.send(result);
});
// 修改任务
todoRouter.post('/modifyTask', async (req, res) => {
// 执行修改操作
const task = await Task.findOneAndUpdate({_id: req.body._id}, _.pick(req.body, ['title', 'completed']),{new: true})
// 响应
res.send(task);
});
// 查询未完成任务数量
todoRouter.get('/unCompletedTaskCount', async (req, res) => {
// 执行查询操作
const result = await Task.countDocuments({completed: false});
// 响应
res.send({num: result})
});
// 更改任务全部状态
todoRouter.get('/changeAllTasksComplete', async (req, res) => {
// 状态
const { status } = req.query;
// 执行更改状态操作
const result = await Task.updateMany({}, {completed: status});
// 响应
res.send(result);
});
// 将todo案例路由作为模块成员进行导出
module.exports = todoRouter;
task.js(任务集合的规则)
// 引入mongoose模块
const mongoose = require('mongoose');
// 创建任务集合规则
const taskSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
completed: {
type: Boolean,
default: false
}
}, {versionKey: false});
// 创建todo集合
const Task = mongoose.model('task', taskSchema);
// 将集合构造函数作为模块成员进行导出
module.exports = Task;
客户端
index.html
基本的html结构
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Todo List</title>
<link rel="stylesheet" href="assets/css/base.css">
<link rel="stylesheet" href="assets/css/index.css">
<link rel="stylesheet" href="/js/nprogress/nprogress.css">
</head>
<body>
<section class="todoapp">
<header class="header">
<h1>todos</h1>
<input type="text" class="new-todo" placeholder="What needs to be done?" autofocus id="task">
</header>
<!-- This section should be hidden by default and shown when there are todos -->
<section class="main">
<input class="toggle-all" type="checkbox">
<ul class="todo-list" id="todo-list"></ul>
</section>
<!-- This footer should hidden by default and shown when there are todos -->
<footer class="footer">
<!-- This should be `0 items left` by default -->
<span class="todo-count"><strong id="count">0</strong> item left</span>
<!-- Remove this if you don't implement routing -->
<ul class="filters">
<li>
<a class="selected" href="javascript:;">All</a>
</li>
<li>
<a href="javascript:;">Active</a>
</li>
<li>
<a href="javascript:;">Completed</a>
</li>
</ul>
<!-- Hidden if no completed items are left ↓ -->
<button class="clear-completed">Clear completed</button>
</footer>
</section>
</body>
</html>
创建模板(动态添加的任务)
注意这里使用的模板第三方插件是template-web.js;nprogress是加载进度显示的第三方插件
<script src="/js/jquery.min.js"></script>
<script src="/js/template-web.js"></script>
<script src="/js/nprogress/nprogress.js"></script>
<!-- 任务列表模板 -->
<script type="text/html" id="taskTpl">
{{each tasks}}
<li class="{{$value.completed ? 'completed' : ''}}">
<div class="view">
<input class="toggle" type="checkbox" {{$value.completed ? 'checked' : ''}}>
<label>{{$value.title}}</label>
<button class="destroy" data-id="{{$value._id}}"></button>
</div>
<input class="edit">
</li>
{{/each}}
</script>
逻辑
<script type="text/javascript">
// 用于存放任务列表的数组
var taskAry = [];
// 选择任务列表容器
var taskBox = $('#todo-list');
// 添加任务的文本框
var taskInp = $('#task');
// 用于存储未完成任务数量的strong标签
var strong = $('#count');
// 当页面中有ajax请求发送时触发
$(document).on('ajaxStart', function () {
NProgress.start()
})
// 当页面中有ajax请求完成时触发
$(document).on('ajaxComplete', function () {
NProgress.done()
})
</script>
功能
- 展示任务列表
准备一个放置任务列表的数组
向服务器端发送请求,获取已存在的任务
将已存在的任务存储在任务列表数组中
通过模板引擎将任务列表数组中的任务显示在页面中
// 向服务器端发送请求 获取已经存在的任务
$.ajax({
url: '/todo/task',
type: 'get',
success: function (response) {
// 将已存在的任务存储在taskAry变量中
taskAry = response;
// 拼接字符串 将拼接好的字符串显示在页面中
render();
// 计算未完成任务数量
calcCount ()
}
})
2**.添加任务**
为文本框绑定键盘抬起事件,在事件处理函数中判断当前用户敲击的是否是回车键
当用户敲击回车键的时候,判断用户在文本框中是否输入了任务名称
向服务器端发送请求,将用户输入的任务名称添加到数据库中,同时将任务添加到任务数组中
通过模板引擎将任务列表数组中的任务显示在页面中
// 获取文本框并且添加键盘抬起事件
taskInp.on('keyup', function (event) {
// 如果用户敲击的是回车键
if (event.keyCode == 13) {
// 判断用户是否在文本框中输入了任务名称
var taskName = $(this).val();
// 如果用户没有在文本框中输入内容
if (taskName.trim().length == 0) {
alert('请输入任务名称')
// 阻止代码向下执行
return;
}
// 向服务器端发送请求 添加任务
$.ajax({
type: 'post',
url: '/todo/addTask',
contentType: 'application/json',
data: JSON.stringify({title: taskName}),
success: function (response) {
// 将任务添加到任务列表中
taskAry.push(response);
// 拼接字符串 将拼接好的字符串显示在页面中
render();
// 清空文本框中的内容
taskInp.val('');
// 计算未完成任务数量
calcCount ()
}
})
}
});
3.删除任务
为删除按钮添加点击事件
在事件处理函数中获取到要删任务的id
向服务器端发送请求,根据ID删除任务,同时将任务数组中的相同任务删除
通过模板引擎将任务列表数组中的任务重新显示在页面中
// 当用户点击删除按钮时触发ul标签身上的点击事件
taskBox.on('click', '.destroy', function () {
// 要删除的任务的id
var id = $(this).attr('data-id');
// 向服务器端发送请求删除 任务
$.ajax({
url: '/todo/deleteTask',
type: 'get',
data: {
_id: id
},
success: function (response) {
// 从任务数组中找到已经删除掉的任务的索引
var index = taskAry.findIndex(item => item._id == id);
// 将任务从数组中删除
taskAry.splice(index, 1);
// 重新将任务数组中的元素显示在页面中
render();
// 计算未完成任务数量
calcCount ()
}
})
});
4、更改任务状态
为任务复选框添加onchange事件
在事件处理函数中获取复选框是否选中
向服务器端发送请求,将当前复选框的是否选中状态提交到服务器端
将任务状态同时也更新到任务列表数组中
通过模板引擎将任务列表数组中的任务重新显示在页面中并且根据任务是否完成为li元素添加completed类名
事件委托
// // 当用户改变任务名称前面的复选框状态时触发
taskBox.on('change', '.toggle', function () {
// 代表复选框是否选中 true 选中 false 未选中的
const status = $(this).is(':checked');
// 当前点击任务的id
const id = $(this).siblings('button').attr('data-id');
// 向服务器端发送请求 更改任务状态
$.ajax({
type: 'post',
url: '/todo/modifyTask',
data: JSON.stringify({_id: id, completed: status}),
contentType: 'application/json',
success: function (response) {
// 将任务状态同步到任务数组中
var task = taskAry.find(item => item._id == id);
// 更改任务状态
task.completed = response.completed;
// 将数组中任务的最新状态更新到页面中
render();
// 计算未完成任务数量
calcCount ()
}
})
});
5、修改任务名称
为任务名称外层的label标签添加双击事件,同时为当前任务外层的li标签添加editing类名,开启编辑状态
将任务名称显示在文本框中并让文本框获取焦点
当文本框离开焦点时,将用户在文本框中输入值提交到服务器端,并且将最新的任务名称更新到任务列表数组中
使用模板引擎重新渲染页面中的任务列表。
// // 当双击事件名称的时候触发
taskBox.on('dblclick', 'label', function () {
// 让任务处于编辑状态
$(this).parent().parent().addClass('editing');
// 将任务名称显示在文本框中
$(this).parent().siblings('input').val($(this).text())
// 让文本框获取焦点
$(this).parent().siblings('input').focus();
})
// 当文本框离开焦点的时候
taskBox.on('blur', '.edit', function () {
// 最新的任务名称
var newTaskName = $(this).val();
// 编辑任务的id
var id = $(this).siblings().find('button').attr('data-id');
// 向服务器端发送请求 修改任务名称
$.ajax({
url: '/todo/modifyTask',
type: 'post',
data: JSON.stringify({_id: id, title: newTaskName}),
contentType: 'application/json',
success: function (response) {
// 将当期任务的最新状态同步到任务数组中
var task = taskAry.find(item => item._id == id);
// 修改任务名称
task.title = response.title;
// 将任务数组中的任务同步到页面中
render();
// 计算未完成任务数量
calcCount ()
}
})
});
6、过程中使用到的函数
// 拼接字符串 将拼接好的字符串显示在页面中
function render() {
// 字符串拼接
var html = template('taskTpl', {
tasks: taskAry
});
// 将拼接好的字符串显示在ul标签中
taskBox.html(html);
}
// 用于计算未完成任务的数量
function calcCount () {
// 存储结果的变量
var count = 0;
// 将未完成的任务过滤到一个新的数组中
var newAry = taskAry.filter(item => item.completed == false);
// 将新数组的长度赋值给count
count = newAry.length;
// 将未完成的任务数量显示在页面中
strong.text(count)
}
//将任务数组中未完成的数据渲染到页面上