Meteor - Reloading template section after variable change - meteor

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}}

Related

Access an original TemplateInstace from the helper in Meteor

Could anybody point me how to access an original TemplateInstance from the meteor helper. I'm aware of the Template.instance() but it appears to return the template instance where the helper was called, not the template instance for which the helper was defined.
Imagine we have two tiny templates:
<template name='demo'>
<h1>{{helper}}</h1>
{{# border}}
<h2>{{helper}}</h2>
{{/border}}
</template>
<template name='border'>
<div style="border:1px solid red">
{{> UI.contentBlock}}
</div>
</template>
With the following behavior:
Template.demo.created = function() {
this.result = "OK";
}
Template.demo.helpers({
helper: function() {
var tmpl = Template.instance();
return tmpl.result || "FAILED";
}
});
I've expected to obtain two "OK" for the demo template: the second one should be in the red border. But since Template.instance() returns original TemplateInstance only when helper is called at the top level of its owner template the result is "FAILED" (of course in the red border).
Question: Is there any public api to get the original TemplateInstance (without need to traverse view/parentView/_templateInstace)?
I think the best way to do this might be to either just set a Session variable, or use a Reactive Variable (using the reactive-var package - here is the documentation).
I've made a meteor pad to show how this more - here.
Basically:
Template.demo.created = function() {
result = new ReactiveVar('OK');
}
Template.demo.helpers({
helper: function() {
return result.get() || "FAILED";
}
});
I think your main problem is that you not setting a template instance variable correctly. Try the below code...
Set an instance variable:
Template.instance().result.set("OK");
Get an instance variable:
Template.instance().get("result");
So your updated code would be:
Template.demo.created = function() {
Template.instance().result.set("OK");
}
Template.demo.helpers({
helper: function() {
return Template.instance().get("result") || "FAILED";
}
});
It seems that it's known and already fixed (?) Meteor bug. More here: https://github.com/meteor/meteor/issues/3745
Comment from rclai on GitHub:
This was already addressed and fixed for the next release.
Run meteor like this, not sure if it still works:
meteor --release TEMPLATE-CURRENT-DATA#0.0.1
Another alternative is to use aldeed:template-extensions, which has super nice features, especially with dealing with template instances and I believe their way of fetching the template instance is a workaround this issue.

Meteor: Access Template Helper (or variable) from another helper

How can I reference a template helper from another one? For example...
Template.XXX.helpers({
reusableHelper: function() {
return this.field1 * 25 / 100; //or some other result
},
anotherHelper: function() {
if (this.reusableHelper() > 300) //this does not work
return this.reusableHelper() + ' is greater than 300';
else
return this.reusableHelper() + ' is smaller than 300';
}
});
I have also tried Template.instance().__helpers.reusableHelper - all with no luck.
Alternatively is there a way to define reactive Template instance variables?
XXX is a sub-template that renders multiple times on the same page.
You can but only with global template helpers.
Blaze._globalHelpers.nameOfHelper()
Here is an example calling Iron:Router's pathFor global helper.
Template.ionItem.helpers({
url: function () {
var hash = {};
hash.route = path;
hash.query = this.query;
hash.hash = this.hash;
hash.data = this.data;
var options = new Spacebars.kw(hash);
if (this.url){
return Blaze._globalHelpers.urlFor(options)
} else if( this.path || this.route ) {
return Blaze._globalHelpers.pathFor(options)
}
}
});
EDIT: To your second question. You can call the same template as many times as you like on a page and pass different data attributes directly into it and/or use #each block template wrapper to iterate over data. #each will call a template many times giving it a different data context each time.
#each Example
<template name="listOfPosts">
<ul>
{{#each posts}}
{{>postListItem}} <!--this template will get a different data context each time-->
{{/each}}
</ul>
</template>
Attributes Example
<template name="postDetails">
{{>postHeader title="Hello World" headerType="main" data=someHelper}}
{{>postHeader title="I am a sub" headerType="sub" data=newHelper}}
{{>postBody doc=bodyHelper}}
</template>
This like using of common code, you can make another javascript function which contains the your reusable code and call it from wherever you required.
Like in your code-
function calcField(field){
return field * 25 / 100
}
and in you template helper-
Template.XXX.helpers({
reusableHelper: function() {
return calcField(this.field1);
},
anotherHelper: function() {
if (calcField(this.field1) > 300)
return calcField(this.field1) + ' is greater than 300';
else
return calcField(this.field1) + ' is smaller than 300';
}
});
and
Alternatively is there a way to define reactive Template instance
variables?
you can use Session variables or Reactive variable
Disclaimer: This may not answer your question directly, but it might be helpful for people stuck with a similar use case:
Sometimes it's easy to get locked into the "Meteor way", that standard Javascript rules are forgotten.
Two use cases that sound similar to what you're trying to do:
1. For helpers/events that you can access anywhere on the client-side, simply set a global helper.
Put this in, say, client/helpers.js:
Helpers = {
someFunction: function(params) {
/* Do something here */
}
}
Now Helpers.someFunction() is available to all templates.
If you want to bind the local template instance to it for some reason, again, it's standard JS:
var boundFunction = Helpers.someFunction.bind(this);
2. To create reusable Blaze helpers inside of templates, use Template.registerHelper
For example, this function uses the "numeral" library to format numbers:
Template.registerHelper('numeral', function(context, opt) {
var format = (opt.hash && opt.hash.format) || '0,0.00';
return numeral(context || 0).format(format);
});
You can use this in any template like so:
{{numeral someNumberVariable format='0,0'}}
I found a better solution with collection hooks:
Item = new Mongo.Collection('Items');
Item.helpers({
isAuthor: function(){
return this.authorId == Meteor.userId();
},
color: function(){
if(this.isAuthor())
return 'green';
else
return 'red';
}
});
I then becomes functions of this, usable in both helpers and templates.
i had something similar -- i had 2 helpers in the same template that needed access to the same function. however, that function 1) needed access to a reactive var in the template, and 2) is a filter function, so i couldn't just pass in the data of that reactive var.
i ended up defining the filter function in the templates onCreated() and stored it in a reactive var, so the helpers could access it.
Template.Foo.onCreated(function () {
this.fooData = new ReactiveVar();
function filterFoo(key) {
var foo = Template.instance().fooData.get();
// filter result is based on the key and the foo data
return [true|false];
}
this.filterFoo = new ReactiveVar(filterFoo);
});
Template.Foo.helpers({
helper1: function() {
var filterFn = Template.instance().filterFoo.get();
return CollectionA.getKeys().filter(filterFn);
},
helper2: function() {
var filterFn = Template.instance().filterFoo.get();
return CollectionB.getKeys().filter(filterFn);
},
});
Since this answer is currently missing - I wanted to add an update
In the current meteor version, you should be able to call:
var TEMPLATE_NAME = //the name of your template...
var HELPER_NAME = //the name of your helper...
Template[TEMPLATE_NAME].__helpers[' '+HELPER_NAME]
You should call it like this, if you want to make sure the helper has access to this:
var context = this;
Template[TEMPLATE_NAME].__helpers[' '+HELPER_NAME].call(context,/* args */);
But be careful - this could break in future Meteor versions.
Adding on to Nils' answer, I have been able to access Template level helpers in events using the following code:
'click a#back': (event, instance) ->
if instance.view.template.__helpers[' complete']() && instance.view.template.__helpers[' changed']()
event.preventDefault()
this just came up again at work, and this time we used modules. in this case, we had a number of large, related functions that had to maintain data across calls. i wanted them outside the template file but not totally polluting the Meteor scope. so we made a module (polluting the Meteor scope 1x) and called the functions therein from the template.
lib/FooHelpers.js:
FooHelpers = (function () {
var _foo;
function setupFoo(value) {
_foo = value;
}
function getFoo() {
return _foo;
}
function incFoo() {
_foo++;
}
return {
setupFoo: setupFoo,
getFoo: getFoo,
incFoo: incFoo
}
})();
FooTemplate.js:
Template.FooTemplate.helpers({
testFoo: function() {
FooHelpers.setupFoo(7);
console.log(FooHelpers.getFoo());
FooHelpers.incFoo();
console.log(FooHelpers.getFoo());
}
});
console output is 7, 8.

Is there are more elegant way to walk template nesting?

I'm trying to access a parents data context
To get to it, I have a line that looks like :-
template.view.parentView.parentView.parentView.parentView.dataVar.curValue
Which in terms of UI, I have
template[dataIwant] renders another template with a modal dialog which uses autoform
I then use an autoform hook to get a before save event, which I want to use to add an extra value to the document being saved.
I then walk the template that's passed in the hook back to the top template. Seems like I should be able to do this in a more elegant way?
Came up with this code today because I needed it also :
_.extend(Blaze.View.prototype,{
closest: function(searchedViewName){
currentView = this;
while (currentView && currentView.name != searchedViewName){
currentView = currentView.parentView;
}
return currentView;
}
});
<template name="parent">
{{> child}}
</template>
Template.parent.created = function(){
this.reactiveVar = new ReactiveVar(false);
};
<template name="child">
{{parentName}}
{{parentVar}}
</template>
Template.child.helpers({
parentName:function(){
return Template.instance().view.closest("parent").name;
},
parentVar:function(){
return Template.instance().view.closest("parent")._templateInstance.reactiveVar.get();
}
});
So far so good, but I've already spotted use cases where this won't work (using Template.contentBlock in your template definition is breaking the whole thing for some unknown reason).

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>

dynamically inserting templates in meteor

Ok so I've got my template in its own file named myApp.html. My template code is as follows
<template name="initialInsertion">
<div class="greeting">Hello there, {{first}} {{last}}!</div>
</template>
Now I want to insert this template into the DOM upon clicking a button. I've got my button rendered in the DOM and I have a click event tied to it as follows
Template.chooseWhatToDo.events = {
'click .zaButton':function(){
Meteor.ui.render(function () {
$("body").append(Template.initialInsertion({first: "Alyssa", last: "Hacker"}));
})
}
}
Now obviously the $("body").append part is wrong but returning Template.initialInsertion... doesn't insert that template into the DOM. I've tried putting a partia {{> initialInsertion}}but that just errors out because I dont have first and last set yet... any clues?
Thanks guys
In meteor 1.x
'click .zaButton':function(){
Blaze.renderWithData(Template.someTemplate, {my: "data"}, $("#parrent-node")[0])
}
In meteor 0.8.3
'click .zaButton':function(){
var t = UI.renderWithData(Template.someTemplate, {my: "data"})
UI.insert(t, $(".some-parrent-to-append"))
}
Is first and last going into a Meteor.Collection eventually?
If not, the simplest way I know is to put the data into the session:
Template.chooseWhatToDo.events = {
'click .zaButton' : function () {
Session.set('first', 'Alyssa');
Session.set('last', 'Hacker');
}
}
Then you would define:
Template.initialInsertion.first = function () {
return Session.get('first');
}
Template.initialInsertion.last = function () {
return Session.get('last');
}
Template.initialInsertion.has_name = function () {
return Template.initialInsertion.first() && Template.initialInsertion.last();
}
Finally, adjust your .html template like this:
<template name="initialInsertion">
{{#if has_name}}
<div class="greeting">Hello there, {{first}} {{last}}!</div>
{{/if}}
</template>
This is the exact opposite solution to your question, but it seems like the "Meteor way". (Basically, don't worry about manipulating the DOM yourself, just embrace the sessions, collections and template system.) BTW, I'm still new with Meteor, so if this is not the "Meteor way", someone please let me know :-)
I think you may want to use Meteor.render within your append statement. Also, note that if you are passing data into your Template, then you must wrap Template.initialInsertion in an anonymous function, since that's what Meteor.render expects. I'm doing something similar that seems to be working:
Template.chooseWhatToDo.events = {
'click .zaButton':function(){
$("body").append(Meteor.render(function() {
return Template.initialInsertion({first: "Alyssa", last: "Hacker"})
}));
}
}
Hope this helps!
Many answer here are going to have problems with the new Blaze engine. Here is a pattern that works in Meteor 0.8.0 with Blaze.
//HTML
<body>
{{>mainTemplate}}
</body>
//JS Client Initially
var current = Template.initialTemplate;
var currentDep = new Deps.Dependency;
Template.mainTemplate = function()
{
currentDep.depend();
return current;
};
function setTemplate( newTemplate )
{
current = newTemplate;
currentDep.changed();
};
//Later
setTemplate( Template.someOtherTemplate );
More info in this seccion of Meteor docs

Resources