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']}
}
]
});
Related
I am working through this Meteor tutorial and I have updated imports/ui/task.html to only show the tasks to the user who created them as follows:
<template name="task">
{{#if isOwner}}
<li class="{{#if checked}}checked{{/if}} {{#if private}}private{{/if}}">
<button class="delete">×</button>
<input type="checkbox" checked="{{checked}}" class="toggle-checked" />
<span class="text">{{text}}</span>
</li>
{{/if}}
</template>
However, I still have the incomplete count showing tasks by all users and I want to change it to just the user who is logged in. This is the part of imports/ui/body.js that I think needs to be changed.
Template.body.helpers({
tasks() {
const instance = Template.instance();
if (instance.state.get('hideCompleted')) {
// If hide completed is checked, filter tasks
return Tasks.find({ checked: { $ne: true } }, { sort: { createdAt: -1 } });
}
// Otherwise, return all of the tasks
// Show newest tasks at the top. This is the meat of the thing!
return Tasks.find({}, { sort: { createdAt: -1 } });
},
incompleteCount() {
return Tasks.find({ checked: { $ne: true } }).count();
},
});
You only need to filter on ownerId which is used in this tutorial to associate a task with a user.
incompleteCount() {
return Tasks.find({ ownerId: Meteor.userId(), checked: { $ne: true } }).count();
},
Note that when you use multiple criteria like this they are implicitly ANDed.
I'm struggling to implement reactive tables based on a FS.Collection object. I've tried both aldeed/meteor-tabular and aslagle/reactive-table but both fail because the collection doesn't appear to exist. However, if I subscribe and retrieve fields from the Collection without using a reactive table package then the data displays just fine. What am I missing? It can't be a coincidence that both packages fail to work...
Here's my implementation with the aslagle/reactive-table package...
//template
<template name="documentTable">
{{#if Template.subscriptionsReady}}
{{> reactiveTable settings=settings}}
{{else}}
{{> spinner}}
{{/if}}
{{#if currentUser}}
{{> fileUpload}}
{{/if}}
</template>
//documents js
Template.documents.onCreated( () => {
p_id = FlowRouter.current().params.id;
Template.instance().subscribe('documents', p_id);
});
Template.documents.helpers({
documents: function () {
return Documents.find();
},
settings: function () {
return {
collection: documents,
showFilter: false,
rowsPerPage: 5,
showNavigation: auto,
showRowCount: true,
fields: ['_id','userId','propertyId','uploadedAt']
};
}
});
//collection definition
if (Meteor.isServer) {
var docStore = new FS.Store.S3("documents", {
region: "eu-west-1",
accessKeyId: (Meteor.isServer && !process.env.AWS_ACCESS_KEY_ID ? Meteor.settings.AWSAccessKeyId : null),
secretAccessKey: (Meteor.isServer && !process.env.AWS_SECRET_ACCESS_KEY ? Meteor.settings.AWSSecretAccessKey : null),
bucket: Meteor.isServer && process.env.AWS_S3_BUCKET || Meteor.settings.AWSBucket,
folder: "documents"
});
Documents = new FS.Collection("Documents", {
stores: [docStore],
filter: {
allow: {
contentTypes: ['application/pdf']
}
}
});
}
// end server
if (Meteor.isClient) {
var docStore = new FS.Store.S3("documents");
Documents = new FS.Collection("Documents", {
stores: [docStore],
filter: {
allow: {
contentTypes: ['application/pdf']
}
}
});
}
// end client
// allow rules
Documents.allow({
insert: function(userId) {
// only allow insert from signed in user
return userId != null;
},
update: function(userId) {
// only allow update from signed in uesr
return userId != null;
},
download: function() {
return true;
},
});
In the reactive-table case I'm getting the error that the argument is not an instance of Mongo.Collection, a cursor or an array while with meteor-tabular it fails to start because it encounters a ReferenceError and states that Documents isn't defined.
Anyone any thoughts on this?
I'm using aslagle/reactive-table with mongo quite well, with a pub/sub model; I don't know what your new FS is? is that a mongo collection?
I have something like this when I use the reactive-table...
//on the server
Documents = new Mongo.Collection('documents');
//on the client js
Documents = new Mongo.Collection('documents');
Template.documents.helpers({
settings: function () {
return {
collection: Documents.find(),
rowsPerPage: 5,
showFilter: false,
showNavigation: auto,
showRowCount: true,
fields: [
{key: '_id',
label: 'ID' },
{key: 'userId',
label: 'User#' },
{key: 'propertyId',
label: 'Property#' },
{key: 'uploadedAt',
label: 'Uploaded' },
]
};
}
});
//on the client html file
{{> reactiveTable class="table table-bordered table-hover" settings=settings}}
I currently use meteor for a microproject of mine to get a bit usage experience with it. Shortly after setting up I ran into some trouble getting Data i recieve from an API call to a third party site to the client into the template. I checked the usual places for answers and found some information but nothing seems to get it working for me.
So I have a simple API Call to the Steam Web Api:
Meteor.methods({
getPlayerStats: function() {
return HTTP.get("http://api.steampowered.com/ISteamUserStats/GetUserStatsForGame/v0002/?appid=730&key=XXXXXXXXXXXXXXX&steamid=XXXXXXXX");
}
});
(Api key and steam id removed for anonymity purpose, but the call indeed returns a valid response)
So I use Iron Router for template rendering.
Router.route('/profile/:steamid', {
name:'profile',
template: 'profile',
data: function() {
Meteor.call('getPlayerStats', function(err, res) {
if(err) {
return {err: err, stat: null};
} else {
var redata = JSON.parse(res.content);
var stats = redata.playerstats.stats;
console.log({err: null, stats: stats});
return {err: null, stats: stats};
}
});
}
});
So as you can see i return an object in the data method containing either err or a parsed version of the result i get from the api call. The console.log actually returns everything as intended in the client browser, that is an object like this:
{err: null, stats: [{name: "xx", value: "XY"},...]}
And now the part that actually gets me wondering, the template:
<template name="profile">
<p>{{err}}</p>
<ul>
{{#each stats}}
<li>{{name}} - {{value}}</li>
{{/each}}
</ul>
</template>
Which fails to render anything, not the err (which is null and therefor not very important) but neither the stats array.
Anyone has any idea where I went wrong on this one?
You cannot return data from asynchronous call. Instead, You can do it in the template's created function by using ReactiveVar or Session Variable like this
Template.profile.created = function () {
// or Template.profile.onCreated(function () {
var template = this;
template.externalData = new ReactiveVar(null);
Meteor.call('getPlayerStats', function(err, res) {
if(err) {
template.externalData.set({err: err, stat: null});
} else {
var redata = JSON.parse(res.content);
var stats = redata.playerstats.stats;
console.log({err: null, stats: stats});
template.externalData.set({err: null, stat: stats});
}
});
};
// }); //comment the above line and use this, if you used onCreated instead of created.
Then in your helpers,
Template.profile.helpers({
externalData: function () {
var template = Template.instance();
return template.externalData.get();
}
});
Then in your template html,
<template name="profile">
{{#if externalData}}
{{#if externalData.err}}
<p>There is an error. {{externalData.err}}</p>
{{else}}
<ul>
{{#each externalData.stats}}
<li>{{name}} - {{value}}</li>
{{/each}}
</ul>
{{/if}}
{{/if}}
</template>
I am having a problem getting my Insert Autoform to work properply. I am trying to have it similar to the example http://autoform.meteor.com/insertaf and my code is below. I have already removed insecure and autopublish
client/templates/venues/venue_submit.html
<template name="venueSubmit">
<!-- {{> quickForm collection="Venues" id="venueSubmit" type="insert"}} -->
{{#if isSuccessfulvenueSubmit }}
<h2>Thanks for the Venue </h2>
{{else}}
{{#autoForm id="insertVenueForm" type="insert" collection=Collections.Venues omitFields="createdAt" resetOnSuccess=true}}
{{> afQuickField name="Venue"}}
<div class="form-group">
<button type="submit" class="btn btn-primary">Add Venue</button>
<button type="reset" class="btn btn-default">Reset Form</button>
</div>
{{/autoForm}}
{{/if}}
</template>
client/templates/venues/venue_submit.js
Schemas = {};
Template.registerHelper("Schemas", Schemas);
Schemas.Venue = new SimpleSchema({
Venue: {
type: String,
label: "Venue Name",
max: 200,
autoform: {
placeholder: "Name of the Venue"
}
},
....
});
AutoForm.debug()
var Collections = {};
Template.registerHelper("Collections", Collections);
Venues = Collections.Venues = new Mongo.Collection("Venues");
Venues.attachSchema(Schemas.Venue);
Venues.allow({
insert: function (userId, doc) {
return true;
},
remove: function (userID, doc, fields, modifier) {
return true;
},
remove: function (userId, doc) {
return true;
}
});
if (Meteor.isClient) {
Meteor.subscribe("Venues")
};
server/Publications.js
Meteor.publish('venue', function () {
return Venues.find(id);
});
The AutoForm insert type generates a document and inserts in on the client. Without the autopublish and insecure packages installed you need to make sure to subscribe to the appropriate collection on the client. It does not exist if you do not subscribe to it.
if (Meteor.isClient) {
Meteor.subscribe("Venues")
}
Your problem is that you have venue_submit.js inside the client folder, but its contents are not intended solely for the client.
You can leave venue_submit.html exactly as it is.
Change venue_submit.js to:
Template.registerHelper("Schemas", Schemas);
AutoForm.debug();
Template.registerHelper("Collections", Collections);
Meteor.subscribe("Venues");
You only need the lines here that relate to the client: the two template helpers, the AutoForm debug (which you don't need except for debugging), and the subscribe to the Venues collection.
Change server/Publications.js to include everything related to the server side:
Meteor.publish('Venues', function () {
return Venues.find({});
});
Venues.allow({
insert: function (userId, doc) {
return true;
},
remove: function (userID, doc, fields, modifier) {
return true;
},
remove: function (userId, doc) {
return true;
}
});
It includes the publish function and the permissions on the collection.
Now create lib/schema.js:
Schemas = {};
Schemas.Venue = new SimpleSchema({
Venue: {
type: String,
label: "Venue Name",
max: 200,
autoform: {
placeholder: "Name of the Venue"
}
}
});
Collections = {};
Venues = Collections.Venues = new Mongo.Collection("Venues");
Venues.attachSchema(Schemas.Venue);
Everything in lib will be available to both the client and server and the contents of this folder will be loaded first so the schema and collection definitions will be available to all the rest of the code. Note the lack of a var keyword for the Collections. Using var sets the scope to only within the file. Omitting it makes the Collections variable available throughout your code.
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);
}