I'm currently dealing with handlebars.js in an express.js application. To keep things modular, I split all my templates in partials.
My problem: I couldn't find a way to pass variables through an partial invocation. Let's say I have a partial which looks like this:
<div id=myPartial>
<h1>Headline<h1>
<p>Lorem ipsum</p>
</div>
Let's assume I registered this partial with the name 'myPartial'. In another template I can then say something like:
<section>
{{> myPartial}}
</section>
This works fine, the partial will be rendered as expected and I'm a happy developer. But what I now need, is a way to pass different variables throught this invocation, to check within a partial for example, if a headline is given or not. Something like:
<div id=myPartial>
{{#if headline}}
<h1>{{headline}}</h1>
{{/if}}
<p>Lorem Ipsum</p>
</div>
And the invokation should look something like this:
<section>
{{> myPartial|'headline':'Headline'}}
</section>
or so.
I know, that I'm able to define all the data I need, before I render a template. But I need a way to do it like just explained. Is there a possible way?
Handlebars partials take a second parameter which becomes the context for the partial:
{{> person this}}
In versions v2.0.0 alpha and later, you can also pass a hash of named parameters:
{{> person headline='Headline'}}
You can see the tests for these scenarios: https://github.com/wycats/handlebars.js/blob/ce74c36118ffed1779889d97e6a2a1028ae61510/spec/qunit_spec.js#L456-L462
https://github.com/wycats/handlebars.js/blob/e290ec24f131f89ddf2c6aeb707a4884d41c3c6d/spec/partials.js#L26-L32
Just in case, here is what I did to get partial arguments, kind of. I’ve created a little helper that takes a partial name and a hash of parameters that will be passed to the partial:
Handlebars.registerHelper('render', function(partialId, options) {
var selector = 'script[type="text/x-handlebars-template"]#' + partialId,
source = $(selector).html(),
html = Handlebars.compile(source)(options.hash);
return new Handlebars.SafeString(html);
});
The key thing here is that Handlebars helpers accept a Ruby-like hash of arguments. In the helper code they come as part of the function’s last argument—options— in its hash member. This way you can receive the first argument—the partial name—and get the data after that.
Then, you probably want to return a Handlebars.SafeString from the helper or use “triple‑stash”—{{{— to prevent it from double escaping.
Here is a more or less complete usage scenario:
<script id="text-field" type="text/x-handlebars-template">
<label for="{{id}}">{{label}}</label>
<input type="text" id="{{id}}"/>
</script>
<script id="checkbox-field" type="text/x-handlebars-template">
<label for="{{id}}">{{label}}</label>
<input type="checkbox" id="{{id}}"/>
</script>
<script id="form-template" type="text/x-handlebars-template">
<form>
<h1>{{title}}</h1>
{{ render 'text-field' label="First name" id="author-first-name" }}
{{ render 'text-field' label="Last name" id="author-last-name" }}
{{ render 'text-field' label="Email" id="author-email" }}
{{ render 'checkbox-field' label="Private?" id="private-question" }}
</form>
</script>
Hope this helps …someone. :)
This can also be done in later versions of handlebars using the key=value notation:
{{> mypartial foo='bar' }}
Allowing you to pass specific values to your partial context.
Reference: Context different for partial #182
This is very possible if you write your own helper. We are using a custom $ helper to accomplish this type of interaction (and more):
/*///////////////////////
Adds support for passing arguments to partials. Arguments are merged with
the context for rendering only (non destructive). Use `:token` syntax to
replace parts of the template path. Tokens are replace in order.
USAGE: {{$ 'path.to.partial' context=newContext foo='bar' }}
USAGE: {{$ 'path.:1.:2' replaceOne replaceTwo foo='bar' }}
///////////////////////////////*/
Handlebars.registerHelper('$', function(partial) {
var values, opts, done, value, context;
if (!partial) {
console.error('No partial name given.');
}
values = Array.prototype.slice.call(arguments, 1);
opts = values.pop();
while (!done) {
value = values.pop();
if (value) {
partial = partial.replace(/:[^\.]+/, value);
}
else {
done = true;
}
}
partial = Handlebars.partials[partial];
if (!partial) {
return '';
}
context = _.extend({}, opts.context||this, _.omit(opts, 'context', 'fn', 'inverse'));
return new Handlebars.SafeString( partial(context) );
});
Sounds like you want to do something like this:
{{> person {another: 'attribute'} }}
Yehuda already gave you a way of doing that:
{{> person this}}
But to clarify:
To give your partial its own data, just give it its own model inside the existing model, like so:
{{> person this.childContext}}
In other words, if this is the model you're giving to your template:
var model = {
some : 'attribute'
}
Then add a new object to be given to the partial:
var model = {
some : 'attribute',
childContext : {
'another' : 'attribute' // this goes to the child partial
}
}
childContext becomes the context of the partial like Yehuda said -- in that, it only sees the field another, but it doesn't see (or care about the field some). If you had id in the top level model, and repeat id again in the childContext, that'll work just fine as the partial only sees what's inside childContext.
The accepted answer works great if you just want to use a different context in your partial. However, it doesn't let you reference any of the parent context. To pass in multiple arguments, you need to write your own helper. Here's a working helper for Handlebars 2.0.0 (the other answer works for versions <2.0.0):
Handlebars.registerHelper('renderPartial', function(partialName, options) {
if (!partialName) {
console.error('No partial name given.');
return '';
}
var partial = Handlebars.partials[partialName];
if (!partial) {
console.error('Couldnt find the compiled partial: ' + partialName);
return '';
}
return new Handlebars.SafeString( partial(options.hash) );
});
Then in your template, you can do something like:
{{renderPartial 'myPartialName' foo=this bar=../bar}}
And in your partial, you'll be able to access those values as context like:
<div id={{bar.id}}>{{foo}}</div>
Yes, I was late, but I can add for Assemble users: you can use buil-in "parseJSON" helper http://assemble.io/helpers/helpers-data.html. (Discovered in https://github.com/assemble/assemble/issues/416).
Not sure if this is helpful but here's an example of Handlebars template with dynamic parameters passed to an inline RadioButtons partial and the client(browser) rendering the radio buttons in the container.
For my use it's rendered with Handlebars on the server and lets the client finish it up.
With it a forms tool can provide inline data within Handlebars without helpers.
Note : This example requires jQuery
{{#*inline "RadioButtons"}}
{{name}} Buttons<hr>
<div id="key-{{{name}}}"></div>
<script>
{{{buttons}}}.map((o)=>{
$("#key-{{name}}").append($(''
+'<button class="checkbox">'
+'<input name="{{{name}}}" type="radio" value="'+o.value+'" />'+o.text
+'</button>'
));
});
// A little test script
$("#key-{{{name}}} .checkbox").on("click",function(){
alert($("input",this).val());
});
</script>
{{/inline}}
{{>RadioButtons name="Radio" buttons='[
{value:1,text:"One"},
{value:2,text:"Two"},
{value:3,text:"Three"}]'
}}
Related
I'm trying to set up an MDC dialog warning. Instead of copy-pasting it into every view that requires it, I'm wrapping the dialog in its own template. The template seems to work, the dialog opens up and functions as normal, however, I can't set a helper function for it that works. I tried using the helper function of the parent template, and even creating the new template its own js file. Neither of these solutions grab the data correctly.
<template name="transactionAlert">
...
<div class="mdc-dialog__content" ><p>Are you sure you wish to continue with this transaction? It could cost up to: <b class="warning-value">${{maxCost}} USD</b></p>
...
</template>
<template name="transactionCreate">
...
{{>transactionAlert}}
</template>
Template.transactionAlert.onCreated(function transactionAlertOnCreated() {
console.log('test')
})
Template.transactionAlert.helpers({
maxCost(){
console.log('test 2')
const instance = Template.instance()
return instance.maxTxCost.get().toString().slice(0,5);
}
})
I tried using the helper function of the parent template
Such problems are often caused by design issues, rather than missing or wrong implementation. If we consider the your transactionAlert to be stateless (it does not contain any relevant view logic or internal state management) then it should also neither access properties nor helpers that are out of it's scope.
Otherwise you will create such a tight coupling that it will throw back in your face in two years or so (when the refactoring session is calling).
In contrast the responsibilities of the parent Template are to
manage state of the data (subscriptions, data post-processing etc.)
check the conditions, whether the transactionAlert should appear or disappear
pass the proper parameters to the transactionAlert Template
As a consequence you may design your transaction alert as a parameterized template:
<template name="transactionAlert">
...
<div class="mdc-dialog__content" ><p>Are you sure you wish to continue with this transaction? It could cost up to: <b class="warning-value">${{maxCost}} USD</b></p>
...
</template>
As you can see it looks exactly the same. The difference is, that you remove the Template.transactionAlert.helpers and cause the Template to look for maxCost being passed to the template.
Now in your parent Template you will pass the data to the transactionalert, once the condition for alerting applies:
<template name="transactionCreate">
{{#if showAlert}}
{{>transactionAlert maxCost=getMaxCost}}
{{/if}}
</template>
where the helper is now:
Template.transactionCreate.helpers({
showAlert () {
return Template.instance().showAlert.get()
},
getMaxCost(){
const instance = Template.instance()
return instance.maxTxCost.get().toString().slice(0,5);
}
})
Because you need reactivity to show/hide the alert you will make use of the Template's internal Tracker:
Template.transactionCreate.onCreated(function () {
const instance = this
instance.showAlert = new ReactiveVar(false)
instance.autorun(() => {
const maxCost = instance.maxTxCost.get()
if (/* max cost exceeds limit */) {
instance.showAlert.set(true)
} else {
instance.showAlert.set(false)
}
})
})
Edit: Additional information on reactivity
Reactivity is a main concept of Meteor's client ecosystem. It bases on the Tracker package, which is linked to any Template instance. The guide to the reactive data stores explains the concept a bit further: https://guide.meteor.com/data-loading.html#stores
In keystonejs (a node cms), I am using handlebars as templating engine in keystonejs. In handlebar file, I am using #each to iterate through an array.
{{#each data.positions}}
<h1 onclick = "callFunction()"> {{title}} </h1>
{{/each}}
Here I want to call a function within route, How can I do it?
Other way that is coming in my mind to to initialize locals variable. Like I have a variable locals.selectedPositionIndex in route file and I want to assign #index value of specific h1 element to this variable when any h1 element is clicked.
You could include a script at the bottom of your Handlebars template and include the function there:
<script>
document.querySelector('h1').addEventListener('click', function() {
// Code here
})
</script>
Alternatively, there is a question here with a useful answer:
Passing a function into a Handlebars template
I have amended the answer from the above question to work with KeystoneJS:
In your view you can assign your function to locals:
locals.func = function () {
// Code here
};
Then you will need to create a Handlebars helper in /templates/views/helpers/index.js:
_helpers.stringifyFunc = function(fn) {
return new hbs.SafeString("(" + fn.toString().replace(/\"/g,"'") + ")()");
};
You can then use this in your Handlebars template:
<h1 onclick="{{ stringifyFunc func }}">{{ title }}</h1>
I hope this helps.
For few days, I've begun to use Handlebars and I'm currently trying Decorators. I've understood how it works when it remains simple :
Ex: decorate the name alone : bill gaTes -> Mr Bill GATES
I then tried to use decorator in a "each" loop, to do exactly the same as before but with a list of people instead of just one. The problem is: when I am in the decorator function, I need to know which element (from the array) I am looking at.
So I would like to either give in argument the index of the "each" loop (then, as I have access to the data, I could retrieve the current element) or the current element.
I tried to use #index (which usually works well), but I get undefined when debugging.
And I can't find a way to get the current element in my decorator.
Here is the code:
index.html
<!doctype html>
<html>
<head>
<title>Test Handlebars Decorators 4</title>
</head>
<body>
<div id = "main"> </div>
<script id = "rawTemplate" type = "text/x-handlebars-template">
{{#each this}}
Decorated: {{formatGenderHelper this}}
{{* activateFormatter #index}}
<br />
{{/each}}
</script>
<script type = "text/javascript" src = "js/lib/handlebars.min-latest.js"></script>
<script type = "text/javascript" src = "js/lib/jquery-2.2.4.min.js"></script>
<script type = "text/javascript" src = "js/data.js"></script>
<script type = "text/javascript" src = "js/index.js"></script>
</body>
</html>
data.js
var data = [{
"firstname": "Bill",
"lastname": "Gates",
"gender": "MALE"
}, {
"firstname": "Hillary",
"lastname": "Clinton",
"gender": "FEMALE"
}];
function formatMale(author) {
return "M. " + author.firstname + " " + author.lastname.toUpperCase();
}
function formatFemale(author) {
return "Mme " + author.firstname + " " + author.lastname.toUpperCase();
}
Handlebars.registerDecorator('activateFormatter', function(program, props, container, context) {
var genderHelper;
var gender = context.args[0] || context.data.root[0].gender;
switch(gender) {
case "MALE":
genderHelper = formatMale;
break;
case "FEMALE":
genderHelper = formatFemale;
break;
default:
console.log('Gender format not set. Please set before rendering template.');
genderHelper = function() {};
}
container.helpers = {
formatGenderHelper: genderHelper
};
});
index.js
// Get the template
var rawTemplate = $('#rawTemplate').html();
// Compile the template
var compiledTemplate = Handlebars.compile(rawTemplate);
// Apply the template on the data
var content = compiledTemplate(data);
// Finally, re-inject the rendered HTML into the DOM
$('#main').html(content);
If you need further information, please let me know.
Thanks you for helping :)
There are two issues that make your example fail. One is a slight issue with your code, and the other is the way decorators seem to work, which is essentially just "not in loops" (unless you're using partials, see the last section of this answer).
First, you haven't told the decorator what to do with the #index that you're passing to it. You could change the decorator function, but since you have the decorator inside the #each block, the this is the same one that gets passed to formatGenderHelper, meaning that we can pass the decorator this.gender which resolves to just the gender string. This means that the writer of the template knows exactly what they're passing to the decorator, and the logic isn't trying to second-guess what the template is telling it.
index.html
...
{{#each this}}
Decorated: {{formatGenderHelper this}}
{{* activateFormatter this.gender}}
<br />
{{/each}}
...
Also, I figure you are basing your example on Ryan Lewis's sitepoint demo. One problem/limitation his code has that isn't immediately obvious (because in the simple case it isn't even an issue), is that his decorator overwrites of all of the available helpers except for the formatting one it provides. To avoid "Error(s): TypeError: Cannot read property 'call' of undefined" errors when doing things that are a little bit more complex, I recommend using this in your decorator function.
data.js
Handlebars.registerDecorator('activateFormatter', function(program, props, container, context) {
// leaving out the code here for brevity
container.helpers = Object.assign({}, container.helpers, {
formatGenderHelper: genderHelper
});
});
The second issue is a general one with using decorators in loops, that is simply a result of the way Handlebars works with decorators. According to the decorators API doc,
Decorators are executed when the block program is instantiated and are passed (program, props, container, context, data, blockParams, depths).
This means (afaict) that when the template for the block is first instantiated, meaning before any of the other helpers or programs that will need to run on the block, it executes the decorator, and the decorator makes the modifications it needs, before executing the program that created the block (in this case the #each). This means that to get the decorator to differentiate each iteration of the #each differently, it needs to run in a grandchild block of the #each, so it is executed after the #each but before the formatGenderHelper, however, if you're doing this and redefining the decorated helper with each iteration based on the iterated-over context, you're better off just registering a helper that has the gender formatting logic baked in.
However, that's sort of a non-answer. So, to answer your question, I've found that you can make handlebars do what you're trying to do, but it's sort of a hack. Since the trick is that you need the block with the decorator to be rendered from a sub-sub block of the #each we can render it in a partial. To do this we can stick it in another file and register that as a partial, but that's not really convenient, so we have two better options. 1) we can wrap the code in an undefined partial-block and let Handlebars render it as the fallback block when that partial fails, or (the slicker option) 2) we can use the provided builtin decorator #*inline to define an inline partial.
Fail-over partial block method
index.html
...
{{#each this}}
{{#>nothing}}
Decorated: {{formatGenderHelper this}}
{{* activateFormatter this.gender}}
<br />
{{/nothing}}
{{/each}}
...
#*inline method
index.html
...
{{#*inline "formattedAuthor"}}
Decorated: {{formatGenderHelper this}}
<br />
{{/inline}}
{{#each this}}
{{#>formattedAuthor}}
{{* activateFormatter this.gender}}
{{/formattedAuthor}}
{{/each}}
...
The key here is that we use our *activeFormatter decorator to dynamically reconfigure the partial each time it gets called. The second, inline example demonstrates this more clearly. There may certainly be other good use-cases, but this is where I see decorators really shining, i.e. allowing us to dynamically reconfigure the logic or helpers of partials right from where we call them.
However, there is a caveat: if our partial uses a helper that is only provided in a decorator that is called from outside of that partial's definition (like we have above in example 2) the partial will not be able to find the helper if that decorator is not called in the right place. This is also why it's better to use either of the two methods above for providing the partial: we keep the definition of the partial in the same file as the decorator call, so we can know that the helper is only provided dynamically and not registered globally.
I often find myself dividing my work into templates that still could use the same helpers.
So, say I have this template structure:
<template name="MainTemplate">
<div>{{> FirstTemplate}}</div>
<div>{{> SecondTemplate}}</div>
<div>{{> ThirdTemplate}}</div>
<div>{{> FourthTemplate}}</div>
</template>
Now each of these templates wants to use the same helper, let's call it dataHelper:
Template.MainTemplate.helpers({
dataHelper: function() {
//do some stuff
return result
}
})
Sadly, this helper can't be accessed in template first through fourth by simply typing {{dataHelper}} like how events work.
My solution has been to create a global helper instead, but that seems a tad overkill, especially since I have a few pages that don't care about these helpers at all. Another solution is to create four separate helpers but, hey, DRY.
Am I missing something simple here?
There isn't an obvious way to do this in the current version of meteor. One solution is for the child template to "inherit" the helpers from the parent. You can do this pretty easily using meteor-template-extension. Here's an example:
html
<body>
{{> parent}}
</body>
<template name="parent">
<h1>parent</h1>
{{> child}}
</template>
<template name="child">
<h2>child</h2>
<p>{{saySomething}}</p>
</template>
js
Template.parent.helpers({
saySomething: function() {
return Random.choice(['hello', 'dude!', 'i know right?']);
}
});
Template.child.inheritsHelpersFrom('parent');
The template child inherits all of its parent's helpers so it has direct access to saySomething.
This technique has two drawbacks:
you have to specify the inheritsHelpersFrom relationship
all of the parent's helpers are inherited
You can access your parent helpers using either a notation like {{yourParentHelper ..}} with two dots. Have a look here for more informations (end of the article)
You can also access parent data context in javascript like that:
var parent_data = Template.parentData();
By the way, you can add a parameter to reach the third parent, for instance:
var parent_data = Template.parentData(3);
The double dot notation seems to work best within {{#each}} loops, and I'm not having any luck within actual child templates. One option would be to use {{#with}}, although that limits you to basically one helper. e.g.:
<template name="parent">
{{#with dataHelper}}
{{> first}}
{{> second}}
{{/with}}
</template>
This will set the data context of the child helpers to dataHelper, and you can access them with {{this}} inside the template. I suppose you could make dataHelper an object and then pass in multiple pieces of data that way.
Ok, so let me preface this by saying I'm completely new to Ember. I'm having an interesting time just trying to get a basic binding to work. Here's my relevant code:
App.js
var App = Ember.Application.create({
rootElement: '#emberApp'
});
And routes.js:
App.Router.map(function () {
});
App.IndexRoute = Ember.Route.extend({
model: function () {
return { Foo: 3 };
}
});
And then here is my HTML document:
<div id="emberApp"></div>
<script src="~/Scripts/jquery-1.10.2.js"></script>
<script src="~/Scripts/handlebars.js"></script>
<script src="~/Scripts/ember-1.4.0.js"></script>
<script src="~/Scripts/EmberApp.js"></script>
<script src="~/Scripts/Routes.js"></script>
<script type="text/x-handlebars" data-template-name="index">
<div>The current foo value: {{Foo}}</div>
{{input valueBinding=Foo}}
</script>
The intended result is that when I type in the input field created, the value that is bound in the template changes at the same time. It should be fairly simple right? I have to be missing something.
The template correctly renders The current foo value: 3, and it renders a text field. However, typing anything into this field does nothing to the above binding. I have tried marking it with a valueBinding tag, as well as switching to Ember.TextField instead of a input helper. I've also tried making a custom Ember.Object.extend class and returning that as the model for the index route.
What am I missing in order to bind the text box value to the {{Foo}} value in the template?
EDIT - Ok, I've figured out it's because of the capitalization of the variable: foo works, but not Foo. Why is this?
I'm expecting to receive JSON results similar to this:
{
RemoteUserId: 0,
UserPhotoUrl: '....',
RemoteUserName: 'Bob',
}
I'm assuming this means I need to 'hide' these values by making controller wrappers for each element? ie:
remoteUserId: function() {
return this.get('RemoteUserId');
}.property()
I'm afraid you've been bitten by one of Embers naming conventions which is normally awesome as it usually means things just work, but occasionally will bite you if you're not aware of it.
Ember expects that Classes or Namespaces are capitalized and that instances are lowercase. When Ember sees the Foo property used in a binding it assumes it's a namespace and will then look for a global variable called Foo instead of a controller property.
When you use {{Foo}} in a template the behavior is slightly different as Ember will first check the current context (the controller) to see if the property exists there. If it does it uses that value, otherwise it will assume it's a namespace and look for it globally. Bindings don't work like templates due to performance concerns as you don't want to have to check two locations for a value in a binding that could be updated very frequently (like a textbox being typed in).
This is why you can use Foo in the template and it works:
<script type="text/x-handlebars" data-template-name="index">
<!-- This works! -->
<div>The current foo value: {{Foo}}</div>
</script>
But when you try to use Foo as part of a binding it won't work:
<script type="text/x-handlebars" data-template-name="index">
<!-- This doesn't work as Ember thinks Foo is global (i.e., a namespace) -->
{{input valueBinding=Foo}}
</script>
Your best bet is to just follow ember conventions and make sure all your property names start with a lowercase character. However, if you want to continue using properties in your controllers that start with a capital character then you will need to explicitly tell Ember that the property is from the controller and is not global when you try to use it in a binding:
<script type="text/x-handlebars" data-template-name="index">
<!-- Tell Ember Foo is in the controller which is what we want-->
{{input valueBinding=controller.Foo}}
</script>
Here is a Fiddle demonstrating everything written above:
http://jsfiddle.net/NQKvy/881/
Its because in Ember properties should start with a lowercase letter! Uppercase letters are globals.
so u could simple convert your JSON bevore import it into ember:
var decapitalize = function(source) {
var result = {};
for(var prop in source) {
result[prop.substr(0,1).toLowerCase() + prop.substr(1)] = source[prop];
}
return result;
};