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 di
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.
I think that discussion on meteor-core is outdated (though it is recent). Per the docs (quoted below), this should be possible:
Template.myTemplate.events({
foo: function(event, template) {
var _div = template.find('...');
}
});
The handler function receives two arguments: event, an object with information about the event, and template, a template instance for the template where the handler is defined. The handler also receives some additional context data in this, depending on the context of the current element handling the event. In a Handlebars template, an element's context is the Handlebars data context where that element occurs, which is set by block helpers such as #with and #each.
Update
Assuming each hideable div had a unique id, what if you tried something like this? I haven't tested it.
Template.jsonObject.hidden = function(){
var _id = $(".classOfPossibleHiddenDiv").prop("id");
return Session.get(_id) || false;
};
Template.jsonObject.events({
'click input' : function(event, template){
Session.set($(template.find(".classOfPossibleHiddenDiv")).prop("id"), true);
}
});
UPDATED ANSWER - METEOR 0.8.3
Meteor now provides the UI._templateInstance()
function, which gives access to the template instance in template helpers. This allows for a much cleaner implementation:
1.
In the below HTML code, remove {{#with templateInstanceId=getTemplateInstanceId}}
2. Replace the JS code by this one:
var templateInstanceId = 0;
Template.divWithToggleButton.created= function()
{
this.templateInstanceId = ++templateInstanceId;
};
Template.divWithToggleButton.events(
{
'click button': function(event, template)
{
Session.set("visible", template.templateInstanceId);
},
});
Template.divWithToggleButton.visible= function()
{
return Session.equals("visible", UI._templateInstance().templateInstanceId);
};
FORMER ANSWER
I had the same requirement in my project: uniquely identifying a template instance for using with Session.set
.
Here is my simple hack, using context replacement and a 'sliding' unique instance id (more on this later):
Anywhere in client code, put:
var templateInstanceId = 0;
UI.registerHelper('getTemplateInstanceId', function()
{
return templateInstanceId++;
});
Then use {{#with templateInstanceId=getTemplateInstanceId }}
in templates you want to uniquely identify instances. With your example, it could be something like this (not tested):
HTML:
<!-- Div with a toggle visibility button. Use as a Block Helpers (with # instead of >) -->
<template name="divWithToggleButton">
{{#with templateInstanceId=getTemplateInstanceId }}
<div>
<!-- The 'toggle visibility' button -->
<button type="button">Toggle Visibility</button>
<!-- The div content -->
{{#if visible}}
{{> UI.contentBlock}}
{{/if}}
</div>
{{/with}}
</template>
JS (client code):
Template.divWithToggleButton.events(
{
'click button': function(event, template)
{
Session.set("visible", this.templateInstanceId);
},
});
Template.divWithToggleButton.visible= function()
{
return Session.equals("visible", this.templateInstanceId);
};
Now about this strange 'sliding' unique instance id:
This id is updated at each template rendering, I found no other way. It means any new rendering will invalidate the unique id stored in the Session. New rendering occurs if your div contains reactive data sources. Depending on your case, this can be a problem or not.
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 calledget
, 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>