Meteor with ViewModel package not updating accross multiple child templates - meteor

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

Related

rendering alternate components under the same flow:router URL

How can I render two different views in a one page app without changing URLs. I'm using meteor with the default blaze as well as the flow:router package. Right now I have it set up like this:
routes.js..
FlowRouter.route("/", {
name: "App.home",
action() {
BlazeLayout.render("App_body", {
main: "App_home",
mainContent: "calendar"
});
}
});
FlowRouter.route("/list", {
name: "App.list",
action() {
BlazeLayout.render("App_body", { main: "App_home", mainContent: "list" });
}
});
but this way I'm using the url /list and i dont want that. I would like to simply render an alternate component template in the same url. I'm very new to coding so forgive me if this is obvious. Essentially I just want two different view styles: a list and a calendar. So I would like a way to set it up so that a spacebars template can be rendered if a certain button is clicked, and a different one can be rendered instead if another button is clicked.
Thanks so much for any help, i've been at this for a couple of days :)
Create another template, which renders particular view conditionally. Something like this:
FlowRouter.route("/", {
name: "App.home",
action() {
BlazeLayout.render("App_body", {
main: "App_home",
mainContent: "listOrCal"
});
}
});
<template name="listOrCal">
{{#if showList}}
{{> list}}
{{else}}
{{> calendar}}
{{/if}}
<button id="switchView">Switch view</button>
</template>
Template.listOrCal.onCreated(function listOrCalOnCreated() {
this.showList = new ReactiveVar(true);
})
Template.listOrCal.helpers({
showList() {
return Template.instance().showList.get();
}
})
Template.listOrCal.events({
'click #switchView' {
let showList = Template.instance().showList.get();
Template.instance().showList.set(!showList);
}
})
You can handle this within a single template like so:
FlowRouter.route('/', {
name: 'App.home',
action() {
BlazeLayout.render('App_body', { main: 'App_home', mainContent: 'ListOrCalendar' });
}
And then the ListOrCalendar template would look like this:
{{#if displayList}}
{{> List}}
{{else}}
{{> Calendar}}
{{/if}}
<button>Switch</button>
You would set up a ReactiveVar in the ListOrCalendar template:
Template.ListOrCalendar.onCreated(function() {
const instance = this;
instance.displayList = new ReactiveVar(true);
});
See ReactiveVar explanation here (ignore Session)
Then you would have a helper which returns the value of your ReactiveVar:
Template.ListOrCalendar.helpers({
displayList() {
const instance = Template.instance();
return instance.displayList.get();
}
});
Finally, you would hook up an event to change the value of displayList to switch between templates:
Template.ListOrCalendar.events({
"click button"(event, instance) {
const displayListCurrent = instance.displayList.get();
const displayListNew = !displayListCurrent;
instance.displayList.set(displayListNew);
// or, more concisely, instance.displayList.set(!instance.displayList.get());
}
});
So, in summary:
When the template is created, your ReactiveVar is true
so your displayList returns true
so the #if displayList condition in the template is satisfied
and so the List template is displayed
When the button is clicked
The ReactiveVar is set to false
so the displayList helper returns false
so the #if displayList condition in the template is not satisfied and it goes to the else statement
and so, finally, the Calendar template is displayed
When the button is clicked again, the ReactiveVar is toggled back to true, and on we go as above
This might seem daunting or over-complicated, but there's nothing fancy going on here at all. You'll get used to it pretty quickly

Meteor {{#if}} helper if object or field exists

I am looping through documents in a template with Blaze spacebars to create a list
<template name="objectTemplate">
{{#if checkIfObjectExists}}
({{document.[0].object.object1}})
{{/if}}
</template>
I know that in some documents, some objects do not exist in that object position. normally if I didnt have (), it would be blank and I could move on, but in this case when empty, I will have a lot of () which is not good.
I created a helper, but its not working. I have tried null, 0, typeOf etc and still cant get it right. Anyhow here is the helper
Template.objectTemplate.helper ({
checkIfObjectExists: function() {
if (this !== 'null') {
return true;
} else {
return false;
}
}
});`
You can use _.has(object, key) if you want to check if the object document.[0].object has the property object1 set. The function _.isObject(value) will check instead if document.[0].object.object1 is an Object (this also includes arrays).
So, depending on your requirements, your template helpers should look like this:
Template.objectTemplate.helper({
checkIfObjectPropertyExists: function() {
return _.has(this.document[0].object, "object1");
},
checkIfPropertyIsObject: function() {
return _.isObject(this.document.[0].object.object1);
}
});
You could also register an Underscore.js global template helper and then use it directly in your Meteor templates:
Template.registerHelper('_', function () {
return _;
});
<template name="objectTemplate">
{{#if _.has this.document.[0].object 'object1'}}
({{document.[0].object.object1}})
{{/if}}
</template>
Your if is not in the right place. Your objectTemplate is probably called that way :
{{#each datum in data}}
{{>objectTemplate data=data}}
{{/each}}
So it's always rendered. Even if the datum is empty. The this you check in your helper will always be true, it's the template himself.
So, you should call it that way :
{{#each datum in data}}
{{#if datum.thingToTest}}
{{>objectTemplate datum=datum}}
{{/if}}
{{/each}}
The entire sub template won't be called.

Meteor data context to child template

I have a route and a template, that has the right data context as defined in the routes.js:
this.route('entrant_view_race', {
path: '/organise/entrants/:_id/',
yieldTemplates: navOrganise,
waitOn: function() {
return Meteor.subscribe('entrants');
},
data: function() {
return Races.findOne(this.params._id);
}
});
The data context is set to the data function above no problems. Within entrant_view_race template/route we have another template:
<div class="row">
<div class="col-md-4">
{{> chart_entrant_status}}
</div>
</div>
Now within chart_entrant_status is a subscription which passes a param which is defined in the data context:
Meteor.subscribe('entrantStatusCount', this._id);
but this._id is undefined. I believed that whatever the data context of the parent is passed to child templates unless you explicitly define such as {{> chart_entrant_status dataContext}} ? How do I pass the _id of the parent context to the child template (I don't want to use session variables).
EDIT:
chart_entrant_status template looks like this:
<template name="chart_entrant_status">
{{#chart_container title="Entrants" subtitle="Status" class="race-status"}}
{{_id}}
<div id="chart"></div>
{{> table_entrant_status}}
{{/chart_container}}
</template>
Note {{_id}} is rendered fine so the context is alive to that point. And the subscription when the template is rendered:
Template.chart_entrant_status.rendered = function() {
Meteor.subscribe('entrantStatusCount', this._id); // this._id is undefined - if I substitute for the actual id as a string Ba48j2tkWdP9CtBXL I succeed....
}
But no cigar... struggling to find where I lose the data context...
EDIT2: This returns this._id fine...
Template.chart_entrant_status.helpers({
stuff: function() {
return this._id; // returns the right id in {{stuff}} in the template
}
})
So is the data context not available to Template.chart_entrant_status.rendered?
EDIT4: solved. It's this.data._id.... ahhhh
this.data._id was the correct answer

In iron router, how to avoid recompute the entire data field when only a subset is changed

In Iron-router, we can pass the data to a page in the data field. For example:
Router.map(function () {
this.route('myroute', {
path: '/route',
template: 'myTemplate',
data: function () {
return {
title: getTitle(),
description: getDescription(),
}
}
});
});
In the template, title and description are actually some value passed to subtemplates:
<template name="myTemplate">
{{> titleTemplate title}}
{{> descriptionTemplate description}}
</template>
Since the data field in the iron-router is reactive, whenever a session variable change, the data field is recalculated.
In this case, however, the session variable in getTitle function only changes the template "titleTemplate", and the session variable in getDescription() function only changes the template "descriptionTemplate".
If the session variable in the getTitle() function changes, I would like to only execute the getTitle() function, and do not execute the getDescription() function. If possible, I would also like to only render the "titleTemplate" and do not render "descriptionTemplate".
I wonder whether that is possible. If this is not the right way of writing the Meteor application, what is a better way to do it?
Thanks.
This is an interesting situation. Despite the fact that the getTitle and getDescription functions may be dependent on completely different reactive variables, they will both be recomputed whenever either one of them changes.
One possible solution is to pass the functions themselves instead of the result of calling the functions. That may or may not be convenient depending on how they are used in the sub-templates, but it will prevent them from both being run every time. Here is a simple example:
<template name="myTemplate">
{{> titleTemplate title}}
{{> descriptionTemplate description}}
</template>
<template name="titleTemplate">
<p>{{excitedTitle}}</p>
</template>
<template name="descriptionTemplate">
<p>{{this}}</p>
</template>
var getTitle = function() {
console.log('computed title');
return Session.get('title');
};
var getDescription = function() {
console.log('computed description');
return Session.get('description');
};
Router.map(function() {
this.route('home', {
path: '/',
template: 'myTemplate',
data: function() {
return {
title: getTitle,
description: getDescription
};
}
});
});
Meteor.startup(function() {
Session.setDefault('title', 'title1');
Session.setDefault('description', 'description1');
});
Template.titleTemplate.excitedTitle = function() {
return "" + (this.toUpperCase()) + "!";
};
From the console you can change the session variables (title and description) and you will see that only one function will be run at a time. I hope that helps.
One way to solve this is to not use the data context, but just use template specific helpers. Since I don't know what your getTitle and getDescription function do, I can't tell whether that is an option for you. It depends on whether you need to use the this object in those functions and need this to refer to the route object or not. If not, then the following seems like the better solution:
JS:
Router.map(function () {
this.route('myroute', {
path: '/route',
template: 'myTemplate'
});
});
Template.myTemplate.title = getTitle;
Template.myTemplate.description = getDescription;
HTML:
<template name="myTemplate">
{{> titleTemplate title}}
{{> descriptionTemplate description}}
</template>

in Meteor, how do i update a property on only one instance of a template?

If I have an {{# each}} binding in Meteor, and I want to update a property on only one instance of the template inside the #each. How would I do that? I've tried setting a value on the "template" object inside the events map, but that doesn't seem to be reactive. I've also tried binding to a Session property, but that will cause every instance to update instead of just the one I want...
for example:
{{#each dates}}
{{> dateTemplate}}
{{/each}}
<template name="dateTemplate">
{{date}}
<span style="color: red;">{{errorMsg}}</span> <--- how do i update errorMsg?
</template>
Template.dateTemplate.events({
'click': function(event, template) {
template.errorMsg = 'not valid'; <--- this doesn't do anything
}
});
EDIT TO ADDRESS ANSWER BELOW:
Template.dateTemplate.events({
'click': function(event, template) {
template.errorMsg = function() { return 'not valid';} <--- this also doesn't do anything
}
});
You don't have to use handlebars for this, because its not something that needs reactivity to pass the message through, reactive variables work best with db data, or data that would be updated by another client over the air.
You could use JQuery (included by default) to update it, it can also get a bit fancier:
<template name="dateTemplate">
{{date}}
<span style="color: red;display: none" class="errorMessage"></span>
</template>
Template.dateTemplate.events({
'click': function(event, template) {
$(template.find('.errorMessage')).html('Your Error Message').slideDown();
}
});
Ive edited it so the error is hidden by default, and slides down with an animation
I'm experimenting handling this by passing a different reactive object to each instance of the template. Then the template can bind to the reactive object (which is unique per instance) and we don't have any extra boilerplate.
It ends up looking like this:
Initial render:
Template.firstTemplateWithPoll(ContextProvider.getContext())
Template.secondTemplateWithPoll(ContextProvider.getContext())
// (I actually pass getContext an identifier so I always get the same context for the same template)
JS:
Template.poll.events = {
'click .yes' : function() {
this.reactive.set('selection', 'yes');
},
'click .no' : function() {
this.reactive.set('selection', 'no');
}
};
Template.poll.selection = function(arg) {
return this.reactive.get('selection');
}
Template:
<template name="poll">
<blockquote>
<p>
Your selection on this poll is {{selection}}
</p>
</blockquote>
<button class='yes'>YES</button>
<button class='no'>NO</button>
</template>
template.errorMsg should be a function that returns your error.
Template.dateTemplate.events({
'click': function(event, template) {
template.errorMsg = function() { return 'not valid'; };
}
});

Resources