Iterate through array inside array - meteor

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.

Related

Accessing another variable with contents of each loop

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/

How would I access a field in a collection whos' key contains a space?

Data was imported from a csv into mongo. No practical to remove the space in the data.
Example:
{{#each item}}
{{name}}
{{"Transaction Number"}}
{{/each}}
The "Transaction Number" is the field name. Can place into a helper, but would like to use the #each.
Template.hello.helpers({
item: function() {
return Items.find()
},
counter: function () {
return Session.get('counter');
}
});
Thank you for any help.
Maybe a transform could be helpful here, although ultimately you want to fix the field key to be valid IMO.
Items.find({}, {transform: function(doc) {
doc.transactionNumber = doc['Transaction Number'];
return doc;
}});
Then you should be able to access it within the template correctly:
{{#each item}}
{{transactionNumber}}
{{/each}}
You can specify new variables as doc.whateverYouLike which can be quite useful when doing 'aggregate' queries like adding a variable that represents a field from another collection.
You could rely on this trick :
JS
Template.hello.helpers({
transactionNumber: function(){
return this["Transaction Number"];
}
});
HTML
{{#each item}}
{{name}}
{{transactionNumber}}
{{/each}}

Why isn't the URL being generated for this route?

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 }}

meteor: Iterating over a collection by field value

I have items with a category field and name field, e.g.:
{ category: 'CategoryOne', name: "ItemOne" },
{ category: 'CategoryOne', name: "ItemTwo" },
{ category: 'CategoryTwo', name: "ItemThree" },
... etc
What I would like to do is display these under a heading for the category.
I am new to meteor, and having quite the time doing two things:
(1) Getting a reactive list of categories, or
(2) Iterating through the items, displaying them grouped by category.
I'm not sure what is the correct Meteor approach here.
Unfortunately minimongo doesn't have support for aggregation yet so this is a bit difficult. The following is how I would approach it.
First create 2 template helpers. The first just puts together a list of the categories and returns an array of category names, The second takes the category name as a parameter and returns a cursor of all of the records in that category.
Template.categories.helpers({
categories: function(){
var added = [];
return Items.find().map(function (item) {
if(_(added).indexOf(item.category) === -1){
return item.category;
}
});
},
categoryItems: function(category){
return Items.find({category:category});
}
});
Next the template needs nested {{#each}} blocks with the first one iterating over the categories array and passing the category names to the next each as the parameter of the next helper.
<template name="categories">
{{#each categories}}
<h1>{{this}}</h1>
<ul>
{{#each items this}}
<li>{{name}}</li>
{{/each}}
</ul>
{{/each}}
</template>

How to get an array value at index using Handlebars.js?

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.

Resources