Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Meteor: Is there a reactive per-template scope?

I'm rendering the same Handlebars template in multiple (arbitrarily many) locations on the same page. Inside each template, I want a button to toggle the visibility of a div. When I save this state with Session.set, clicking one button obviously toggles all the divs in all the template instantiations which is not desired.

I could save the state in the data context of the template instance (which is bound to this.data in the Template.myTemplate.rendered and Template.myTemplate.created callbacks), but there are two problems with that.

  1. this.data isn't a reactive data source, so will not propagate to the div
  2. I don't have access to the template instance in Template.myTemplate.events (as discussed on meteor-core)

Finally, I could somehow save it to a collection. But how would I uniquely identify the state for each template rendered? There might also be a hacky way with jQuery, but that's not the way I want to develop Meteor apps that are supposed to work with reactive templates. Especially, when that template gets more complex.

Am I missing something or is there really no equivalent to AngularJS's controllers that get passed a $scope for each template instantiation?

Update: I was thinking of something like this.

template.html:

<template name="jsonObject">
    <input type="button" />
    <div class="{{hidden}}">content</div>
</template>

client.js:

Template.jsonObject.hidden = function(){
    var ret = "";
    if (this._hidden) {
        ret = "hidden";
    }
    return ret;
};

Template.jsonObject.events({
    'click input' : function(event, template){
        template.data._hidden = true;
    }
});
like image 899
mb21 Avatar asked Feb 07 '13 13:02

mb21


2 Answers

This is how I achieved a per-template instance reactive source

I only want to ask the user for the reason for a dinosaur's extinction when its extinction status is set to extinct.

<template name="dinosaur">
   <label for="extinction-status">Extinction status</label>
   <select name="extinction-status">
     <option value="not-extinct">Not extinct</option>
     <option value="extinct">Extinct</option>
   </select>
   {{#if isExtinct extinctStatus newExtinctStatus extinctStatusDep}}
   <label for="extinction-reason">Reason for extinction</label>
   <input name="extinction-reason" type="text" placeholder="Why is it extinct?"/>
   {{/if}}
</template>

To reactively display the reason field, we add a Deps.Dependency to this.data in the template created function

Template.dinosaur.created = function() {
  this.data.newExtinctStatus = null;
  this.data.extinctStatusDep = new Deps.Dependency;
};

We listen for when the user changes the extinction status selection, and update the newExtinctStatus and called changed on our extinctStatusDep.

Template.dinosaur.events({
  'change [name="extinction-status"]': function(event, template) {
    var extinctStatus = $(event.target).val();
    template.data.newExtinctStatus = extinctStatus;
    template.data.extinctStatusDep.changed();
  }
});

In the helper we say we depend on the Deps.Dependency we are passed

Template.dinosaur.helpers({
  isExtinct: function(status, newStatus, statusDep) {
    if (statusDep) {
      statusDep.depend();
    }
    if (newStatus) {
      if (newStatus == 'extinct') {
        return true;
      }
    else if (status == 'extinct') {
        // No new status is set, so use the original.
        return true;
    }
  }
});

It is a bit of a hack involving adding to this.data, but it allows for per-template reactivity, useful for when you rely on data that has not been saved to something in a Collection yet.

like image 39
ElDog Avatar answered Sep 22 '22 17:09

ElDog


All other answers are too complicated and/or outdated. As of Meteor 1.0, the recommended solution to maintaining per-template state is to use reactive variables stored in the template instance:

A template instance object represents an occurrence of a template in the document. It can be used to access the DOM and it can be assigned properties that persist as the template is reactively updated. [...] you can assign additional properties of your choice to the object.

Reactive variables are provided by the reactive-var core package:

A ReactiveVar holds a single value that can be get and set, such that calling set will invalidate any Computations that called get, according to the usual contract for reactive data sources.

Your code becomes:

Template.jsonObject.onCreated = function () {
  this.hidden = new ReactiveVar(false);
};

Template.jsonObject.helpers({
  hidden: function () {
    return Template.instance().hidden.get() ? 'hidden' : '';
  }
});

Template.jsonObject.events({
  'click input': function (event, template) {
    template.hidden.set(true);
  }
});

The HTML is the same as you'd expect:

<template name="jsonObject">
  <button>Click Me</button>
  <p>Hidden is {{hidden}}.</p>
</template>
like image 141
Dan Dascalescu Avatar answered Sep 24 '22 17:09

Dan Dascalescu