【问题标题】:Backbone. Form with file upload, how to handle?骨干。表单带文件上传,如何处理?
【发布时间】:2011-11-17 17:27:09
【问题描述】:

我只想通过 REST API 组织工作流程。我有一个允许上传图片的表单(enctype="multipart/form-data")。如何通过主干处理此表单?请帮助我,我如何将它序列化为带有文件字段的 JSON。

谢谢。 生命力

【问题讨论】:

    标签: backbone.js


    【解决方案1】:

    如果您使用的是 HTML5,您可以使用文件 api 中的 readAsDataURL 方法来读取它并将其存储在您的模型中。

    这是我用来读取和存储的代码。

    var Image = Backbone.Model.extend({
    
        readFile: function(file) {
            var reader = new FileReader();
            // closure to capture the file information.
            reader.onload = (function(theFile,that) {
                return function(e) {
                    //set model
                    that.set({filename: theFile.name, data: e.target.result});
    
                };
            })(file,this);
    
            // Read in the image file as a data URL.
            reader.readAsDataURL(file);
        }   
    });
    

    【讨论】:

    • 我很想看到一个更成熟的例子,也许包括从视图中传递文件等。
    • 很好奇。为什么“那个”不必封装在闭包中?作为...函数(theFile,that){....})(file,this)。对 JS 闭包有点菜鸟。
    • 感谢您指出这一点,实际上也需要通过。虽然这个 sn-p 将适用于单个调用,但多个后续调用将返回到最新的调用。
    • que pasa c'est 'FileReader'
    【解决方案2】:

    你可以试试jquery.iframe.transport插件。

    【讨论】:

      【解决方案3】:

      恕我直言,您不能将文件序列化为 JSON。 如果您需要与文件一起发送一些数据,您可以使用 POST 方法将它们作为查询参数发送。

      www.example.com/upload?param1=value1&param2=value2
      

      【讨论】:

      • 我看到服务器在发布查询时会踩踏查询字符串参数。 IMO 这是不可靠的。
      • 恕我直言,当您使用越来越流行的XMLHttpRequest 发送文件时,这是唯一的选择。例如,请参阅 AJAX 上传 valums.com/ajax-upload,通过拖放和多个文件上传。 POST 中的查询参数与 HTML 标准或任何 RFC 不矛盾,是吗?此外,您的服务器通常在您的完全控制之下。
      • 我无法举出好的例子,我只知道我发现组合发布和查询字符串数据是不可靠的。很抱歉这么含糊... :-\
      • 您可以通过对数据进行base64编码来发送带有JSON的文件数据。
      【解决方案4】:

      没有通过 AJAX 提交文件的好方法。所以我写了一个函数来伪造它——它将一个秘密 iframe 插入到你的 DOM 中,它永远不可见,但仍然作为提交表单的目标,它安装了一个函数来响应你的调用,当文件已上传。

      让你的上传表单的提交按钮触发我写的这个函数。它使用 jQuery 是因为它既简单又好用,但原则上这不是绝对必要的:

      function backgroundUpload(form, container) {
          $(container).append('<iframe name="targetFrame" id="targetFrame" style="display: none; height: 0px; width:0px;" ></iframe>');
          $(form).attr('target', 'targetFrame');
      
          window.backgroundUploadComplete = function() {
              //clear your form:
              $(form).find(':file').val('');
              $(form).find(':text').val('');
      
              //do whatever you do to reload your screenful of data (I'm in Backbone.js, so:)
              window.Docs.fetch().complete( function() { populateDocs(); });
      
              //get rid of the target iframe
              $('#targetFrame').remove();
          };
          $(form).submit();
      }
      

      然后让执行文件解析和保存的表单处理程序返回字符串:

      <script>window.parent.backgroundUploadComplete();</script>
      

      您的表单可能如下所示:

      <form id="uploadForm" method="POST" action="/your/form/processor/url" enctype="multipart/form-data">
      <input type="file" name="file"/>
      <!-- and other fields as needed -->
      <input type="button" onClick="backgroundUpload(this.form, $('#documents'));" value="Upload" />
      </form>
      

      (#documents 是这个表单所在的 div。可能是任何 DOM 元素,它只需要一个 home。)

      【讨论】:

        【解决方案5】:
        events : {
                "click #uploadDocument" : "showUploadDocumentDetails",
                "change #documents" : "documentsSelected",
                "click .cancel-document" : "cancelDocument"
            },
            showUploadDocumentDetails : function(event) {
                $('#id-gen-form').attr("enctype","multipart/form-data");
                $('#id-gen-form').attr("action",this.model.url);
                var config = {
                        support : "image/jpg,image/png,image/bmp,image/jpeg,image/gif",     // Valid file formats
                        form: "id-gen-form",                    // Form ID
                        dragArea: "dragAndDropFiles",       // Upload Area ID
                        uploadUrl: this.model.url               // Server side upload url
                    };
        
                        initMultiUploader(config);
        
        
        
        
                if($('#uploadDocument').attr("checked")){
                    $('#id-documentCategory-div').show();
                    $('#id-documentName-div').show();
                    this.model.set({"uploadDocument": "YES"},{silent: true});
                }
                else{
                    $('#id-documentCategory-div').hide();
                    $('#id-documentName-div').hide();
                    this.model.set({"uploadDocument": "NO"},{silent: true});
                }
            },
            cancelDocument : function(event) {
                var targ;
                if (!event) event = window.event;
                if (event.target) targ = event.target;
                else if (event.srcElement) targ = event.srcElement;
                 $('#' + event.target.id).parent().parent().remove();
                 var documentDetails = this.model.get("documentDetails");
                 documentDetails = _.without(documentDetails, _(documentDetails).find(function(x) {return x.seqNum == event.target.id;}));
                 this.model.set({
                        "documentDetails" : documentDetails
                    }, {
                        silent : true
                    });
            },
            documentsSelected : function(event) {
                /*var targ;
                if (!event) event = window.event;
                if (event.target) targ = event.target;
                else if (event.srcElement) targ = event.srcElement;
                if (targ.nodeType == 3) // defeat Safari bug
                targ = targ.parentNode;
                        var files = event.target.files; // FileList object
        
                        var html = [];
                        var documentDetails = [];
                        $(".files").html(html.join(''));
                        var _this = this;
                        _this.model.set({
                            "documentDetails" : documentDetails
                        }, {
                            silent : true
                        });
                         var seqNum = 0;
                    for(var i=0; i< files.length; i++){
        
                        (function(file) {
                            html.push("<tr class='template-upload' style='font-size: 10px;'>");
                            html.push("<td class='name'><span>"+file.name+"</span></td>");
                            html.push("<td class='size'><span>"+file.size+" KB <br/>"+file.type+"</span></td>");
                            //html.push("<td><div class='progress progress-success progress-striped active'style='width: 100px;' role='progressbar' aria-valuemin='0' aria-valuemax='100' aria-valuenow='0'><div class='bar' style='width:0%;'></div></div></td>");
                            if(LNS.MyesqNG.isMimeTypeSupported(file.type)){
                                if(!LNS.MyesqNG.isFileSizeExceeded(file.size)){
                                    html.push("<td class='error' colspan='2'></td>");
                                    var reader = new FileReader();  
                                    console.log(reader);
                                        reader.onload = function(e) { 
                                              var targ;
                                            if (!e) e = window.event;
                                            if (e.target) targ = e.target;
                                            else if (e.srcElement) targ = e.srcElement;
                                            if (targ.nodeType == 3) // defeat Safari bug
                                            targ = targ.parentNode;
                                            console.log(e.target.result);
                                              var content = e.target.result;
                                              var document = new Object();
                                              document.name = file.name;
                                              document.type = file.type;
                                              document.content = content;
                                              document.seqNum = "document"+seqNum;
                                              seqNum++;
                                              documentDetails.push(document);
                                             // _this.model.set({"documentDetails" : documentDetails},{silent:true});
                                          };
                                        reader.readAsDataURL(file, "UTF-8");
                                }else{
                                     seqNum++;
                                    html.push("<td class='error' colspan='2'><span class='label label-important'>Error</span> Too long</td>");
                                }
                        }else{
                             seqNum++;
                            html.push("<td class='error' colspan='2'><span class='label label-important'>Error</span> Not suported</td>");
                        }
                         html.push("<td><a id='document"+i+"' class='btn btn-warning btn-mini cancel-document'>Cancel</a></td>");
                         html.push("</tr>");
                        })(files[i]);
                    }
                    $(".files").html(html.join(''));*/
        
              }
        
        
        LNS.MyesqNG.isMimeTypeSupported = function(mimeType){
            var mimeTypes = ['text/plain','application/zip','application/x-rar-compressed','application/pdf'];
            if($.inArray(mimeType.toLowerCase(), mimeTypes) == -1) {
                return false;
            }else{
                return true;
            }
        };
        
        LNS.MyesqNG.isFileSizeExceeded = function(fileSize) {
            var size = 2000000000000000000000000000;
            if(Number(fileSize) > Number(size)){
                return true;
            }else{
                return false;
            }
        };
        
        
        Use this, it can work but not more than 5 MB file
        

        【讨论】:

          【解决方案6】:

          根据 Anthony 的回答 (https://stackoverflow.com/a/10916733/2750451),我在咖啡脚本中编写了一个基于 defer 对象的解决方案。

           readFile: (file) =>
             def = $.Deferred()
             reader = new FileReader()
          
             reader.onload = (ev) =>
               def.resolve
                 name: file.name
                 binary: ev.target.result
          
             reader.onerror = ->
               def.reject()
          
             reader.readAsDataURL(file)
             def.promise()
          

          那么,你可以这样使用它

               readFile(file)
                 .done (parsedFile) =>
                   # do whatever you want with parsedFile
                   @model.set
                     image_name: parsedFile.name
                     image: parsedFile.binary
                   @model.save
                 .fail ->
                   console.log "readFile has failed"
          

          为了在服务器端处理它(因为它是 Base64 编码的),这里是 RoR 中的解决方案(基于https://stackoverflow.com/a/16310953/2750451

           my_object.image =      decode_image(params[:image])
           my_object.image.name = params[:image_name]
          
           def decode_image(encoded_file)
             require 'base64'
             image_data_string = split_base64(encoded_file)[:data]
             Base64.decode64(image_data_string)
           end
          
           def split_base64(uri)
             if uri.match(%r{^data:(.*?);(.*?),(.*)$})
               return {
                 type:      $1, # "image/png"
                 encoder:   $2, # "base64"
                 data:      $3, # data string
                 extension: $1.split('/')[1] # "png"
                 }
             end
           end
          

          【讨论】:

            【解决方案7】:

            详细说明 Anthony Chua 的回答。您需要将图像处理添加到Backbone.Form.editors

            Backbone.Form.editors.Image = Backbone.Form.editors.Text.extend({
                tagName: 'div',
            
                events: {
                    'change input[type=file]': 'uploadFile',
                    'click .remove': 'removeFile'
                },
            
                initialize: function(options) {
                    _.bindAll(this, 'filepickerSuccess', 'filepickerError', 'filepickerProgress');
                    Backbone.Form.editors.Text.prototype.initialize.call(this, options);
                    this.$input = $('<input type="hidden" name="'+this.key+'" />');
                    this.$uploadInput = $('<input type="file" name="'+this.key+'" accept="image/*" />');
                    this.$loader = $('<p class="upload-status"><span class="loader"></span> please wait..</p>');
                    this.$error = $('<p class="upload-error error">Error</p>');
                    this.$list = $('<ul class="file-list">');
                },
            
                // return an array of file dicts
                getValue: function() {
                    var val = this.$input.val();
                    return (val ? JSON.parse(val) : [])[0].value;
                },
            
                setValue: function(value) {
                    var str, files = value;
                    if (_(value).isObject()) {
                        str = JSON.stringify(value);
                    } else {
                        files = value ? JSON.parse(value) : [];
                    }
                    this.$input.val(str);
                    this.updateList(files);
                },
            
                render: function(options) {
                    Backbone.Form.editors.Text.prototype.render.apply(this, arguments);
            
                    this.$el.append(this.$input);
                    this.$el.append(this.$uploadInput);
                    this.$el.append(this.$loader.hide());
                    this.$el.append(this.$error.hide());
                    this.$el.append(this.$list);
                    return this;
                },
            
                uploadFile: function() {
                    var fileInput = this.$uploadInput.get(0);
                    var fileObj = fileInput.files[0]
                    var reader = new FileReader();
                    var that = this;
                    // closure to capture the file information.
                    reader.onload = function(file){
                        var dataURL = reader.result;
                        var fileValue = {
                            value: dataURL,
                            name: fileObj.name,
                            content_type: fileObj.type
                        }
                        that.filepickerSuccess(fileValue);
                    };
            
                    // Read in the image file as a data URL.
                    reader.readAsDataURL(fileObj);
                },
            
                filepickerSuccess: function(files) {
                    console.log('File (raw)', files);
                    this.$loader.hide();
                    this.$error.hide();
                    this.$uploadInput.val('');
            
                    // when uploading one file, it returns just an object
                    if (!_(files).isArray()) { files = [files]; }
            
                    // turn response array into a flatter array of objects
                    var newFiles = _(files).map(function(file, index) {
                        return {
                            url: "#",
                            value: file.value,
                            filename: file.name,
                            key: index,
                            content_type: file.type
                        };
                    });
                    console.log('File (processed)', newFiles);
                    this.setValue(newFiles);
                },
            
                filepickerError: function(msg) {
                    console.debug('File error', msg);
                    this.$loader.hide();
                    this.$error.show();
                },
            
                filepickerProgress: function(percent) {
                    this.$loader.show();
                    this.$error.hide();
                },
            
                updateList: function(files) {
                    // this code is currently duplicated as a handlebar helper (I wanted to let this
                    // backbone-forms field stand on its own)
            
                    this.$list.empty();
                    _(files).each(function(file) {
                        var a = $('<a>', {
                            target: '_blank',
                            href: file.url,
                            text: file.filename + ' (' + file.content_type + ') '
                        });
                        var li = $('<li>').append(a);
                        li.append(a, ' ', $('<a href="#" class="remove"><i class="icon-remove"></i></a>').data('key', file.key));
                        this.$list.append(li);
                    }, this);
            
                    this.$list[files.length ? 'show' : 'hide']();
                },
            
                removeFile: function(ev) {
                    if (ev) ev.preventDefault();
                    var files = this.getValue();
                    this.setValue([]);
                }
            
            });
            

            你可以使用上面的代码如下

            var ImgSlot = Backbone.Model.extend({
              defaults: {
              },
              schema: {
                imageField: {
                  type: "Image"
                }
              }
            })
            

            使用以下方式渲染表单:

            this.form = new Backbone.Form({
              model: new ImgSlot(),
              submitButton: "Example Image file input handling"
            }).render();
            
            var errors = that.form.commit({validate: true})
            if(errors != null)
              {
                return false;
              }
            var data = that.form.model.attributes;
            console.debug(data.imageField); // Will return base64 of image selected.
            

            【讨论】:

              【解决方案8】:

              在 HTML5(包括 IE9)之前无法通过 AJAX 提交文件。

              您需要通过 ajax 同步模型属性,然后发送另一个带有该文件的 html 表单帖子,然后以某种方式同步它们。一般情况下,通过ajax保存模型,取回一个id,将id添加到其他表单,然后发布文件。

              “jquery.form”中的 jQuery 插件可以帮助您构建一个表单来发布文件。它管理“隐藏的 iframe 技巧”,使其在最终用户看来就像 AJAX。

              您可能只需要花一些时间在谷歌上搜索“隐藏的 iframe 技巧”...

              【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2012-06-11
              • 2012-11-02
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-11-14
              相关资源
              最近更新 更多