Backbone. Form with file upload, how to handle?

2019-02-01 01:26发布

问题:

I want to organize the workflow only through the REST API. I have a form that allows to upload image (enctype="multipart/form-data"). How do I handle this form via backbone? Help me please, how I can to serialize it into JSON with a file field.

Thanks. Vitaliy

回答1:

If you are using HTML5, you can use the readAsDataURL method from the file api to read and store it on your models.

Here's the code i use to read and store.

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);
    }   
});


回答2:

You could try the jquery.iframe.transport plugin.



回答3:

IMHO, you cannot serialize a file into JSON. If you need to send some data along with the file you can send them as query params with POST method.

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


回答4:

There's no good way to submit a file via AJAX. So I wrote a function to fake it--it inserts a secret iframe into your DOM that is never visible but still works as a target to submit your form on, and it installs a function for your response to call that cleans house when the file is uploaded.

Have your upload form's submit button fire this function I wrote. It uses jQuery because it's easy and nice, but in principle that shouldn't be strictly necessary:

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();
}

Then have your form handler that does your file parsing and saving return the string:

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

Your form can look like:

<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 is the div that this form lives in. Could probably be any DOM element, it just needs a 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:

Based on Anthony answer (https://stackoverflow.com/a/10916733/2750451), I've written a solution in coffeescript based on a defer object.

 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()

Then, you could use it this way

     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"

To handle it on the server side (because it's Base64 encoded), here the solution in RoR (based on 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:

It is not possible to submit a file over AJAX before HTML5 (including IE9).

You need to sync the model attributes over ajax, and then send another html form post with the file, and then sync them up somehow. Generally, save the model over ajax, get an id back, add the id to the other form, and then post the file.

The jQuery plug in "jquery.form" may help you to construct a form to post the file. It manages the "hidden iframe trick" so that it looks like AJAX to the end user.

You might just need to spend some time googling "hidden iframe trick" ...



标签: backbone.js