I'm definitely missing something about the way Handlebars works. I need to call different partials depending on the value of a variable. Currently the only way I've found to do it is this:
<template name="base">
{{#if a}}{{> a}}{{/if}}
{{#if b}}{{> b}}{{/if}}
{{#if c}}{{> c}}{{/if}}
</template>
And in the corresponding JS:
Template.base.a = function () {
return (mode === "a");
}
Template.base.b = function () {
return (mode === "b");
}
Template.base.c = function () {
return (mode === "c");
}
...which strikes me as extremely verbose. What I'd really like to do is something like:
<template name="base">
{{> {{mode}} }}
</template>
In other words, the value of mode would be the name of the partial that is called.
This seems like it must be a very common use-case, but I can't find any examples of this online. Where have I gone wrong?
The partials are stored in Handlebars.partials so you can access them by hand in your own helper. There are a few tricky bits here though:
The contents of Handlebars.partials can be strings or functions so you have to compile the partials on first use.
Handlebars doesn't know if the partial will be text/plain or text/html so you'll need to call your helper in {{{...}}} or {{...}} as appropriate.
This stuff isn't exactly documented (at least not anywhere that I can find) so you have to reverse engineer the Handlebars source and fumble about with console.log(arguments) to figure out how to use Handlebars.partials.
You have to pass this by hand when you call the helper.
Fear not, it isn't really that complicated. Something simple like this:
Handlebars.registerHelper('partial', function(name, ctx, hash) {
var ps = Handlebars.partials;
if(typeof ps[name] !== 'function')
ps[name] = Handlebars.compile(ps[name]);
return ps[name](ctx, hash);
});
should do the trick. Then you can say:
{{{partial mode this}}}
and get on with more interesting things.
Demo: http://jsfiddle.net/ambiguous/YwNJ3/2/
Update for 2016: Version 3 of handlebars added Dynamic Partials. From the docs:
It's possible to dynamically select the partial to be executed by using sub expression syntax.
{{> (whichPartial) }}
Will evaluate whichPartial and then render the partial whose name is returned by this function.
Subexpressions do not resolve variables, so whichPartial must be a function. If a simple variable has the partial name, it's possible to resolve it via the lookup helper.
{{> (lookup . 'myVariable') }}
Related
I am using a helper inside another helper. I am trying to pass a value ‘post_id’, that i am getting dynamically from ‘show_post’ helper.I want to pass it and then use it inside the query that is returning a set of result back to helper. for some reason , app is crashing. Can someone guide me through this.
{{#each show_post}}
{{posttext}}
{{postedBy}}
{{#each show_comment({{post_id}}) }}
//<--- i am passing the value to helper like this.
<span> <p>{{postedBy}}: <h5>{{commenttext}}</h5>{{createdAt}}</p></span>
{{/each}}
{{/each}}
Template.display_block.helpers({
show_post(){
return PostComment.find({parent: 0},{sort: {createdAt: -1}});
}
});
Template.display_block.helpers({
show_comment(e){
var t1 = e;
var now = PostComment.find({post_id:{$regex:new RegExp(’^’ + t1)}});
return now;
}
});
The first helper generates a array(Mongo Db result).I want to use this array’s elements(that are dynamic) in next helper. So i want to use a variable that can hold the value of array element of first helper and then pass it to next helper. In second helper, i want to pass this variable and use this variable to sort a result in Mongo query. I am new to this so i am unable to understand how this instance
Firstly you don't need to wrap helper arguments in double curly braces or parens.
{{#each show_comment post_id}}
Will do what you need.
You're also making life a bit more complicated for yourself than necessary. You can use the current data context through this in your code. Also unclear why you're using a regex unless you're concatenating something to the post _id.
This allows you to simplify down to:
html:
{{#each show_post}}
{{posttext}}
{{postedBy}}
{{#each show_comment}}
<span><p>{{postedBy}}: <h5>{{commenttext}}</h5>{{createdAt}}</p></span>
{{/each}}
{{/each}}
js:
Template.display_block.helpers({
show_comment(){
return PostComment.find({post_id: this._id);
}
});
everybody :) For example I have something like this:
{{> someCoolHelper
someParam1='someVal1'
someParam2='someVal2'
someParam3='SomeVal3'
someParam4=someAnotherHelper 'value param to this helper'
}}
So I have a function, that computes some value depending on parameter that can be passed in it. So how can I do this?
as you may read in a forum thread at meteor forum this is not possible by now in spacebars.
We began with a new package for chaining methods and arguments but its not released yet. What you could do is to use the WITH element like
Instead something like:
{{> someCoolHelper
someParam1='someVal1'
someParam2='someVal2'
someParam3='SomeVal3'
someParam4=someAnotherHelper 'value param to this helper'
}}
can be managed by:
{{#with someAnotherHelper 'value param to this helper' }}
{{> someCoolHelper
someParam1='someVal1'
someParam2='someVal2'
someParam3='SomeVal3'
someParam4=this
}}
{{/with}}
I don't like it, but sometimes necessary
Tom
P.S.: Or drop Spacebars and use React - you won't have such limitations there.
The placeholder solution until template sub expressions are available in Spacebars is to create an helper returning the adequate value in JS :
HTML
{{> myTemplate param1="A" param2=param2}}
JS
function someOtherHelper(param){
console.log(param);
}
Template.registerHelper("someOtherHelper", someOtherHelper);
Template.myTemplate.helpers({
param2: function(){
return someOtherHelper("B");
}
});
You can pass the output of a helper to another helper using parentheses. Pretty sure this is what you're after:
http://blazejs.org/guide/spacebars.html#Calling-helpers-with-arguments
You can also pass the output of a helper to a template inclusion or other helper. To do so, use parentheses to show precedence:
{{> Todos_item (todoArgs todo)}}
Here the todo is passed as argument to the todoArgs helper, then the output is passed into the Todos_item template.
Something I did in a recent project uses this functionality to allow conditional rendering of CSS classes to a component (stripped down for brevity):
# global helper to provide ternary operator
Template.registerHelper('ternary', (condition, resultTrue, resultFalse) => {
return condition ? resultTrue : resultFalse
})
# in my-template.js:
Template.My_template.helpers({
isComplete(score) {
return score === 1
}
})
# in my-template.html:
{{> some_component class=(ternary (isComplete this.score) "green" "red")}}
I'm trying to write a custom iterator in spacebars (I'm using meteor 1.1.3). The iterator is to be a sequential for loop (basically to replace my usage of #each when needed since I believe #each is not guaranteed to be sequential in its iteration).
I have tried the following:
In lib -
UI.registerHelper 'sequentialFor', () ->
ret = ""
for i in [0...#.length]
id = #[i]
ret = ret + Template.noop
ret
noop.html -
<template name="noop">
{{> UI.contentBlock this}}
<template>
main.html -
{{#sequentialFor ids}}
<div id="wow-{{this}}">stuff</div>
{{/sequentialFor}}
ids in the above is an array of strings passed from one of main's template helpers.
Right now it complains the the return from my UI helper is [object Object] [object Object].
For sanity's sake I know that if I replace my UI helper with:
UI.registerHelper 'sequentialFor', () ->
//ret = ""
//for i in [0...#.length]
// id = #[i]
// ret = ret + template
id = #[0]
Template.noop
I get that the div in my main.html shows up with the appropriate id as a part of its id attribute as desired. However, I can't seem to make the for loop work.
I can't simply return the div in main.html directly from the helper because I have a lot of divs that I need to wrap with my new iterator, each of which has very different attributes.
I guess the simple question is, how do I define my own block iterator (akin to #each) in spacebars?
The more difficult question may be, what is wrong with my approach above?
I have considered a wide array of resources but have only the found the following to be very helpful:
How to pass an object from to a block helper back to the block in meteor blaze?
https://github.com/meteor/meteor/wiki/Using-Blaze
https://github.com/meteor/meteor/blob/devel/packages/spacebars/README.md
Iterating over basic “for” loop using Handlebars.js
NOTE I'm using coffeescript
I managed to get a custom iterator using a recursive technique similar to what you might use in Haskell or Lisp:
<body>
{{#countdown n=5}}
<p>item {{this}}</p>
{{/countdown}}
</body>
<template name="countdown">
{{#if positive}}
{{> Template.contentBlock n}}
{{#countdown n=nMinusOne}}
{{> Template.contentBlock this}}
{{/countdown}}
{{/if}}
</template>
Template.countdown.helpers({
positive: function () {return this.n > 0;},
nMinusOne: function () {return this.n - 1;}
});
See meteorpad.
The performance is probably far worse than the usual {{#each}}.
It appears to me that you want to create a <div> for each of an array of IDs (correct me if I'm wrong). This is how I would go about it, no custom iterator necessary:
Template.registerHelper('ids', function(arrayWithIds) {
if (!arrayWithIds) return [];
// do some sorting or whatever with arrayWithIds, for example:
var arrayOfIds = _.map(arrayWithIds, function(obj) {
return obj._id;
});
return arrayOfIds;
});
Then in main.html:
{{#each ids someDataSetWithIds}}
// `someDataSetWithIds` is the helper's parameter
// `this` in each case is an ID
<div id="wow-{{this}}"></div>
{{/each}}
If your helper returns an object, you would use this._id in the template, instead. Did I misunderstand what you're trying to achieve?
Is there a way to do a conditional "and" or "or" in a Meteor template? What I am trying to do is something like this:
{{#if isInRole 'ADMIN' || isInRole 'INSPECTOR'}}
...do some stuff
{{/if}}
using helpers provided by a 3rd party package alanning:roles. This seems to have come up multiple times in my coding with Meteor and I can and have worked around it, usually by duplication of the block code, but it would be really nice if there already existed some way to handle an OR or AND condition without crazy gyrations.
Meteor using spacebar which is based on handlebars. In handlebars, there are now direct way for logical operators.
You can do small workaround to handle the or by creating a Template.helper
Template.registerHelper("equals_or", function(param, arr) {
arr = arr.split(",");
if (arr.indexOf(param) !== -1) {
return true;
}
else {
return false;
}
});
Then in the HTML you can do
{{#if equals_or isRole "ADMIN,INSPECTOR"}}
...do some stuff
{{else}}
...do some other stuff
{{/if}}
I might not be a perfect solution, but it is doing the job.
Check this answer too.
I want to create a template/js combo similar to the ones below. What I would like is to have two variables available to the 'collection' template
<template name="collection">
Title: {{title}}
<UL>
{{#each items}}
{{> item}}
{{/each}}
</UL>
</template>
<template name="collection_items">
<LI>{{item_title}}</LI>
</template>
Where the javascript function would be something like:
Template.collection.data = function() {
var record = Record.findOne({whatever:value});
return { title: record.title, items: record.items }
}
I've tried using Handlebars' {{#with data}} helper and return an object as above, but that just crashed the template. I've tried creating a 'top level' function like:
Template.collection = function () {... }
but that also crashed the template.
What I'm trying to avoid is having two separate functions (one Template.collection.title, and one Template collection.items) where each of them calls a findOne on the Record collection where really its the same template and one call should suffice.
Any ideas?
Template.collection = function () {... }
Template.collection is not a function, it's an instance and thus an object.
You can type Template.collection in the console to see something essential as well as Template.collection. and autocomplete that to see its methods and fields.
For a #with example, the Todos indeed doen't seem to contain one as you have outlined in your comments. So, an example use of it can be found here:
https://github.com/meteor/meteor/blob/master/packages/templating/templating_tests.js#L75
https://github.com/meteor/meteor/blob/master/packages/templating/templating_tests.html#L92
Here is another example that I tried that works on both the current master and devel branch:
<head>
<title>test</title>
</head>
<body>
{{> hello}}
</body>
<template name="hello">
{{#with author}}
<h2>By {{firstName}} {{lastName}}</h2>
{{/with}}
</template>
And the JS part of it:
if (Meteor.is_client) {
Template.hello.author = function () {
return {
firstName: "Charles",
lastName: "Jolley"
};
};
}
Any specific reason why you're hoping to avoid two functions?
From your code sample I see one issue: the first template is calling a second template with this line:
{{> item}}
But your second template is not called 'items'. I believe that your second template should be called this way:
<template name="item">
Seems that it would be simple enough to have helper functions for the first and the second. Although I haven't gotten it to work with my own code, I believe the second helper function would want to use the 'this' convention to refer to the collection you're referring to.
Cheers - holling
Tom's answer is correct. I want to just chime in and add that in my scenario the reason why #with was failing was because due to the 'reactive' nature of meteor my first call to load the model resulted in 'undefined' and I didn't check for it. A fraction later it was loaded ok.
The moral is to do something like
var record = Record.findOne({whatever:value})
if (record) {
return record;
} else {
// whatever
return "loading"
}