[EDIT 18/2/2014] Full Cordova support straight out of the box! Follow the guidelines at the project Github page
[EDIT 20/12/2014]: Due to big amount of requests I have added the drag and drop support for file uploads. Currently upload of drag and dropped files cannot be canceled
[EDIT 10/11/2014]: For the most updated documentation to this upload control please go to the project Github page.
In this post I’ll introduce two new packages using which you can easily setup a file upload service to your Meteor server. This solution has following perks:
- Uses the famous jQuery file upload system from blueimp.
- As a result you can upload any file size (default limit 11GB)
- Displays upload progress
- Uploads can be canceled
- You can upload multiple files
- Images can be resized to various sizes using the imagemagick
- Saves and serves file from arbitrary folder on your server. This solves problems with hot-code reload, when files are saved into Meteor’s public directory
- Possibility to save files to subfolders
- Possibility to rename files on the server
- Simple configuration and installation! No need to install 10+ packages like with Collection-FS solution
Here is a screenshot of the interface:
Please note that since we are using blueimp’s juery solution, this solution has a full potential of performing client image resizes, chunked uploads, upload resume and more. These features will be gradually added. Pull requests are welcome!
Demo application can be found on Github
Quick Start
Install packages
$ meteor add tomi:upload-server $ meteor add tomi:upload-jquery
Create directory in the application root
mkdir -p .uploads/tmp
Configure server
//file:/server/init.js Meteor.startup(function () { UploadServer.init({ tmpDir: process.env.PWD + '/.uploads/tmp', uploadDir: process.env.PWD + '/.uploads/' }) });
Use template
<template name="yourTemplate"> {{> upload_bootstrap }} </template>
DONE!
Installation
The installation is very simple and consists of installing two packages:
$ meteor add tomi:upload-server $ meteor add tomi:upload-jquery
I have separated these two packages, because often you will want to run your upload service as a separate application. There are several options supported by blueimp, such as Java, ASP, Ruby and more or even node.js Express server installed via NPM. If you wish to use self standing server, install only the tomi:upload-jquery package.
Configuration
Server
First, we need to initialise the server and configure upload paths (see below for explanation of the different options):
//file:/server/init.js Meteor.startup(function () { UploadServer.init({ tmpDir: '/Users/tomi/Documents/Uploads/tmp', uploadDir: '/Users/tomi/Documents/Uploads/', getDirectory: function(file, formData) { return formData.contentType; }, finished: function(file, folder, formFields) { console.log('Write to database: ' + folder + '/' + file); } }) });
Following options are available for UploadServer.init(options):
Field | Type | Default | Description |
---|---|---|---|
tmpDir | String | null | Temporary upload directory |
uploadDir | String | null | Path to the upload directory |
uploadUrl | String | ‘/upload/’ | Upload route |
maxPostSize | int | 11000000000 | Maximum post size (11 GB) |
minFileSize | int | 1 | Minimum file size |
maxFileSize | int | 10000000000 | Maximum file size (10 GB) |
acceptFileTypes | RegEx | /.+/i, | Accepted types of files (e.g. prohibit .exe) |
imageTypes | RegEx | Images which can be resized with Imagemagick (e.g. /\.(gif\|jpe?g\|png)$/i ) |
|
imageVersions | Object | {} | Defines the sizes of images which will be converted and saved to upload directory. For example {thumbnailBig: {width: 400, height: 300}, thumbnailSmall: {width: 200, height: 100}} |
getDirectory | function | functions which decides the subdirectory in which the file will be saved. In this function is not defined, no sub-directory is created. For example: function(file, formData) { return '/my/sub/directory'; } |
|
getFileName | function | Renames the file on the server. In no function is specified, file is saved with the original file name. For example: `function(file, formData) { return ‘Saved-‘ + file; } | |
finished | function | Callback |
Client
On client we have a possibility to use pre-defined templates, existing for semantic-ui and bootstrap. We can use several upload forms on the page. We can distinguish what kind of content we are uploading, by providing the contentType
parameter. We can also limit the file type available for the file picker using parameter fileTypes
. Following is an example of how you can include the upload form on your page:
<template name="home"> <h3>PDFs</h3> {{> upload_semantic_ui contentType='pdfs' fileTypes='.pdf' }} <h3>Images</h3> {{> upload_bootstrap contentType='images' fileTypes='.jpg' multiple=true }} </template>
If you wish to create your own template for uploading, you can use following call in the rendered
callback to set up uploading functionality:
Template['your_upload_template'].rendered = function () { Uploader.render.call(this); };
When using this approach, your template MUST contain following:
- element with class
.uploadFilePicker
holding the file picker - element with class
.uploadProgressHolder
holding the file picker - element with class
.uploadProgressBar
showing the progress bar - element with class
.uploadProgressLabel
showing the progress label
If you wish to use custom URL for your uploads this can be configured as following:
Uploader.uploadUrl = 'http://yoururl';
Very useful package. Thanks! I switched to it, because the collectionFS does not support cordova so far. What would be the best way to directly display the uploaded image after it has been uploaded successfully? Also on other devices – how to make full use of Meteor reactivity with your package?
Thanks and best,
Miriam
Dear Miri
Thanks for your comments. We are steadily improving functionality of this package and making it more robust. When you download the latest version of the example from https://github.com/tomitrescak/meteor-uploads.git, I have added an example of how to show images after they are uploaded.
The trick is to hook onto
Uploader.finished
function and then you can perform following.In there I add uploaded file info into Session. Once that is done, you can reactively show the list of uploaded files such as here.
To get advantage of reactivity you need to fill the data on uploaded files into a collection. The best place to do this is here.
I hope this helped.
I am having difficulty inserting data on uploaded files into a collection. When adding a ‘Collection.insert()’ function into the ‘finished:’ callback, I get the following error:
“Error: Meteor code must always run within a Fiber. Try wrapping callbacks that you pass to non-Meteor libraries with Meteor.bindEnvironment.”
I have opened an issue on the meteor-uploads issue tracker.
Brylie, you are right. There are several workarounds for this problem, but the “easiest” is to just insert data on client’s finished callback instead on server. See the example here
Thanks a lot! That really helped.
Thanks for the work on this. Seems like a nice alternative.
I wasn’t able to get it to work under Cordova on ios. I tried the sample app from github. It works fine on mobile and desktop browsers. Under cordova on iOS it allows me to browse for a file, but nothing happens when you hit the upload button.
Did you get this to work, Miri?
I look forward to further development, particularly increased imagemagick functionality if that is on the roadmap.
Sorry for the late reply. No I did not get it to work – my comment above was a little misleading.
So, it does not work on mobile? If so, what is the problem? I did not have a chance to test it.
Miri, I checked the mobile support and it works in browser, but it fails in an App in simulator. Yet, the problem seems to be more connected to the simulator. Do you have a chance of testing it on a physical device?
on the physical device it shows the following error: meteor-uploads[11630:60b] data.sumbit.always: textStatus= parsererror
Could it be that this is a similar issue as with file collection before (cors)? https://github.com/CollectionFS/Meteor-CollectionFS/issues/463
Miri, got it working. See (https://github.com/tomitrescak/meteor-uploads/issues/43#issuecomment-74832916). Long story short, Cordova is pointing app not to a server but to a Meteor.local. Therefore if you set the upload url on client as Uploader.uploadUrl = Meteor.absoluteUrl(“upload”); all will work flawlessly 😉
Wow cool! Thanks. Can confirm it is working on ios device and simulator.
When I try to run it and press “browse” nothing happens. I am using Android and tested on simulator and device.
I have tested it with iOS only, can you provide some logs? Maybe there is an error?
Also, there is an issue that the plugin does not work with the latest jQUERY, works with 1.19+
Where can I find the parameters for the upload templates?
Sorry for the late answer, but upload template does have only one parameter “multi=true/false”. There are no other parameters.
How can I grab the uploaded picture if I want to post it via a Meteor method in a form for later retrieval? Example:
var post = {
title: $(e.target).find(‘[name=title]’).val(),
description: $(e.target).find(‘[name=description]’).val(),
image: ?
}
You can always grab the url of the uploaded picture in a callback. This callback executes on server as well as in client.
E.g. for the client:
Uploader.finished = function(index, file) {
Uploads.insert({file: file.name});
}
Thanks for this. Works great, except for when I deploy my app to meteor.com. Any idea how to configure it so it’ll work there?
As far as I know, this solution will work on meteor.com only if you are uploading into your “public” folder, since you have no write priviledges to write into any other folder. Unfortunately this will trigger the hot code reload. My solution is aimed to be used on dedicated servers, easily deployed with ‘Meteor Up’ by Arunoda.
Thanks for your response! I was afraid of that. My intention was to move toward a PaaS like Modulus at some point. Are you familiar with Modulus by chance and how easy it would be to configure your package to work with it?
I am not very familiar with modulus. Yet, my package can do whatever is permitted on the server. So, the moment you have a write permitted directory on modulus server, you can write into it!
Yea I meant to update this blog yesterday. At the time of this writing, Modulus provides a local storage option (free, but limited size and only accessible via the local instance) and a cloud option which essentially acts like dir you have access to (charged per GB, but no limit and accessible across instances). I tried it real quick and files were indeed uploaded (although I have a couple kinks to work out).
Thanks again for your time! I figured I’d ask before I spent the money, but it appears as though everything will work fine with Modulus.
it doesn’t work for me , i did exactly what you described :
here the error on client console :
TypeError: Cannot read property ‘autoStart’ of null
at Template.upload.created (http://genjurosama-199720.euw1-2.nitrousbox.com/packages/tomi_upload-jquery.js?b6bf3c0cc365d2eb8f9acc93ee3dcc4bfd892687:2569:29)
at null. (http://genjurosama-199720.euw1-2.nitrousbox.com/packages/blaze.js?efa68f65e67544b5a05509804bf97e2c91ce75eb:2995:20)
at fireCallbacks (http://genjurosama-199720.euw1-2.nitrousbox.com/packages/blaze.js?efa68f65e67544b5a05509804bf97e2c91ce75eb:1835:16)
at Object.Tracker.nonreactive (http://genjurosama-199720.euw1-2.nitrousbox.com/packages/tracker.js?517c8fe8ed6408951a30941e64a5383a7174bcfa:513:12)
at http://genjurosama-199720.euw1-2.nitrousbox.com/packages/blaze.js?efa68f65e67544b5a05509804bf97e2c91ce75eb:1832:13
at Object.Blaze._withCurrentView (http://genjurosama-199720.euw1-2.nitrousbox.com/packages/blaze.js?efa68f65e67544b5a05509804bf97e2c91ce75eb:2043:12)
at Object.Blaze._fireCallbacks (http://genjurosama-199720.euw1-2.nitrousbox.com/packages/blaze.js?efa68f65e67544b5a05509804bf97e2c91ce75eb:1831:9)
at Object.Blaze._createView (http://genjurosama-199720.euw1-2.nitrousbox.com/packages/blaze.js?efa68f65e67544b5a05509804bf97e2c91ce75eb:1849:9)
at Object.Blaze._materializeView (http://genjurosama-199720.euw1-2.nitrousbox.com/packages/blaze.js?efa68f65e67544b5a05509804bf97e2c91ce75eb:1853:9)
at Blaze._DOMMaterializer.def.visitObject (http://genjurosama-199720.euw1-2.nitrousbox.com/packages
I have submitted a patch, it should be ok now.
Can anyone please tell me how do i restrict the number of images being uploaded.
Well, you can use the `validate` callback in which you will check the number of uploaded files by the user. Make sure you read the documentation on Github (https://github.com/tomitrescak/meteor-uploads) which is kept up to date.
Thanks Tomi thanks for the help.I really appreciate it.I am trying to limit to number of images uploaded to 20 but even after you help i am not able to figure it out completely.will try and let you know if i can figure it out.thanks for the help tough.
I would like to use the package to upload a file that I get for example from using the device camera (without using the file picker). I guess I have to use Uploader.startUpload.call(Template.instance(), e);
But what would be the easiest parameters to just use it with a file (image.jpeg)? Would be great if you have a hint.
Thanks!