I have a collection that contains blog posts and I'm trying to get them to display on the page. The code below doesn't list out all the blog posts and I'm not sure why. As far as I can tell it is publishing.
Path: blog.html
{{#each Blog}}
<p>{{details}}</p>
{{/each}}
Path: blog.js
Template.blog.onCreated(function() {
var self = this;
self.autorun(function(){
var id = FlowRouter.getParam('id');
self.subscribe('blog', id);
});
});
Path: Blog mongoDB example
{
"_id": "JvLqxFisXc3PLeqSh",
"details": "Test three",
}
Path: publish.js
Meteor.publish('blog', function (id) {
check(id, String);
return Blog.find({});
});
I need to add a helper.
Template.blog.helpers({
blogPost: ()=> {
return Blog.find({});
}
)}
Path: blog.html
{{#each blogPost}}
<p>{{details}}</p>
{{/each}}
Related
With dburles:collection-helpers package you can add collection helpers on any Mongo.collection. But I can't do that on FS.Collection. I get TypeError: Object [object Object] has no method 'helpers'. Transform function doesn't work either.
var createUploader = function(fileObj, readStream, writeStream) {
fileObj.uploadedBy = Meteor.users.find({_id: fileObj.uploader});
readStream.pipe(writeStream);
};
Photos = new FS.Collection("photos", {
stores: [
new FS.Store.GridFS("photos", {transformWrite: createUploader})
],
filter: {
allow: {
contentTypes: ['image/*']
}
}
});
Can't do this? Notice when a photo is inserted from the client FS.File gets userId, hence fileObj.uploadedBy = Meteor.users.find({_id: fileObj.uploader});
Ok I know this is not the not-so-easy solution I was looking for. Since I am using publish-composite package. I can just publish users' data(only profile field) with photos. And on the client I can do template helper like this:
Template.photo.helpers({
photoUploader: function() {
var currentPhoto = Photos.findOne();
var user = Meteor.users.findOne({_id: currentPhoto.uploader});
return user.profile.name;
},
});
and
<template name="photos">
{{#each photos}}
{{> photo}}
{{/each}}
...
then
<template name="photo">
{{photoUploader}}
...
matb33-collection-helpers package works by applying a transform function to a collection. CollectionFS already has its own transform function applied, therefore you cannot overwrite that with ones from the collection helpers package.
As suggested at the issue tracker
Since CFS already applies a transform, it would not be a good idea to use collection helpers. You should be able to do pretty much the same thing by extending the FS.File prototype with your own functions, though.
You could define custom functions on the prototype. The prototype will have access to other properties of the doc through this so you would basically have the same functionality with collection helpers.
Another option would be to store the file related information on the individual file object during the insert as metadata such as:
Template.photoUploadForm.events({
'change .photoInput': function(event, template) {
FS.Utility.eachFile(event, function(file) {
var newPhoto = new FS.File(file);
newPhoto.metadata = {uploadedBy: Meteor.user().profile.name};
Photos.insert(newPhoto, function (err, fileObj) {
if (!err) console.log(fileObj._id + " inserted!")
});
});
}
});
Your code can also be rewritten to implement a beforeWrite filter instead of a transformWrite as in
Photos = new FS.Collection("photos", {
stores: [
new FS.Store.GridFS("photos", {
beforeWrite: function (fileObj) {
fileObj.metadata = {uploadedBy: Meteor.user().profile.name};
}
})
],
filter: {
allow: {
contentTypes: ['image/*']
}
}
});
Finally, you can opt to storing the ID of the user and publishing a reactive join
Photos = new FS.Collection("photos", {
stores: [
new FS.Store.GridFS("photos", {
beforeWrite: function (fileObj) {
fileObj.metadata = {
uploadedBy: Meteor.userId()
};
}
})
],
filter: {
allow: {
contentTypes: ['image/*']
}
}
});
And for the publication, you can use reywood:publish-composite
Meteor.publishComposite('photosWithUsers', function() {
return {
find: function() {
return Photos.find();
},
children: [
{
find: function(photo) {
return Meteor.users.find(photo.uploadedBy, {
fields: {username: 1, 'profile.name': 1}
});
}
}
]
};
});
Of course on the client, you need to subscribe to the photosWithUsers publication.
Now to access that information in the client, since you cannot apply a transform or a helper on the collectionFS documents, you can create a global template helper:
Template.registerHelper('getUsername', function(userId) {
check(userId, String);
var user = Meteor.users.findOne(userId);
return user && user.profile.name + ' (' + user.username + ')';
});
Now you can use that helper in your templates:
<template name="somePhoto">
{{#with FS.GetFile "Photos" photo}}
<img src="{{url}}" alt="This photo has been uploaded by {{getUsername uploadedBy}}">
{{/with}}
</template>
Template.somePhoto.helpers({
photo: function() {
return Photos.findOne();
}
})
Let's say I have two routes.
One route is a "new Item" route and one is a "view Item" route.
In order to create a blank form I was just inserting a "blank" object into the database and then redirecting to the item view.
I understand this may not be the best way to do this, and I'm open to suggestions because I don't find a lot of tutorials that deal with this paradigm. Most just have a separate input form (which I don't want).
So the problem I'm running into is that when I navigate to my /new route, it ends up duplicating all the template content in the dom for my /org route even though it's just getting redirected to my /org route. I can reload the /org route all day with different "org data" and it never duplicates anything in my DOM. But the instant I hit that /new route button, it will add another set of DOM elements in duplicate fashion.
Can someone help me shed some light on what's going on? Here's my /new route code...
Router.route('/new/', {
name: 'new',
action: function() {
var newId = Organizations.insert({
// Set some defaults
init_date: new Date(),
modified_date: new Date()
});
this.redirect('/org/' + newId);
}
});
Here's my /org route code:
Router.route('/org/:_id', {
name: 'org',
subscriptions: function() {
return [
Meteor.subscribe('contacts', this.params._id),
Meteor.subscribe('organization', this.params._id)
];
},
onBeforeAction: function() {
user = Meteor.user();
if (!Roles.userIsInRole(user, ['admin'])) {
this.redirect('/submit-ticket');
this.stop();
} else {
this.next();
}
},
action: function() {
if (this.ready()) {
Session.set('currentOrgId', this.params._id);
this.layout('appLayout');
this.render('orgHead', {
to: 'orghead',
data: function() {
return Organizations.findOne({
_id: this.params._id
});
}
});
this.render('orgInfo', {
to: 'content',
data: function() {
return Organizations.findOne({
_id: this.params._id
});
}
});
} else {
this.layout('appLayout');
this.render('loading', {
to: 'content'
});
}
}
});
I'm reading the book 'Discover meteor' and have a question about pagination(pagination chapter).
I have a code in my router.js:
//router.js
...
PostsListController = RouteController.extend({
template: 'postsList',
increment: 4,
postsLimit: function() {
return parseInt(this.params.postsLimit) || this.increment;
},
findOptions: function() {
return {sort: {submitted: -1}, limit: this.postsLimit()};
},
subscriptions: function() {
this.postsSub = Meteor.subscribe('posts', this.findOptions());
},
posts: function() {
return Posts.find({}, this.findOptions());
},
data: function() {
var hasMore = this.posts().count() === this.postsLimit();
var nextPath = this.route.path({postsLimit: this.postsLimit() + this.increment});
return {
posts: this.posts(),
ready: this.postsSub.ready,
nextPath: hasMore ? nextPath : null
};
}
});
...
Router.route('/:postsLimit?', {
name: 'postsList'
});
And this working fine. My problem description:
I have another route ('/news') and whant to make pagination for this route too. How i should properly extend PostsListController to make it?
Every my post have a tag option, in this case it is a 'news', so i want to see only posts with 'news' tag.
I'm tryed to just copy-paste this controller(PostsListController) and:
renamed it;
set another template;
changed:
posts: function() {
return Posts.find({}, this.findOptions());
}
to:
posts: function() {
return Posts.find({postType: 'news'}, this.findOptions());
}
It not working, on my /page news i can see only all my news articles and spinner. I'm added:
Router.route('/news/:postsLimit?', {
name: 'newsTemplate',
controller: NewsTemplateController
});
But when i'm goind to /news/1 i'm see all my posts(not only one) and button 'show more'.
I think this copy-paste approach so bad but i have not ideas how to make it working proper way.
Your first issue where /news shows all the posts is because your first Route specification is too generic.
Router.route('/:postsLimit?', {
name: 'postsList'
});
This route specification will send all requests with one parameter to the PostsListController
ie. all of these paths will route to the PostsListsController:
/asdf
/test
/news
To fix this, you might want to make the first route more specific:
Router.route('/posts/:postsLimit?', {
name: 'postsList'
});
I am not sure why you are getting more than one item when going to /news/1.
Can you post your code for that controller?
I have a template helper called notifications and I want to return 3 collection cursors to my template, so that I can view all
Template
<ul class="dropdown-menu notification">
{{#if notificationCount}}
{{#each notifications}}
{{> notification}}
{{/each}}
{{else}}
<li><span>No Notifications</span></li>
{{/if}}
</ul>
Helper
notifications: function() {
if (Meteor.user()) {
var accepted = Notifications.find({ origin: Meteor.user().username, status: 'ACCEPTED' });
var denied = Notifications.find({ rival: Meteor.user().username, status: 'DENIED' });
var confirmed = Notifications.find({ rival: Meteor.user().username, status: 'CONFIRMED' });
return accepted, denied, confirmed;
}
}
What is the best way to go about this? Thanks!
The literal answer to your question is to run fetch on all of the cursors and concatenate them into a single array.
return accepted.fetch().concat(denied.fetch(), confirmed.fetch());
Because all of your documents come from a single collection, you can alternatively use a more sophisticated query. Give this a try:
var username = Meteor.user().username;
return Notifications.find({
$or: [
{
origin: username,
status: 'ACCEPTED'
}, {
rival: username,
status: {$in: ['DENIED', 'CONFIRMED']}
}
]
});
I'm having trouble getting Meteor.publish to update in response to a changing form field. The first call to publish seems to stick, so the query operates in that subset until the page is reloaded.
I followed the approach in this post, but am having no luck whatsoever.
Any help greatly appreciated.
In lib:
SearchResults = new Meteor.Collection("Animals");
function getSearchResults(query) {
re = new RegExp(query, "i");
return SearchResults.find({$and: [ {is_active: true}, {id_species: {$regex: re}} ] }, {limit: 10});
}
In client:
Session.set('query', null);
Template.searchQuery.events({
'keyup .query' : function (event, template) {
query = template.find('.query').value
Session.set("query", query);
}
});
Meteor.autosubscribe(function() {
if (Session.get("query")) {
Meteor.subscribe("search_results", Session.get("query"));
}
});
Template.searchResults.results = function () {
return getSearchResults(Session.get("query"));
}
On server:
Meteor.publish("search_results", getSearchResults);
Template:
Search for Animals
<body>
{{> searchQuery}}
{{> searchResults}}
</body>
<template name="searchQuery">
<form>
<label>Search</label>
<input type="text" class="query" />
</form>
</template>
<template name="searchResults">
{{#each results}}
<div>
{{_id}}
</div>
{{/each}}
</template>
Update [WRONG]
Apparently, the issue is that the collection I was working with was (correctly) generated outside of Meteor, but Meteor doesn't properly support Mongo's ObjectIds. Context here and related Stackoverflow question.
Conversion code shown there, courtesy antoviaque:
db.nodes.find({}).forEach(function(el){
db.nodes.remove({_id:el._id});
el._id = el._id.toString();
db.nodes.insert(el);
});
Update [RIGHT]
So as it turns out, it was an issue with RegExp / $regex. This thread explains. Instead of:
function getSearchResults(query) {
re = new RegExp(query, "i");
return SearchResults.find({$and: [ {is_active: true}, {id_species: {$regex: re}} ] }, {limit: 10});
}
At the moment, one needs to do this instead:
function getSearchResults(query) {
// Assumes query is regex without delimiters e.g., 'rot'
// will match 2nd & 4th rows in Tim's sample data below
return SearchResults.find({$and: [ {is_active: true}, {id_species: {$regex: query, $options: 'i'}} ] }, {limit: 10});
}
That was fun.
PS -- The ddp-pre1 branch has some ObjectId functionality (SearchResults = new Meteor.Collection("Animals", {idGeneration: "MONGO"});)
Here's my working example:
UPDATE the original javascript given was correct. The problem, as noted in the comments, turned out to be that meteor doesn't yet support ObjectIds.
HTML:
<body>
{{> searchQuery }}
{{> searchResults}}
</body>
<template name="searchQuery">
<form>
<label>Search</label>
<input type="text" class="query" />
</form>
</template>
<template name="searchResults">
{{#each results}}
<div>
{{id_species}} | {{name}} - {{_id}}
</div>
{{/each}}
</template>
Javascript:
Animals = new Meteor.Collection("Animals");
function _get(query) {
re = new RegExp(query, "i");
console.log("rerunning query: " + query);
return Animals.find({$and: [ {is_active: true}, {id_species: {$regex: re}} ] }, {limit: 10});
};
if (Meteor.isClient) {
Session.set("query", "");
Meteor.autosubscribe(function() {
Meteor.subscribe("animals", Session.get("query"));
});
Template.searchQuery.events({
'keyup .query' : function (event, template) {
query = template.find('.query').value
Session.set("query", query);
}
});
Template.searchResults.results = function () {
return _get(Session.get("query"));
}
}
if (Meteor.isServer) {
Meteor.startup(function() {
if (Animals.find().count() === 0) {
Animals.insert({name: "panda", is_active: true, id_species: 'bear'});
Animals.insert({name: "panda1", is_active: true, id_species: 'bearOther'});
Animals.insert({name: "panda2", is_active: true, id_species: 'bear'});
Animals.insert({name: "panda3", is_active: true, id_species: 'bearOther'});
}
});
Meteor.publish("animals", _get);
}