Probably one of the biggest issues that I've had so far with our latest Ember projects has been working with has been getting a good way to upload files on an Ember model. In the past, we had used jquery.fileupload from BlueImp to handle the file uploads on smaller projects. However, one of the issues that we have with our new application is performance and DOM size.
The application is a image gallery uploader where images are being sent to different image fields. The page has hundreds or more of these image fields on the page so using a tool like BlueImp or something similar proved a bit of a problem for a few reasons.
1. Uneeded DOM elements
With all of these image fields on the page and the ability to upload to any of the fields at any time, there becomes an issue with the DOM nodes needed for something like BlueImp. Therefore, to make this available to BlueImp, you would have to add file inputs for each image field along side of the HTML button which is triggering our upload action. On top of this, some of the AJAX uploading tools will create even more DOM (iFrames, hidden inputs, etc) on initialization for each input. That's a ton of DOM when you consider 100+ extra file inputs, iFrames, and more. I just in the concept phase, I saw rendering issues with this.
2. Initialization
While right now, our application loads all of the sub views containing the image uploader at once, we are moving to an infinite scroll later in the life-cycle of the application.
This wouldn't be a problem except for the fact that all of the AJAX file uploader libraries I had found listen directly to a input selector instead of doing something similar to the bind delegation which keeps event listeners active when new elements are popped in to the DOM.
So on each render, I would have to initialize the file uploader library to look for input[type=file]
or something along those lines.
- Reusability
The code to upload a new image vs the code required to update an existing image field can use a lot of the same information. Unfortunately, I found that making reusable listeners with something like BlueImp was a bit tough since everything is initialized at once.
Solution (For Now)
My solution to this problem was to keep a new input element programatically created by Javascript and never insert it into the DOM to aid in rendering performance. I can still check onchange and more, but this let's me keep the performance quick and snappy and saves me 61 kB (not including the widget libraries that are needed) by not requiring BlueImp. Then I build a FormData object and send a request via jQuery to interact with my server. So, here is a taste of what my Ember controller looks like:
App.PhotosImageEditorController = Ember.ObjectController.extend({
createImageFileUpload: function (onChangeEvent) {
input = $('<input>').attr('type', 'file');
input.on('change', {controller: this.}, onChangeEvent);
input.click();
},
createFormDataForNewPhoto: function (file) {
formData = new FormData();
formData.append('photo', file);
formData.append('image_category_id', this.imageCategory.id);
formData.append('image_field_id', this.id);
formData.append('guitar_id', App.guitarId);
return formData;
},
createNewPhoto: function (controller, data) {
// Do stuff to populate relationship with a new photo
},
updatePhoto: function (method, url, data) {
controller = this
$.ajax({
data: data,
cache: false,
contentType: false,
processData: false,
type: method,
url: url,
success: function (data) {
if (controller.guitarPhoto)
controller.updateExistingPhoto(controller, data);
else
controller.createNewPhoto(controller, data);
}
});
},
registerNewImageUploader: function (ev) {
newFile = this.files[0];
formData = ev.data.controller.createFormDataForNewPhoto(newFile);
ev.data.controller.updatePhoto('POST', '/api/guitar_photos', formData);
},
actions: {
uploadImage: function() {
this.createImageFileUpload(this.registerImageUpdater);
}
uploadNewImage: function() {
this.createImageFileUpload(this.registerNewImageUploader);
}
}
});
This still isn't quite ideal for me.
The I'm still having to do a lot of data manipulation with the returned result which is not shown because it doesn't have to do with image uploading as much as just pulling in external data and repopulating OneToOne relationships in Ember Data (which is really awkward right now).
Also, I also want to check on garbage collection of the input object that I have created.
It's better than having inputs in memory, rendered in the DOM, data bound, and with event listeners attached for each uploader view, but looking at the code, I sense that the input
variable isn't being cleaned up once everything is said and done (I think this is just a matter of doing something in my success
function in updatePhoto
).
There are also quick ways for this to be improved: for instance, my createFormDataForNewPhoto
could be DRYed up to accept a Javascript object and build a FormData object based on parameter keys (it could even serialize some of the data for that).
Let me know what you think. Am I missing something really obvious, or am I on to something?