Using handlebars.js as a template engine I need to query a json file and from here loop through an array within an object in said file. The flow is intended to work like: If a value define via data-attribute on click matches a string within an object array then loop and show data based on those conditions.
Essentially I need a query it like I would back-end to a database,(Using Laravel) e.g:
$attr = "Red";
$prodcuts = DB::table('products')->Where('tags', $tag)->get();
I am working from a very large json file for this. For example sake I created a much smaller one if you need it reference sake:
https://gist.github.com/Panoply/15dc30a9fc598d07b24f0f13a5d42df4
I am aware that with handlebars.js I need to create a helper to make a string with a value:
Handlebars.registerHelper("ifValue", function(conditional, options) {
if (conditional !== options.hash.equals) {
return options.fn(this);
} else {
return options.inverse(this);
}
});
Then I would have my handlebars loop:
<ul>
{{#each products}}
{{#ifvalue tags equals='Blue'}}
<li>{{title}}</li>
<li>{{handle}}</li>
<li>{{tags}}</li>
{{#with images.[0]}}<li>{{src}}</li>{{/with}}
{{#with variants.[0]}}<li>{{price}}</li>{{/with}}
{{/ifvalue}}
{{/each}}
</ul>
Issue is I get a blank result. You can try these code block at http://tryhandlebarsjs.com/ – Considering that the tags are in an object array I might be having some conflict.
Figured it out. Handlebars is touchy. If you stumble across this, here is how I fixed it:
Handlebars:
{{#products}}
{{#inArray tags "Red"}}
{{title}}
{{#with images.[0]}}<img src="{{ this.src }}">{{/with}}
{{/inArray}}
{{/products}}
Helper (Create an inArray):
Handlebars.registerHelper('inArray', function(array, value, options) {
if (array.indexOf.call(array, value) >= 0) {
return options.fn(this);
} else {
return options.inverse(this);
};
});
Related
I am using Meteor and tried to use #each, but I have a problem with it. I have some values stored in a var like:
var a = [465,77987,2132,2];
I wanted to render each value of a on HTML. I thought #each would be the right way to do it? Actually I don´t know how to use #each and the docs I found don´t really help me. I have written the following code:
JS:
Template.page.helpers({
values: function() {
return a;
}
});
HTML:
{{#each values}}
{{> page}}
{{/each}}
But this is obviously wrong, because I get the this Error in the client console:
Error: {{#each}} currently only accepts arrays, cursors or falsey values.
I thought it is an array or not?
Update
So now I have used the #each in approach, however I still get the same error-message:
Error: {{#each}} currently only accepts arrays, cursors or falsey
values. at badSequenceError (observe-sequence.js:183) at
observe-sequence.js:148 at Object.Tracker.nonreactive (tracker.js:631)
at observe-sequence.js:125 at Tracker.Computation._compute
(tracker.js:339) at Tracker.Computation._recompute (tracker.js:358) at
Object.Tracker._runFlush (tracker.js:523) at onGlobalMessage
(meteor.js:401)
JS:
Template.page.helpers({
values: function() {
var a = [465,77987,2132,2]
return a;
}
});
HTML:
{{#each a in values }}
{{> a}}
{{/each}}
Update 2
I know now where this error comes from. I'm sorry, actually my first post was not correct. I forgot, that these values [465,77987,2132,2] are not directly stored in var a.
They are stored in a Session-Variable and var a = Session.get('values') and I think that´s the point why I get this error...So, I don´t think that this can work with #each ? Maybe I have to save it to my MongoDB first and than render it on the HTML or something like that.
try the each/in construct:
{{#each value in values}}
{{value}}
{{/each}}
c.f. http://blazejs.org/guide/spacebars.html#Each-in
you specified the entire template inside the each, which is what it was complaining about.
I'll put all the code in an example. Consider the 'import' only if you are using the recommended application structure. The files must be in the same directory. First in page.js:
import './page.html';
Template.page.helpers({
values: function() {
var a = [465,77987,2132,2];
return a;
}
});
Second in page.html:
<template name="page">
{{#each a in values}}
{{a}}
{{/each}}
</template>
I have an asynchronous method which does a Facebook request for retrieving photos from the current logged in user.
I have a template where I loop over these results. But since the call to Facebook is asynchronous, it fails.
<template name="mytemplate">
{{#if photos}}
{{> images pics=photos}}
{{/if}}
</template>
<template name="images">
{{#with pics}}
{{#each this}}
{{images}}
{{/each}}
{{/with}}
</template>
Here is my javascript:
Template.mytemplate.helpers({
photos: function () {
return Template.instance().myAsyncValue.get();
}
});
Template.mytemplate.created = function (){
var self = this;
self.myAsyncValue = new ReactiveVar("Waiting for response from server...");
Meteor.call('getPhotos', function (err, data) {
if (err)
console.log(err);
else
self.myAsyncValue.set(data.data);
});
}
I already have a variable that is being watched, but the problem is that my template is already created and the result is not an array yet.
Can someone show me the best practices on how to use an asynchronous array result in a template. I want to create image tags in the template, not with jquery or javascript.
Thanks
If I understand this right, you're getting an error when the helpers are loaded because photos is not an array. Try this:
Template.mytemplate.helpers({
photos: function () {
var val = Template.instance().myAsyncValue.get();
return val.constructor === Array && val;
}
As a side question for you, why are you using a ReactiveVar for an array? I have only found a use for using them on primitives, since reactivity doesn't do a deep scan, but maybe I'm missing out :)
Thanks for the quick response.
I've figured out after I wrote the question that my problem was not giving an actual array when the call was not yet made.
Regarding your side question. I have to use this ReactiveVar in order to keep track of the result, no? Because my variable hasn't got a value first. The template is already being setup. Then when I get a response, the value changed and my each loop reacts over the received array. That's how I understood this ReactiveVar anyway :)
While refactoring some code I got this problem: When applying a Handlebars template that uses a partial, it complains about You must pass a string or Handlebars AST to Handlebars.compile. You passed function .... The function is this:
function (context, options) {
options = options || {};
var namespace = options.partial ? options : env,
helpers,
partials;
if (!options.partial) {
helpers = options.helpers;
partials = options.partials;
}
var result = templateSpec.call(
container,
namespace, context,
helpers,
partials,
options.data);
if (!options.partial) {
checkRevision(container.compilerInfo);
}
return result;
}
What I did:
A partial is used to iterate over a list (called members) to build a ul of checkboxes. I found out I needed to make a list of lists and put the call to the partial inside an #each and updated the template input.
I have this:
Template:
...
{{#each hierarchy.levels}}
<ul>
{{> mypartial}}
</ul>
{{/each}}
Partial (simplified):
{{#each members}}
<li>{{this.id}}</li>
{{/each}}
I've checked that each hierarchy.levels in my data structure has a members list.
If I replace the #each in my template with #with hierarchy.levels.[0] (for instance) it works but it won't work when iterating over levels.
I solved it. One of the 2nd level lists were empty (ie zero items) which led to Handlebars rendering it as an empty string which again led to Handlebars thinking that something was false and trying to render the partial as a regular template.
I'm using a handlebar helper for calculating how many rows there is in an array. If it's above 2 it returns true, and it works as it's suppose to. Looks like this:
define('templates/helpers/countRows', ['handlebars'], function ( Handlebars ) {
function countRows(selectedArray) {
var selectedArrayLength = selectedArray.length;
if (parseInt(selectedArrayLength) > 2) {
return true;
}
}
Handlebars.registerHelper('countRows', countRows);
return countRows;
});
The problem is that I want to set a condition in my hbs template to check if the value is true or not before outputting it. If it isn't true, I don't want it to output. I was hoping I could do something like this:
{{#if countRows "my array"}}
markup that only gets displayed if value is true
{{/if}}
But this isn't valid unfortunately..
The best approach would be to define computed properties on your controller to handle this type of logic.
App.ThingsController = Ember.ArrayController.extend({
enoughRows: Ember.computed.gte('content.length', 2)
});
Then in your template:
{{#if enoughRows}}
...
{{/if}}
Having logic like this embedded in a template is hard to debug and test. Following this philosophy, handlebars makes it hard to do condition checks outside of true/false.
If you need to repeat this kind of logic in many controllers, consider making a mixin.
App.EnoughRowsMixin = Ember.Mixin.create({
enoughRows: Ember.computed.gte('content.length', 2)
});
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"}]'
}}