Use a separate variable for each template created - meteor

Here's what I have:
home.jade
body
div {{> hello 'World' }}
div {{> hello 'Town' }}
hello.jade
template(name="hello")
button.sayHello Say {{name}}
hello.coffee
obj = {}
Template.hello.created = ->
obj.name = this.data
Template.hello.helpers
name: -> obj.name
Template.hello.events
'click .sayHello': -> console.log obj.name
It displays the two buttons correctly ("Say World" and "Say Town"). But if you click any button the output is always "Town" (the last one to be created and rendered).
How can I share a variable in a template so that it's unique for each template created? In other words I want to be able to set values on a variable in the created event and then be able to access them in the events (for each template created/rendered).

You just need to pass an object as the context to the hello template. Try replacing all of your code with:
hello.jade
body
div {{> hello name='World' }}
div {{> hello name='Town' }}
template(name="hello")
button.sayHello Say {{name}}
hello.coffee
Template.hello.events
'click .sayHello': (e, t) ->
console.log t.data.name
Recommended reading:
guide-to-meteor-templates-data-contexts
spacebars-secrets

In your example obj is shared by every instance of template hello.
Try to use instance of template as a key in obj:
obj = {}
Template.hello.created = ->
// this is instance of template
obj[this] = {name : this.data }
Template.hello.helpers
// here is problem, there is no access to template instance:
// see https://github.com/meteor/meteor/issues/1529
// name: -> obj[this].name
Template.hello.events
'click .sayHello': (e, tmpl)-> console.log obj[tmpl].name

Related

Meteor - Reloading template section after variable change

i want to refresh/reload a part of my template after a variable change so that if the variable is true it shows a content A or else it will show content B. I'm sure this is a quite simple question but i'm having troubles on finding the solution.
Something like this:
Template.x.created = function() {
this.variable = false;
}
Template.x.helpers({
'getValue': function(){
return this.variable;
}
});
Template:
<template name="x">
{{#if getValue}}
<content A>
{{else}}
<content B>
{{/if}}
</template>
You need to create a reactive data source to get the template helper to re-run when the variable changes, as a normal variable won't let the helper know when it changes value. The simplest solution is to use ReactiveVar:
Template.x.onCreated(function() {
this.variable = new ReactiveVar(false);
});
Template.x.helpers({
'getValue': function() {
// Note that 'this' inside a template helper may not refer to the template instance
return Template.instance().variable.get();
}
});
If you need to access the value somewhere outside this template, you can use Session as an alternative reactive data source.
#Waiski answer is a good one, but I want to share a simple Template helper I build because a lot of Templates need this:
Using registerHelper you can build a global helper like so:
Template.registerHelper('get', function (key) {
let obj = Template.instance()[key]
return (obj && obj.get) ? obj.get() : obj
})
Use it in every template:
Template.x.onCreated(function() {
this.foo = new ReactiveVar(true)
this.bar = new ReactiveVar('abc')
})
Html:
{{#let foo=(get 'foo')}}
{{#if get 'bar'}}
Bar is true. Foo: {{foo}}
{{/if}}
{{/let}}

Best way to prevent a template helper to be rerun when it is unnecessary?

I'm trying to prevent a template helper to be rerun when it is unnecessary. I made a simple application to illustrate this behavior:
Let's say I want to display some items that contain only a title and a description.
<template name="Tests">
{{#each items}}
{{> TestsItems}}
{{/each}}
</template>
<template name="TestsItems">
<div class="title">{{title}}</div>
<div class="description">{{description}}</div>
</template>
I have autopublish enabled.
Template.Tests.helpers({
items: function () {
return Items.find();
}
});
Template.TestsItems.helpers({
description: function () {
// I'm using this helper to do some updates
// on a jQuery plugin when the description field change.
// see example 1: https://github.com/avital/meteor-ui-new-rendered-callback/
console.log("The description is run");
return this.description;
}
});
When a new update is made on the title field only, you can see that the description helper is rerun. What I'm trying to achieve is to only rerun this helper when there is a new value for the description field and not every time a field has changed in the document.
As {{#constant}} and {{#isolate}} are deprecated, how can I get this behavior in the latest Meteor versions?
Note 1: Create a new subtemplate including the description does not fix the problem.
I would avoid side effects in template helpers. Instead I would use an autorun:
Template.TestItems.rendered = function () {
var _id = this.data._id;
this.autorun(function () {
// Select only the description field, so that we only
// trigger a re-run if the description field changes
var description = Items.findOne(_id, {fields: {description: 1}}).description;
// update the JQuery plugin
});
}

Display error with using firebase-element inside template repeat

<template repeat="memberId in members | objKeys">
<firebase-element data={{member}} location="{{'SOME_LOCATION/' + memberId}}"></firebase- element>
<h2>member.name</h2>
</template>
objKeys: function(members) {
return Object.keys(members);
}
the data looks like this
members = {
'memberId_1': true,
'memberId_2': true,
'memberId_3': true
}
and at another location store actual users data.
Here, I'm expecting the template repeat to render each user (member1, member2, member3) accordingly. However, it prints out same name for 3 entries as if it re-use the variable "member" for all 3 firebase element in the template repeat, which doesn't really make sense.
I've tried to modified objKeys functions to return
[ {memberId: memberId_1, member: {}},
{memberId: memberId_2, member: {}},
{memberId: memberId_3, member: {}]
then use the inner member object for firebase element but the result is still the same
<template repeat="{{item in members | objKeys}}>
<firebase-element data={{item.member}} location={{'SOME_LOCATION/' + item.memberId}}> </firebase-element>
</template>
Do I not understand template repeat correct and use it incorrectly here ? Or is it a bug with polymer template.
I think the problem is that the repeater binds {{member}} to itself.
Check your Firebase - you'll see that the binding not only displays the name name repeatedly in your view, it also set all the values to the same name in your Firebase.
Try this instead, using {{members[memberId}}:
<polymer-element name="member-test">
<template>
<firebase-element data="{{members}}" location="{{'https://YOUR_APP_NAME.firebaseio.com/members/'}}"></firebase-element>
<template repeat="{{memberId in members | objKeys}}">
<firebase-element data="{{members[memberId]}}" location="{{'https://YOUR_APP_NAME.firebaseio.com/members/' + memberId}}"></firebase-element>
<h2>{{members[memberId].name}}</h2>
</template>
</template>
<script>
Polymer({
objKeys: function(members) {
if(!members) return null;
return Object.keys(members);
}
});
</script>
</polymer-element>
This assumes that your Firebase has data in the following format:
{ members:
{ memberID1: { name: "Name1" },
memberID2: { name: "Name2" }
}
}

Why isn't the URL being generated for this route?

So, I'm working on a Meteor project and I can't get this route to generate properly, or at all for that matter.
<template name="browseAll">
<h3>List of classes with books available!</h3>
<ul>
{{#each aggCount}}
<li>{{ _id }} ({{ count }})</li>
{{/each}}
</ul>
</template>
The data that is being iterated over is a result of aggregation using MongoInternals, and that is as follows:
(server/methods.js excerpt):
classCount: function() {
// Attempt aggregation of the books table to count by class, maybe.
var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;
var col = db.collection("books");
var aggregateSync = Meteor._wrapAsync(col.aggregate.bind(col));
var pipeline = [
{$group: {_id: "$class", count: {$sum: 1}}},
{$sort: {_id: 1}}
];
var theAnswer = aggregateSync(pipeline);
return theAnswer;
}
It seems that the data is coming through okay, and sample data from aggregation (coming into the template) looks like this:
[ { _id: 'ADNR1234', count: 2 }, { _id: 'ARTH1234', count: 1 } ]
That's the template code I've got, and this is the route that it's supposed to be working with:
this.route('browse-class', {
path: '/browse/:_class',
data: function() {
var booksCursor = Books.find({"class": this.params._class},{sort:{"createdAt": 1}});
return {
theClass: this.params._class,
numBooks: booksCursor.count(),
books: booksCursor
};
}
});
I don't understand it. The data is being SHOWN, and what I want to do is generate a URL for browse-class (route) that takes the value of {{ _id }} in the helper as a parameter, so as to generate something like this:
application.org/browse/CLSS
Be aware that {{pathFor}} must be called with a data context properly set :
{{#with class}}
{{pathFor "browse-class"}}
{{/with}}
Optionnaly it is possible to pass the data context as a parameter :
{{pathFor "browse-class" class}}
The data context provided to pathFor is used when generating the route path, if you defined a route path like this :
path: "/browse/:_id"
Then it will use the _id from the class to properly generate a URL.
For the text of the link, I doubt you want to display the _id, your class documents probably include a "label" so you could use this :
{{ label }}

handlebars - cannot read property of undefined

I have a script that executes once a modal is launched. The js to configure the js object is:
var source = $("#print-script").html();
var template = Handlebars.compile(source);
var data = {
image: image,
title: title,
caption: subtitle,
category: template
};
$("#print-template").html(template(data));
All the variables are set above the object declaration and are working. My html reads:
<script id="print-script" type="text/x-handlebars-template">
<div class="print-only" id="print-template">
<img src="{image}"/>
<h2><span class="icon"></span>{category}</h2>
<h3>{title}</h3>
<p class="headline">{caption}</p>
<div class="description">{long_description}</div>
</div>
</script>
I'm getting a Uncaught TypeError: Cannot read property 'image' of undefined. I have confirmed that the object (data) is being populated with content. Thoughts?
I'd guess that the problem is right here:
var template = Handlebars.compile(source);
var data = {
//...
category: template // <------------------- oops
};
Note that template is the compiled template function there so when the template tries to fill in {category}, it will execute the template function. But the template function is expecting some values to fill in the template with and then it is called through {category}, those values won't be there and you'll get a TypeError. Run these two demos with your console open and you should see what's going on:
Error using category: template:
http://jsfiddle.net/ambiguous/9wEyS/
Works using category: function() { return 'string' }:
http://jsfiddle.net/ambiguous/YgfZx/
turns out there needs to be another object in the data:
var data = { print:{
image: featuredImage,
title: title,
caption: subtitle,
category: template
}
};

Resources