23
votes

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.

4
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

4 Answers

16
votes

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

7
votes

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

7
votes

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/

2
votes

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.