public
Last active

Save files in Meteor

  • Download Gist
README.md
Markdown

Create a Meteor app and put the client_/server_ files in a client/server directories. Also, create a public dir to save the uploaded files.

client_example.html
HTML
1 2 3 4 5 6 7
Template.example.events({
'change input': function(ev) {
_.each(ev.srcElement.files, function(file) {
Meteor.saveFile(file, file.name);
});
}
});
client_example.js
JavaScript
1 2 3
<template name="example">
<input type=file />
</template>
client_save_file.js
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
/**
* @blob (https://developer.mozilla.org/en-US/docs/DOM/Blob)
* @name the file's name
* @type the file's type: binary, text (https://developer.mozilla.org/en-US/docs/DOM/FileReader#Methods)
*
* TODO Support other encodings: https://developer.mozilla.org/en-US/docs/DOM/FileReader#Methods
* ArrayBuffer / DataURL (base64)
*/
Meteor.saveFile = function(blob, name, path, type, callback) {
var fileReader = new FileReader(),
method, encoding = 'binary', type = type || 'binary';
switch (type) {
case 'text':
// TODO Is this needed? If we're uploading content from file, yes, but if it's from an input/textarea I think not...
method = 'readAsText';
encoding = 'utf8';
break;
case 'binary':
method = 'readAsBinaryString';
encoding = 'binary';
break;
default:
method = 'readAsBinaryString';
encoding = 'binary';
break;
}
fileReader.onload = function(file) {
Meteor.call('saveFile', file.srcElement.result, name, path, encoding, callback);
}
fileReader[method](blob);
}
server_save_file.js
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
/**
* TODO support other encodings:
* http://stackoverflow.com/questions/7329128/how-to-write-binary-data-to-a-file-using-node-js
*/
Meteor.methods({
saveFile: function(blob, name, path, encoding) {
var path = cleanPath(path), fs = __meteor_bootstrap__.require('fs'),
name = cleanName(name || 'file'), encoding = encoding || 'binary',
chroot = Meteor.chroot || 'public';
// Clean up the path. Remove any initial and final '/' -we prefix them-,
// any sort of attempt to go to the parent directory '..' and any empty directories in
// between '/////' - which may happen after removing '..'
path = chroot + (path ? '/' + path + '/' : '/');
// TODO Add file existance checks, etc...
fs.writeFile(path + name, blob, encoding, function(err) {
if (err) {
throw (new Meteor.Error(500, 'Failed to save file.', err));
} else {
console.log('The file ' + name + ' (' + encoding + ') was saved to ' + path);
}
});
 
function cleanPath(str) {
if (str) {
return str.replace(/\.\./g,'').replace(/\/+/g,'').
replace(/^\/+/,'').replace(/\/+$/,'');
}
}
function cleanName(str) {
return str.replace(/\.\./g,'').replace(/\//g,'');
}
}
});

Works a charm mate. Cheers!

Worked perfectly, thanks!

I've got this working now with a button to trigger the upload rather than when the file is selected...but it refreshes the page when finished. Any suggestions on how to keep meteor from refreshing the page?

The page refresh occurs because Meteor is monitoring the public directory for file changes. I got around it as suggested here: http://stackoverflow.com/questions/13201723/generating-and-serving-static-files-with-meteor

now i can upload file with these code, but after upload where to get my file ?

i use http://localhost:3000/filename_just_uploaded.jpg

but i cant get it

Thanks for this.

You've reversed the names of two files (client_example.js and client_example.html), but otherwise it's really helpful.

Doesn't work for me on my Mac with the latest FireFox (17.0.1).

Firebug show error message: TypeError: ev.srcElement is undefined on line "_.each(ev.srcElement.files, function(file) {" in file: client_example.js.

It does work on Opera, Safari and Chrome. Thanks

Just works! Thank you!

@JamieCzuy ev.currentTarget.files it's ok

Thanks a lot for the gist. I was able to successfully upload files using this method, thought I did it a tad differently and for a more narrow use case.

https://gist.github.com/4413727

By the way, for IE10 compatibility you need to use an array buffer. Juste change line 30 in client_save_file.js, replace "blob" by "new Blob([blob])"

Thanks for this! I have a problem though. (through localhost) it crashes Chrome (v27.0.1453.93) on Ubuntu 13.04 with large files(30 MB+). Haven't tested other platforms. Any guesses as to why this might be happening?

From 0.6.0 you need to use Npm.require instead of meteor_bootstrap

fs = Npm.require('fs');

A single regex for cleaning paths and names, for both windows and *nix, could be

return str.replace(/\.\.|\/|\\/g, ''); // And yes I know! Looks like library shelf

Thanks so much for this script. I have a little problem though. I've made it so that the function loops so multiple files can be uploaded at once, by clicking a button. Thing is, when I do more than 1 file, some of the files show up as 0kb in the public folder. As empty files they're useless to me. Ideas? Thanks again!

Thank you so much for this script!
I ended up needing using fs.writeFileSync instead of fs.writeFile.
For some reason, with fs.writeFile, it would only upload an empty file at first. You would then have to upload it again for it to finish the job. It was weird, but then @mike-engel suggested giving fs.writeFileSync a try and ka-bam!

These does nothing. It doesn't give me an error or nothing, but I can't find the file.

This would work after meteor update to 0.6.5 . The path is wrong.
I find a work round with process.env['PWD'].
Just put it before your path. like following:
chroot = Meteor.chroot || (process.env['PWD'] +'/public') ;

Thank you so much for this, I've tried three other examples and none worked or I misunderstood them. Working beautifully.

I ran into problems using Meteor 6.6.3 where it wasn't saving files. Found this StackOverflow which resolved my problem (Error: ENOENT): http://stackoverflow.com/questions/18616151/fs-writefile-doesnt-work-on-meteor-0-6-5-1

Turns out you can't just use public/myFilename because Meteor changed what the root directory of your app is. When I used "../../../../../public/" it starting working properly.

works fine! after changing to Npm.require and changing the path to reflect new location of chroot in meteor. Beware that uploading to public causes a restart to happen on the server. You should create a folder suffixed by ~ like images~ this will work without meteor restarting or updating the client.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.