Handlebars helper for template composition - handlebars.js

I have a Handlebar helper to invoke a template within a template,
the usage is this :
applyTemplate subTemplateId arg1=123 arg2="abc" ...
It is also possible to pass html content
{{# applyTemplate "tli" a=1 b="y"}}
... any content here will get passed to the sub template with {{content}}
{{/ applyTemplate }}
This jsFiddle illustrates how it works : http://jsfiddle.net/maxl/ywUjj/
My problem : I want the variables in the calling scope to be accessible
in the sub templemplate, in the jsFiddle, notice how {{topLevelVar}}
is not available.
Thanks

From this example i would say you can use fn to access the context in your helper method
http://yehudakatz.com/2010/09/09/announcing-handlebars-js/
applyTemplate: function(context, fn) {
for(var i=0, j=context.length; i < j; i++) {
buffer.push(fn(context[i]))
}
}
Where fn is the inner "template" part, and context the model that gets applied to it.

Starting from the solution on http://jsfiddle.net/dain/NRjUb/ we can achieve the same result but with inline templates as:
<script id="topLevel" type="text/x-handlebars-template">
{{# defTpl "test1"}}
La plantilla <b>diu</b> {{part}},{{../topLevelVar}}
{{/ defTpl }}
{{# each sub }}
Iplant-->{{eTpl "test1" part=this}}--fi plant<br>
{{/each}}
</script>
And registering the handlebars helpers like:
(function()
{
var h={};
Handlebars.registerHelper('defTpl', function(name, context){
// the subtemplate definition is already compiled in context.fn, we store this
h[name]=context.fn;
return "";
});
// block level /inline helper
Handlebars.registerHelper('eTpl', function(name, context){
if (!h[name]) return "Error , template not found"+name;
var subTemplate = h[name];
//if this isn't a block template , the function to render inner content doesn't exists
var innerContent = context.fn?context.fn(this):"";
var subTemplateArgs = $.extend({}, context.hash, {content: new Handlebars.SafeString(innerContent)});
return new Handlebars.SafeString(subTemplate(subTemplateArgs))
});
})();
And calling this with:
var _template = Handlebars.compile($('#topLevel').html());
$('body').append(_template({topLevelVar:123, content:"cascading",sub:[45,30,12]}));
Hope this helps :)

Add "../" before topLevelVar to access the parent context.
For example:
{{../topLevelVar}}
<script id="tli" type="text/x-handlebars-template">
<tr><td>{{a}}----> {{content}} <----- {{b}}</td></tr>
</script>
<script id="zaza" type="text/x-handlebars-template">
<table>
{{# applyTemplate "tli" a=1 b="y"}}<input type="text" value='a'>{{../topLevelVar}}{{/ applyTemplate }}
{{# applyTemplate "tli" a=2 b="z"}}<input type="text" value='b'>{{/ applyTemplate }}
</table>
</script>

Related

Define object & pass it to a partial

What I want to do:
{{>myPartial foo={bar:1} }}
I want to define an object while passing it to a partial. Is that possible?
I know it's possible to pass an existing object like
{{>myPartial foo=foo}}
But I want to define my object within my markup.
Why? Well basically because it's just to define layout. I want to avoid to determine layout decisions on the backend.
My partial is a table layout, and I want to hide specific columns.
But instead of using multiple properties like
{{>myPartial hideFoo=true hideBar=true}}
I want to use a single object hide
{{>myPartial hide={foo:true,bar:true} }}
You can pass a new context to a partial:
{{> myPartial context }}
Example:
var data = {
title: "Foo Bar",
foo: ["foo1", "foo2"],
bar: ["bar1", "bar2"],
hide: {
foo: true,
bar: false
}
};
var content = "{{title}} {{> myPartial hide }}";
var partialContent = "<div class=\"{{#if foo}}hideFoo{{/if}} {{#if bar}}hideBar{{/if}}\">Hide</div>";
var template = Handlebars.compile(content);
Handlebars.registerPartial("foo", partialContent);
template(data);
Output:
<div class="hideFoo hideBar">Hide</div>
Another way is to pass a JSON string, instead of an object, using a helper in the way:
//helper
Handlebars.registerHelper("parseJSON", function(string, options) {
return options.fn(JSON.parse(string));
});
//template
{{#parseJSON '{"foo": true,"bar": true}'}}
{{> myPartial}}
{{/parseJSON}}
Demo:
//Compile main template
var template = Handlebars.compile($("#template").html());
//Register partial
Handlebars.registerPartial("myPartial", $("#myPartial").html());
//Register parseJSON helper
Handlebars.registerHelper("parseJSON", function(string, options) {
return options.fn(JSON.parse(string));
});
//Your data
var data = {
title: "Foo Bar"
};
document.body.innerHTML = template(data);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.5/handlebars.min.js"></script>
<!-- template.html -->
<script id="template" type="text/x-handlebars-template">
<h1>{{title}}</h1>
<h3>First Partial:</h3>
{{#parseJSON '{"foo": true,"bar": false}'}}
{{> myPartial}}
{{/parseJSON}}
<h3>Second Partial:</h3>
{{#parseJSON '{"foo": false,"bar": false}'}}
{{> myPartial}}
{{/parseJSON}}
</script>
<script id="myPartial" type="text/x-handlebars-template">
<div>hide.foo: {{foo}}</div>
<div>hide.bar: {{bar}}</div>
</script>

Meteor Block Helper that acts like a template

Here's what I want, a custom block helper that can act like a template, monitoring for it's own events etc. The html would look like this:
{{#expandable}}
{{#if expanded}}
Content!!!
<div id="toggle-area"></div>
{{else}}
<div id="toggle-area"></div>
{{/if}}
{{/expandable}}
And here's some javascript I have put together. This would work if I just declared the above as a template, but I want it to apply to whatever input is given to that expandable block helper.
Template.expandableView.created = function() {
this.data._isExpanded = false;
this.data._isExpandedDep = new Deps.Dependency();
}
Template.expandableView.events({
'click .toggle-area': function(e, t) {
t.data._isExpanded = !t.data._isExpanded;
t.data._isExpandedDep.changed();
}
});
Template.expandableView.expanded = function() {
this._isExpandedDep.depend();
return this._isExpanded;
};
I know I can declare block helpers with syntax like this:
Handlebars.registerHelper('expandable', function() {
var contents = options.fn(this);
// boring block helper that unconditionally returns the content
return contents;
});
But that wouldn't have the template behavior.
Thanks in advance! This might not be really possible with the current Meteor implementation.
Update
The implementation given by HubertOG is super cool, but the expanded helper isn't accessible from within the content below:
<template name="expandableView">
{{expanded}} <!-- this works correctly -->
{{content}}
</template>
<!-- in an appropriate 'home' template -->
{{#expandable}}
{{expanded}} <!-- this doesn't work correctly. Expanded is undefined. -->
<button class="toggle-thing">Toggle</button>
{{#if expanded}}
Content is showing!
{{else}}
Nope....
{{/if}}
{{/expandable}}
In the actual block helper, expanded is undefined, since the real thing is a level up in the context. I tried things like {{../expanded}} and {{this.expanded}}, but to no avail.
Strangely, the event handler is correctly wired up.... it fires when I click that button, but the expanded helper is simply never called from within the content, so even console.log() calls are never fired.
Meteor's new Blaze template engine solves this problem quite nicely.
Here's an excerpt from the Blaze docs showing how they can be used.
Definition:
<template name="ifEven">
{{#if isEven value}}
{{> UI.contentBlock}}
{{else}}
{{> UI.elseBlock}}
{{/if}}
</template>
Template.ifEven.isEven = function (value) {
return (value % 2) === 0;
}
Usage:
{{#ifEven value=2}}
2 is even
{{else}}
2 is odd
{{/ifEven}}
You can achieve this by making a helper that returns a template, and passing the helper options as a data to that template.
First, make your helper template:
<template name="helperTemplate">
<div class="this-is-a-helper-box">
<p>I'm a helper!</p>
{{helperContents}}
</div>
</template>
This will work as a typical template, i.e. it can respond to events:
Template.helperTemplate.events({
'click .click-me': function(e, t) {
alert('CLICK!');
},
});
Finally, make a helper that will return this template.
Handlebars.registerHelper('blockHelper', function(options) {
return new Handlebars.SafeString(Template.helperTemplate({
helperContents: new Handlebars.SafeString(options.fn(this)),
}));
});
The helper options are passed as a helperContents param inside the template data. We used that param in the template to display the contents. Notice also that you need to wrap the returned HTML code in Handlebars.SafeString, both in the case of the template helper and its data.
Then you can use it just as intended:
<template name="example">
{{#blockHelper}}
Blah blah blah
<div class="click-me">CLICK</div>
{{/blockHelper}}
</template>

assemble - Render a list of strings as Handlebars partial

Using the assemble i actually stuck on a problem which i can't fix myself.
I'm defining a bunch of widgets in the YAML front matter section and including an partial aside {{> aside}}. Until here everything works as expected!
What i'm trying to do now, is to take the list widgets and rendering my partials within the aside template. But anyhow it does not work as expected.
src/templates/layouts/layout-default.hbs
---
layout: src/templates/layouts/layout-default.hbs
widgets:
- widget_link-list
- widget_welcome-message
---
<section role="main">
<h1>Template TwoCol</h1>
{{> body }}
</section>
<aside role="complementary">
{{> aside}}
</aside>
src/templates/partials/aside.hbs
{{#each widgets}}
{{.}}
{{/each}}
Using {{.}} prints my above defined list as string. But if i try to do {{> .}} this, the console drops the following warning:
Warning: The partial . could not be found Use --force to continue.
I found a way by creating a custom helper that can be invoked from any Handlebars template. Now i'm able to use {{renderPartial 'partialName' context}}.
In my template:
var aside = ['my-widget-a','my-widget-b','my-widget-x'];
{{#each aside}}
{{renderPartial this ../this}}
{{/each}}
Javascript Module
module.exports.register = function (Handlebars, context) {
Handlebars.registerHelper("renderPartial", function (name) {
var fn,
template = Handlebars.partials[name];
if (typeof template !== 'Function') {
// not compiled, so we can compile it safely
fn = Handlebars.compile(template);
} else {
// already compiled, just reuse it
fn = template;
}
var output = fn(context).replace(/^\s+/, '');
return new Handlebars.SafeString(output);
});
};

Meteor template: Pass a parameter into each sub template, and retrieve it in the sub-template helper

I am trying to figure out how to pass a parameter into a sub-template that is in an each block and use the parameter in the sub-template as well as sub-template helper. Here is what I tried so far:
template:
<template name="parent">
{{#each nodes }}
{{> child myParam}}
{{/each}}
</template>
<template name="child">
{{ paramName }}
</template>
js:
Template.parent.nodes = function() {
//return a list
};
Template.parent.myParam = function() {
return {"paramName" : "paramValue"};
};
Template.child.someOtherHelper = function() {
//How do I get access to the "paramName" parameter?
}
So far, it hasn't been working, and it seems somehow mess up my input node list also.
Thanks for help.
When you use {{> child myParam}}, it's calling the child template and associates myParam as current template data context, meaning that in the template you can reference {{paramName}}.
In someOtherHelper you could use this.paramName to retrieve "paramValue".
However, when you're using {{#each nodes}}{{> child}}{{/each}}, it means that you pass the content of the current list item (fetched from a LocalCursor or directly an array item) as the template data of child, and you can reference the list item properties using {{field}} in html or this.field in js.
What's happening here is when you call {{> child myParam}}, the myParam helper content OVERWRITES the current node item as template data, that's why it's messing your node list.
A quick (dirty) trick would be to simply extend the myParam helper so that it also contains the template data from the {{#each}} block.
Template.parent.helpers({
nodes:function(){
// simulate typical collection cursor fetch result
return [{_id:"A"},{_id:"B"},{_id:"C"}];
},
myParam:function(){
// here, this equals the current node item
// so we _.extend our param with it
return _.extend({paramName:"paramValue"},this);
}
});
Template.child.helpers({
someOtherHelper:function(){
return "_id : "+this._id+" ; paramName : "+this.paramName;
}
});
<template name="parent">
{{#each nodes}}
{{> child myParam}}
{{/each}}
</template>
<template name="child">
{{! this is going to output the same stuff}}
<div>_id : {{_id}} ; paramName : {{paramName}}</div>
<div>{{someOtherHelper}}</div>
</template>
Depending on what you're precisely trying to achieve, there might be a better approach but this one gets the job done at least.

Reaching an object in the general context using "each" function of handlebars

I'm trying to use handlebars. It worked pefectly until I used the each function. The function works fine but it seems like it references everything to the parameters it takes. Therefore I cannot reach the object I want. I think a bit of code will be clearer anyway.
<div id="myDiv"></div>
<script type="text/x-handlebars-template" id="handlebar">
{{#each items}}
<p>{{this.name}} {{this.surname}}</p>
<p>{{somethingInTheGeneralContext}}</p>
{{/each}}
</script>
<script>
$( document ).ready(function() {
var source = $("#handlebar").html();
var template = Handlebars.compile(source);
var context = {
items: [
{
name:"Paul",
surname:"Buchon"
},
{
name:"Pierre",
surname:"Bino"
},
{
name:"Jean",
surname:"Duflou"
}
],
somethingInTheGeneralContext: "It works !"
};
var html = template(context);
$("#myDiv").html(html);
});
</script>
This prints :
Paul Buchon
Pierre Bino
Jean Duflou
And my somethingInTheGeneralContext is lost somewhere...
Any idea how I can fix it ?
Thanks!
I believe you can use {{../param}} to break out of the each context and go back to the parent level, where somethingInTheGeneralContext exists:
<script type="text/x-handlebars-template" id="handlebar">
{{#each items}}
<p>{{this.name}} {{this.surname}}</p>
<p>{{../somethingInTheGeneralContext}}</p>
{{/each}}
</script>

Resources