Image upload is one of those pretty daunting things for beginner programmer (myself before I finish this). Luckily good folks that use meteor has already done the bulk of the work for us.
However, it’s still no simple feat to put it all together. I used the dropzone.js package for the drag-and-drop client-side image handling, then use CollectionFS to handle storing image record in database and upload to Amazon S3 as well.
There’s also a great package someone wrote to compress and correct orientation, saved my life! : ccorcos:clientside-image-manipulation.
How-to:
<div class="row"> <div class="col-md-12"> <form action="#" class="dropzone" id="dropzone"></form> </div> </div>
First initialize a dropzone field (photoUpload.html).
var processAndUploadImage = function(file, userId) { // photo processing for orientation, max width and max height processImage(file, 800, 800, function(processedFile) { var newFile = new FS.File(processedFile); newFile.userId = userId; Images.insert(newFile, function (error, fileObj) { if (error) { toastr.error('error message...'); } else { toastr.success("success message..."); } }); }); };
var myDropzone = new Dropzone("#dropzone", { dictDefaultMessage: message, maxFilesize: 10, accept(file) { if (file.status == 'added') { if (file.type !== 'image/jpeg' && file.type !== 'image/jpg' && file.type !== 'image/png' && file.type !== 'image/gif') { toastr.error('error message...'); myDropzone.removeFile(file); return false; } processAndUploadImage(file, userId); // clear out dropzone myDropzone.removeFile(file); } } });
Then in the js file (photoUpload.js), put under the onRendered function for the template.
Basically what’s happening here is, I initiate the dropzone with new Dropzone, which lets the dropzone field above to allow drag-and-drop or click-to-upload. upon file ‘accept’, it’ll run the processAndUploadImage function. It’s a separate function here because for my app, I actually reuse that function, but for this simple example this would actually be unnecessary.
The processAndUploadImage uses the ccorcos:clientside-image-manipulation package to set max width or max height to 800, keeping the aspect ratios. It’ll also auto correct orientation problem, which happens when you upload photo with mobile phone.
Lastly just calling the collectionFS API and inserting the image. Voila!
if (Meteor.isServer) { var imageStore = new FS.Store.S3('images', { region: "ap-northeast-1", accessKeyId: Meteor.settings.AWSAccessKeyId, secretAccessKey: Meteor.settings.AWSSecretAccessKey, bucket: Meteor.settings.AWSBucket }); Images = new FS.Collection("Images", { stores: [imageStore], filter: { maxSize: 10 * 1048576, allow: { contentTypes: ['image/*'] } } }); } if (Meteor.isClient) { var imageStore = new FS.Store.S3('images'); Images = new FS.Collection("Images", { stores: [imageStore], filter: { maxSize: 10 * 1048576, allow: { contentTypes: ['image/*'] } } }) } Images.allow({ insert: function(userId) { return userId != null }, update: function(userId, image) { return userId === image.userId || Roles.userIsInRole(userId, ['admin']); }, remove: function(userId, image) { return userId === image.userId || Roles.userIsInRole(userId, ['admin']); }, download: function() { return true; } });
Finally for the image collection (you’ll need to setup an S3 account first, there are tutorials out there about how to do that. Once that’s setup, put the keys in a settings.json file. NEVER put it in plain sight and commit to git.).
And the allow/deny rules as well, I’m not sure if this could all be turned into Meteor methods and not need them, but I don’t quite think so … I might be wrong though, but the code works for sure anyways.
After setting the keys in the settings.json file, everything is good to go! Phew!
Packages:
cfs:standard-packages
cfs:filesystem
cfs:s3
cfs:ui
ccorcos:clientside-image-manipulation
Challenges:
There was a HUGE gotcha with dropzone.js, which I spent about 2-3 hours getting through … with meteor photo upload, somehow the dropzone ‘complete’ event occasionally does not fire, especially on larger files (>1MB). I had done testing with small files, so I thought it worked, until I tried big files and didn’t work …. spent a long time through trial and error to come to the conclusion that I can use the ‘accept’ property to trigger my upload.
It’s not the most detailed tutorial but hope that helps. I’d always be happy to give some extra help if you came across this and need more, you can find my contact info in the homepage. Thanks for reading!