23

Can someone provide code examples or documentation on implementing a form with a file field using EmberJS and Ember Data?

I'm familiar with Ember Data already but I'm not sure how to implement file-uploads correctly.

sandstrom
  • 14,554
  • 7
  • 65
  • 62
Rafael Vega
  • 4,575
  • 4
  • 32
  • 50
  • if you have to support anything lower than IE10, as of right now, all of the answers below will not work. Prior to IE10, you'd have to create a form and a hidden iframe for it to target and upload your file separate from the rest of your data if you wanted to do anything asynchronous. FileReader and FormData are not supported in IE9. Plainly put, unless you're really restricting your usership to "modern" browsers, these answers are inadequate. – Ben Lesh Feb 13 '14 at 19:39

4 Answers4

16

Here is part of an ember-data adapter I wrote to do file uploads (same server -not cross domain)

DS.DjangoRESTAdapter = DS.RESTAdapter.extend({
        bulkCommit: false,

        createRecord: function(store, type, record) {
            var root = this.rootForType(type), json = {};

            var data = new FormData();
            data.append('username', record.get('username'));
            data.append('attachment', record.get('attachment'));

            this.django_file_ajax('http://localhost:8000/people/new/', "POST", {
                data: data,
                context: this,
                success: function(pre_json) {
                    json[root] = pre_json;
                    this.didCreateRecord(store, type, record, json);
                }
            });
        },

        django_file_ajax: function(url, type, hash) {
            hash.url = url;
            hash.type = type;
            hash.contentType = false;
            hash.processData = false;
            hash.context = this;

            jQuery.ajax(hash);
        }

    });

})();

It's not IE8 friendly as is because I'm using the "FormData" helper to do multipart file upload but it's a good proof of concept.

Here is the ember-data model to go w/ the above adapter

PersonApp.Person = DS.Model.extend({
  id: DS.attr('number'),
  username: DS.attr('string'),
  attachment: DS.attr('string')
});

Here is the handlebars template

<script type="text/x-handlebars" data-template-name="person">
{{view PersonApp.UploadFileView name="logo_image" contentBinding="content"}}
</script>

Here is the custom ember view

PersonApp.PersonView = Ember.View.extend({
  templateName: 'person'
});

PersonApp.UploadFileView = Ember.TextField.extend({
    type: 'file',
    attributeBindings: ['name'],
    change: function(evt) {
      var self = this;
      var input = evt.target;
      if (input.files && input.files[0]) {
        var reader = new FileReader();
        var that = this;
        reader.onload = function(e) {
          var fileToUpload = e.srcElement.result;
          var person = PersonApp.Person.createRecord({ username: 'heyo', attachment: fileToUpload });
          self.get('controller.target').get('store').commit();
        }
        reader.readAsDataURL(input.files[0]);
      }
    }
});

If you want to see a full blown spike with this in action checkout a multi file upload example I did recently.

https://github.com/toranb/ember-file-upload

Toran Billups
  • 27,111
  • 40
  • 155
  • 268
  • PersonApp.UploadFielView is a solution but seems (for me) to linked with the controller. I'm not EmberJs expert, but how to improve this solution to allow 2 input Files on the page (logo_image & detail_image) by example ? – fvisticot Dec 17 '12 at 17:49
  • Great question -the above view PersonApp.UploadFileView would need to be modified a bit so you don't commit() onload directly. Instead you could have a submit button that does the input validation and finally invokes commit() after the user selects both logo and detail image (might be able to reuse the custom view above with an additional property like a dynamic id / name) – Toran Billups Dec 17 '12 at 23:58
  • Seems a good approach, can you please detail the answer with a sample code ? would be very useful for me (I'm really not an ember.js expert yet...) – fvisticot Dec 18 '12 at 08:26
  • This will not work in anything lower than IE10, because of FileReader and FormData. – Ben Lesh Feb 13 '14 at 19:31
7

Look at the links below. The first link or blog post contains a link to a working jsfiddle that handles upload with emberjs. Note I didn't write the blog or create the fiddle. But it should solve your issue.

http://chrismeyers.org/2012/06/12/ember-js-handlebars-view-content-inheritance-image-upload-preview-view-object-binding/ - dead link

http://devblog.hedtek.com/2012/04/brief-foray-into-html5-file-apis.html

EdwinW
  • 1,007
  • 2
  • 13
  • 32
brg
  • 3,915
  • 8
  • 37
  • 66
  • 3
    I did not down vote, but I want to say, yea, lots ppl just down vote for nothing, absolutely NOTHING! I guess they just vote down because they have too many points... – weia design Oct 30 '13 at 14:31
  • Thanks @Adam, that is probably them abusing their power. – brg Nov 01 '13 at 11:54
  • As of this comment, both of these linked solutions will not work in anything below IE10. They're using FileReader. – Ben Lesh Feb 13 '14 at 19:30
7

It's fairly simple, the general steps are:

  1. Hook up an input within Ember.
  2. Read the data from the local file specified in the input element.
  3. Encoded the data as base64.
  4. Set the value of your ember-data model to the base64 string.
  5. On the server, decode the base64 string and voila, your binary file data is on the server.

It should be noted though that base64 encoding large files has performance issues, but for smaller images or text it won't be a problem.


You could also send the file 'outside' of Ember Data, and push the response (such as a JSON payload representing a model) into the store via pushPayload. If so, FormData or other methods in XHR2 can be used.

Read more about client-side manipulation of files here: http://www.html5rocks.com/en/tutorials/file/dndfiles/

Read more about XHR2 and FormData for file uploads here: http://www.html5rocks.com/en/tutorials/file/xhr2/

sandstrom
  • 14,554
  • 7
  • 65
  • 62
2

I tried a few different solutions and ended up writing a FormData adapter and a File transform. Then any model that needs to upload file data can just use the FormDataAdapter and define the file attributes as type "file":

app/transforms/file.coffee

FileTransform = DS.Transform.extend
  serialize: (jsonData) ->
    jsonData

  deserialize: (externalData) ->
    externalData

app/models/user.coffee

User = DS.Model.extend
    avatar: DS.attr('file')

app/adapters/form_data.coffee

get = Ember.get;

App.FormDataAdapter = ApplicationAdapter.extend
  ajaxOptions: (url, type, hash) ->
    hash = hash || {}
    hash.url = url
    hash.type = type
    hash.dataType = 'json'
    hash.context = @

    if hash.data and type != 'GET' and type != 'DELETE'
      hash.processData = false
      hash.contentType = false
      fd = new FormData()
      root = Object.keys(hash.data)[0]
      for key in Object.keys(hash.data[root])
        if hash.data[root][key]
          fd.append("#{root}[#{key}]", hash.data[root][key])

      hash.data = fd

    headers = get(@, 'headers')
    if headers != undefined
      hash.beforeSend = (xhr) ->
        for key in Ember.keys(headers)
          xhr.setRequestHeader(key, headers[key])

    hash

app/adapters/user.coffee

UserAdapter = FormDataAdapter.extend()

Sorry about the CoffeeScript, but it's pasted from this blog post: http://blog.mattbeedle.name/posts/file-uploads-in-ember-data/. You can read a more detailed description there. This solution should probably be combined with a HTML5 FileReader input to enable image previews and client side file type validation.

Matt Beedle
  • 171
  • 1
  • 9