Ajax主要就是使用 XmlHttpRequest 对象来完成请求的操作,该对象在主流浏览器中均存在(除了早期的IE)。创建 XMLHttpRequest 对象的语法:
xmlhttp=new XMLHttpRequest();
老版本的 Internet Explorer (IE5 和 IE6)使用 ActiveX 对象:
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
创建对象之后,就可以通过对象来调用下面的这些方法了:
发送GET请求
使用上面的方法,先发送一个空的GET请求:
<!-- ajax.html 文件 -->
<body>
<input type="text" placeholder="随便写点值,看看页面是否有刷新">
<input type="button" value="Ajax" />
<script>
document.getElementsByTagName(‘input‘)[0].onclick = function () {
var xmlhttp = new XMLHttpRequest();
xmlhttp.open(‘GET‘, ‘/ajax/‘);
xmlhttp.send();
};
</script>
</body>
为了能够真正有服务响应这个请求,还得写一个处理函数:
# views.py 文件
def ajax(request):
return render(request, ‘ajax.html‘)
打开控制台,在网络里点击按钮触发事件后会看到我们发送的请求。点击这个请求可以看到标头。还有正文,正文里的响应正文返回的就是整个页面的html。并且整个过程里页面也是不会刷新的。
发送POST请求
上面发送的是GET请求,如果要发送POST请求,不只是要改一下method参数,还必须设置一下请求头:
xmlhttp.setRequestHeader(‘Content-Type‘, ‘application/x-www-form-urlencoded; charset-UTF-8‘);
另外还会有csrf的问题,csrf_token可以放到表单里,另外也可以设置到请求头中。
xmlhttp.setRequestHeader(‘X-CsrfToken‘, "{{ csrf_token }}");
之前一直用{% csrf_token %},这是生成一个html,这里使用的是{{ csrf_token }},直接就是token的字符串。
另外,客户端的Cookie里也会有一个csrf的值,看下来和{{ csrf_token }}的值是不同的,但是获取过来再同样放到请求头里也是可以通过csrf验证的:
xmlhttp.setRequestHeader(‘X-CsrfToken‘, getCookie(‘csrftoken‘));
// 这里需要一个getCookie方法,有很多的实现方式,比如下面用正则匹配的
function getCookie(name) {
var arr,reg=new RegExp("(^| )"+name+"=([^;]*)(;|$)");
if(arr=document.cookie.match(reg)){
return arr[2];
} else {
return null;
}
}
补充一个知识点:关于状态码和状态文本,使用HttpResponse返回的时候也是可以设置的:
return HttpResponse(json.dumps(ret), status=404, reason="Not Found")
并且一般这个状态码返回的往往都是200,因为即使后台有错误,我们捕获或者验证处理了,之后还是会正常返回数据的。如果需要用到这种状态码,就像上面一样返回的时候带上status参数设置状态码。或者我们不要这种通用的状态码,而是在我们自己写的ret的字典里,也搞一套规则表示应用返回的状态信息。两种用法都有人用,而且貌似自己搞一套的更多。
解决兼容性的问题,只需要解决这一句代码就好了 var xmlhttp = new XMLHttpRequest();
如果有这个对象,那么就使用这个对象,如果没有这个对象,就用另外一个对象。下面的function就是提供了一个返回正确的对象的方法:
<script>
function GetXmlhttp(){
var xmlhttp = null;
if(XMLHttpRequest){
xmlhttp = new XMLHttpRequest();
}else{
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
return xmlhttp;
}
// 使用的时候就不是创建对象了,而是通过上面的方法获取到对象
// var xmlhttp = GetXmlhttp();
</script>
下面3句的效果是一样的,是讲兼容性的时候顺带提到的。即下面的3种方式引用都是可以的。
> XMLHttpRequest
< function XMLHttpRequest() { [native code] }:
> window.XMLHttpRequest
< function XMLHttpRequest() { [native code] }:
> window[‘XMLHttpRequest‘]
< function XMLHttpRequest() { [native code] }:
原生方法帮助我们了解原理,使用的话还是jQuery会方便的多。
之前使用回调函数success的时候,只用到了一个参数。这个回调函数最多是有3个参数的:
由于HTML标签的iframe标签具有局部加载内容的特性,所以可以使用其来伪造Ajax请求。
在标签内部加上一个src的属性,就会在这个元素的内部创建包含另外一个文档的内联框架(就是嵌套一个网页):
<body>
<iframe src="http://blog.51cto.com/steed"></iframe>
<h2>注意带上前面的http://</h2>
<h2>举例:http://blog.51cto.com/steed</h2>
<label for="url">URL:</label><input type="text" id="url">
<input type="button" value="发送iframe请求" onclick="iframeRequest();">
<script src="http://lib.sinaapp.com/js/jquery/1.12.4/jquery-1.12.4.min.js"></script>
<script>
function iframeRequest() {
var url = $(‘#url‘).val();
$(‘iframe‘).attr(‘src‘, url);
}
</script>
</body>
上面还附带了一个方法,跟上input框的url,刷新iframe标签里嵌套的页面。但是页面整体是不刷新的,只有嵌套框架的内部会变化。上面的例子要说明的问题是:iframe标签也可以实现不刷新页面发送请求并且拿到返回的数据(偷偷的发请求)。
接下来,在上面的基础上,现在在form里写一个iframe标签,并且通过target属性和iframe的name相关联。原本页面上的form请求,现在都在iframe里实现了不刷新页面的提交和数据返回:
<form action="/ajax/" method="POST" target="ifm">
{% csrf_token %}
<iframe name="ifm"></iframe>
<input type="text" name="username" />
<input type="submit" />
</form>
下面是对应的处理函数:
# views.py 文件
import time
def ajax(request):
if request.method == ‘GET‘:
return render(request, ‘ajax.html‘)
elif request.method == ‘POST‘:
ret = {‘code‘: True, ‘data‘: request.POST.get(‘username‘)}
time.sleep(3)
return HttpResponse(json.dumps(ret))
上面虽然返回了值,但是是在页面上显示的,如何拿到这些数据。iframe内部是一个完整的Document对象,这里需要使用一个特殊的方法才能取到里面的值。另外,iframe内部是在提交并且返回数据只会才会有我们需要的值的,提交之后立刻获取也是获取不到的,需要等到数据返回后才能获取到。上面的处理函数里加了一个sleep,效果更佳明显。
获取数据的时机
iframe的数据加载完成后,会触发一个onload事件,给onload事件绑定一个函数,此时再去获取,就能获取到标签内部最新的值。不过这样还不完美,第一次加载页面的时候也会触发onload事件,要解决这个问题,需要为submit绑定事件,通过submit事件来给iframe标签绑定onload事件:
<script>
document.getElementsByTagName(‘form‘)[0].onsubmit = function () {
document.getElementsByTagName(‘iframe‘)[0].onload = function () {
alert(123)
};
}
</script>
获取数据的方法
既然iframe内部是一个DOM对象,使用 .contentWindow.document
就是内层的DOM对象。之后就是之前学习的知识了:
<script>
document.getElementsByTagName(‘form‘)[0].onsubmit = function () {
document.getElementsByTagName(‘iframe‘)[0].onload = function () {
var text = this.contentWindow.document.getElementsByTagName(‘body‘)[0].innerText;
alert(text);
};
}
</script>
如果是用jQuery的话,就使用jQuery的方法:
<script src="http://lib.sinaapp.com/js/jquery/1.12.4/jquery-1.12.4.min.js"></script>
<script>
$(‘form‘).submit(function () {
$(‘iframe‘).load(function () {
var text = $(this).contents().find(‘body‘).text();
alert(text);
})
})
</script>
之前通过Ajax发送的都是普通数据。发送普通数据的时候,推荐还是用jQuery,退而求其次是用原生的,用伪Ajax并不方便。
下面看看不普通的数据,就是上传文件。
下面的input是系统自带的上传按钮:
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.upload{display: inline-block; padding: 10px; background-color: blue; color: white;}
</style>
</head>
<body>
<input type="file" id="file" name="file" />
<a class="upload">上传</a>
</body>
上传按钮在不用的浏览器里看到的样子也是不同的,并且样式并不能完全的按自己的需要来定制。如果要做一个好看的上传按钮,需要特殊处理一下。
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
div.file{position: relative; width: 100px; height: 50px; line-height: 50px;}
div.file>#file{position: absolute; width:100%; height:100%; z-index: 20; opacity: 0;}
div.file>.upload{position: absolute; width:100%; height:100%; z-index: 10;
display: inline-block; background-color: blue; color: white; text-align: center;}
</style>
</head>
<body>
<div class="file">
<input type="file" id="file" name="file" />
<a class="upload">上传</a>
</div>
</body>
上面的思路就是,把默认的input和我们的标签重叠。把默认的input放在上面,但是透明度设置为0就是看不见。把我们自定制的标签放在下面,但是上层由于全透明看不见,看到的效果就是我们自定制标签的效果。但是点击的效果还是点击了默认的input按钮,因为它才是真正在上层的元素,只是看不见而已。
要自定制好看的上传按钮,基本都是基于这个方式来实现的。
这里需要先引入一个FormData对象。FormData对象用以将数据编译成键值对,以便用XMLHttpRequest来发送数据。这个对象有一个append()方法,为当前的FormData对象添加键值对:
void append(String name, String value);
void append(String name, Blob value, optional String filename);
第一种用法就是传入2个字符串,前面是Key,后面是字符串。
第二种用法是,第二个参数传入一个Blob对象,即一个不可变、原始数据的类文件对象。简单理解成文件对象就好了,在上面的例子中,通过 var file_obj = document.getElementById(‘file‘).files[0];
就能获取到这个文件对象。
最后还有一个可选的(optional)第三个参数,指定文件的文件名。
页面的代码如下,主要看js的部分:
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
div.file{position: relative; width: 100px; height: 50px; line-height: 50px;}
div.file>#file{position: absolute; width:100%; height:100%; z-index: 20; opacity: 0;}
div.file>.upload{position: absolute; width:100%; height:100%; z-index: 10;
display: inline-block; background-color: blue; color: white; text-align: center;}
</style>
</head>
<body>
<div class="file">
<input type="file" id="file" name="file" />
<a class="upload">上传</a>
</div>
<input type="button" value="提交" id="btn" />
<script>
document.getElementById(‘btn‘).onclick = function () {
var file_obj = document.getElementById(‘file‘).files[0];
var form_data = new FormData();
form_data.append(‘username‘, ‘root‘);
form_data.append(‘file‘, file_obj);
var xmlhttp = new XMLHttpRequest();
xmlhttp.open(‘POST‘, ‘/upload/‘);
xmlhttp.setRequestHeader(‘X-CsrfToken‘, "{{ csrf_token }}");
// form_data.append(‘csrfmiddlewaretoken‘, "{{ csrf_token }}"); // 也可以加在form里
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4){
var obj = JSON.parse(xmlhttp.responseText);
console.log(obj);
}
};
xmlhttp.send(form_data)
}
</script>
</body>
对应后端的处理函数:
# views.py 文件
def upload(request):
if request.method == ‘GET‘:
return render(request, ‘upload.html‘)
elif request.method == ‘POST‘:
username = request.POST.get(‘username‘)
file_obj = request.FILES.get(‘file‘)
print(type(file_obj), file_obj) # 从这里看到已经收到文件了
print(file_obj.__dict__) # 看看有哪些属性,比如:文件名、大小、文件类型
ret = {‘code‘: True, ‘data‘: username}
# 保存文件
with open(file_obj.name, ‘wb‘) as file:
for item in file_obj.chunks():
file.write(item)
return HttpResponse(json.dumps(ret))
只有js的部分和上面不同:
<script src="http://lib.sinaapp.com/js/jquery/1.12.4/jquery-1.12.4.min.js"></script>
<script>
$(‘#btn‘).click(function () {
var file_obj = $(‘:file‘)[0].files[0];
var form_data = new FormData();
form_data.append(‘username‘, ‘root‘);
form_data.append(‘file‘, file_obj);
$.ajax({
url: ‘/upload/‘,
type: ‘POST‘,
data: form_data,
headers: {‘X-CsrfToken‘: "{{ csrf_token }}"},
processData: false, // 上传文件必须要有这句,告诉jQuery不要对data进行加工
contentType: false, // 上传文件必须要有这句,告诉jQuery不要设置contentType
success: function (data, textStatus, jqXHR) {
console.log(data);
console.log(textStatus);
console.log(jqXHR);
}
})
})
</script>
上面使用Ajax上传文件,都必须依赖 FormDate 对象。但是这个对象不是所有浏览器都支持的,没错,还是老版的IE。要考虑兼容性问题,就需要用到这里的伪Ajax。
使用伪造Ajax上传文件和上传一般的内容几乎没什么差别,加一个 type="file"
的input框。
注意:form标签要上传文件,需要加上 enctype="multipart/form-data"
这个属性
<form action="/upload/" method="POST" target="ifm" enctype="multipart/form-data">
{% csrf_token %}
<iframe name="ifm"></iframe>
<input type="text" name="username" />
<input type="file" name="file">
<input type="submit" />
</form>
<script src="http://lib.sinaapp.com/js/jquery/1.12.4/jquery-1.12.4.min.js"></script>
<script>
$(‘form‘).submit(function () {
$(‘iframe‘).load(function () {
var text = $(this).contents().find(‘body‘).text();
var obj = JSON.parse(text);
console.log(obj);
})
})
</script>
上面还多了一个iframe的大框,设置 style="display: none;"
即可。剩下的上传按钮的美化方法应该是一样的。
这个方法的兼容性是最高的,所以伪Ajax在上传文件的应用场景中是推荐使用的方法。
如果上传的文件是张图片,可以生成预览。把上传的图片存放到一个存放静态文件的目录里,setting.py里也设置好静态文件的目录。比如:
STATICFILES_DIRS = (
os.path.join(BASE_DIR, ‘static‘),
)
这里预览的图片是从服务器上获取的。也就是先把图片上传的服务器,然后服务器端返回文件在服务器上的路径给预览,预览再通过路径拿到服务器端的文件,最后显示在页面上。
处理函数修改一下,默认是保存到根目录的,现在保存到专门的目录里:
# views.py 文件
import os
def upload(request):
if request.method == ‘GET‘:
return render(request, ‘upload.html‘)
elif request.method == ‘POST‘:
username = request.POST.get(‘username‘)
file_obj = request.FILES.get(‘file‘)
# print(type(file_obj), file_obj) # 从这里看到已经收到文件了
# print(file_obj.__dict__) # 看看有哪些属性,比如:文件名、大小、文件类型
img_path = os.path.join(‘static/imgs/‘, file_obj.name) # 生成文件保存的路径
ret = {‘code‘: True, ‘data‘: username, ‘img‘: img_path} # 返回的信息要有文件的路径
# 保存文件
with open(img_path, ‘wb‘) as file:
for item in file_obj.chunks():
file.write(item)
return HttpResponse(json.dumps(ret))
主要的变化就是生成了文件保存的路径,另外将路径返回给客户端。
下面是html的完整代码:
<div id="preview"></div>
<form action="/upload/" method="POST" target="ifm" enctype="multipart/form-data">
{% csrf_token %}
<iframe name="ifm" style="display: none;"></iframe>
<input type="text" name="username" />
<input type="file" name="file">
<input type="submit" />
</form>
<script src="http://lib.sinaapp.com/js/jquery/1.12.4/jquery-1.12.4.min.js"></script>
<script>
$(‘form‘).submit(function () {
$(‘iframe‘).load(function () {
var text = $(this).contents().find(‘body‘).text();
var obj = JSON.parse(text);
// console.log(obj);
var imgTag = document.createElement(‘img‘);
imgTag.src = "/" + obj.img; // 这里注意前面需要添加个‘/‘
$(‘#preview‘).empty(); // 可能有上次的预览,要先清空
$(‘#preview‘).append(imgTag);
})
})
</script>
上面预设了一个图片预览的div。Ajax请求返回后,获取到其中的图片路径(这个路径应该是直接拼接到“127.0.0.1:8000”后面就能访问到图片的)。创建一个img标签,设置好src,然后加到预览的div里。加进去之前,要把div清空一下,因为有上一次预览加进去的img标签
上面的上传文件都是分两步上传的,首先是input[type=‘file‘]标签选择文件,然后是submit或者button按钮绑定事件来进行提交。
这里也可以不要后面的submit或者button按钮,为input[type=‘file‘]绑定一个onchange事件触发一个form表单的submit事件,或者是替换到原来button按钮的onclick事件。前一种的实现只要加上下面的这个事件绑定:
<script>
$(‘:file‘).change(function () {
$(‘form‘).submit();
});
<script>
原文地址:http://blog.51cto.com/steed/2118055