Patterns and practices for passing data between templates

It’s not that I encourage or prefer globals. I’ve simply made my peace with them. (It should be noted, btw, that Session gets placed in the Meteor application scope, not the actual global scope of the interpreter.)

Ideally, I’d like to see mitar’s solution get adopted, so we could do something like:

FooListPage extends MyDataSurface extends BlazeComponent
FooFooterTemplate extends MyDataSurface extends BlazeComponent

And then we could attach Session/ReactiveDict/ReactiveVar objects to the MyDataSurface object scope, instead of the Meteor scope.

Thanks for this. Will you expand on what this means exactly, and say why this distinction is important?

For application scope read: http://docs.meteor.com/#/full/namespacing Basically meteor provides its own namespace. And I’m sure you already know the reasons why not using global scope is encouraged.

I am a bit confused though, I thought Session is a globally scoped and attached to window.

Not 100% sure if this is what you mean, but you can communicate between helpers and event handlers (template logic) by storing state in a number of ways. As a matter of policy I avoid Session in almost all cases. I feel their global scope leads to bad habits and lack of good discipline regarding separation-of-concerns as your application grows. Also because of their global scope, Session can lead to trouble when rendering multiple instances of a template. For those reasons I feel other approaches are more scalable.

Alternative approaches

#1 addClass/removeClass
Instead of setting a state then reacting to it elsewhere, can you perform the needed action directly. Here classes display and hide blocks as needed:

'click .js-edit-action': function(event, t) {
    var $this = $(event.currentTarget),
    container = $this.parents('.phenom-comment');

    // open and focus
    container.addClass('editing');
    container.find('textarea').focus();
},

'click .js-confirm-delete-action': function(event, t) {
      CardComments.remove(this._id);
},

#2 ReactiveVar scoped to template instance

if (Meteor.isClient) {  
  Template.hello.created = function () {
  // counter starts at 0
  this.counter = new ReactiveVar(0);
};

Template.hello.helpers({
  counter: function () {
  return Template.instance().counter.get();
  }
});

Template.hello.events({
  'click button': function (event, template) {
    // increment the counter when button is clicked
    template.counter.set(template.counter.get() + 1);
  }
});
}

#3 Iron-Router’s state variables

Get

Router.route('/posts/:_id', {name: 'post'});

PostController = RouteController.extend({
  action: function () {
    // set the reactive state variable "postId" with a value
    // of the id from our url
    this.state.set('postId', this.params._id);
    this.render();
  }
});

Set

Template.Post.helpers({
  postId: function () {
    var controller = Iron.controller();

    // reactively return the value of postId
    return controller.state.get('postId');
  }
});

#4 Collection data
Another approach is to simply state by updating data in your collection. Sometimes this makes perfect sense.

#5 update the data context
Sometimes it makes sense to just add data to the context. But may not be appropriate when using iron-router as it could rewrite the data context on you.

#summary
Session is often the worse choice in my opinion. Also I don’t personally use #3 as I feel like being less tied to iron-router is better incase we ever want to switch to another router package such as “Flow”.

1 Like

Apart from a dispatcher pattern, I’ve been using a design where I directly set template data scope {{>myTemplate dataScope}} but also augmenting (injecting) a reactive var/dict from another template into that scope dataScope.dependency=this.sate.get() (also requiring a helper within a parent template which encloses such patterns just for the enclosure to manage multiple possible scopes) hence achieving inter-template reactive communication. Not a silver bullet, but works great for many situations.

Also surprised this is not mentioned above, but you can use parentData to “Accesses other data contexts that enclose the current data context.”

http://docs.meteor.com/#/full/template_parentdata

1 Like

I read this too, but my interpretation was that this is used for a Template within a Template scenario… today I do this with {{…/…}} (like the command line) as the example suggests is an alternative.

/** This is poseo code as I could not find an example... the exact syntax could be wrong.  If I have time later (and no one has done it already), I'll try to tighten this up. */

<template name='parent'>
  {{> child param_1='test'}}
</template>

<template name='child'>
  {{#if child_helper param_1}}
    <div>{[helper_value}}</div>
    <!-- get parent context value -->
    {{> child_of_child ../param_1}}  <-- I think this is how it works... 
  {{/if}}
</template>

Template.helpers('child_helper', function (param1} {
  // do stuff return stuff
  return helper_value;
});

Blaze.getView() is also very powerful. If one designs the templates so that it can be determined by one or few seletors, the template’s first enclosed dom node can be found and that can be used as a reference to get the view, from which templateInstance() would be accessed.

Not very clean, but it also works regardless of the relationship between templates.

1 Like

Completely agree with this sentiment.


When I first started with Meteor I used IR for a lot, but as time goes by I’m pulling back from this as much as I can – for many reasons.

I simply do not want to use IR’s state and do not want to pass the parameter around in a URL. Yet, this view is only a personal preference.

This is a perfectly valid way to pass data from one Template to the next using under the appropriate circumstances.

1 Like

[quote=“funkyeah, post:44, topic:2951, full:true”]I am a bit confused though, I thought Session is a globally scoped and attached to window.
[/quote]

As I understand it, Session and Template and the other API objects all go through the same bundling process, which wraps each file in a closure, and is what causes variables without ‘var’ to behave in a non-local manner. The bundle process takes all the files and attaches them to the Meteor application scope, and keeps a special eye out for Meteor.startup() clauses, which it puts at the top of the file. The Meteor object is what then gets eventually attached to the window. Session may look like it’s global and attached to the window when one is writing JavaScript, but the bundle process boxes and wraps all the JavaScript in a Meteor object. That’s how I understand things, anyhow.

I like the idea of using a scoped-Session over the other approaches for my solution. In my situation the following exists:

Note: I might remove the following if it’s mudding the waters.

=> Two distinct ‘web pages’ or ‘screens’, Templates
=> One Template is not embedded within the other, i.e., not a sub-Template aka component-Template.
=> Template2 needs an [ID] (and maybe more data) from a Document that is subscribed to by Template1.
=> To get to Template2, there is an /Event tied to an element rendered on Template1
=> Template2 will replace (not share realestate) the screen with Template1
=> Template2 could update Template1 related Document and insert a Template2 related Document
=> Template1’s data view could be affected by Template2’s update to a Document related to Template1.
=> After a Save changes /Event is called on Template2, I might redirect back to Template1

/** What and how much data to pass from Template1 to Template2 is still not clear at this stage. **/

ReactiveDict, a scoped–HCP survivable-reactive variable, seems promising for many reasons explained in prior posts.


My sticking point, what I can’t get my head around yet, is how (without using IR, or global Session) to pass an [ID] from Template1 to Template2.

Hmm… digging around in the API, and it looks like Blaze.getData also takes a DOM element. So, as long as you’re strict about boxing your template contents with a unique id, you should be good to go.

But even with that, you have the problem of knowing which page you’re on, and which query selector to run. If the main templates that get rendered to your primary >yield were all html5 elements such as the article tag, one might be able to use that and tell the other templates to look for the article tag, and grab the data context of its element.

But how would the other templates know that the yield re-rendered? This would have been a fine approach with Spark, but the Blaze patch algorithm is specifically designed to avoid global flushes. So, it seems we’re back to some type of reactive variable with a tracker.

2 Likes

Going back to your original post

=> One Template is not embedded within the other, i.e., not a sub-
Template aka component-Template.
=> Template2 needs an [ID] (and maybe more data) from a Document that
is subscribed to by Template1.

I think this is misguided. Templates should be thought of as a data sink,
not a data source; they consume data–they are, afterall, the where the
presentation happens.

If TemplateA could use some data which TemplateB is subscribed to, but they
have no actual relationship (one is not the parent of the other) the way to
proceed is to setup a subscription for that data in TemplateA (we would do
it in an iron-router controller). That would be a more sound way to
proceed. There is an explicit declaration of the data needed by the
template where the subscription is established.

In your model, things are too dependent on other things not changing. What
if one day you refactor TemplateB so that is doesn’t need the fieldX
anymore? Now you might get yourself into trouble if you modify that
subscription or publication and remove the field from the query because you
have to consider how many other templates might be expecting you to be
providing that field to them.

It sounds like a bit of a mess to me. What are you really trying to
accomplish? Can you state the actual problem you are trying to accomplish
free of your notion regarding the implementation details? i.e. I want to
use a different template depending on the value of some data in my
collection, or something like that?

Max Hodges | Partner at White Rabbit Japan http://whiterabbitjapan.com |
t 03.6303.1840 | m 090.1795.4446

Maybe I misrepresented the situation, it’s very simple actually.

There is a list of people in Template1. This list has an [ID] of a unique person embedded within an element on that page. When the user clicks a person, they should be taken to a message detail screen for that user. This is the workflow.

The technicals of how to achieve this vary. Of course each Template will have their own subscription. The only stumbling block in my case is how to get the [ID] from Template1 to Template2. This [ID] from Template1 to Template2 will be used by its Subscribe to pass to the Publish and filter out records for the Template2 screen. It’s really that simple.

A few parameters: I don’t need IR for the Subscription, I don’t want to use a URL to pass the parameters, and I’d rather not use a global Session.

I’ll remove the parameters list in my last post before this if it’s mudding the waters.


My sticking point, what I can’t get my head around yet, is how (without using IR, or global Session) to pass an [ID] from Template1 to Template2.

1 Like

Have you worked with iron router before? Sounds like you might want to start! Very roughly, you’d have a route for your “list” and a route for the “detail” views.

Router.map(function(){
 this.route('things',  { path: '/things' }); // List of the people or whatever
 this.route('thing',  { path: '/thing/:_id'}); // View a specific entity
});

in js you’d call the ‘thing’ route like this

  Router.go('thing', {_id: thingId});

or you could just make it a link in your template with this special pathFor helper included in iron-router

    <td><a href="{{pathFor 'thing'}}">View</a></td>

You maybe wondering: “Wait! You’re not passing any “id” in there. How does it know which id to pass to the route?”

Magic! iron-router will automatically pass the {{id}} from the current data context. Here’s another example which shows the wider context

first the route

...
this.route('admin.order.show', { path: '/admin/order/:_id',  });
...

Now a template fragment

...
{{#each orders}}
            <tr>
              <td>{{status}}</td>
              <td><a href="{{pathFor 'order.show'}}">{{orderNumber}}</a></td>
              <td>{{date createdAt "short"}}</td>
            </tr>
{{/each}}
...

So here you can see we are building some table rows. We are looping through order and building some td elements. In one of them we are building links. Since admin.order.show expects an “id” (as defined in the route) iron-router will pass it automatically. But you can also override if it you want using the WITH helper (see iron router guide, link at bottom)

There are a lot of iron-router tutorials on the web to help you get started. But just to continue for a moment, so the “id” is getting passed to the route, and the controller for that route will have access to the “id” and you can use it there to construct a data context for the template

here is what the controller might look like this, passing it the “id”

OrderShowController = RouteController.extend({
  waitOn: function() {
  return [
    Meteor.subscribe('order', this.params._id),
 ];
},

data: function() {
return {
  order:          Orders.findOne(this.params._id),
  };
},
});

Guide for iron router

1 Like

Wow – thank you @maxhodges, this this very helpful!

Say, is there another way to pass that _id around, outside of using IR and the URL?

I actually don’t have much experience building Meteor apps without iron-router. It’s one of the most popular packages around. meteor docs mentions it 5 times.

I taked to Ekate, who works at MDG on the package manager, and asked her if they were going to make a native router. She had to be a bit evasive with her answer because they might change their mind at some point, but basically iron-router does the job well enough to where for the time being it’s pretty much the official package for doing this sort of thing. You’ll need a routing package eventually if you ever want to have urls like myapp.com/task-list or myapp.com/task/12

https://atmospherejs.com/?q=routing

But as an alternative, you could try Flow. It’s a new router with some fresh ideas

@maxhodges That has been a common IR workflow for me as well when getting started. However I got pretty stuck when trying to keep both the list and the detail view on the page at the same time.

Let’s take a users list and a detail page that has some sub-sections as well (comments, activity, profile, billing etc). Also need some actions in there like create new user, update user profile, etc. What would be a good way of settings this up avoiding Session variables (ie Session.selectedUser, Session.selectedUserActiveSection and so on)

I’d really like to see a nested route structure, but I haven’t been able to figure out the right way to get it working. I looked at yield templates in IR as the first solution months ago, but wasn’t able to get it setup properly. I also haven’t seen a good tutorial/guide on setting something up this way, most articles focus on the list of items and detail page as seperate pages completly. I’m trying to build more of an “email style” app dashboard.

But how would the other templates know that the yield re-rendered?

As long as there is a state and we don’t leak when there isn’t, do we really have to know if yield re-rendered? (Well, except for some edge cases perhaps)

Apart from all that, I think we should not fear routers that much. Most apps need some kind of mechanism to share a state with the outside world (eg link to a post) and when you already have a router in place due to some other requirement like that, why not make full use of it?

Also, I see a lot of people confusing the decoupling of content from logic and design as decoupling everything from html. But we are in a domain where we are dealing with html in essence so there are some very valuable mechanisms like custom data-* properties that we can make extensive use of.

This makes sense @Ben, and thanks for that.

A Tracker only on values that can change and therefore should cause a republish with the changed value – right?