I have a problem. I'm trying to pass a Template's variable in Spacebars HTML to a child template as an argument:
//Assume "var1" is a variable in a template:
<template name='example'>
{{> childTemp val='hello{{var1}}'}}
//Pass ^^ {{var1}} as an argument?
</template>
Normally, the above syntax works for passing the variable inside an HTML element, but it just shows up as the literal string 'hello{{var1})' in the output HTML
Is it possible to pass a template variable as an argument in a child template/how?
You can simply use {{> childTemp val=var1}} syntax, assuming you first do the following:
Template.example.onCreated(function () {
this.var1 = "whatever";
});
Template.example.helpers({
var1 () {
return Template.instance().var1;
}
});
Related
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;
}
});
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
Let's say, I would like to call an Ember Handlebars helper from an emblem.js template.
I have declared the helper through (CoffeeScript syntax):
Ember.Handlebars.registerHelper 't', (key, value, context) ->
...
When attempting to invoke the helper from emblem using
= t "my.i18n.key", "val", count: 42
key is assigned correctly, all but the first arguments are dropped and the second argument is replaced by some options hash as would be the case for a bound helper (the third argument is undefined).
Is there any way to invoke a helper in emblem.js with more than a single argument?
I think you can only pass one value, but you can use the options hash with many bound/computed properties like this:
Ember.Handlebars.registerBoundHelper('truncatedQuery',
function truncatedQueryHelper(value, options) {
console.log('value', value, 'options:',
options.hash['key1'], options.hash['key2'], options.hash['key3']);
// do work...
return somethingUseful;
});
And in your template use optionsHashKeyBinding=propertyOnControllerName like this:
<div class='truncated-query'>
{{truncatedQuery 'value' key1Binding=prop1 key2Binding=prop2 key3=42}}
</div>
Where prop1 and prop2 are on the controller:
App.IndexController = Ember.Controller.extend({
prop1: 'foo',
prop2: function() {
return this.get('prop1') + 'bar';
}.property('prop1'),
});
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 have a problem using knockoutjs with custom template bindings.
Suppose I have a HTML body like this:
<div id="1">
<div data-bind="template:{name: '2', data: data}"></div>
</div>
<div id="2">
<h3 data-bind="text: caption"></h3>
</div>
JS code looks like this:
var ViewModel2 = function () {
this.caption = ko.observable("Caption");
}
var ViewModel1 = function () {
this.data = new ViewModel2();
}
ko.applyBindings(new ViewModel1(), document.getElementById("1"));
If we test this code, everything will work just fine;
See JSFiddle example: http://jsfiddle.net/4eTWW/33/
Now suppose we want to make our custom template binding. We'll use 'templatex' binding instead of 'template'.
In HTML we need to change just one line:
<div data-bind="templatex:{name: '2', data: data}"></div>
Next, let's add custom template binding to JS:
/*Custom binding*/
ko.bindingHandlers.templatex = {
init: function (element) {
ko.bindingHandlers.template.init.apply(this, arguments);
},
update: ko.bindingHandlers.template.update
}
See: http://jsfiddle.net/4eTWW/35/
But in this case we have an error, saying that it can't find 'caption' in the model.
Now let's add template {} to html bindings:
<div data-bind="template: {}, templatex:{name: '2', data: data}"></div>
See: http://jsfiddle.net/4eTWW/36/
And now everything works just fine.
It seems that while binding parent div it can't determine that child div is a template.
So how can I mark it as a template in my custom template binder?
Thanks.
You have wrong update handler, change to this:
ko.bindingHandlers.templatex= {
init: function(element) {
// do things
return ko.bindingHandlers.template.init.apply(this, arguments);
},
update: function(element) {
return ko.bindingHandlers.template.update.apply(this, arguments);
}
}
Here is working fiddle: http://jsfiddle.net/vyshniakov/4eTWW/39/
I don't think you can use a custom binding to create a new template engine. You need to register your custom engine with ko.setTemplateEngine().
From the knockoutjs source:
If you want to make a custom template engine,
[1] Inherit from the ko.templateEngine class (like ko.nativeTemplateEngine does)
[2] Override 'renderTemplateSource', supplying a function with this signature:
function (templateSource, bindingContext, options) {
// - templateSource.text() is the text of the template you should render
// - bindingContext.$data is the data you should pass into the template
// - you might also want to make bindingContext.$parent, bindingContext.$parents,
// and bindingContext.$root available in the template too
// - options gives you access to any other properties set on "data-bind: { template: options }"
//
// Return value: an array of DOM nodes
}
[3] Override 'createJavaScriptEvaluatorBlock', supplying a function with this signature:
function (script) {
// Return value: Whatever syntax means "Evaluate the JavaScript statement 'script' and output the result"
// For example, the jquery.tmpl template engine converts 'someScript' to '${ someScript }'
}
This is only necessary if you want to allow data-bind attributes to reference arbitrary template variables.
If you don't want to allow that, you can set the property 'allowTemplateRewriting' to false (like ko.nativeTemplateEngine does)
and then you don't need to override 'createJavaScriptEvaluatorBlock'.
Example: http://jsfiddle.net/6pStz/ (see Note 7 on this page)