Iterating through an array with Spacebars - meteor

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>

Related

Meteor & Mongo: addToSet inserting

I have some documents in my base:
//example docs
{"_id": "qwerty12345", "name": "Bob", "cards":["cardId1", "cardId2", "cardId3"]}
I'm using this for inserting data:
Template.insert.events({
'click add': function(){
if(confirm("Add card?"));
mycollection.update({_id: Session.get('fooId')}, { $addToSet: { cards: this._id}})
}
});
Then i'm using this helper for my template:
Template.index.helpers({
cards: function(){
query = mycollection.findOne({_id: Session.get('fooId')});
return query.cards;
}
});
And in template:
<img src="{{img}}" class="add">
{{#each cards}}
{{this}}<br>
{{/each}}
This works perfecty, but i have a trouble:
As you see, every image have id and url({{image}}), i'm need to add image url to 'mycollection' too for every card(on click).
How to make it?
And the second problem:
How to allow mongo insert duplicates to "cards" array?
Do you mean every card has id and image field? I guess so.
You can add nested object to an array fields. Like that
mycollection.update({_id: Session.get('fooId')}, { $addToSet: { cards: {id: this._id, image: this.image}}}).
In the template:
{{#each cards}}
{{this.id}}: {{this.image}}<br>
{{/each}}
For second problem: You can use $push instead of $addToSet

Meteor retrieving document _id from reactive table

I am using Reactive-Table to display data saved in my Meteor app as shown from the code below, in each row of the table there is a link to edit the document related to this row. I am trying using the edit link 'click event' to capture the _id of the document related to the row being selected but can't seem to get the _id, can someone please check my code and let me know what I am missing / doing wrong here and how to capture the _id? Thanks
customerslist.html
<template name="customerslist">
<div class="customerslist">
<div class="page-header">
<h1>Customers List</h1>
</div>
<div>
{{> reactiveTable class="table table-bordered table-hover" collection=customersCollection settings=settings}}
</div>
</div>
</template>
customerslist.js
Template.customerslist.helpers({
customersCollection: function () {
return Customers.find({},{sort: {title: 1}});
},
settings: function () {
return {
rowsPerPage: 10,
showFilter: true,
showColumnToggles: false,
fields: [
{ key: 'name', label: 'Customer Name' },
{ key: 'email', label: 'Email' },
{ key: 'phone', label: 'Phone' },
{ key: '_id', label: 'Action', sortByValue: false, fn: function(_id){ return new Spacebars.SafeString('<a name="' + _id +'" class="edtlnk" target="_blank" href="' + _id + '/edit"> Edit </a>'); } }
]
};
}
});
Template.customerslist.customers = function () {
return Customers.find({},{sort: {title: 1}});
};
Template.customerslist.events({
'click .edtlnk': function(e) {
var cust = this;
event.preventDefault();
console.log('Customer ID: '+cust._id);
}
});
The way the package sets up data contexts, this will only be set to the customer object if the event selector matches the tr element. That makes event.currentTarget the tr, but event.target is still the edit link.
You could try something like this:
Template.customerslist.events({
'click .customerslist tr': function(e) {
if ($(e.target).hasClass('edtlnk')) {
var cust = this;
e.preventDefault();
console.log('Customer ID: '+cust._id);
}
}
});
I don't know Meteor though I am starting to play around with it so I don't care about up or down votes at all but learning the answer your question.
I found the Event Maps docs which I am sure you saw as well:
https://docs.meteor.com/#/full/eventmaps
This was listed in the doc:
{
'click p': function (event) {
var paragraph = event.currentTarget; // always a P
var clickedElement = event.target; // could be the P or a child element
}
}
If a selector matches multiple elements that an event bubbles to, it will be called multiple times, for example in the case of 'click
div' or 'click *'. If no selector is given, the handler will only be called once, on the original target element.
The following properties and methods are available on the event object passed to handlers:
type String
The event's type, such as "click", "blur" or "keypress".
target DOM Element
The element that originated the event.
currentTarget DOM Element
The element currently handling the event. This is the element that matched the selector in the event map. For events that bubble, it may be target or an ancestor of target, and its value changes as the event bubbles.
It seems to me that since target and currentTarget are DOM elements can't you get what you need from those or are you referring to the _id available in Meteor on an insert callback?

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

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.

Handlebars array access with dynamic index

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

Resources