Access parent template ReactiveVar from child template?

#1

Is it possible to set the ReactiveVar on a parent template from a child template? I have a template where I am using a ReactiveVar to indicate if an input field is visible or not and in order to be able to set the focus on the input field I found it necessary to move the input field to a child template. However I’d like to subsequently hide the input field by setting the reactive field when the input field loses focus, however I can’t figure out how to set the ReactiveVar on the parent back to the original value from the child template.

Alternately, if anyone know a good way to set the focus on a input field made visible from a template helper it would probably solve the issue as well. I found that the focus() method only worked when I called it from the Rendered event on the child template.

Thanks in advance!

6 Likes
Accessing siblings template context to grab UI's data
[SOLVED] Template.currentData() not reactive? Other ways of modifying template data context?
#2

I ran into this too recently, and I don’t like this solution but this can work. It’s very ugly, but in a helper you can use Template.instance().parentView.reactiveVar.

If you need to access it from an event it’s even uglier. You have to trace it back up the chain until you get right instance. If there’s an each rendering out the sub-template you’ll have to use parentView on it as well, same goes with with

    'click': function (e, template) {
      template.view.parentView.parentView.parentView._templateInstance.reactiveVar = false;
    }

As soon as I wrote that above in my template I abandoned that idea.

Hopefully someone else has a better answer, this is the exact reason I started looking into react. I wanted to stop using Session for everything because I was getting into some more complicated multi-template interactions and couldn’t find a good way to simplify the data flow without lots of Session.

3 Likes
#3

That is exactly the case where you should use sessions. Or set the var in your parent template so you have it in the data context of your child template like this:

{{> childTemplate reactiveVar=reactiveVar}}

Do not use _ attributes unless you know exactly what you are doing.

3 Likes
#4

Another vote for using Session (or something else, like a Session wrapper, i don’t like global variables very much).
The job of your child input template is just to get an input, not to know when to draw itself, or changing state of variables from parents.
Imho, you should track somewhere else that your input has been recorded, that the input template should be destroyed, and the focus put where you want to. That’s not the responsabilitiy of your input template.
Now, this is perhaps the limitation of reactive programming, there are some situations like that where your UI has a “story”, a sequence of steps that can’t be described by relations alone. I wonder if React solves thoses problems.

#5

I had meant to mention in the original post that Session would not work because I couldn’t have this data set at a global scope. Was meant to indicate the editability of a grid cell within a grid using a common template, so the variable had to be ‘scoped’ to the cell that was being edited.

I ended up solving this using an approach similar to what was suggested by krevativ. Instead of storing the ReactiveVar in my template in the template’s ‘created’ function, I created the ReactiveVar in my data context which is available to my child template.

#6

Yeah, I said my idea was a horrible idea and I didn’t even use it… :smile:

#7

To me it seems like a totally reasonable way of thinking, especially since Blaze has become more sophisticated.

For instance, using the features of Blaze.render you can render a modal anywhere on the page and still keep the view hierarchy intact, with the child view reporting upwards by means of a TemplateVar on the parent.

Traversing views manually is a dead end, but with a couple of utility methods (workman:templating-ext), it’s not a problem.

#8

Here is a function to get a parent instance from its name (source):

Blaze.TemplateInstance.prototype.parentInstance = function(templateName)
    {
    if (!/^Template\./.test(templateName))
        templateName = 'Template.' + templateName;
    var view = this.view;
    while (view = view.parentView)
        if (view.name === templateName)
	        return view.templateInstance();
    };

Also, have a look at meteor-template-extension.

Accessing template.instance from autoform onSuccess callback
#9

This is absolutely a good idea, I disagree that Sessions would be the correct solution. Suppose you have a repeating template using {{#each}} in which you need a ReactiveVar. Then, you have a nested {{#each}} which uses a third-party component that must be initialized with jQuery using onRendered. This sub-template needs to have access to the parent’s ReactiveVar.

5 Likes
#10

@framnk, could you give us more detailed information about how you implemented your solution?

#11

@davidystephenson I can only assume he used Template.parentData() to access the parent data context

#12

How can reactiveVars be accessed via Template.parentData()?

1 Like
#13

@davidystephenson

<body>
{{> parent reactive=someVar }}  <!-- someVar is a reactive var. in Iron-router, it would be initialized in the route's data property -->
</body>

<template name="parent">
 {{> child }}
</template>

Template.child.created = function() {

    this.foo = Template.parentData().reactive // child can access the parent's data context, where the reactive var is
}

my personal note is I would never use this pattern unless the child template is exclusively used together with the parent template, as it couples the templates in an error prone way. (i.e. an uninformed programmer steps in and splits the two templates, unaware of the effects on the child template)
I would use this pattern only if I wanted to split a big template to smaller templates for convenience, not practical modularity

an alternative suggestion is passing the reactive var to the child’s own data context in order to minimize coupling

#14

@chenroth

Will this work even if the variable was declared as a new ReactiveVar in the parents template’s created variable?

#15

unfortunately, no.

The inability in Meteor to store read/write template data, with similar ease of access as the read-only data context, has given me much grief :frowning:

To me, the mandatory access pattern of instance variables (i.e. declaring variables on the instance on template creation, such as this.foo = “bar”) is anti-DRY and error prone

#16

I share your concern. I have been looking everywhere for a way to do access and modify an external template’s reactive variables, but cannot seem to find a way.

#18

@davidystephenson @chenroth

Hy, maybe you may vote for my PR and follow this discussion on

2 Likes
#19

My guess is that MDG is not willing to invest time on this because it it only a partial fix to a more global problem: how to create UI components in Meteor (see here).
It would be great if @dgreensp could complete your github thread by sharing his plan on that :- )

#20

I know this is an old topic, but it’s at the top of Google search results, so I thought it would be worthwhile to give my two cents on the matter. I like the following pattern:

  • Create a handler for your ReactiveVar in the parent template, for example for an array:
Template.parent.onCreated(function parentOnCreated() {
  this.things = new ReactiveVar([]);
  this.thingsHandler = {
    push: (value) => {
      const arr = this.things.get();
      arr.push(value);
      this.things.set(arr);
    },
    remove: (value) => {
      const arr = this.things.get();
      const index = arr.indexOf(value);
      if (index > -1) {
        arr.splice(index, 1);
        this.things.set(arr);
      }
    }
  };
});

Template.parent.helpers({
  thingsHandler: () => Template.instance().thingsHandler
});
  • Pass it as the child template’s data context:
  {{> child things=thingsHandler}}
  • Use it:
Template.child.onCreated(function childOnCreated() {
  this.data.things.push('apple');
});

Template.child.events({
  'click .item': function pushItem() {
    this.things.push(this.name);
  }
});

Sorry, I’m not very imaginative with example names.

2 Likes
#21

Agreed that this is old, but figured I’d share a solution I’m pretty happy with currently. Some hacked together ideas that allow you to modify vars from “anywhere”. For clarity’s sake, I’ll call the template with the reactiveVar the dataTemplate and the template looking to access/modify that var the seekerTemplate.

Some notes: I typically wrap dataTemplates in a ready check, as looking for a reactiveVar (or dict, or function) attached to an external template can result in a race condition, and even though the variable itself is reactive, if the initial lookup fails, then you’re stuck. This is mitigated by the fact that the seekerTemplate is usually a child of the dataTemplate. It is possible to use the strategy with sibling templates, but be warned that additional checks may be necessary to guarantee that the template instance is ready.

If anyone is interested, let me know as I’ve been considering documenting some of this stuff out. I’ve been using “components” for a while to access the properties of other templates for a while, and have found it to be pretty helpful.

Also, I’ve found that attaching the display:none div with a class name that matches the template name gives a reasonably safe method of fetching the template instance from Blaze.getView, but I’d be interested to hear other approaches.

<!-- client.html -->
<body>
  <h1>Welcome to Meteor!</h1>
  {{> dataTemplate}}
</body>

<template name="dataTemplate">
  <div class="dataTemplate" style="display:none;"></div>
  {{#if templateReady}} <!-- any conditionals come after the first div -->
    <!-- template stuff -->
    <p>Current State: {{ currentState }}</p>
    {{> seekerTemplate}}
  {{/if}}
</template>

<template name="seekerTemplate">
  <p>Current State: {{ getReactiveVar 'dataTemplate' 'currentStateVar' }}</p>
</template>
// client.js
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';

import './main.html';

Template.dataTemplate.onCreated(function() {
  let self = this;
  self.currentStateVar = new ReactiveVar(true);
  self.templateReadyVar = new ReactiveVar(false);
});

Template.dataTemplate.onRendered(function() {
  this.templateReadyVar.set(true);
});

Template.dataTemplate.helpers({
  currentState () {
    return Template.instance().currentStateVar.get();
  },
  templateReady () {
    return Template.instance().templateReadyVar.get();
  }
});

Template.registerHelper('getReactiveVar', function (templateName, varName){
  let elem = document.getElementsByClassName(templateName)[0];
  if (Blaze.getView(elem)) {
    console.log(Blaze.getView(elem));
    if (typeof Blaze.getView(elem)._templateInstance[varName] !== 'undefined') {
      return Blaze.getView(elem)._templateInstance[varName].get();
    } else {
      console.log('no reactive var');
    }
  } else {
    console.log('no view');
  }
});

For fun in the browser, run:

elem = document.getElementsByClassName('dataTemplate')[0];
Blaze.getView(elem)._templateInstance['currentStateVar'].set('hello, world');