前后端交互之Ajax编程02
模板引擎
官方地址:https://aui.github.io/art-template/zh-cn/docs/
将数据和html连接起来,这里是客户端模板引擎,art-template模板引擎;
- 下载模板引擎
服务器端下载方式:npm install art-template --save
客户端下载方式:保存到D:\web_study\Ajax\02\public\js
- 将模板引擎的库文件引入到当前页面
<script src="/js/template-web.js"></script>
- 准备art-template模板
<script type="text/html" id="tpl">
<h1>{{username}}{{age}}</h1>
</script>
- 告诉模板引擎哪个数据和哪个模板拼接
<script type="text/javascript">
//3. 告诉模板引擎哪个数据和哪个模板拼接
var html = template('tpl', {
username: 'zhangsan',
age: 30
});
//container是一个div盒子的ID
document.getElementById('container').innerHTML = html;
</script>
案例
1. 验证邮箱i地址唯一性
用户输入完成后,鼠标离开焦点的时候,判断用户是否注册过,根据判断结果给出不同的提示信息显示在页面中;
- 获取页面中的元素
// 获取页面中的元素
var emailInp = document.getElementById('email');
var info = document.getElementById('info');
- 鼠标离开焦点以后,
// 当文本框离开焦点以后
emailInp.onblur = function () {
// 获取用户输入的邮箱地址
var email = this.value;
// 验证邮箱地址的正则表达式
var reg = /^[A-Za-z\d]+([-_.][A-Za-z\d]+)*@([A-Za-z\d]+[-.])+[A-Za-z\d]{2,4}$/;
// 如果用户输入的邮箱地址不符合规则
if (!reg.test(email)) {
// 给出用户提示
info.innerHTML = '请输入符合规则的邮箱地址';
// 让提示信息显示为错误提示信息的样式
info.className = 'bg-danger';
// 阻止程序向下执行
return;
}
- 使用Ajax向服务器发送请求,首先需要引入
ajax.js
文件<script src="/js/ajax.js"></script>
// 向服务器端发送请求
ajax({
type: 'get',
url: 'http://localhost:3000/verifyEmailAdress',
data: {
//email就是用户输入的邮箱地址
email: email
},
success: function (result) {
console.log(result);
info.innerHTML = result.message;
info.className = 'bg-success';
},
error: function (result) {
console.log(result)
info.innerHTML = result.message;//result是响应结果
info.className = 'bg-danger';
}
});
在app.js文件中设置邮箱地址验证路由
//设置邮箱地址验证路由
app.get('/verifyEmailAdress', (req, res) => {
// 接收客户端传递过来的邮箱地址
const email = req.query.email;
// 判断邮箱地址注册过的情况
if (email == 'itheima@itcast.cn') {
// 设置http状态码并对客户端做出响应
res.status(400).send({ message: '邮箱地址已经注册过了, 请更换其他邮箱地址' });
} else {
// 邮箱地址可用的情况
// 对客户端做出响应
res.send({ message: '恭喜, 邮箱地址可用' });
}
});
2. 搜索框内容自动提示
- 获取搜索框
// 获取搜索框
var searchInp = document.getElementById('search');
// 获取提示文字的存放容器
var listBox = document.getElementById('list-box');
- 在用户文本框中输入的时候触发事件
searchInp.oninput = function () {}
获取用户输入的内容var key = this.value;
- 向服务器发送请求
// 向服务器端索取和用户输入关键字相关的内容
ajax({
type: 'get',
url: 'http://localhost:3000/searchAutoPrompt',
data: {
key: key
},
success: function (result) {
// 使用模板引擎拼接字符串 先引入模板
var html = template('tpl', {result: result});
// 将拼接好的字符串显示在页面中
listBox.innerHTML = html;
// 显示ul容器
listBox.style.display = 'block';
}
})
}, 800)
- 以上代码中的success方法中的函数:先引入模板引擎,然后写模板
<script src="/js/template-web.js"></script>
<script type="text/html" id="tpl">
{{each result}}
<li class="list-group-item">{{$value}}</li>
{{/each}}
</script>
搜索框只要有输入就会立即触发oninput
事件,为了使得输入完整再触发这个事件,所以在发送请求之前加入延时;所以把ajax({});
代码放到了延时函数timer = setTimeout(function(){},800)
里面,在每一次触发oninput
事件的时候需要清除上一次开启的定时器;如果搜索框没有内容就隐藏下拉框
if (key.trim().length == 0) {
// 将提示下拉框隐藏掉
listBox.style.display = 'none';
// 阻止程序向下执行
return;
}
在app.js中写好搜索框路由
// 输入框文字提示
app.get('/searchAutoPrompt', (req, res) => {
// 搜索关键字
const key = req.query.key;
// 提示文字列表
const list = [
'黑马程序员',
'黑马程序员官网',
'黑马程序员顺义校区',
'黑马程序员学院报名系统',
'传智播客',
'传智博客前端与移动端开发',
'传智播客大数据',
'传智播客python',
'传智播客java',
'传智播客c++',
'传智播客怎么样'
];
// 搜索结果
let result = list.filter(item => item.includes(key));
// 将查询结果返回给客户端
res.send(result);
});
3. 省市三级联动
先准备获取省份,根据省份id获取城市,根据城市id获取县城路由
// 获取省份
app.get('/province', (req, res) => {
res.json([{
id: '001',
name: '黑龙江省'
},{
id: '002',
name: '四川省'
},{
id: '003',
name: '河北省'
},{
id: '004',
name: '江苏省'
}]);
});
// 根据省份id获取城市
app.get('/cities', (req, res) => {
// 获取省份id
const id = req.query.id;
// 城市信息
const cities = {
'001': [{
id: '300',
name: '哈尔滨市'
}, {
id: '301',
name: '齐齐哈尔市'
}, {
id: '302',
name: '牡丹江市'
}, {
id: '303',
name: '佳木斯市'
}],
'002': [{
id: '400',
name: '成都市'
}, {
id: '401',
name: '绵阳市'
}, {
id: '402',
name: '德阳市'
}, {
id: '403',
name: '攀枝花市'
}],
'003': [{
id: '500',
name: '石家庄市'
}, {
id: '501',
name: '唐山市'
}, {
id: '502',
name: '秦皇岛市'
}, {
id: '503',
name: '邯郸市'
}],
'004': [{
id: '600',
name: '常州市'
}, {
id: '601',
name: '徐州市'
}, {
id: '602',
name: '南京市'
}, {
id: '603',
name: '淮安市'
}]
}
// 响应
res.send(cities[id]);
});
// 根据城市id获取县城
app.get('/areas', (req, res) => {
// 获取城市id
const id = req.query.id;
// 县城信息
const areas = {
'300': [{
id: '20',
name: '道里区',
}, {
id: '21',
name: '南岗区'
}, {
id: '22',
name: '平房区',
}, {
id: '23',
name: '松北区'
}],
'301': [{
id: '30',
name: '龙沙区'
}, {
id: '31',
name: '铁锋区'
}, {
id: '32',
name: '富拉尔基区'
}]
};
// 响应
res.send(areas[id] || []);
});
- 获取省份信息
// 获取省市区下拉框元素
var province = document.getElementById('province');
var city = document.getElementById('city');
var area = document.getElementById('area');
ajax({
type: 'get',
url: 'http://localhost:3000/province',
success: function(data) {
// 将服务器端返回的数据和html进行拼接
var html = template('provinceTpl', {
province: data
});
// 将拼接好的html字符串显示在页面中,这里的province是id标签得到的
province.innerHTML = html;
}
});
- 省份模板
<script type="text/html" id="provinceTpl">
<option>请选择省份</option>
{{each province}}
<option value="{{$value.id}}">{{$value.name}}</option>
{{/each}}
</script>
- 为省份下拉框添加事件
- 根据省份id获取城市信息
ajax({
type: 'get',
url: '/cities',
data: {
id: pid
},
success: function(data) {
var html = template('cityTpl', {
city: data
});
city.innerHTML = html;
}
})
};
- 城市模板
- 县是一样的做法,
FormData
- FormData对象的作用
- 创建普通表单,表单必须有name属性
- 获取按钮,获取表单,为按钮添加点击事件
- 在按钮点击事件里面将普通的html表单转换为表单对象
- 创建ajax对象,并且配置,发送请求,监听xhr的onload事件;
- 使用formidable模块接收客户端传递过来的FormData参数
- FormData对象的实例方法;
在属性已经存在的情况下,set会覆盖原有的属性,append保留原有属性; - FormData二进制文件上传
实现文件上传路由
// 实现文件上传的路由
app.post('/upload', (req, res) => {
// 创建formidable表单解析对象
const form = new formidable.IncomingForm();
// 设置客户端上传文件的存储路径
form.uploadDir = path.join(__dirname, 'public', 'uploads');
// 保留上传文件的后缀名字
form.keepExtensions = true;
// 解析客户端传递过来的FormData对象
form.parse(req, (err, fields, files) => {
// 将客户端传递过来的文件地址响应到客户端
res.send({
path: files.attrName.path.split('public')[1]
});
});
});
- FormData文件上传进度显示
// 在文件上传的过程中持续触发 bar是进度条元素
xhr.upload.onprogress = function (ev) {
// ev.loaded 文件已经上传了多少
// ev.total 上传文件的总大小
var result = (ev.loaded / ev.total) * 100 + '%';
// 设置进度条的宽度
bar.style.width = result;
// 将百分比显示在进度条中
bar.innerHTML = result;
}
- 上传完成之后在页面显示
// 将客户端传递过来的文件地址响应到客户端
res.send({
path: files.attrName.path.split('public')[1]
});
动态创建img标签
if (xhr.status == 200) {
// 将服务器端返回的数据显示在控制台中
var result = JSON.parse(xhr.responseText);
// 动态创建img标签
var img = document.createElement('img');
// 给图片标签设置src属性
img.src = result.path;
// 当图片加载完成以后
img.onload = function() {
// 将图片显示在页面中
box.appendChild(img);
}
}
同源政策
Ajax请求限制,Ajax只能向自己的服务器发送请求。
同源: 页面有相同的协议、域名、端口。否则不同源
为什么有同源政策: 保证用户信息安全,防止恶意的网站窃取数据
Ajax不能向非同源服务器发送请求:
非同源请求
JSONP解决非同源限制问题
- 使用JSONP解决同源限制问题;不属于Ajax请求,但是可以模拟Ajax请求;
- 将不同源的服务器端请求地址写在script标签src属性中
- 服务器端响应数据必须是一个函数的调用,真正要发送给客户端的数据需要作为函数调用的参数
以上要发送给客户端的数据是{name:“张三”,age:“20”} - 在客户端全局作用域下定义函数 fn
- 在 fn 函数内部对服务器端返回的数据进行处理
JSONP代码优化
在客户端将script
中的代码更改为,客户端的函数名字改变不会影响服务器端;
对应的s2 app.js
文件里面的/better路由需要:
封装jsonp
调用jsonp jsonp({url:’http://localhost:3001/better?callback=fn2'});
对jsonp封装
进行优化
jsonp里面有data需要拼接数据,
然后放到请求地址中
服务端的代码优化
调用jsonp方法;res.jsonp({ name: 'lisi', age: 20 });
就相当于前面的接受了客户端传过来的函数名称,调用这个函数,并且返回给客户端;
CORS跨域资源共享解决非同源问题
CORS跨域资源共享,允许浏览器向跨域服务器发送Ajax请求,克服了Ajax只能同源使用的限制;
点击按钮发送ajax请求,ajax.js请求文件自己写好
在app.js当中没有写cross路由,也没有设置头就会报下面的错误
上面的错误表示从3000来的向3001的请求没有被允许;所以需要设置响应头来允许指定的访问来源
这里需要注意这个拦截所有请求需要放到所有路由的上面*代表允许所有客户端访问;
案例之腾讯天气获取
把jsonp函数单独封装在一个文件中,即可使用;
- 向服务端获取天气信息
- 处理时间格式
访问非同源数据服务器端解决方案
同源 政策是浏览器给予Ajax技术的限制
由自己的客户端去访问自己的服务器端:
在自己的服务器模块中写server路由文件,由自己的服务器去访问另外一个服务器,使用nodejs的第三方模块request
首先需要在s1文件目录下安装 npm install request
跨域中的cookie复习
withCredentials属性
在使用Ajax技术发送跨域请求时,默认情况不会在请求中带cookie信息;
withCredentials:指定在涉及到跨域请求时,是否携带cookie信息,默认值是false;
实现跨域登录功能
输入设置好的用户及密码之后,再点击检测用户登录状态便可以显示处于登录状态;
在检测路由里面判断req.session.isLogin
布尔值;还需要在之前写的拦截所有请求里面写入代码res.header('Access-Control-Allow-Credentials', true);
总结
客户端模板
的使用,FormData
的使用,以及解决非同源问题
的三种方法,一种是jsonp模拟Ajax
请求,服务器返回的必须是一个函数的调用;第二种是CORS跨域
请求,设置响应头信息;第三种是服务器端
解决非同源问题,利用自己服务器去访问另外一个服务器,需要借助nodejs的formidable插件;