I use collectionfs for storing files in my application.
I copy+pasted most of the readme code provided with collectionfs into my application and also added the
{{cfsFileUrl "default1"}}
to my file listing. Everything works on my local machine.
The problem arises when I deploy to ???.meteor.com with
mrt deploy ???.meteor.com
I can upload and download images and also a url is displayed from cfsFileUrl,
BUT:
When I access that url, I get Error 404.
My code:
client.html
<body>
{{loginButtons}}
{{>queueControl}}
<br>ta
<br>
{{>fileTable}}
</body>
<template name="queueControl">
<h3>Select file(s) to upload:</h3>
<input name="files" type="file" class="fileUploader" multiple>
</template>
<template name="fileTable">
{{#each files}}
{{cfsDownloadButton "ContactsFS" class="btn btn-primary btn-mini" content=filename}}<br>
<img src="{{cfsFileUrl "default1"}}">
{{/each}}
</template>
client.js
ContactsFS = new CollectionFS('contacts', { autopublish: false });
Deps.autorun(function () {
Meteor.subscribe('myContactsFiles');
});
Template.queueControl.events({
'change .fileUploader': function (e) {
var files = e.target.files;
for (var i = 0, f; f = files[i]; i++) {
ContactsFS.storeFile(f);
}
}
});
Template.fileTable.files = function() {
//show all files that have been published to the client, with most recently uploaded first
return ContactsFS.find({}, { sort: { uploadDate:-1 } });
};
server.js
ContactsFS = new CollectionFS('contacts', { autopublish: false });
Meteor.publish('myContactsFiles', function() {
if (this.userId) {
return ContactsFS.find({ owner: this.userId }, { limit: 30 });
}
});
ContactsFS.allow({
insert: function(userId, file) { return userId && file.owner === userId; }
});
ContactsFS.fileHandlers({
default1: function(options) { // Options contains blob and fileRecord — same is expected in return if should be saved on filesytem, can be modified
console.log('I am handling default1: ' + options.fileRecord.filename);
console.log(options.destination());
return { blob: options.blob, fileRecord: options.fileRecord }; // if no blob then save result in fileHandle (added createdAt)
}
});
I had a similar problem and posted it on collectionfs issue page. Check it out: https://github.com/CollectionFS/Meteor-CollectionFS/issues/85
Related
My vue component like this :
<template>
<section>
...
<img class="media-object" :src="baseUrl+'/storage/banner/thumb/'+photo" alt="" width="64" height="64">
...
</section>
</template>
<script>
export default {
props: ['banners'],
data() {
return {
baseUrl: App.baseUrl,
bannerId: this.banners.id,
photo: this.banners.photo // result : chelsea.png
}
},
methods: {
onFileChange(e) {
let files = e.target.files,
reader = new FileReader(),
formData = new FormData(),
self = this
formData.append('file', files[0])
formData.append('banner_id', this.bannerId)
axios.post(window.App.baseUrl+'/admin/banner/upload-image',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
).then(function(response) {
if(response.data.status == 'success') {
self.photo = response.data.fileName // result : chelsea.png
}
})
.catch(function(error) {
console.log('FAILURE!!')
})
},
...
}
}
</script>
The result of :src : \my-app\storage\app\public\banner\thumb\chelsea.png
When I upload image, it will call onFileChange method. And the process upload will continue in the backend. It success upload in the folder. And the response will return same filename. So the result of response.data.fileName is chelsea.png
My problem here is : it's not update the image automatic when I upload it. When I refresh the page, the image updated
Why the image is not automatic update/changed when I upload the image?
I fixed it by doing the following, notice I added a variable named rand at the end of the photo url for cache busting. When you get a correct response from your server, simply change that variable to something unique (in this case a timestamp) and voila! your image will refresh.
<template>
<img class="media-object" :src="baseUrl+'/storage/banner/thumb/'+photo + '?rand=' + rand" alt="" width="64" height="64">
</template>
<script>
export default {
data() {
return {
rand: 1
}
},
methods: {
onFileChange(e) {
...
axios.post(url,formData).then(function(response) {
if(response.data.status == 'success') {
self.rand = Date.now()
}
})
},
...
}
}
Your images are cached by the browser.
Try to add any tag to the image like:
chelsea.png?t=<random>
The answer, as provided above, are computed properties as these designed to be reactive, but when it comes to async it best to use promises / observables. However, if you decide not use and are still experiencing problems, then you can use a loading property, like the loading property in the example below to manipulate the DOM i.e. remove the DOM with v-if when you initiate async (axios). Get and set the the image and then restore the DOM element with this.loading = true;. This forces a render of the DOM, which forces a computed property.
<template>
<section>
<div v-if="!loading">
<img class="media-object" :src="getPhoto" :alt="getAlt" width="64" height="64">
</div>
<div v-if="loading">
<!-- OR some spinner-->
<div>Loading image</div>
</div>
</section>
</template>
<script>
export default {
props: ['banners'],
data() {
return {
loading: false,
baseUrl: App.baseUrl,
bannerId: this.banners.id,
photo: {} // result : chelsea.png
}
},
computed: {
getPhoto() {
return App.baseUrl + '/storage/banner/thumb/' + this.photo.fileName;
},
getAlt() {
return photo.alt;
},
},
methods: {
onFileChange(e) {
let files = e.target.files,
reader = new FileReader(),
formData = new FormData(),
self = this
// Set loading to true
this.loading = true;
formData.append('file', files[0])
formData.append('banner_id', this.bannerId)
axios.post(window.App.baseUrl+'/admin/banner/upload-image',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
).then(function(response) {
if(response.data.status == 'success') {
self.photo = response.data.fileName // result : chelsea.png
this.loading = false;
}
})
.catch(function(error) {
console.log('FAILURE!!')
})
},
...
}
}
</script>
Just use computed property, snippet below used getImageUrl to get the updated path. I added button to trigger the mimic change on the data provided.
new Vue({
el: "#app",
data: {
baseUrl: 'baseURl', //dummy
bannerId: '', //dummy
photo: 'initPhoto.png' // dummy
},
computed: {
getImageUrl: function() {
return this.baseUrl + '/storage/banner/thumb/' + this.photo;
}
},
methods: {
mimicOnChange: function() {
this.photo = "chelsea.png"
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.js"></script>
<div id="app">
<span>{{ getImageUrl }}</span>
<br/>
<button #click="mimicOnChange">
On change trigger
</button>
</div>
On you above code, just use the computed directly to your src attribute:
<img class="media-object" :src="getImageUrl" alt="" width="64" height="64">
Try binding full photo's path:
<template>
<section>
...
<img v-if="photoLink" class="media-object" :src="photoLink" alt="" width="64" height="64">
<p v-text="photoLink"></p>
...
</section>
</template>
<script>
export default {
props: ['banners'],
data() {
return {
baseUrl: App.baseUrl,
bannerId: this.banners.id,
photo: this.banners.photo, // result : chelsea.png
photoLink: App.baseUrl + '/storage/banner/thumb/' + this.banners.photo
}
},
methods: {
onFileChange(e) {
let files = e.target.files,
reader = new FileReader(),
formData = new FormData(),
self = this
formData.append('file', files[0])
formData.append('banner_id', this.bannerId)
axios.post(window.App.baseUrl+'/admin/banner/upload-image',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
).then(function(response) {
if(response.data.status == 'success') {
// self.photo = response.data.fileName // result : chelsea.png
console.log('>>>INSIDE SUCCESS');
self.photoLink = self.baseUrl + '/storage/banner/thumb/' + response.data.fileName;
}
})
.catch(function(error) {
console.log('FAILURE!!')
})
},
...
}
}
I've had the same problem where inserting the same image won't trigger any action after the input. I fixed it by clearing the input.
clearInput(e) {
e.target.value = '';
},
I had some weird behaviour with vue, where after upload the img, the base64 data, was on the src img, but the browser somehow did not render it correctly, only doing any action in the form like clicking any button etc.. would magically appear.
So that was solved using a setTimeout.
uploadNewImg () {
let self = this
// Get the selected file
var file = this.$refs.profileImg.files[0]
// Create a new FileReader object
var reader = new FileReader()
reader.onload = function (e) {
// Create a new FormData object
var formData = new FormData()
formData.append('file', file)
setTimeout(function () {
self.profilePic = e.target.result// this is used as src of img tag
}, 1)
}
Looking at your question. I could not see whether you would like the process to be sync or async so I add my solution. I prefer to use Async/await in such cases and This should fix the problem of image render:
async onFileChange(e) {
let formData = new FormData();
formData.append('file', files[0]);
formData.append('banner_id', this.bannerId);
//.... Add headers and payload for post request
const {data} = await axios.post(window.App.baseUrl+'/admin/banner/upload-image', payload);
if(data.status === 'success') {
self.photo = data.fileName // result : chelsea.png
}
}
I'm uploading multiple files on submission of a form.
This is the image upload input (html):
<form class="form-inline" id="cost-estimate-form">
<div class="form-field-short col s12 m6">
<i class="material-icons prefix">insert_photo</i>
<label for="input-file">Upload photos</label>
<input id="input-file" type="file" name="images" accept="image/jpeg, image/png, application/pdf" multiple/> <!-- todo: ugly on safari -->
</div>
<!-- rest-->
<button class="btn waves-effect col s6 m3 offset-m6" type="submit" name="action">Submit
<i class="material-icons right">send</i>
</button>
And the .js
'submit #cost-estimate-form': function(event, tmpl){
event.preventDefault();
let files;
if(event.target.images) {
files = event.target.images.files;
}
Meteor.call('travelRequests.insert', tmpl.data, function(err, trId) {
if (err) {
alertError(err.message);
}
else {
if (files) {
var imageDetails = [];
for (var i = 0, j = 0, ln = files.length; i < ln; i++) {
Image.insert(files[i], function (err, fileObj) {
if (err) {
console.log('Error uploading image: ');
console.log(err);
} else {
console.log('[DB] insert image(id=' + fileObj._id);
j++;
let imagePath = '/uploads/images-' + fileObj._id + '-' + fileObj.name();
imageDetails.push({id: fileObj._id, name: fileObj.name(), path: imagePath});
if (j === ln) { // when last file is successful
Meteor.call('travel.addImages', trId, imageDetails,
function (err, _) {
if (err) alertError(err.reason);
});
console.log('travel.addImages');
}
}
});
}
}
}
});
Router.go('travel_requests_list');
Meteor.call('travelRequests.insert'... creates an entity. Then I try to update the images uploaded for that entity after uploading the files with Meteor.call('travel.addImages',....
However, when clicking submit button on the form, the next screen refreshes multiple times and I get error in client:
cfs_power-queue.js:525 Error: "Queue" network [undefined], Error
at cfs_upload-http.js:382
at cfs_upload-http.js:108
at underscore.js:794
at XMLHttpRequest.xhr.onreadystatechange (cfs_upload-http.js:167)
And in the mongodb, some files are completely uploaded and some are not:
Example complete file:
{
"_id" : "MEWTZaXLX9gvx5utc",
"original" : {
"name" : "IMG_3867.JPG",
"updatedAt" : ISODate("2017-07-19T02:57:55Z"),
"size" : 4231984,
"type" : "image/jpeg"
},
"uploadedAt" : ISODate("2017-09-15T02:30:40.204Z"),
"copies" : {
"images" : {
"name" : "IMG_3867.JPG",
"type" : "image/jpeg",
"size" : 4231984,
"key" : "images-MEWTZaXLX9gvx5utc-IMG_3867.JPG",
"updatedAt" : ISODate("2017-09-15T02:30:40Z"),
"createdAt" : ISODate("2017-09-15T02:30:40Z")
}
}
}
Example incomplete file:
{
"_id" : "cgHcSCRPvzgekW6Ai",
"original" : {
"name" : "IMG_3869.JPG",
"updatedAt" : ISODate("2017-07-19T02:58:10Z"),
"size" : 4108047,
"type" : "image/jpeg"
},
"chunkSize" : 2097152,
"chunkCount" : 1,
"chunkSum" : 2
}
Collection definition:
Image = new FS.Collection("images", {
/* the file director: .meteor/local/uploads */
stores:[new FS.Store.FileSystem("images",{path:Meteor.settings.uploadRoot+"/uploads"})],
filter: {
allow: {
contentTypes: ['image/*', 'application/pdf'] //allow only images and pdf in this FS.Collection
}
}
});
if(Meteor.isServer){
Image.allow({
'insert': function () {
return true;
},
'update': function () {
return true;
},
'download':function(){
return true;
}
});
}
Why does this happen? Should I wait for file upload to finish before routing to the next screen? How do I do that if that is the problem?
I'm new to meteor so any help is appreciated.
As you are not using kadira's FlowRouter, I may be not be the right person to answer about the redirection issues as Router mechanism can hold data inside routes as well which is very bad. As per latest Meteor conventions, Subscription must be at template level implementation.
But, I can tell you that you can wait for completion of image upload. When the image is uploaded successfully only then you can perform next operation which can be either redirection to next page or any other operation.
Below is the code to wait for the completion of your image upload. The basic Idea is the url of image is generated only when the Image gets uploaded completely. so we wait for url to be generated, Once we receive url, it means we are done.
var myImage = event.target.images.files[0];
if(myImage){
var myFile = new FS.File(myImage);
Images.insert(myFile, function(err, result){
if(!err){
var liveQuery = Images.find(result._id).observe({
changed: function(newImage, oldImage) {
if (newImage.url() != null) {
liveQuery.stop();
// Here the image is uploaded successfully.
}
}
});
} else {
//console.log(err);
}
});
}
I am using meteor-postgres and want to display results from SQL query.
Html:
...
<template name="myForm">
<form class="search" method="GET">
<input required="Required" type="text" name="width" placeholder="cm"/>
<input type="submit"/>
</form>
Results:
<ul>
<!--How do I render results here-->
</ul>
...
JS
Services = new SQL.Collection('services');
// in client
Template.myForm.events({
"submit form": function (event) {
var width = event.target.width.value;
// TypeError: table is undefined,
// Maybe because I am on client side?
console.log(services.first().fetch());
// How can I get data here and render it to html?
}
});
I dont know what else should I say but StackOverflows want me to add more text!
I decided to use https://github.com/numtel/meteor-pg because it has nice examples.
Update
It is better and simpler to use PgSubscription.change:
Template:
<template name="hello">
<label>id</label>
<input type="number" id="myid" />
{{#each services}}
{{id}}
{{/each}}
</template>
JS
myServices = new PgSubscription('services');
if (Meteor.isClient) {
Template.hello.helpers({
services: function () {
return myServices.reactive();
}
});
function handleInput(event, template) {
var idVal = template.find("#myid").value;
console.log("client change detected, new value:");
console.log(idVal);
myServices.change(idVal);
}
Template.hello.events({
"keyup #myid": function(event,template) {
handleInput(event,template);
},
"change #myid": function(event,template) {
handleInput(event,template);
}
});
}
if (Meteor.isServer) {
var PG_CONNECTION_STRING = "postgres://root:root#localhost:5433/parcelService";
var liveDb = new LivePg(PG_CONNECTION_STRING, "myApp");
var closeAndExit = function () {
liveDb.cleanup(process.exit);
};
// Close connections on hot code push
process.on('SIGTERM', closeAndExit);
// Close connections on exit (ctrl + c)
process.on('SIGINT', closeAndExit);
Meteor.publish('services', function (id) {
console.log("server services, id:");
console.log(id);
return liveDb.select(
'SELECT * FROM services WHERE id = $1', [ id ]
);
});
}
Old Method:
On client side I defined ReactiveVar for template
Template.myForm.created = function () {
this.asyncServices = new ReactiveVar(["Waiting for response from server..."]);
}
and helper for data
Template.myForm.helpers({
myData: function () {
return Template.instance().asyncServices.get();
}
});
now trigggering action with
Template.myForm.events({
'keyup input': function (evt, template) {
var asyncServicesX = Template.instance().asyncServices;
Meteor.call('services', params, function(error, response){
if (error) throw error;
asyncServicesX.set(response);
});
}
And finally method on server side, where sql is executed:
Meteor.methods({
'services': function (params) {
Future = Npm.require('fibers/future');
var future = new Future();
// Obtain a client from the pool
pg.connect(PG_CONNECTION_STRING, function (error, client, done) {
if (error) throw error;
// Perform query
client.query(
'select * from services where col1=$1 and col2=$2',
params,
function (error, result) {
// Release client back into pool
done();
if (error) throw error;
future["return"](result.rows);
}
)
});
return future.wait();
}
});
I have a simple tickets app. When I create a new ticket, I can add attached file related to this one.
Download button don't works. Files metadata was correctly inserted into collection and I see it. Maybe, this is the reason: resources is not in the upload folder. I use the cfs:filesystem connector and here my code:
ticket.html
<head>
<title>orangeticket-test</title>
</head>
<body>
<h1>Tickets</h1>
{{> newTickets}}
{{> tickets}}
</body>
<template name="newTickets">
<input type="text" id="ticket" placeholder="name" >
<input id="submit" type="submit">
</template>
<template name="tickets">
{{#each tickets}}
<div class="tickets">
<p>Titolo: {{name}}</p>
{{> addFile}}
</div>
{{/each}}
</template>
<template name="addFile">
<form>
<input id="file" type="file">
<input id="addFile" type="submit">
</form>
{{#each files}}
<div class="file">
<strong>{{this.name}}</strong> Download
</div>
{{/each}}
</template>
tickets.js
Tickets = new Meteor.Collection('Tickets');
var fileStore = new FS.Store.FileSystem("fileStore", {
path: "~/uploads/files"
});
Files = new FS.Collection("Files", {
stores: [fileStore]
});
if (Meteor.isClient) {
Template.tickets.helpers({
tickets: function () {
return Tickets.find();
}
});
Template.newTickets.events({
'click #submit': function (e, t) {
e.preventDefault();
var ticket = t.find('#ticket').value;
Tickets.insert({name:ticket});
}
});
Template.addFile.events({
'click #addFile' : function(e, t){
var file = t.find('#file').files[0];
var ticket = this._id;
var newFile = new FS.File(file);
newFile.metadata = {
ticketId: ticket
};
Files.insert(newFile, function (err, fileObj) {
if (!err) {
console.log(fileObj);
}
});
}
});
Template.addFile.helpers({
files: function() {
var ticket = this._id;
return Files.find({'metadata.ticketId':ticket});
}
})
}
if (Meteor.isServer) {
Meteor.startup(function () {
console.log('server works');
});
}
clone git project:
git#gitlab.com:arcidiaco.a/orangeticket-test.git
thanks a lot!
I solved the problem: the submit button refresh the page while file was uploaded. This action interrupt the upload action. I deleted submit button and used instead only file input with change event:
Template.addFile.events({
'canche #file' : function(e, t){
var file = t.find('#file').files[0];
var ticket = this._id;
var newFile = new FS.File(file);
newFile.metadata = {
ticketId: ticket
};
Files.insert(newFile, function (err, fileObj) {
if (!err) {
console.log(fileObj);
}
});
}
});
I'm trying to display photos from the NPM FB-Graph (https://npmjs.org/package/fbgraph) package by following this example (http://www.andrehonsberg.com/article/facebook-graph-api-meteor-js). I've managed to connect the API and render data, however I'm having trouble extracting the EJSON data into my picture template.
First off, let me show you the code I'm working with. Here is my client code:
function Facebook(accessToken) {
this.fb = Meteor.require('fbgraph');
this.accessToken = accessToken;
this.fb.setAccessToken(this.accessToken);
this.options = {
timeout: 3000,
pool: {maxSockets: Infinity},
headers: {connection: "keep-alive"}
}
this.fb.setOptions(this.options);
}
Facebook.prototype.query = function(query, method) {
var self = this;
var method = (typeof method === 'undefined') ? 'get' : method;
var data = Meteor.sync(function(done) {
self.fb[method](query, function(err, res) {
done(null, res);
});
});
return data.result;
}
Facebook.prototype.getUserData = function() {
return this.query('me');
}
Facebook.prototype.getPhotos = function() {
return this.query('/me/photos?fields=picture');
}
Meteor.methods({
getUserData: function() {
var fb = new Facebook(Meteor.user().services.facebook.accessToken);
var data = fb.getPhotos();
return data;
}
});
Meteor.methods({
getPhotos: function() {
var fb = new Facebook(Meteor.user().services.facebook.accessToken);
var photos = fb.getPhotos;
return photos;
}
});
Here is my client code:
Template.fbgraph.events({
'click #btn-user-data': function(e) {
Meteor.call('getUserData', function(err, data) {
$('#result').text(JSON.stringify(data, undefined, 4));
});
}
});
var fbPhotos = [];
Template.fbgraph.events({
fbPhotos : function(e) {
Meteor.call('getUserData', function(err, data) {
$('input[name=fbPhotos]').text(EJSON.stringify(data, undefined, 4));
});
}
});
Template.facebookphoto.helpers({
pictures: fbPhotos
});
And here are my templates:
<template name="fbgraph">
<div id="main" class="row-fluid">
{{> facebookphoto}}
</div>
<button class="btn" id="btn-user-data">Get User Data</button>
<div class="well">
<pre id="result"></pre>
</div>
</template>
<template name="facebookphoto">
<div class="photos">
{{#each pictures}}
{{> photoItem}}
{{/each}}
</div>
</template>
<template name="photoItem">
<div class="photo">
<div class="photo-content">
<img class="img-rounded" src="{{picture}}">
</div>
</div>
</template>
Right now, I'm testing the data with the id="results" tag and the Facebook API returns data as below.
{
"data": [
{
"picture": "https://photo.jpg",
"id": "1234",
"created_time": "2013-01-01T00:00:00+0000"
},
{
"picture": "https://photo.jpg",
"id": "12345",
"created_time": "2013-01-01T00:00:00+0000"
}
}
However I'm having difficulty pulling each of the pictures out of the EJSON and render them in templates. What I'd like to do is to display each picture in the {{picture}} template. I believe the problem with the code is somewhere in the client, but I'm not sure how to fix it.
Thanks in advance!
It looks like in your client code you have
Template.fbgraph.events({ ... })
defined twice. Did you mean to write:
Template.fbgraph.helpers({
fbPhotos : function(e) {
Meteor.call('getUserData', function(err, data) {
$('input[name=fbPhotos]').text(EJSON.stringify(data, undefined, 4));
});
}
});
A simpler way to do it might just be to call the getUserData method in your facebookphoto template itself, thus:
Template.facebookphoto.helpers({
pictures : function(e) {
Meteor.call('getUserData', function(err, data) {
$('input[name=fbPhotos]').text(EJSON.stringify(data, undefined, 4));
});
}
});