Wildhoney / EmberDroplet

Ember.js HTML5 file uploading with drag & drop and image/file preview.
http://ember-droplet.herokuapp.com/
MIT License
197 stars 31 forks source link

Override uploadAllFiles? #31

Open theworkerant opened 10 years ago

theworkerant commented 10 years ago

How can I hook into the file upload process. Can I override an action on the controller to manage what is done with the response? Perhaps a callback on the controller didUploadAllFiles?

reblevins commented 10 years ago

@theworkerant I think what you will need to do is something like this. (I'm really new to this, so forgive me if I'm wrong.)

This will go in the controller where you are using the DropletController mixin:

uploadedSomeFiles: function uploadedSomeFiles() {
    // this gets fired once all the files have uploaded
    var files = this.get('uploadedFiles');
    files.forEach(function(file) {
        // do something here with the files
    });
}.observes('files.length', 'files.@each.uploaded')

However, in my case, the file object that is returned wasn't the object returned from my API. Perhaps I was doing something wrong. What I wanted to do was render uploaded images back to the view once they had uploaded and been created by my Rails API. In the end, what I had to do was actually modify the mixin code itself (not best practice, I know) like this:

ember-droplet-mixin.js (starting at app. line 178):

request.send(formData);

request.onload = function() {
    // Here I load my model
    var media = App.Media.find();

    // as you probably know, within onload you have access to responseText which in my case was
    // the JSON object that my Rails API was rendering
    $.each($.parseJSON(request.responseText), function(idx, obj) {
        media.pushObject(obj);
    });
}

return deferred.promise();

Perhaps there's a better way to do this, but you'll need to inspect the object returned by 'uploadedFiles' yourself to see if that is what you need or if you perhaps need the responseText.

g-cassie commented 10 years ago

This is what we do in our app:

var FilesUploadController = Ember.Controller.extend(DropletController, {
    uploadAllFiles: function() {
      var self = this;
      $.when(self._super()).then(function(data){
        // do whatever you want here
        // data will contain the json response from your server
      });
    }
});
reblevins commented 10 years ago

@g-cassie I modified my app to use this code, but it never gets fired for some reason. Here's the full code:

App.MediaController = Ember.Controller.extend(DropletController, {
    dropletUrl: 'http://localhost:3000/images',

    useArray: true,

    uploadAllFiles: function() {
        // this never fires
        console.log('Fired!');
        var self = this;
        $.when(self._super()).then(function(data){
            var media = App.Media.find();

            $.each($.parseJSON(data), function(idx, obj) {
                media.pushObject(obj);
                console.log(data);
            });

        });
    },

    actions: {
        deleteImage: function (media) {
            media.deleteRecord().fail( function(e) {
                App.displayError(e);
            });
            console.log("delete!");
        }
    }
});
g-cassie commented 10 years ago

Sorry, uploadAllFiles needs to go in the actions object.

var FilesUploadController = Ember.Controller.extend(DropletController, {
   actions:{ 
   uploadAllFiles: function() {
      var self = this;
      $.when(self._super()).then(function(data){
        // do whatever you want here
        // data will contain the json response from your server
      });
      }
    }
});
reblevins commented 10 years ago

Great, @g-cassie, that worked like a charm. My original problem was, that the data being returned wasn't my actual objects being rendered back from my Rails API, but data is.

Don't know if this will help anyone, but here's my code for others following along:

actions:{ 
        uploadAllFiles: function() {
            var self = this;
            $.when(self._super()).then(function(data){
                var media = App.Media.find();

                        // $.parseJSON(data) is no longer necessary, since data has apparently already
                        // been converted to an object which Ember.js knows how to handle.
                        $.each(data, function(idx, obj) {
                                media.pushObject(obj);
                        });
                        media.reload();
            });
        }
}

I need to reload() my media model, otherwise the deleteImage action on each individual file doesn't work for some reason until I refresh the page. It seems like a lot of overhead and I'm not sure if there's a better way to do this, so I'm open to suggestions.

g-cassie commented 10 years ago

Looks like you are using the old version ember-data which I know very little about so unfortunately I can't really offer any guidance.

reblevins commented 10 years ago

I'm actually using ember-model, but have pretty much hobbled together stuff I've found on stackoverflow. Thanks anyway. I suppose, if I was doing it the regular (non-AJAX way), I would upload the files and refresh the entire page (i.e., query the database for the entire model) again, so perhaps this method isn't so much more 'expensive' than it should be.

You've helped me a lot, and I hope the OP has been able to glean something from this. ;-) @theworkerant did this help?

theworkerant commented 10 years ago

@g-cassie Yea that looks like just what I wanted. Thanks.

g-cassie commented 10 years ago

@reblevins We use ember-data and it just isn't sophisticated enough to do something like this so in this kind of situation we end up tacking on a reload api call, even though the file upload response actually contains all the information the app needs. I don't really want to spend a bunch of time hacking around this to cut out an api call when (hopefully) an ember-data release is just around the corner.

Wildhoney commented 10 years ago

@reblevins thanks! I've been away for the weekend, so was unable to properly reply; although it seems as you though you folks have figured everything out.

However, Is there something I should include into Droplet? As $.when(self._super()) doesn't seem to be the most enlightening of code. I didn't want to do much with the API response, because that could be anything depending on your API.

As per OP's request, I could add a didUploadFiles of sorts, but that would simply pass through the latest additions into the files array.

theworkerant commented 10 years ago

@Wildhoney :+1:

I love callbacks. didUploadFiles seems like it should exist for an Ember ... "plugin" (?) and couldn't hurt much (possibly a naive statement).

Wildhoney commented 10 years ago

Folks! Please let me know if this change is any good :+1:

reblevins commented 10 years ago

Great, it fired and the images got added to the page, but I got this error:

Uncaught TypeError: Cannot read property 'length' of undefined jquery.js?body=1:359
jQuery.extend.each jquery.js?body=1:359
App.ImagesController.Ember.Controller.extend.didUploadFiles images.js?body=1:11
Ember.tryInvoke ember.js?body=1:1354
(anonymous function) ember-droplet-mixin.js?body=1:167

Here's my controller code:

didUploadFiles: function(response) {
    var images = this.store.findAll('image');

    $.each(response, function(idx, obj) {
        images.pushObject(obj);
    });
    images.save();
}

Apparently, this: $.each(response, function(idx, obj) { is the offending bit of code in my controller so it may not have anything to do with your code.

EDIT: console.log(response); returned undefined

Wildhoney commented 10 years ago

Oops! Please try again.

reblevins commented 10 years ago

Yes, that did the trick! console.log(data); returns the array of JSON objects from my API.

And, actually, kind of annoying, but all that code I was using just to update the view (which took me more than a week to figure out), was unnecessary. I was using ember-model and now I switched back to Ember Data and all I need is this:

didUploadFiles: function(response) {
    var images = this.store.findAll('image');
}

That reloads the images and the view updates automatically. So, thanks for the useArray and didUploadFiles, that did the trick. Someday, I'm going to write a tutorial chronicling all of my experiences with Rails 4 + Carrierwave + Ember, etc...

xtagon commented 10 years ago

@reblevins If you ever do write that Rails 4 + Carrierwave + Ember tutorial, I'd be very interested in reading it! :+1: