骨干。表单有文件上传,如何处理?

时间:2011-11-17 17:27:09

标签: backbone.js

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

感谢。 维塔利

8 个答案:

答案 0 :(得分:27)

如果您使用的是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);
    }   
});

答案 1 :(得分:3)

您可以尝试jquery.iframe.transport插件。

答案 2 :(得分:1)

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

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

答案 3 :(得分:1)

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

让你上传表单的提交按钮激活我写的这个功能。它使用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元素,它只需要一个家。)

答案 4 :(得分:1)

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

答案 5 :(得分:0)

根据Anthony回答(https://stackoverflow.com/a/10916733/2750451),我根据推迟的对象在coffeescript中编写了一个解决方案。

 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

答案 6 :(得分:0)

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.

答案 7 :(得分:-14)

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

您需要通过ajax同步模型属性,然后使用该文件发送另一个html表单帖子,然后以某种方式同步它们。通常,将模型保存在ajax上,获取id,将id添加到其他表单,然后发布文件。

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

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