事起缘由:

最近被表单跨域POST提交挤爆了头,特别是要上传大文件时,JSONP就不管用了,FormData的ajax提交虽能提交大文件可惜还是存在跨域报错,各种折磨,坑死姐姐了!

奈何后台不会为此专门设置CORS,早有耳闻复杂的iframe可以实现无刷新表单提交,只是就不想去碰。闻说ajaxForm也有无刷新表单提交的功效,于是前往尝试,奈何该报跨域错误的还是报了,并没啥卵用(。。。也有可能是我姿势不对→→ →→ →_→。。。)。

timg.gif-43kB

好吧,我决定一次性了解隐藏iframe实现的跨域表单提交原理,顺便封装个公用组件,方便以后快速使用。

原理:

既然已经明确跨域的POST请求不能使用ajax异步提交,那我们只好放弃了ajax这条路。

幸好天无绝人之路,跨域的POST请求是可以使用form表单直接提交,但是这样页面也会跟着刷新,并且返回的数据竟然在新窗口打开。这就尴尬了。所以我们想要做的,就是怎样让form表单既能按浏览器默认方式提交,但是却又不会刷新整个页面。

怎样让表单提交无刷新?江湖流传的做法是:把form提交到本域的一个空iframe中,让服务器处理完后跳转到同域下的空白页里,并给这个空白页添加返回后的数据。甚好,这样确实无刷新提交了。

只是,怎么让 API接口页A 与 本域的空白页B 之间的请求不跨域呢?

如果A和B属于相同大域,设置两边的document.domain为相同的父域名就OK了;如果是完全不同的两个域,也有许多现成的解决方案,例如经典的window.name。当然也可以使用几乎所有现代浏览器(IE8+)都支持用window.postMessage实现不同iFrame的数据通讯。

在这里,我先做A、B属于同一域的情况。其他类似的自己举一反三吧~~

那我们的返回数据要怎么取回来呢?

当数据接口A 在B空白页面加载数据请求时,会将返回的数据存放在url中,通过解析url的query就能取回返回的数据啦~

通过监听隐藏iframe的onload事件,就可以触发父页面的callback函数啦~

原理如下图所示:

此处输入图片的描述

备注:图片来源http://www.jianshu.com/p/4773501f1adf

组件代码:

/* 
 * 使用隐藏的iframe发送表单提交
 * options参数说明:
        url: api接口地址
        blankUrl:  当前域下blank.html页面的url地址
        form:  form表单DOM
        type:  请求method
        formId:  form表单ID
        iframeId:  隐藏iframe 的id
        success:  表单提交成功后的回调函数,参数为返回的data数据
        error:  表单提交失败后的回调函数
 */
var SubmitByIframe = function(options) {
    this.options = options;
    this.url = options.url;
    this.blankUrl = options.blankUrl || 'blank.html';
    this.$form = $(options.form);
    this.type = options.type || 'POST';
    this.formId = options.formId || options.form;
    this.iframeId = options.iframeId || 'hidden_iframe';
    this.contentType = options.contentType || 'multipart/form-data';
    this.success = options.success;
    this.error = options.error;
}
SubmitByIframe.prototype = {
    init: function() {
        var self = this;
        // 创建并插入隐藏的iframe
        var iframeStr = '<iframe id="' + self.iframeId + '" name="' + self.iframeId + '" src="' + self.blankUrl + '" style="display:none"></iframe>';
        $('body').append(iframeStr);

        // form表单属性初始化
        self.$form.attr({
            target: self.iframeId,
            enctype: self.contentType,
            id: self.formId,
            method: self.type
        });
        var hiddenUrlInput = '<input type="hidden" name="url"/>';
        self.$form.append(hiddenUrlInput);
    },

    getUrlValue: function(s){
        if (s.search(/#/)>0){
            s = s.slice(0,s.search(/#/));
        }
        var r = {};
        if (s.search(/\?/)<0){
            return r;
        }
        var p = s.slice(s.search(/\?/)+1).split('&');
        for(var i=0,j=p.length; i<j; i++){
            var tmp = p[i].split('=');
            r[tmp[0]] = tmp[1];
        }
        return r;
    },

    bindSubmit: function() {
        var self = this;
        $(self.iframeId).unbind('load').unbind('errorupdate');
        $('body').on('load', self.iframeId, function() {
            try{
                var res = self.getUrlValue($(self.iframeId).prop('contentWindow').location.href);
                if(res) {
                    self.success && self.success(res);
                } else {
                    self.error && self.error();
                }
            }catch(err){
                self.error && self.error();
            }
        })
        .on('errorupdate', self.iframeId, function() {
            self.error && self.error();
        });
    },

    submit: function() {
        this.bindSubmit();
        this.$form.attr('action', this.url).submit();
    },

    render: function() {
        this.init();
    }
};

SubmitByIframe.create = function(options) {
    var formSubmit = new SubmitByIframe(options);
    formSubmit.render();
    return formSubmit;
};

blank.html页面的代码:

<html>
<head>
    <title>B页面</title>
</head>
<body>
    <div>B页面</div>
    <script>        
        try{
            //这里将document.domain设置成api接口的domain
            document.domain = "example.com";
        }
    </script>
</body>
</html>

方法调用:

<form id='form'></form>
var apiUrl = 'aa.example.com/api/aaa';
var submitForm = SubmitByIframe.create({
    url: apiUrl,
    blankUrl: 'blank.html',
    form: $('#form'),
    formId: '#form',
    type: 'POST',
    iframeId: '#hidden_iframe',
    contentType: 'multipart/form-data',
    success: function(res) {
        console.log(res);
    },
    error: function() {
        alert('error');
    }
});
submitForm.submit();

结果简直完美,可以愉快地使用POST请求,还能上传特大的文件,而且页面无刷新。

撒花,撒花~~~

此处输入图片的描述

本文作者:子匠_Zijor,转载请注明出处:http://www.dengzhr.com/js/1346