I have a list of student users and collection of classes. When I publish the list of students I want it to display the classes they are attending. My code currently displays all the classes that every student is attending under each student instead of the classes that are relevant to the individual student.
How can I get it to display the right classes under the right stendents.
Path: classes.js
Template.classes.helpers({
studentList: ()=> {
return Meteor.users.find({_id: { $ne: Meteor.userId() }});
},
classes: ()=> {
return Classes.find({_id: { $ne: Meteor.userId() }});
},
});
Path: classes.html
{{#each studentList}}
{{profile.firstName}}
{{#each classes}}
{{class}}
{{/each}}
{{/each}}
Path: Classes.js
Schemas.Classes = new SimpleSchema({
class: {
type: String
},
teacherUserId: {
type: String,
autoValue: function() {
return this.userId
},
autoform: {
type: "hidden"
}
},
studentUserId: {
type: String,
optional: true,
autoform: {
type: "hidden"
}
}
});
The code above means: "Find all classes where the id of the class isn't equal to the current user's id." I think you want: "Find all classes where the current user's id is in the list of students."
Assuming classes have a students field, which is an array of user ids, you'd do this:
return Classes.find({students: Meteor.userId()});
Thanks to everyone's input my problem is solved! Tim hit the nail on the head. For those of you encountering a similar problem, check out the code below. I hope it helps. Thanks again Tim.
Path: classes.js
Template.classes.helpers({
classes: ()=> {
return JobOffers.findOne({candidateUserId: this._id});
},
});
Path: classes.html
{{#each studentList}}
{{profile.firstName}}
{{#with classes}}
{{class}}
{{/with}}
{{/each}}
When you are defining classes: () => Classes.find({_id: { $ne: Meteor.userId() }}) what you are telling the computer is:
Every time I ask you for the box labeled 'classes' I want you to go
through the box we called 'Classes' and fill 'classes' with everything
you find that doesn't have the '_id' property set to whatever you find
when you look inside of the box that 'Meteor.userId()' gives you.
That is not what you are wanting to ask your worker for. You want to ask your worker:
Every time I ask you for the box labeled 'classes' I want you to go
through the box we called 'Classes' and fill 'classes' with everything
that you find where the '_id' is set to a certain string that I am passing
you.
Which, without really trying to write it for you, it might have something to do with this being used somewhere in your helper function
Related
Is it possible to alter (add more fields for each record) a returned collection in onBeforeAction or similar hook?
I have InvoiceHistory collection which I am paginating through. I also want to display as part of each invoice the company name, business address, email address and VAT registration number - and these four fields are stored in another collection. So I would like to add these four fields to each record returned from InvoiceHistory. If there is another way to do it I am open to suggestions. I am using Alethes meteor pagination which loses the helper fields returned in its itemTemplate when you navigate/browse/page to the second page. Alethes pagination also relies on iron-router, is it maybe possible to achieve what I want with the help of iron-router
========================================================================
#Sean. Thanks. Below is the code that uses meteor publish composite:
if (Meteor.isServer) {
import { publishComposite } from 'meteor/reywood:publish-composite';
publishComposite('invoicesWithCompanyDetails', function(userId, startingDate,endingDate) {
return {
find() {
// Find purchase history for userId and the two dates that were entered. Note arguments for callback function
// being used in query.
return PurchaseHistory.find({Id:userId,transactionDate:{$gte:new Date(decodeURIComponent(startingDate)),
$lt:new Date(decodeURIComponent(endingDate))}});
},
children: [
{
find() {
return CompanySharedNumbers.find(
{ _id:"firstOccurrence" },
{ fields: { companyName: 1, companyAddress: 1 } }
);
}
}
]
}
});
}
Router.route('/store/invoices/:_username/:startingDate/:endingDate', { //:_startingDate/:_endingDate
name: 'invoices',
template: 'invoices',
onBeforeAction: function()
{
...
},
waitOn: function() {
var startingDate = this.params.startingDate;
var endingDate = this.params.endingDate;
return [Meteor.subscribe('systemInfo'),Meteor.subscribe('testingOn'),Meteor.subscribe('invoicesWithCompanyDetails',startingDate,endingDate)];
}
});
Pages = new Meteor.Pagination(PurchaseHistory, {
itemTemplate: "invoice",
availableSettings: {filters: true},
filters: {},
route: "/store/invoices/:_username/:startingDate/:endingDate/",
router: "iron-router",
routerTemplate: "invoices",
routerLayout: "main",
sort: {
transactionDate: 1
},
perPage: 1,
templateName: "invoices",
homeRoute:"home"
});
Instead of adding new fields to all your document. What you could do is to add a helper to load a company document :
Template.OneInvoice.helpers({
loadCompany(companyId){
return Company.findOne({_id: companyId});
}
});
And use it in your template code:
<template name="OneInvoice">
<p>Invoice id: {{invoice._id}}</p>
<p>Invoice total: {{invoice.total}}</p>
{{#let company=(loadCompany invoice.companyId)}}
<p>Company name: {{company.name}}</p>
<p>Company business address: {{company.businessAddress}}</p>
{{/let}}
</template>
This way, the code is very simple and easily maintainable.
If I'm understanding you correctly, this sounds like a job for a composite publication.
Using a composite publication you can return related data from multiple collections at the same time.
So, you can find all your invoices, then using the company ID stored in the invoice objects you can return the company name and other info from the Companies collection at the same time.
I've used composite publications before for complex data structures and they worked perfectly.
Hope that helps
I have a page where I display a list users. I would like the current user "is_teacher" to be able to 'like' the individual users in the list. The likes are only visible to the teacher. I thought the best way to do this would be to add a likes: String in the collection and store the students_Id but my code must be way off because its not working. Can someone checkout what I've done I let me know where I'm going wrong.
Path: Schemas.js
Schema.UserProfile = new SimpleSchema({
likes: {
type: [String],
optional: true
}
});
Path: sudentList.html
<template name="studentList">
{{#each student}}
{{#if like}}
<button class="like">Unlike</button>
{{else}}
<button class="like">Like</button>
{{/if}}
{{/each}}
</template>
Path: studentList.js
Template.studentList.helpers({
like: function() {
return Meteor.users.findOne({likes: this._id});
}
});
Template.studentList.events({
'click #like':function(event,template) {
var student = this._id;
Meteor.users.update({likes: student}, {$set:{likes:!student}});
}
});
Posting as an answer because I can't comment.
It's really unclear what's going on because your code is a bit of a mess, and we need a bit more information than "it's not working" to diagnose the issue.
Some obvious points are:
Your button has a class (.), but your event is bound to an id (#)
Your update function is odd. (To say the least!)
likes is an array, so you wouldn't use $set with a string
student is a string, so !student is just false
Presumably since your schema is for user profiles, your find/update operations should go for profile.likes not just likes
This is just a guess, but I think your event should be more like:
Template.studentList.events({
'click #like':function(event,template) {
var student = this._id;
var teacher = Meteor.userId();
Meteor.users.update({_id: student}, {$push:{"profile.likes": teacher}});
}
});
But it's hard to say because I'm not 100% sure what you're trying to do.
I am new to meteor, and have a basic understanding of what is going on, but I am stuck with this example (the problem has been simplified as much as possible):
I have a template, and a child template:
<template name="test">
{{#each items}}
{{> testItem}}
{{/each}}
{{#each items}}
{{> testItem}}
{{/each}}
</template>
<template name="testItem">
<div {{ b "click: toggle"}}>{{value}}</div>
</template>
Template.test.viewmodel({
items: [],
onCreated: function() {
this.items().push({ value: 'test' });
}
})
Template.testItem.viewmodel({
toggle: function() {
this.value("changed");
}
});
The thing here is we have a single array of items in the viewmodel, and we render it through a child template multiple times.
When we toggle the item, it only toggles the single item template, not the other representation of it. It is behaving like it is copying the value, or some sort of scoping is taking place.
My expectation would be the second item to also change, but this is not the case - what am I missing, or misunderstanding here?
EDIT - Additional Investigation
If I change the item through the parent, and notify it has changed, the changes propogate throughout the child templates
Template.testItem.viewmodel({
toggle: function () {
this.parent().items()[0].value = "changed";
this.parent().items().changed();
}
});
Thanks!
You're right, when you do this.value("changed"); you're changing the value of the testItem view model, not the parent array. If you're going to modify the properties of objects in an array I highly recommend you use a client side Mongo collection instead. It will save you a lot of headaches.
Items = new Mongo.Collection(null);
Template.test.viewmodel({
items: function() {
return Items.find();
},
onCreated: function() {
Items.insert({ _id: "1", value: 'test' });
}
})
Template.testItem.viewmodel({
toggle: function() {
Items.update({ _id: this._id() }, { value: 'changed' });
}
});
btw, I rarely check SO. You will get quicker responses on viewmodelboard.meteor.com
I have some documents in my base:
//example docs
{"_id": "qwerty12345", "name": "Bob", "cards":["cardId1", "cardId2", "cardId3"]}
I'm using this for inserting data:
Template.insert.events({
'click add': function(){
if(confirm("Add card?"));
mycollection.update({_id: Session.get('fooId')}, { $addToSet: { cards: this._id}})
}
});
Then i'm using this helper for my template:
Template.index.helpers({
cards: function(){
query = mycollection.findOne({_id: Session.get('fooId')});
return query.cards;
}
});
And in template:
<img src="{{img}}" class="add">
{{#each cards}}
{{this}}<br>
{{/each}}
This works perfecty, but i have a trouble:
As you see, every image have id and url({{image}}), i'm need to add image url to 'mycollection' too for every card(on click).
How to make it?
And the second problem:
How to allow mongo insert duplicates to "cards" array?
Do you mean every card has id and image field? I guess so.
You can add nested object to an array fields. Like that
mycollection.update({_id: Session.get('fooId')}, { $addToSet: { cards: {id: this._id, image: this.image}}}).
In the template:
{{#each cards}}
{{this.id}}: {{this.image}}<br>
{{/each}}
For second problem: You can use $push instead of $addToSet
I want to render a reactive template based on this document:
Sprint:
WorkStories:
Tasks
I know this can be done by making a Meteor collection for each "level," but that means the result is actually being stored as a seperate document in the database. I want to know if its possible to have one collection/document for Sprint, that has standard collection of WorkStories with a standard collection of Tasks each, rendered to a reactive template.
I've seen [Meteor.deps.Context][1], but I can't figure out how to wire it up (or even if it's the right tool), and none of the examples do anything like this.
I've also seen [this question][2], but he seems to be asking about connecting related-but-separate documents, not rendering a single document.
Because database queries on collections are already reactive variables on the client, the below will render one Sprint document with nested WorkStories that has nested Tasks in a template:
HTML:
<head>
<title>Sprints Example</title>
</head>
<body>
{{> sprints }}
</body>
<template name="sprints">
{{#each items}}
<div>Name: {{name}}</div>
{{#each this.WorkStories}}
<div>{{name}}</div>
{{#each this.Tasks}}
<div>{{name}}</div>
{{/each}}
{{/each}}
{{/each}}
</template>
Javascript:
Sprints = new Meteor.Collection("sprints");
if (Meteor.isClient) {
Template.sprints.items = function () {
return Sprints.find({});
};
}
if (Meteor.isServer) {
Meteor.startup(function () {
if (Sprints.find().count() === 0) {
Sprints.insert({ name: 'sprint1', WorkStories: [{ name: 'workStory1', Tasks: [{ name: 'task1' }, { name: 'task2' }, { name: 'task3' }] }, { name: 'workStory2', Tasks: [{ name: 'task1' }, { name: 'task2' }, { name: 'task3' }] }] });
}
});
}
UPDATE WITH ANSWER
Per #Paul-Young's comment below, the problem with my usage of $set was the lack of quotes in the update. Once the nested object renders in the Template, as of Meteor 0.5.3 you can update sub arrays simply:
Sprints.update(Sprints.findOne()._id, { $set: { "WorkStories.0.name": "updated_name1" } });
BACKGROUND INFO
This does load the initial object, but updating seems problematic. I was able to get the Template to rerender by calling the below in the console:
Sprints.update(Sprints.findOne()._id, { name: 'sprint777', WorkStories: [{ name: 'workStory1232', Tasks: [{ name: 'task221' }, { name: 'task2' }, { name: 'task3' }] }, { name: 'workStory2', Tasks: [{ name: 'task1' }, { name: 'task2' }, { name: 'task3' }] }] })
Which follows these rules, per the Meteor Docs:
But if a modifier doesn't contain any $-operators, then it is instead interpreted as a literal document, and completely replaces whatever was previously in the database. (Literal document modifiers are not currently supported by validated updates.
Of course, what you want is to use the $set style operators on nested documents and get the templates to re-render when their nested properties change without having to replace the entire document in the table. The 0.5.3 version of Meteor included the ability to search for sub-arrays:
Allow querying specific array elements (foo.1.bar).
I've tried to do the . sub array search and haven't been able to update the WorkStories subdocument of the original entity yet, so I posted a question in the google talk.
Hope this helps