How to pass Reactive variables among different templates in Meteor? - 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.

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

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

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)

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.

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

How do I access one 'sibling' variable in a meteor template helper when I am in the context of another?

How do I access one 'sibling' variable in a meteor template helper, when I am in the context of another? I want to determine whether the user that is logged in and viewing the page is the same user that posted the ride offering, so that I can hide or show the "bid" button accordingly.
For example, here is my template (html) file:
<!-- client/views/ride_offers.html -->
<template name="RideOfferPage">
<p>UserIsOwner:{{UserIsOwner}}</p>
{{#with CurrentRideOffer}}
{{> RideOffer}}
{{/with}}
</template>
<template name="RideOffer">
<div class="post">
<div class="post-content">
<p>Details, Author: {{author}}, From: {{origin}}, To: {{destination}}, between {{earliest}} and {{latest}} for {{nseats}} person(s). Asking ${{price}}.
<button type="button" class="btn btn-primary" >Bid</button><p>
<p>UserIsOwner:{{UserIsOwner}}</p>
</div>
</div>
</template>
And here is my JavaScript file:
Template.RideOfferPage.helpers({
CurrentRideOffer: function() {
return RideOffers.findOne(Session.get('CurrentOfferId'));
},
UserIsOwner: function() {
return RideOffers.find({_id: Session.get('CurrentOfferId'), userId: Meteor.userId()}).count() > 0;
}
});
In the "RideOffer" template, I am able access the variables author, origin, ..., price. But I am unable to access the boolean UserIsOwner. I am, however, able to access the boolean UserIsOwner in the "RideOfferPage" template.
Does anyone know how I can access the boolean UserIsOwner in the "RideOffer" template?
Cheers,
Put the userIsOwner function outside the helper as an anonymous function and then call it from both templates.
Template.RideOfferPage.helpers({
CurrentRideOffer: function() {
return RideOffers.findOne(Session.get('CurrentOfferId'));
},
UserIsOwner: checkUserIsOwner()
});
Template.RideOffer.helpers({
UserIsOwner: checkUserIsOwner()
});
checkUserIsOwner= function() {
return RideOffers.find({_id: Session.get('CurrentOfferId'), userId: Meteor.userId()}).count() > 0;
}
There are several ways to do what you're asking.
In your particular example you are not asking about siblings, but about parents, since the RideOfferPage template renders the RideOffer template. You can access variables in the parent data context (but not helpers) like so:
<template name="RideOffer">
<div class="post">
<div class="post-content">
<!--
other stuff
-->
<p>UserIsOwner:{{../UserIsOwner}}</p>
</div>
</div>
</template>
In other cases, you may have a template being rendered as a sibling of this one. In that case, you can't actually know what the sibling is until the template is actually on the page; however, you can find it in the rendered callback:
Template.foo.rendered = function() {
var current = this.firstNode;
var next = $(currentItem).next(); // or .prev()
if (next.length) {
nextData = Spark.getDataContext(next[0]);
}
// Do something with nextData
};
Finally, you can get the parent context of any rendered DOM element by repeatedly iterating through its parents. This isn't super efficient but I've used it in places where there is extensive drag and drop with DOMFragments moving around on the page:
Template.bar.events = {
"click .something": function(e) {
var target = e.target;
var context = Spark.getDataContext(target);
var parentContext = context;
while (parentContext === context) {
parentContext = Spark.getDataContext(target = target.parentNode);
}
// Do something with parentContext
}
};
I'm curious to know if there is a better way to do the last thing, which may potentially have to iterate through many DOM elements. In any case, you may want to check out my meteor-autocomplete package for this and other cool tricks.

Resources