How to get the parent template instance (of the current template) - meteor

Is there a clean way to get the parent template of the current template? Nothing is officially documented in Meteor's API.
I'm talking about the Blaze.TemplateInstance, not the context (i.e. not Template.parentData).

In the end, I've extended the template instances similarly with Meteor's parentData, like this:
/**
* Get the parent template instance
* #param {Number} [levels] How many levels to go up. Default is 1
* #returns {Blaze.TemplateInstance}
*/
Blaze.TemplateInstance.prototype.parentTemplate = function (levels) {
var view = this.view;
if (typeof levels === "undefined") {
levels = 1;
}
while (view) {
if (view.name.substring(0, 9) === "Template." && !(levels--)) {
return view.templateInstance();
}
view = view.parentView;
}
};
Example usage: someTemplate.parentTemplate() to get the immediate parent

Is there a clean way to get the parent template of the current
template?
Currently, none that I know of, but this is supposed to happen sometime in the future as part of a planned "better API for designing reusable components" (this is discussed in the Meteor post 1.0 roadmap).
For the moment, here is a workaround I'm using in my projects :
// extend Blaze.View prototype to mimick jQuery's closest for views
_.extend(Blaze.View.prototype,{
closest:function(viewName){
var view=this;
while(view){
if(view.name=="Template."+viewName){
return view;
}
view=view.parentView;
}
return null;
}
});
// extend Blaze.TemplateInstance to expose added Blaze.View functionalities
_.extend(Blaze.TemplateInstance.prototype,{
closestInstance:function(viewName){
var view=this.view.closest(viewName);
return view?view.templateInstance():null;
}
});
Note that this is only supporting named parent templates and supposed to work in the same fashion as jQuery closest to traverse parent views nodes from a child to the top-most template (body), searching for the appropriately named template.
Once this extensions to Blaze have been registered somewhere in your client code, you can do stuff like this :
HTML
<template name="parent">
<div style="background-color:{{backgroundColor}};">
{{> child}}
</div>
</template>
<template name="child">
<button type="button">Click me to change parent color !</button>
</template>
JS
Template.parent.created=function(){
this.backgroundColor=new ReactiveVar("green");
};
Template.parent.helpers({
backgroundColor:function(){
return Template.instance().backgroundColor.get();
}
});
Template.child.events({
"click button":function(event,template){
var parent=template.closestInstance("parent");
var backgroundColor=parent.backgroundColor.get();
switch(backgroundColor){
case "green":
parent.backgroundColor.set("red");
break;
case "red":
parent.backgroundColor.set("green");
break;
}
}
});

What I've been doing so far is that if I need to access the parent instance in a child template's function, I try to instead refactor this function to declare it on the parent template, and then pass it as argument to the child, who can then execute it.
As an example, let's say I want to increment a template variable on the parent template from within the child template. I could write something like this:
Template.parentTemplate.onCreated(function () {
var parentInstance = this;
parentInstance.count = new ReactiveVar(1);
});
Template.parentTemplate.helpers({
incrementHandler: function () {
var parentInstance = Template.instance();
var count = parentInstance.count.get();
return function () {
var newCount = count + 1;
parentInstance.count.set(newCount);
};
}
});
Then include my child template:
{{> childTemplate handler=loadMoreHandler}}
And set up my event:
Template.childTemplate.events({
'click .increment-button': function (event, childInstance) {
event.preventDefault();
childInstance.data.handler();
}
});

If you don't want to extend Blaze.TemplateInstance you can access the parent instance like this:
Template.exampleTemplate.onRendered(function () {
const instance = this;
const parentInstance = instance.view.parentView.templateInstance();
});
Only tested in Meteor 1.4.x

You can use a package like Aldeed's template-extension
The following method is available there:
templateInstance.parent(numLevels, includeBlockHelpers)

Related

How to pass Reactive variables among different templates in Meteor?

I have 2 separate templates:
<template name = "MusicControl">
<!-- Some Logics here -->
</template>
<template name = "MusicSystem">
<!-- Some Logics here ("click" event) -->
</template>
I have 2 JavaScript files associated with these 2 templates.
What I want is that if an event occurs ("click" event) on the MusicControl template, it sets some sort of global variable (but not Session variable) so that I can access it in another template as a helper function.
How to implement in Reactive-Dict in Meteor?
Do not worry I have the helper functions defined for both the template in their respective js.
And one thing, these <templates> are independent of each other, I just want to listen the event in <template 1> on <template 2> by using some sort of global variable.
A simple version of #zim's answer is:
HTML (actually Spacebars)
<template name="Parent">
{{> Child1 sharedVar1=sharedVar}}
{{> Child2 sharedVar2=sharedVar}}
</template>
JavaScript
import { ReactiveVar } from 'meteor/reactive-var';
// Just initialize the variable. Could also be within the scope of a template.
var myReactiveVar = new ReactiveVar();
Template.Parent.helpers({
// This is what will be sent to Child1 and Child2.
sharedVar: function () {
return myReactiveVar;
}
});
Template.Child1.helpers({
myValue: function () {
// As usual, this will be reactive.
return Template.instance().data.sharedVar1.get();
}
});
Template.Child2.events({
'event selector': function (event, template) {
// This change will trigger an autorun of Child1 myValue helper.
template.data.sharedVar2.set(myNewValue);
}
});
(of course you can split these into several JS files)
Example with a demo app using Meteor 1.6.1 and Blaze: https://github.com/ghybs/meteor-blaze-templates-share-data
for this scenario, i generally use a reactive variable owned by the parent, whose job it is to coordinate among its children. i would not use a global variable here.
below are the basics. Child1 sets the shared var and Child2 uses it. the parent owns it. Child1 and Child2 have no relationship to one another.
<template name="Parent">
{{> Child1 sharedVarSetter=getSharedVarSetterFn}}
{{> Child2 sharedVar=sharedVar}}
</template>
JS:
Template.Parent.onCreated(function() {
this.sharedVar = new ReactiveVar();
});
Template.Parent.helpers({
sharedVar() {
return Template.instance().sharedVar.get();
},
getSharedVarSetterFn() {
let template = Template.instance();
return function(newValue) {
template.sharedVar.set(newValue);
}
}
});
Template.Child1.onCreated(function() {
this.sharedVarSetterFn = new ReactiveVar(Template.currentData().sharedVarSetter);
});
and somewhere in Child1 (helper, event handler, what have you):
let fn = template.sharedVarSetterFn.get();
if (_.isFunction(fn)) {
fn(newValue);
}
here, i've shown just 1 shared var. but if you have multiple, a reactive dict could work the same way.

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.

Meteor, call function in child template from parent template

If I have a parent template Container with a child template Content avec only a button :
<head>
<title>test</title>
</head>
<body>
{{> Container}}
</body>
<template name="Container">
{{# Content callback = callBack }}
<button>ok</button>
{{/Content}}
</template>
<template name="Content">
{{> UI.contentBlock}}
</template>
If can pass a function to the callback. Like that :
Template.Container.helpers( {
callBack: function () {
return function () {
console.log( 'this is my callback' );
}
}
} );
So in my content template, I can call a function from my parent template. Like this for instance :
Template.Content.events( {
'click button': function ( e, tmpl ) {
tmpl.data.callback();
}
} );
But sometimes, I need to make it happen the other way. The parent calling a function in his child. What's your way of doing it ?
EDIT :
I saved it in a meteorpad to show it in action and to make it easy to fork : http://meteorpad.com/pad/48NvCFExxW5roB34N/test-pass-callback-to-parent
Here's a pattern you could use. Define a class Child and a template child; the idea is that inside the child template, the data context is a Child instance. For example, I'll create a component which has a number that can be incremented by pressing a button.
<template name="child">
<button class="increment">{{number.get}}</button>
</template>
function Child() {
this.number = new ReactiveVar(0);
}
Template.child.events({
"click .increment": function () {
this.number.set(this.number.get() + 1);
}
});
In the parent's created callback, create a Child instance and store it on the template instance. Then in the parent template, call out to child, passing in the Child as a data context:
Template.parent.created = function () {
this.childInstance = new Child();
}
Template.parent.helpers({
childInstance: function () {
return Template.instance().childInstance;
}
});
<template name="parent">
{{> child childInstance}}
</template>
Now you can define methods on the Child prototype and call them from the parent template, for example:
Child.prototype.getNumberTimesTwo = function () {
return this.number.get() * 2;
}
<template name="parent">
{{> child childInstance}}
That number multiplied by two is: {{childInstance.getNumberTimesTwo}}
</template>
Based on my experience with Meteor, it seems like it favors more of an event driven UI design. This means that you would not directly call the parent or child methods directly, but you would fire a custom event or set a Session variable. So you could do something like:
Template.Container.helpers( {
callBack: function () {
Session.get('button.lastClickTime');
console.log( 'this is my callback' );
}
} );
Template.Content.events( {
'click button': function ( e, tmpl ) {
Session.set('buttom.lastClickTime', new Date());
}
} );
The Session object is reactive so the callback method will be called anytime that 'button.lastClickTime' Session value is set.
Then you could just reverse the set/get calls to notify the child from the parent.
You can register an event handler on the parent template that triggers on events in the child template by using a Selector that matches elements in the child template, like this:
Template.Container.events( { // 'Container' is the parent template
'click button': function ( e, tmpl ) { // Selector for an element in the child-template*
// You will now have access to the parent's context instead of the child's here.
}
} );
*) Assuming there are no other buttons in the parent template. If so, make a unique name to the button so you can uniquely select it from the parent.
You can also create template functions in the child and then set those on the parent when the child gets created. This requires use of the meteor-template-extension package. Though if you dig into that package you could just pull out the code that does the parent() function.
Template.child.onCreated(function() {
let instance = this;
instance.someFunction = function() {...};
instance.parent(1, false).someFunction = instance.someFunction;
});
Then this can be called by the parent in an event handler (or anywhere) for example.

this.findAll not working on sub-template

When I try to use this.findAll on a template where the selector is in a sub-template, findAll returns nothing.
Here's the HTML:
<template name="products">
{{#each productList}}
{{> product }}
{{/each}}
</template>
<template name="product">
<div class="box">{{name}}</div>
</template>
Here's the JS:
Template.products.helpers({
productList: function() {
var all = Products.find({}).fetch();
return all;
}
});
Template.products.rendered = function(){
var boxes = this.findAll('.box');
console.log(boxes.length);
}
Output of boxes.length is 0. Any ideas how I could get the "box" elements?
According to the docs for findAll:
Only elements inside the template and its sub-templates can match parts of the selector.
So it should work for sub-templates. I tried this with a fixed array of products and it worked, which implies that you are just seeing a delay between the call to rendered and the products being fetched. For example if you do:
Template.products.events({
'click .box': function (e, t) {
var boxes = t.findAll('.box');
console.log(boxes.length);
}
});
Then if you click on one of the boxes, you should see the correct number logged to the console. In short, I think the test may just be invalid. If you are using iron-router, you could try adding a waitOn for the products - that may ensure they arrive before the rendered call.
Here's what I did to run a script after all products have been loaded.
I've added last_product property in all the products.
Template.products.helpers({
productList: function() {
var all = Products.find({}).fetch();
var total = all.length;
var ctr = 0;
all.forEach(function(doc){
doc.last_product = false;
ctr++;
if(ctr == total)
{
doc.last_product = true;
}
return doc;
});
return all;
}
});
Then instead of "Template.products", I used "Template.product" to detect if the last product is rendered. When the last product is rendered, run the script.
Template.product.rendered = function(){
if(this.data.last_product){
var boxes = $('.pbox');
console.log(boxes.length);
}
}
boxes.length now has the correct length.
Thanks to David for the idea!
Here's the correct answer. I've added this to my iron-router route:
action : function () {
if (this.ready()) {
this.render();
}
}
Found the answer from https://stackoverflow.com/a/23576039/130237 while I was trying to solve a different problem.

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