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

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>

Related

Meteor - Reloading template section after variable change

i want to refresh/reload a part of my template after a variable change so that if the variable is true it shows a content A or else it will show content B. I'm sure this is a quite simple question but i'm having troubles on finding the solution.
Something like this:
Template.x.created = function() {
this.variable = false;
}
Template.x.helpers({
'getValue': function(){
return this.variable;
}
});
Template:
<template name="x">
{{#if getValue}}
<content A>
{{else}}
<content B>
{{/if}}
</template>
You need to create a reactive data source to get the template helper to re-run when the variable changes, as a normal variable won't let the helper know when it changes value. The simplest solution is to use ReactiveVar:
Template.x.onCreated(function() {
this.variable = new ReactiveVar(false);
});
Template.x.helpers({
'getValue': function() {
// Note that 'this' inside a template helper may not refer to the template instance
return Template.instance().variable.get();
}
});
If you need to access the value somewhere outside this template, you can use Session as an alternative reactive data source.
#Waiski answer is a good one, but I want to share a simple Template helper I build because a lot of Templates need this:
Using registerHelper you can build a global helper like so:
Template.registerHelper('get', function (key) {
let obj = Template.instance()[key]
return (obj && obj.get) ? obj.get() : obj
})
Use it in every template:
Template.x.onCreated(function() {
this.foo = new ReactiveVar(true)
this.bar = new ReactiveVar('abc')
})
Html:
{{#let foo=(get 'foo')}}
{{#if get 'bar'}}
Bar is true. Foo: {{foo}}
{{/if}}
{{/let}}

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.

With Iron Router, Find() and FindOne()

I loaded Iron:Router with my Meteor, and now I'm having difficulties loading both all of a collection, and one specific entry.
What I'm trying to do: In the Nav Bar, I want to list all of a user's previous entries. I got that working great. It lists each into a drop down, provides a proper link, and loads up only what the user has previous input. In the body it's suppose to load up specific information for each entry.
Whats not working: It's either not giving me a findOne() where the params._id equal the id in the route, or it's not loading anything. The location is correct. It's just not loading the info like it should.
UPDATE: I moved some things around and got the 'name' field to print out, still not able to get it to verify owner just yet. Console prints out: "Exception in template helper: ReferenceError: currentCharacter is not defined" Replacing with current code.
What am I doing wrong? Below is the code:
Route:
this.route('character/:_id', {
template: 'character',
subscriptions: function() {
return Meteor.subscribe("characterlist");
return Meteor.subscribe("characterlist", this.params._id);
},
data: function () {
var routeid = this.params._id;
return {
currentCharacter: CharList.findOne({_id: routeid}),
characterlist: CharList.find({'owner':Meteor.userId()})
};
}
});
Template Helper Class:
Template.character.helpers({
characterlist: function () {
return CharList.find({}, {sort: {createdAt: -1}});
},
currentCharacter: function () {
return CharList.findOne({'_id':Router.current().params._id});
},
isOwner: function () {
return currentCharacter.owner === Meteor.userId();
}
});
HTML:
<template name='character'>
<div class="container-body">
{{#if currentUser}}
<div class="well">
{{currentCharacter.name}}
{{#with currentCharacter}}
{{#if isOwner}}
<p>Character: {{name}}</p>
{{else}}
<p>You are not approved to make spends for this character.</p>
{{/if}}
{{/with}}
</div>
{{else}}
<h4>Please log in.</h4>
{{/if}}
</div>
</template>
Let me sigh metaphorically out loud online. Like usual when I ask for help I find what i did wrong.
It needs to get "this" object and pull the "owner" type out. Once it has "this.owner" it can verify the user is of course the correct owner. I was trying to be smart and didn't realize it was asking for the wrong information for four hours! Code below:
isOwner: function () {
return this.owner === Meteor.userId();
}

IronController Helpers not working

i am farely new to meteor, so it might be obvious.
I am trying to define some routes:
Router.route('/translations', {name:'translation.index'});
Router.route('/translations/:_id', {name:'translation.show'});
I also have defined a Controller which should define how to get the Data:
TranslationIndexController = RouteController.extend({
template: 'TranslationIndex',
data: function () {
return Translations.find();
},
sayHello: function(){
return 'hello';
}
});
I have some Collection that is just fetched and some random tempalte helper. My template looks like this:
<template name="TranslationIndex">
TestOutput:
{{sayHello}}
{{#each data}}
<li>askljdfh</li>
{{/each}}
</template>
But neither my hello nor my collection is shown. PS: I have checked if my collection contains data by using Translations.find().count() in the console.
You need to call the controller in your route, e.g.,
Router.route('/translations', {
name:'translation.index',
controller: TranslationIndexController
});
The data should then be available.

Meteor template.rendered and this.data access

I have a template
<template name='order'>
{{vendor.name}}
</template>
rendered with
Template.order.vendor = function () {
return {name: 'Chanel', address: 'Paris' };
};
When I try to access this.data in
Template.order.rendered = function () {
console.log(this.data);
};
I get 'undefined'.
What is the correct way of getting e.g. vendor.name and vendor.address in Template.order.rendered?
Thank you.
In Template.rendered, this.data corresponds to the data the template was "fed" with, either as a parameter or using the {{#with}} construct.
vendor is just a helper function returning data available in the order template, but not binded to "this.data".
SO to solve your problem, you have a number of options :
Defining a parent template and moving the vendor helper to this parent, then you can alternatively call order with vendor as a parameter or use a {{#with block}}
<template name="parent">
{{> order vendor}}
{{#with vendor}}
{{> order}}
{{/with}}
</template>
<template name="order">
{{name}}
</template>
Template.parent.vendor=function(){
return{
name:"Chanel",
address:"Paris"
};
};
Template.order.rendered=function(){
// this.data == vendor object returned in parent helper
console.log(this.data);
};
You can also register a global helper, eliminating the need for an encapsulating parent template :
Handlebars.registerHelper("vendor",function(){
return{
name:"Chanel",
address:"Paris"
};
});
Template.order.rendered = function () {
console.log(Template.order.vendor());
}

Resources