Q:使用multipart/form-data方式上传表单,并且能够获得后台返回的数据
前几个月做项目的时候遇到的这个问题,现在空下来了有时间可以好好总结一下。
那会用chrome开发,使用jquery.form插件可以很完美的解决这个问题。后期在ie8兼容性测试的时候,不知为什么这个插件总是会报错,从源代码里面调试,也找不到问题的具体原因何在。随着项目上线时间越来越近,心里也越来越急,于是最后决定抛弃这个插件,自己写一个实现。
当然最后找到了原因,jquery.form插件在ie8下使用时是用iframe实现的,访问时涉及到二级域名跨域,因此在访问子iframe里面的东西时直接被block了,然后就挂了。(渣渣ie =_=)
那两个域名是类似这样的:
top:
123456.cn
iframe:
yyyyy.aaa.123456.cn
具体本文开头的这个问题是怎么解决的,接下来一步步介绍。
-使用iframe屏蔽submit提交表单后的自动跳转
正常的情况下,要上传包括文件的数据需要使用form中的multipart/form-data。
<form method="post" action="/submitTest" enctype="multipart/form-data">....</form>
如果直接使用form表单里的submit提交给后台,submit之后,后台返回的数据会在前端新建一个页面,并显示在上面。但这样的体验并不友好。
利用在html5中XMLHttpRequest Level 2提供的FormData这个接口,可以实现不依赖submit上传包含文件的数据。
var formData = new FormData();
formData.append('username', '张三');
formData.append('id', 123456);
formData.append('file', file); //file为通过file input选择的文件obj
xhr.send(formData);
只要一个个往里面塞包括二进制的数据,然后回调success就好了。jquery.form默认就是使用这种方法。它会先检查浏览器是否有fileapi以及Formdata这个方法。但是遇到不支持html5的浏览器,比如ie8(渣渣ie =_=),就会走另一种用iframe的方法。
使用iframe是一种经典的老方法。将form的target熟悉指向iframe的name,后台表单返回的数据就会在iframe里面出现。
<iframe src="" frameborder="0" name="iframe"></iframe>
<form method="post" target="iframe" action="/submitTest" enctype="multipart/form-data">
....
</form>
最后将iframe设置为不可见即可
<iframe style="display:none;" src="" frameborder="0" name="iframe"></iframe>
有一点需要提醒一下。在ie中,需要将头设置为Content-Type:text/html,否则iframe里面接收到的是404,略蛋疼。
-使用onload获取后台返回在iframe中的数据
另一方面,我们需要得到后台返回的数据。一般使用的方式是在ajax的时候通过success回调,但明显用iframe不能直接用这样的方式。
iframe相当于一个新的窗口,有个属于自己的window对象。可以利用它的onload方法,当后台刷新iframe里面的数据时,用在父页面使用iframe的onload方法捕获这个动作,然后读取它的body就可以了。
var iframeEle = document.getElementsByName("iframe")[0];
iframeEle.onload = function(){
console.log(document.iframe.window.document.body.innerHTML);
};
之后拿到后台数据该干嘛干嘛- –
在我实际的项目中额外多遇到了一个问题,就是开头提到跨域的问题。所以到这一步之后,还要多做一点工作。
-通过后台返回js代码,解决跨域
由于后台使用的域名为
yyyyy.aaa.123456.cn
,而主域用的是
123456.cn
,直接返回数据会引起跨域的问题。解决思路就是将iframe的domain设置为
123456.cn
。但是由于跨域,不能从父页读取iframe,也不能修改其内容,因此需要用另一种方法实现。
后台返回的数据头中的Content-Type有几种类型,作用是告诉前端这些数据的类型,常见的有
text/plain
text/javascript
text/html
text/json
application/json
...
其中text/html为html语言,前端会将它当做html来解析。
res.set({'Content-Type':'text/html'});
这时我们可以加一个script标记写自己的脚本,标记之外放后台传来的数据。
由于返回的这些代码是直接在iframe上跑起来的,为了解决这个跨域的问题,我们可以这样设计后台返回回来的内容:
<script>document.domain='123456.cn'</script>
{"code":"s_ok","var":[{"name":"jone","age":123}]}
这么操作之后,父页就可以无阻碍的访问子iframe,不受到浏览器的阻拦了。
顺带一提,jquery.form插件在后台返回这段脚本之后,也没有了本文开头说的那些问题。说明我当初遇到的那个问题确实是由于跨域的限制引起的,导致在ie8下jquery.form挂掉的原因。