File Uploads in Ember Data

Recently I’ve been working on an EmberJS application (SalesFlip - go register there now) which needs to allow file uploads. There are a couple of different approaches to this which I’ll discuss here, along with a new one which I would like to propose. If you are wondering what all the import and export statements are in the examples below, it’s because I’m using Ember App Kit. You should use it too!

HTML5 FileReader API

The HTML5 FileReader API is fine for really small file uploads outside of models. It seems to be the currently recommended solution on StackOverflow. Just create an UploadFile view and then bind the file attribute to the relevant attribute in the model. However, the ember-data adapter will then upload the file as a data URI via JSON. This proved pretty much useless for me. Anything over around 60k would fail.


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

`export default User`


UsersNewRoute = Ember.Route.extend
    model: ->

        save: ->

`export default UsersNewRoute`


view "upload-file" name="avatar" fileBinding="avatar"


UploadFile = Ember.TextField.extend
    tagName: 'input'
    attributeBindings: ['name']
    type: 'file'
    file: null
    change: (e) =>
        reader = new FileReader()
        reader.onload = (e) =>
            fileToUpload =
                @set 'file', fileToUpload

`export default UploadFile`

Custom FormData Upload View

With this approach (adapted from here) we define an upload button view which will upload the file on change. This would require some backend magic to work for new users (ones without an ID). Perhaps the file could be saved and then associated with the user when the user is saved.


view "upload-button" userBinding="user"


UploadButton = Ember.View.extend
    tagName: 'input'
    attributeBindings: ['type']
    type: 'file'
    originalText: 'Upload Finished Product'
    uploadingText: 'Busy Uploading...'

    newItemHandler: (data) ->
        @get('').push('item', data)

    preUpload: ->
        parent = @.$().closest('.fileupload-addbutton')
        upload = @get('uploadingText')

        @.$().css('cursor', 'default')
        @.$().attr('disabled', 'disabled')

    postUpload: ->
        parent = @.$().closest('.fileupload-addbutton')
        form = parent.closest('#fake_form_for_reset')[0]
        orig = @get('originalText')

        @.$().css('cursor', 'pointer')

    change: (e) ->
        formData = new FormData()
        formData.append('user_id', @get(''))
        formData.append('file', @.$().get(0).files[0])
            url: '/file_upload_handler/'
            type: 'POST'
            # Ajax events
            success: (data) =>
            error: =>
            # Form data
            data: formData
            # Options to tell jQuery not to process data or worry about content-type.
            cache: false
            contentType: false
            processData: false

`export default UploadButton`

Ember Data FormData Adapter

This is the approach that I have settled on. I created a FormData Adapter which is used by any models which require file upload. Using this approach, adding file uploads to another file is just a matter of creating a new adapter for it which extends the FileUpload adapter. Although I haven’t tried it, it should be easy to combine this approach with the HTML5 File Reader approach to generate image previews and validate file information before uploading.

First define a transform so that files are left untouched


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

  deserialize: (externalData) ->

`export default FileTransform`

Use the newly defined “file” attribute type


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

`export default User`

Create a FileUpload input. This is what assigns the file to the model


FileUploadView = Ember.View.extend
    content: null
    type: 'file'

    change: (event) ->
        if > 0
            @set('content', null)

`export default FileUploadView`


view "file-upload" contentBinding="avatar"

Create a FormDataAdapter. This is what handles uploading the data via FormData instead of the default JSON.


`import ApplicationAdapter from 'appkit/adapters/application'`

get = Ember.get

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

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

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


`export default FormDataAdapter`

Create a UserAdapter for the User model to use


`import FormDataAdapter from 'appkit/adapters/form_data'`

UserAdapter = FormDataAdapter.extend()

`export default UserAdapter`

Need some extra development power?

I don't have a lot of time at the moment, but I'm always interested to hear about new projects. I'm particularly interested in EmberJS/Elixir projects and refactoring Rails monoliths. Drop me a line at hello [at] mattbeedle [dot] name or using my contact form

Hire Me

comments powered by Disqus
comments powered by Disqus