Accessing siblings template context to grab UI's data


#1

So in the interest of keeping things nice and tidy, I’ve made 3 different templates for my Note object.

<template name="note">
  <div class="note">
    {{> noteHeader }}
    {{> noteEditor }}
    {{> noteFooter }}
  </div>
</template>

Inside of the noteFooter template, I want to display a word count based on the textarea within noteEditor. How can I accomplish this cleanly?

I could just go $(textarea).val() but that breaks as soon as I have more than one editor on the screen. I want to keep things tied together properly.

// noteFooter
<p class="word-count">Word count: {{wordCount}}</p>

Template.noteFooter.helpers({
  wordCount: function() {
    console.log(this); // Any thoughts?
  }
});

#2

There is actually alot of information regarding inter template communications, calling parent template data context from child etc etc.
The solution you are looking for is probably going to involve ReactiveVar, ReactiveDict, Session and persisting the information in mini/mongo collections.

For your particular case, I would keep a wordCount field in a collection that the noteEditor template updates and then have your noteFooter reactively read that field.

If you don’t want to keep the wordCount in a collection but have it as a temporary variable, then you’ll need to look into ReactiveVar or Sessions but be warned, there’s alot of information out there and no right way to go about it hehe :slight_smile: (as I found from 2 days of digging into the topic…)


#3

This makes me gag, but it works. Is this the best way to approach this?

So in the parent view, I create the ReactiveVar. I put it in the parent because two of the child templates needs access to it.

Template.noteDetail.created = function () {
  this.wordCount = new ReactiveVar(0);
};

Template.noteDetail.helpers({
  wordCount: function () {
    return Template.instance().wordCount.get();
  }
});

<template name="noteDetail">
  <div class="note">
    {{> noteHeader note=note }}
    {{> noteEditor note=note }}
    {{> noteFooter note=note }}
  </div>
</template>

Template.noteEditor.events({
  'input #content': function(event, template) {
    // Update the word count variables.
    var wordCount = $(event.target).val().split(' ').length;
    Template.instance().view.parentView.parentView._templateInstance.wordCount.set(wordCount);
  }
});

Template.noteFooter.helpers({
  wordCount: function() {
    return Template.instance().view.parentView.parentView._templateInstance.wordCount.get();
  }
});

So it works but again, it feels un-meteor-like.


#4

I actually think splitting out this template into atomic child templates is a bad idea in meteor-land. I’m going wait for some feedback before proceeding. Time for :beers:


#5

The best is to store shared data in the parent template using a ReactiveVar or a ReactiveDict. Then you can access the shared data using this package or the following function:

// Returns an ancestor instance of the current template instance (by name)
// See
// https://groups.google.com/forum/?utm_medium=email&utm_source=footer#!msg/meteor-core/H9XUndnLpf0/N_hp69p9SsEJ
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();
};

#6

That looks like it’s doing the same thing only using one parentView instead of multiple ones. So I get that helper function but I lose the flexibility.


#7

Hehe, yeah, welcome to inter-template hell, have a look at these if you haven’t already:


They link to blogs etc on the topic…

The reason why I suggested collection is because it sounds like a wordCount would be useful in several places, like if you wanted to sort by article size on another page, display the count on the previous page etc… else it’s Session or ReactiveVar/Dict, take your pick! I really don’t like referring to parents because then your logic is dependent on your template nesting levels and how it changes…


#8

As a solution you can pass your ReactiveVar into child views an work with by .get()/.set()

Template.noteDetail.created = function () {
  this.wordCount = new ReactiveVar(0);
};

Template.noteDetail.helpers({
  wordCount: function () {
    return Template.instance().wordCount;
  }
});

<template name="noteDetail">
  <div class="note">
    {{> noteFooter wordCount=wordCount }}
  </div>
</template>

<template name="noteFooter">
  <p> We have now {{wordCount.get}} words </p>
</template>

#9

Actually, this is not bad for passing the whole parent context into the child, thanks for sharing.


#10

I agree with @mordrax Great snippet!

I think a few of us were talking about something similar to this earlier. You can use the bash style “…/…/…” to refer to upstream template data contexts in a helper block.

//in your template html code
{{#with this}}
{{#with .}}
//the above are equivalent and set the current context to the value of "this" above
{{#with ../..}}
//the above gets the parent context.

It is non reactive though… since the data context is “read-only and non-reactive” as in the docs.


#11

I do not think that sharing the context is nice one. It is an evil!
First, passing context (in current Blaze implementation) triggers to whole reset dataVar on nested views - it means that all the logic of the nested template will be calculated on next computation. It is better to pass handlers and parametrs and DO NOT pass reactive computations! I mean that if your currentData isnt changed during the view lifecycle and you do not call parentData from the view - you could say you have a component.
In this way React and Flux-flow looks more relevant for component-based app at the moment.
You could see the Blaze (2.0?) draft and note remove data-context as a one of the main points