js implementation of iframe + form asynchronous file submission scheme analysis

There are a lot of mature upload components in the world, the famous ones are swfUpload component and jquery webupload.


The swfupload component was also used in its own business at the beginning. Its advantages include progress display, asynchronous callback, and controllable uploading processes. There are many advantages, you can refer to the article  http://www.cnblogs.com/youring2/archive/2012/07/13/2590010.html  .


However, everyone will encounter a variety of different product requirements in business, and will also encounter various problems in the use of components. The leader has also been complaining about the various disadvantages of the swfupload component. Taking advantage of solving a bug in uploading files, he wrote a solution that supports asynchronous uploading files.

       

First of all, file upload cannot directly transmit data like jquery's get and post. As for why, there are a lot of questions on the Internet about how to use js to get the value of the input type as file. Here is a brief explanation: Due to the security restrictions of browsers, ie8 and above browsers will not display the absolute path of the file, and the value of the file cannot be obtained by obtaining the value method. Even if you get it, it is just an absolute path of the local machine. It is useless for how the server handles it after you submit it. The server will only process the file data stream you submitted. Now you understand. 

So you need to use the form submission method, and set the enctype="multipart/form-data" of the form. Indicates that the data will be transmitted in binary encoding format, and only this scheme can be used for value transmission of file type.


The submission of the form will cause the location to jump, and the interactive processing of the current page will not be possible. So we use the iframe + form solution to implement the asynchronous submission solution. The specific implementation is to point the target of the form form to an iframe page with an empty src, and the action of the form to point to the address where you actually want to process the image upload logic.

     

<iframe width="0" height="0" frameborder="0" style="display:none" scrolling="no" name="uploadframe" id="uploadframe"></iframe>
<script id="tpl-add" type="text/template">
<div class="formBox">
    <form target="uploadframe" style="display:block" id="uploadform" method="post" enctype="multipart/form-data" action="/fe/uploadApk?asyncjs=1">
        <div id="cert-main" class="c-form c-form-dialog f-pzr" style="width:auto">
            <dl class="f-cb">
                <dt>渠道包名称:</dt>
                <dd><input id="appName" class="c-input required"  type="text" name="appname" minlength="10" maxlength="20" placeholder="长度应为2-20个字符" />
                    <label class="error" for="appName" generated="true" style="display: none;"></label>
                </dd>
            </dl>
            <dl class="f-cb">
                <dt>上传渠道包 </dt>
                <dd style="position:relative">
                    <div id="uploadDiv">
                        <input type="hidden" value="<%$Q%>" name="Q"/>
                        <input type="hidden" value="<%$T%>" name="T"/>
                        <a class="doupload btngray" href="javascript:;">选择文件<input type="file" name="uploadfile" class="uploadfile" value=""/></a>
                        <span id="apkLoding" class="apk-loading" style="display:none">文件上传中</span>
                    </div>
                    <div>
                        <input id="androidApk" class="upload apk ignore"  name="apk" type="hidden"/>
                        <input id="androidAppHash" name="apkhash" type="hidden"/>
                        <p style="width:250px;word-wrap:break-word;word-break:break-all"><span id="apkSuccess" class="apk-success" style="display:none"><span></span>上传成功!</span></p>
                        <label id="apkError" class="error" style="display: none;padding:0;">请上传APK文件</label>
                    </div>
                </dd>
            </dl>
            <dl class="f-cb">
                <dt> </dt>
                <dd>
                <a class="submit c-btn1 uploadsubmit"  href="javascript:void(0)">保存</a>
                <a class="cancel c-btn2" href="javascript:void(0)">取消</a>
                </dd>
            </dl>
        </div>
    </form>
</div>
</script>


Due to the jquery validate component used by the front-end form validation, the previous dom structure caused a problem. That is, the outer layer of the form used to upload files has a form for submitting general data, so when the inner form is submitted, the validation event of the jquery validate component is triggered  . As a result, the form for uploading files cannot be submitted, and it also causes errors in the verification component. Make a record of this pit point for future encounters.


There are two processing schemes for the front-end js and the server. One is to assign the value of the parent page through the traditional window. attribute. This scheme has better compatibility and supports both ie6 and 7. The other is to use the postmessage method of html5 to transmit parent window information, which is not supported by ie7 and below versions, and low-version browsers such as Firefox.


The web-side js handlers of the two schemes are:


/*
 *第一种通过子页面为父窗口window属性赋值方法
 *autor:[email protected]
 *此父页面与上传的iframe跨域通信
 *ie 只能捕获window第一次onload时的iframe onload事件
 *故针对ie需要侦听iframe.onreadystatechange事件,当等于complate时代表加载完成
 *同样现代浏览器又只能侦听onload事件,故使用onload事件去捕获
 *此方案兼容性较好,可以考虑使用
 */
var iframeupload = document.getElementById('uploadframe');
if(!document.all){
    iframeupload.οnlοad=function(e){
        try{
            var result = window.uploadresult;
            doupload(result);
        }catch(e){}
    }
}else{
    iframeupload.onreadystatechange=function(e){
        if (this.readyState && this.readyState != 'complete') return;
        else{
            try{
                var result = window.uploadresult;
                doupload(result);
            }catch(e){}
        }
    }
}


/*
 *第二种通过捕获子页面h5 postmessage到父页面的message
 *autor:[email protected]
 *此父页面与上传的iframe跨域通信
 *此方案只针对支持h5 postmessge的浏览器ie7及以下浏览器无法工作
 *需要通过原生window.addEventListener添加事件监听方法去监听message事件
 *因为jquery的on和window.on都无法捕获到
 *捕获的对象为固定的是data
 */
if(window.addEventListener){
    window.addEventListener('message', function(e){
       console.log(e.data);
       doupload(e.data);
    },false)
}else if(window.attachEvent){
    window.attachEvent('onmessage', function(e){
       doupload(e.data);
    })
}else{
    window.onmessage = function(e){
       doupload(e.data);
    }
}


        The server-side php handler is:


public function actionUploadApk() {
    $from = $this->_request->getParam('asyncjs');
    if($from){
        $data = Array();
        $data['error'] = '0';
        $data['msg'] = ''; 
        $data['url'] = 'http://***.cn';
        
        /*
        *第一种通过子页面为父窗口window属性赋值方法
        *ie下不能使用onload和onreadystatechange来返回值,因为web端无法捕获
        *echo '<script>window.οnlοad=function(){window.top.imgurl="http://up1---512.apk"}</script>';
        *echo '<script>window.onreadystatechange=function(){window.top.imgurl="http://up1---01512.apk"}</script>';
        *ie下直接使用top.--- 或 parent.---指明传值
        *使用json_encode方法编码多此一举,web端还需要进行parseJSON处理
        *echo '<script>window.top.uploadresult = '.json_encode('{"code":200,"msg":"","data":{"app_url":"http://u----201512.apk","app_hash":""}}').'</script>';
        *所以最终的window属性方案为下面,直接赋值为对象
        */
        
        echo '<script>window.top.uploadresult = {"code":200,"msg":"","data":{"app_url":"http://u**01512.apk","app_hash":""}}</script>';
           
        
        /*
        *第二种使用h5的postmessage方法向父窗口传递信息
        *注意第一个参数一般为string类型,当然也可以为object
        *第二个参数为接收message的domain,设置为*的话适用于广播,出于安全考虑只限测试时候使用
        *使用jsonencode,web端可以直接解析json对象
        *postMessage(data,origin)方法接受两个参数
         *1.data:要传递的数据,html5规范中提到该参数可以是JavaScript的任意基本类型或可复制的对象,然而并不是所有浏览器都做到了这点儿,部分浏览器只能处理字符串参数,所以我们在传递参数的时候需要使用JSON.stringify()方法对对象参数序列化,在低版本IE中引用json2.js可以实现类似效果。
        *2.origin:字符串参数,指明目标窗口的源,协议+主机+端口号[+URL],URL会被忽略,所以可以不写,这个参数是为了安全考虑,postMessage()方法只会将message传递给指定窗口,当然如果愿意也可>以建参数设置为"*",这样可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。
        *echo '<script>window.οnlοad=function(){window.parent.postMessage('.json_encode($data).',"*")}</script>';
        *echo '<script>window.οnlοad=function(){window.parent.postMessage({"errno":0,"msg":"","data":{"app_url":"http://****201512.apk","app_hash":""}},"*")}</script>';
        *此方案需要对json对象进行json_encode处理,方便web端处理
        */
        
        echo '<script>window.parent.postMessage('.json_encode('{"code":200,"msg":"","data":{"app_url":"http://u**01512.apk","app_hash":""}}').',"http://xi**0.cn")</script>';
        
    }
}


There are many knowledge points about iframe value passing and browser compatibility, all of which are described in the comments.

In addition to the above two solutions, I also thought of using websocket, or localstorage. After practice, make a record.

     

      

Guess you like

Origin blog.csdn.net/xiaoxigua666/article/details/50538518