Say I have JSON:
{
userinput: [
{name: "brian", "value": "i like pies"},
{name: "susan", "value": "memes are stupid"}
],
feedback: [
{value: "i also like pies"},
{value: "null"}
]
}
And I'm trying to draw a table like this:
name ..... | input ...... | feedback
-----------|----------------|-----------------
brian | I like pies | I also like pies
susan | mems are stupid| null
And while I recognise that it would be better to have feedback as a value of "userinput", what I have is not done like that ...
I'm trying to get the index of feedback inside {{#each userinput}}`, e.g.
{{#each userinput}}
<td>{{name}}</td><td>{{value}}</td><td>{{../feedback[#index].value}}</td>
{{/each}}
But of course {{../feedback[#index].value}} does not work.
What is the best way (without changing the structure of the json) to grab the value of the matching index inside the feedback array?
This can be accomplished using the lookup helper:
The lookup helper allows for dynamic parameter resolution using Handlebars variables. This is useful for resolving values for array indexes.
So the template for your example would look like this:
{{#each userinput}}
<td>{{name}}</td>
<td>{{value}}</td>
<td>
{{#with (lookup ../feedback #index)}}
{{value}}
{{/with}}
</td>
{{/each}}
I guess you will have to write a block helper for this, as it seems #index can only be used as a stand-alone.
I modified the "list" example, to allow a template like this: "{{#list userinput feedback}}<td>{{name}}</td><td>{{value}}</td><td>{{#feedback.value}}</td>{{/list}}". The implementation is like this, accepting two parameters "input" and "feedback" (plus the standard "options").
Handlebars.registerHelper('list', function(input, feedback, options) {
var out = "", data;
// iterate over the input
for (var i=0; i<input.length; i++) {
if (options.data) {
data = Handlebars.createFrame(options.data || {});
// add "feedback" item to the current frame's data
data.feedback = feedback[i];
}
out += "<tr>" + options.fn(input[i], { data: data }) + "</tr>";
}
return out;
});
Here's the Fiddle.
Related
The data being passed into the handlebars template looks like this:
a = {
x: {
name: "xavier"
},
y: {
name: "yamal"
},
}
b = {
properties: {
x: {
property: "number"
}
}
}
Handlebars template looks like this:
<div class="left-panel">
{{a.x.name}} // Prints correctly "xavier"
{{#each b.properties}}
<h4>{{#key}}</h4> // Prints correctly "x"
<h4>{{ ../a.[#key].name }}</h4> // does not print "xavier"
{{/each}}
</div>
As you can see, I want to be able to access the name of a dict in a using the key in b. How can I achieve this?
Your question is essentially the same as this one: Handlebars.js - Access object value with a variable key
The only extra detail is that you will need to make use of Handlebars subexpressions in order to perform the two lookups (first of #key; then of 'name').
A subexpression will allow you to lookup the value of #key on ../a and pass that to a second lookup of the value of 'name' on the result of the first lookup.
The relevant line in your template becomes:
<h4>{{lookup (lookup ../a #key) 'name'}}</h4>
Here is a fiddle for your reference:
https://jsfiddle.net/76484/b0puy52n/
I want to create a quiz application. To sumarize answers, and decide which answer is correct, I have an item in the Collection like this:
{
"_id" : "Q5D63eQA8AnYHJrRc",
"userId" : "X67n8vbiraEsEvWYD",
"username" : "ania",
"test" : {
"T1" : {
"Q1" : "A",
"Q2" : "D",
"Q3" : "D"
},
"T2" : {
"Q1" : "A",
"Q2" : "D",
"Q3" : "D"
}
}}
I've also tried to use [ to indicate that it's an array.
Right now I use:
<ul class="list-group nav nav-pills nav-stacked">
{{#each Answers}}
{{>usernamesList}}
{{/each}}
</ul>
</template>
<template name="usernamesList">
<li>{{username}}</li>
{{test.T1.Q1}}
</template>
I'm able to get the value of Q1, but I can't display every answer in the loop by using another {{#each}}.
I've tried to create an array, but I also have to distinguish which answer belongs to which question and test.
EDIT:
Collection
Answers = new Mongo.Collection("answers");
Helper:
Template.admin.helpers({
Questions: function() {
return Questions.find({});
},
Answers: function() {
return Answers.find({});
}
});
I've also tried to create the collection like this:
{"userId" : "X67n8vbiraEsEvWYD",
"username" : "ania",
"test" : [{
"T1" :[{
"Q1":"A",
"Q2":"D",
"Q3":"D"
}],
"T2" : [{
"Q1":"A",
"Q2":"D",
"Q3":"D"
}]
}]
}
Your usernamesList template could have another #each for something that does return an array. You can use a helper to convert the various objects you want to loop through into arrays of objects with the properties you want to see.
I'm not 100% sure what you're going for here, but it seems like you want three loops:
Users
-> Tests
-> Answers
To do so, given the structure you specify above, you could do something like this:
<template name='main'>
<ul class="list-group nav nav-pills nav-stacked">
{{#each Answers}}
{{>usernamesList}}
{{/each}}
</ul>
</template>
<template name='usernamesList'>
<li>{{username}}</li>
<ul><!-- get each test as a sub-bullet -->
{{#each usersTests}}
<li>{{name}}</li>
<ul><!-- get each question's answer as a sub-bullet -->
{{#each testAnswers}}
<li><strong>{{question}}</strong>: {{answer}}</li>
{{/each}}
</ul>
{{/each}}
</ul>
</template>
Then just reformat your data to be in arrays with the appropriate helpers:
Template.main.helpers({
Answers: function() { return Answers.find(); } // assumes you are returning a cursor that looks exactly as your collection is defined above
});
Template.usernamesList.helpers({
usersTests: function() { // Convert the `test` object into an array of objects:
var test = this.test;
return _.map(Object.keys(test), function(key) {
var answers = _.map(Object.keys(test[key]), function(question) { // also go ahead and convert this to objects
return {question: question, answer: test[key][question]};
});
return {name: key, testAnswers: answers}; // resulting array looks like [{test: 'T1', testAnswers: [{question: 'Q1', answer: 'A', ...}]}]
});
}
});
Once they are arrays you can iterate through them with {{#each}}. Recognize that HTML within the {{#each}} block will get the object you're iterating over as the data context -- so for instance when we iterate over the result of userTests, the data context is now an object in the format:
{test: 'T1',
testAnswers: [{question: 'Q1', answer: 'A', ...}]
}
So testAnswers is directly accessible as an array. We can then iterate over that array by calling {{#each testAnswers}}, and an object in the format
{question: 'Q1', answer: 'A'}
is now the data context, which means the question and answer keys are available.
Depending on your use case and what you may be doing with those objects in the Collection, I might recommend storing them as arrays within the collection itself so that you don't have to go through the mapping from object to array as done in the usersTests helper.
This is somewhat of a part 2 to my last question. Thanks to some helpful folks, I now have a document that looks like this:
{ "_id" : "dndsZhRgbPK24n5LD", "createdAt" : ISODate("2014-11-26T16:28:02.655Z"), "data" : { "cat1" : 493.6, "cat2" : 740.4 }, "owner" : "GiWCb8jXbPfzyc5ZF", "text" : "asdf" }
Specifically, I want to extract the value from each property of data in Spacebars and iterate over it to create a table - and I know the number of fields in object data but the number can vary. Yes, I know this has been asked before but nobody has seemed to be able to give a satisfactory answer that works. But as a final result I would like to display the whole document in a row, like this
<tbody>
<tr>
<td>493.6</td>
<td>740.4</td>
<td>asdf</td>
</tbody>
Thanks in advance for any help.
Here's complete working example:
Cats = new Mongo.Collection(null);
Meteor.startup(function() {
Cats.insert({
data: {
cat1: 100,
cat2: 200
},
text: 'cat1'
});
Cats.insert({
data: {
cat1: 300,
cat2: 400,
cat3: 500
},
text: 'cat2'
});
});
Template.cats.helpers({
cats: function() {
return Cats.find();
},
// Returns an array containg numbers from a cat's data values AND the cat's
// text. For example if the current cat (this) was:
// {text: 'meow', data: {cat1: 100, cat2: 300}}, columns should return:
// [100, 200, 'meow'].
columns: function() {
// The current context (this) is a cat document. First we'll extract an
// array of numbers from this.data using underscore's values function:
var result = _.values(this.data);
// result should now look like [100, 200] (using the example above). Next we
// will append this.text to the end of the result:
result.push(this.text);
// Return the result - it shold now look like: [100, 200, 'meow'].
return result;
}
});
<body>
{{> cats}}
</body>
<template name='cats'>
<table>
{{#each cats}}
<tr>
{{#each columns}}
<td>{{this}}</td>
{{/each}}
</tr>
{{/each}}
</table>
</template>
So, I'm working on a Meteor project and I can't get this route to generate properly, or at all for that matter.
<template name="browseAll">
<h3>List of classes with books available!</h3>
<ul>
{{#each aggCount}}
<li>{{ _id }} ({{ count }})</li>
{{/each}}
</ul>
</template>
The data that is being iterated over is a result of aggregation using MongoInternals, and that is as follows:
(server/methods.js excerpt):
classCount: function() {
// Attempt aggregation of the books table to count by class, maybe.
var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;
var col = db.collection("books");
var aggregateSync = Meteor._wrapAsync(col.aggregate.bind(col));
var pipeline = [
{$group: {_id: "$class", count: {$sum: 1}}},
{$sort: {_id: 1}}
];
var theAnswer = aggregateSync(pipeline);
return theAnswer;
}
It seems that the data is coming through okay, and sample data from aggregation (coming into the template) looks like this:
[ { _id: 'ADNR1234', count: 2 }, { _id: 'ARTH1234', count: 1 } ]
That's the template code I've got, and this is the route that it's supposed to be working with:
this.route('browse-class', {
path: '/browse/:_class',
data: function() {
var booksCursor = Books.find({"class": this.params._class},{sort:{"createdAt": 1}});
return {
theClass: this.params._class,
numBooks: booksCursor.count(),
books: booksCursor
};
}
});
I don't understand it. The data is being SHOWN, and what I want to do is generate a URL for browse-class (route) that takes the value of {{ _id }} in the helper as a parameter, so as to generate something like this:
application.org/browse/CLSS
Be aware that {{pathFor}} must be called with a data context properly set :
{{#with class}}
{{pathFor "browse-class"}}
{{/with}}
Optionnaly it is possible to pass the data context as a parameter :
{{pathFor "browse-class" class}}
The data context provided to pathFor is used when generating the route path, if you defined a route path like this :
path: "/browse/:_id"
Then it will use the _id from the class to properly generate a URL.
For the text of the link, I doubt you want to display the _id, your class documents probably include a "label" so you could use this :
{{ label }}
How can I access to an array element inside handlebars template using a variable instead of an hardcoded value?
I need to do something like:
{{#each condition in conditions}}
{{App.ops.[condition.op].name}}
{{/each}}
at the moment doesn't give me a parse error but on runtime doesn't return me nothing.
If i do something like this:
{{App.ops.[1].name}}
it works but it's not what i'm looking for
Related to my answer on another question
You can use the built-in lookup helper:
The lookup helper allows for dynamic parameter resolution using Handlebars variables. This is useful for resolving values for array indexes.
Using lookup, your example could be written as
{{#each condition in conditions}}
{{#with (lookup ../App.ops condition.op)}}
{{name}}
{{/with}}
{{/each}}
(Note that without knowledge of the structure of your data, I'm making an assumption about the location of App.ops.)
You can write a simple helper just to get value from array
Handlebars.registerHelper('getmyvalue', function(outer, inner) {
return outer[inner.label];
});
and then use it in template like
{{#each outer}}
{{#each ../inner}}
{{getmyvalue ../this this }}
{{/each}}
../this references to current outer item, and this - to current inner item
Example of data coming to template:
{
outer: {
1: { foo: "foo value" },
2: { bar: "bar value" }
},
inner: {
1: { label: "foo" },
2: { label: "bar" }
}
}
You need to create a helper for your problem. Below is the sample solution to your problem using index values. If you want to use some conditions you can also do that.
Handlebars.registerHelper("each_with_index", function(array, options) {
if(array != undefined && array != null && array.length > 0){
var html = new StringBuffer();
for (var i = 0, j = array.length; i < j; i++) {
var item = array[i];
item.index = i+1;
// show the inside of the block
html.append(options.fn(item));
}
// return the finished buffer
return html.toString();
}
return "";
});
Then you can do something like this
{{#each_with_index condition in conditions}}
{{App.ops.[condition.index].name}}
{{/each_with_index}}