Series: Drag-n-Drop Image Uploads

Drag and Drop (with file uploads)

Published on Jun 28, 2015

In this episode, we create a drag and drop file uploader by combining the file upload machinery from episodes 48 and 49 with some browser events from episode 50.

Along the way we'll learn about the dragOver and drop events and get rid of a pesky observer.


Links

Code

//components/file-upload/component.js
import Ember from 'ember';

export default Ember.Component.extend({  
  tagName: 'div',
  classNames: ['uploader', 'dropzone'],

  dragOver(event) {
    event.preventDefault()
  },

  drop(event) {
    event.preventDefault()

    var file = event.dataTransfer.files[0]
    this.sendAction('fileInputChanged', file)
  }
});
//post/edit-fields/template.hbs
<p>{{file-upload fileInputChanged="receiveFile"}}<img id="preview-image" src="{{model.thumbnailImage.thumbnail_image.url}}" class="small-image" />
//post/edit-fields/component.js
import Ember from 'ember';

export default Ember.Component.extend({
  previewImage(file) {
    var reader = new FileReader();

    reader.onload = function (e) {
      $('#preview-image').attr('src', e.target.result);
    }

    reader.readAsDataURL(file);
  },

  actions: {
    receiveFile(file) {
      this.previewImage(file)
      this.set('model.temporaryThumbnailImage', file)
    }
  })
});
//save action in post/edit/controller.js
save: function(){
  var file = this.get('model.temporaryThumbnailImage')
  this.set('model.thumbnailImage', file);
  this.get('model').save().then(()=>{
    this.transitionToRoute('post.show', this.get('model'));
  });
},
//stylesheets/dropzone.css.scss
.dropzone.uploader {
  width: 300px;
  height: 200px;
  background-color: grey;
}

Transcript

We learned in Episode 50 how you can use browser events to trigger methods on Ember components. In this episode, we’ll combine the file uploader from Episodes 48 and 49 with several browser events to create a drag and drop file uploader.

First, we’ll create our file uploader component and set it up as a dropzone. It’s a component with the uploader and dropzone classes. We’ll give it some width and height with css. Then, we’ll put it in the template and place the non drag and drop file-upload.

This is what our dropzone looks like. Of course, just dropping something in there doesn’t work yet. We can try dragging in this image of a cute duckling, but it just navigates to a new page with that image, not the behavior we want. This is the default behavior for drop, and our first mission here will be to stop this default behavior.

Step one is defining a dragOver method, and then calling event.preventDefault. Calling this allows the drop event to occur. It’s a weird API, but it’s JavaScript. Anyways, now that the drop event can occur, we’ll define the drop handler on the component, and call preventDefault on that as well.

Now we can safely drop the image there. And dropping the image does nothing, which is a minor victory in that it doesn’t do the wrong thing anymore, but that’s a far cry from doing the right thing. Let’s make something happen. Specifically, let’s make the image preview work.

We’ll grab the file from the event, and then send the action fileInputChanged with the file. The component declaration will send that to receiveFile action, which we’ll define in our editFields component. This is a great opportunity to get rid of our observer. We used it because we needed a way for Ember to recognize the file had changed, but now since we fire an action when the new file comes in, we can just call image preview in that action. We can also get rid of the code where we’re grabbing the file since it’s passed in.

Now when we drag in our picture, we’ll see that it does preview correctly. Next, we want it actually saving. We need a way to pass information between the component where the file input changed action is, and the controllers where saved action takes place. Previously, the truth was stored on the DOM, however we’re no longer doing that. That’s not a great idea.

One option is to trigger a separate action, which is then caught by each controller that uses it, which then sets a property on that controller, which is then found by the save and put in the thumbnail image. However, that’s bulky and involves a lot of codes strung together.

Another option is to set a temporary field on the model within the component, and then set that to a more permanent field in the save action. I prefer the second option, even if it doesn’t fit strictly within the data down, actions up paradigm.

So now, we’ll grab our duck, put it in there, and save. And it’s working again, and in a more Embery way. Of course, it still doesn’t look great. In an upcoming episode, we’ll show how to use the drag-enter and drag-leave events in order to make this interactive and beautiful. I look forward to seeing you then.

Drag-n-Drop Image Uploads

Subscribe to our mailing list