The problem is setup on JSFiddle: https://jsfiddle.net/sshadmand/fNPvf/16812/
Given ...
The data passed to the template is:
{
"curr_level": 0,
"levels" : [item1, item2, item3 ...]
}
The handlebars template is:
<div>
{{#each levels}}
{{../curr_level}} == {{#index}}
{{#ifeq ../../curr_level #index}}yes{{else}}no{{/ifeq}}
{{/each}}
</div>
The #ifeq helper function is:
Handlebars.registerHelper('ifeq', function(val1, val2, options) {
console.log("if equal", val1, typeof(val1), val2, typeof(val2));
if (val1 == val2){
return options.fn(this);
}
return options.inverse(this);
});
The problem is:
Accessing the parent curr_level variable using ../ works to print the value to the screen, but when the curr_level variable is sent to the helper it is undefined. I have tried passing different depths to the helper e.g. curr_level, ../curr_level, ../../curr_level etc
For all depths "val1" in the helper is undefined. Again, I am able to access the curr_level variable when it is not using the comparison helper.
Note: This is similar to, but very different from this SO question asking about parents. The main difference being I am trying to pass the parent variable to a helper function:
Access properties of the parent with a Handlebars 'each' loop
Related
I currently have a helper that looks like:
Ember.Handlebars.registerHelper('ifEq', function(a, b, opts) {
if (a == b) {
return opts.fn(this);
} else {
return opts.inverse(this);
}
});
and in my template, I do
GRAPH_TYPE: {{graphType}}
{{#ifEq graphType "p_graph"}}
TEST1
{{else}}
TEST2
{{/ifEq}}
However, this displays
GRAPH_TYPE: p_graph TEST2
This leaves me confused as there should be an exact string match above.
So, I dug into the web inspector and noticed that the value of a in the Handlebars helper was of the value graphType. Why wasn't the value passed in and how do I ensure that it is passed in?
You need to register it as a bound helper:
Ember.Handlebars.registerBoundHelper('ifEq', function(a, b, opts) {
^ like so
When you register a normal/basic helper, you're going to see the parameters passed in by string value as you're witnessing.
However, if you want the argument strings to be bound to properties in your template (which, in this case, you do), you need to use the function signature above.
When Blaze makes a call to a function defined inside a Template.xxx.helper, it passes one or more arguments. The first argument is an object which appears to be empty. What is it? What can it be used for?
Here's how to recreate my barebones test, using a Terminal window:
meteor create test
cd test
cat > test.html << EOF
<body>
{{> test}}
</body>
<template name="test">
<p>{{test1 "data"}}</p>
<p>{{test2 key="value"}}</p>
</template>
EOF
cat > test.js << EOF
if (Meteor.isClient) {
Template.test.helpers({
test1: function (argument) {
console.log(this, argument)
return "test1 helper: " + argument
}
, test2: function (argument) {
console.log(this, argument)
return "test2 helper: " + argument.hash.key
}
});
}
EOF
meteor run
Here's what I see in the browser console, after expanding the hash object:
Object {} "data"
Object {} S…s.kw {hash: Object}
hash: Object
key: "value"
__proto__: Object__proto__: Spacebars.kw
What is the this Object {}?
And in particular, is there a way I can use it to discover which HTML element triggered the call?
Inside a template helper, this is the data context of your template instance.
In your example the data context is not set, so it is returning an empty object. But that is not always the case. Imagine the following example:
<template name='parent'>
{{#with currentUser}}
{{> child}}
{{/with}}
</template>
In this case Meteor.user() has been set as the data context for the instance of Template.child, so Meteor.user() is bound to this in Template.child.helpers(). Which allows you to do the following:
Template.child.helpers({
greeting: function(){
console.log(this); // logs Meteor.user() || undefined
return 'Welcome back ' + this.username;
}
});
The data context can be set explicitly, via each or with blocks, or via parent template contexts. As in the example above you'll generally want to check against undefined when using this in helpers.
The short answer to your question whether this in a template helper can identify the DOM node(s) that invoke it is no. You may be able to dig it out of the helper's parameters via the prototype (I haven't checked), but I would consider this an antipattern. If you care where the helper is coming from, just include a parameter. Continuing the prior example:
<template name='child'>
<p>{{greeting}}</p>
<p>{{greeting 'special'}}</p>
</template>
And:
Template.child.helpers({
greeting: function(str){
if (str === 'special'){
return 'Welcome to first class Ambassador ' + this.username;
}
return 'Please take your seat in coach ' + this.username;
}
});
is there a way in registerHelper to get the content of a block?
Lets assume we have the following template:
{{#myif test}}thats the content i want to have{{/myif}}
And the following registerHelper Code:
Ember.Handlebars.registerBoundHelper('myif', function(test)
{
// do something
return <content of handlebars block>;
});
Many thanks!
Handlebars provides the nested block to the helper as options.fn, where options is the last argument of your helper. You can invoke this block with a context object which is where that block will pick up values from.
To pass the context of the helper itself you can call it with this.
In this case you will probably also want options.inverse which is an optional block that will be used if your condition is false.
Ember.Handlebars.registerHelper('myif', function(condition, options) {
if (condition) {
return options.fn(this);
} else {
return options.inverse(this);
}
});
And the subsequent use in the template,
{{#myif condition}}
true block here
{{else}}
else block here
{{/myif}}
For instance, is there a way to nest my "i18n" helper inside another helper's hash variable?
{{view "SearchView" placeholder="{{t 'search.root'}}" ref="search" url="/pages/search" className='home-search' polyfill=true}}
Update: Handlebars now supports subexpressions, so you can just do:
{{view "SearchView" (t 'search.root')}}
Your scenario is not directly supported, but there a couple of workarounds you can use. The handlebars helpers are just javascript code, so you can execute them from within the helper code itself:
function translateHelper() {
//...
}
function viewHelper = function(viewName, options) {
var hash = options.hash;
if(hash.placeholder) {
hash.placeholder = translateHelper(hash.placeholder);
}
};
Handlebars.registerHelper('view', viewHelper);
Handlebars.registerHelper('t', translateHelper);
And just pass the i18n key to as the argument:
{{view placeholder="search.root"}}
This is nice, as long as your helper knows which arguments should be localized, and which not. If that is not possible, you can try running all the helper arguments through Handlebars, if they contain a handlebars expression:
function resolveNestedTemplates(hash) {
_.each(hash, function(val, key) {
if(_.isString(val) && val.indexOf('{{' >= 0)) {
hash[key] = Handlebars.compile(val)();
}
});
return hash;
}
function view(viewName, options) {
var hash = resolveNestedTemplates(options.hash, this);
}
And use the nested template syntax you described:
{{view placeholder="{{t 'search.root'}}" }}
I realize neither of these options are perfect, but they're the best I could think of.
I'm trying to implement a simple "if-equal" helper in handlebars, like so:
Ember.Handlebars.registerHelper('ifeq', function(val1, val2, options) {
return (val1 == val2) ? options.fn(this) : '';
});
And use it like so (let's assume the foo variable is set to "bar"):
{{#ifequal foo "bar"}} show something {{/ifequal}}
The problem is, when val1 is passed in, it's passed in as the string "foo", and not the actual value of the foo variable. In my debugger, I can verify that this[val1] is in fact set to the expected value of the foo variable.
Does Ember somehow alter the behavior?
Ember.Handlebars.registerHelper just passes strings in. There is a registerBoundHelper being worked on, but in your case this should work.
Ember.Handlebars.registerHelper('ifeq', function(val1, val2, options) {
return (this.get(val1) == val2) ? options.fn(this) : '';
});