Get value of an array using dynamic index in handlebar template - handlebars.js

I am new to Handlebars.
I have created an ItemView which is used in CompositeView. Values for this template rendering correctly.
var singleMonth = Marionette.ItemView.extend({
tagName: 'tr',
template: {
type: 'handlebars',
template: monthTemplate
},
months: [ 'JAN','FEB','MAR','APR','JUN','JUL','AUG','SEP','OCT','NOV','DEC' ],
templateHelpers: function() {
var helpers = {};
helpers.months = this.months;
return helpers;
}
});
and this is my template
<td>{{ months.#index.[7] }}</td><td>{{ [12] }}</td>
I want to get respective month value based on value of [7] which will be index for months array.
for Ex. if [7] is 3 then I want to get expression value as 'MAR'.
I am not able to get how to do this.
Can you please tell me how can I do this ?
(NOTE: I don't want to use #each or any loop here )
Thanks

Despite the possible syntax error in your template, the template itself should not be so smart.
Keep it stupid like
<td>{{ thisMonth }}</td>
Then build thisMonth in templateHelpers
templateHelpers: function() {
// The reason for _this is: `this` means model in templateHelpers.
_this = this;
return {
thisMonth: _this.months[7]
}
};

Related

How to show nested data within Vuetify data table item prop?

We have an app with Users and Events. Users Apply to work Events. I want to create a vuetify data table that shows a User the shows that they have applied to, as well as their application status for each show.
Our firebase data is nested as: events - event.uid - applications - application.id(which matches the user.uid) - status
Our table code:
<v-data-table
v-bind:headers="headers"
:items="filteredEvents"
:rows-per-page-items="[10,25, { text: 'All', value: - 1}]"
>
<template slot="items" slot-scope="props">
<td>{{ props.item.title }}</td>
<td>{{ props.item.city }}</td>
<td>{{ props.item.startDate | moment("dddd, MMMM Do YYYY") }}</td>
<td>{{ props.item.applicants.match(user.id).status }}</td>
</template>
</v-data-table>
The applicants status td is where we would want "Pending" or "Accepted" to show up. The code that's there doesn't work. I wish it worked like that but it doesn't.
Computed:
filteredEvents(){
return this.$store.getters.loadedEvents.filter((event) => {
return this.$store.getters.isUserApplied(event.id);
})
}
Vuex:
getInitialEventsState({ commit }) {
let db = firebase.database();
db.ref("/events").on("value", function (snapshot) {
let data = []
for (var key in snapshot.val()) {
let event = snapshot.val()[key];
event.id = key
data.push(event)
}
commit("setLoadedEvents", data)
commit("setEventsMap", snapshot.val());
})
}
isUserApplied: (state) => (id) => {
let user = store.getters.user
if (user && state.eventsMap[id] && state.eventsMap[id].applicants) {
let status = state.eventsMap[id].applicants[user.id]
if(status && status.status) {
return status.status;
} else {
return null;
}
}
return false
}
loadedEvents(state) {
return state.loadedEvents
}
As a user who has Applied for 2 Events, I can see those two events in the data table. The name, city, and startDate all show, but my status does not.
What is the best way to solve this problem?
Thanks!
This may or may not help you as I can not see your events object structure. But you say the data is nested. It would be helpful to see what is arrays versus what is objects. So if you can show it in code form that would help. So other users here may want to see some kind of answer...
When I use firestore data it is usually in the form of an array of objects with each object having/holding other arrays. Getting to that inner nested data can be challenging. So I pass in my array of documents as items to the data-table and then use this this little bit of code in the td tags:
<td class="text-xs-left">
{{ getNestedObject(props.item, ['someArray', index, 'someProp']) }}
</td>
methods: {
getNestedObject(nestedObj, pathArr) {
return pathArr.reduce((obj, key) => (obj && obj[key] !== 'undefined' ? obj[key] : undefined), nestedObj)
},
}
Here was my source of inspiration: Accessing Nested Objects in JavaScript

Search and Sort on the same page

I'm trying to implement sort and search to my items, so i started with sort and it works:
Template
<button class="sort">Sort</button>
{{#each cvs}}
{{> Interviu}}
{{/each}}
JS:
Template.Interviuri.onCreated(function () {
var self = this
self.autorun(function () {
self.sortOrder = new ReactiveVar(-1)
})
Template.Interviuri.helpers({
cvs() {
const instance = Template.instance()
return Cvs.find({}, { sort: { createdAt: instance.sortOrder.get() } })
},
})
Template.Interviuri.events({
'click .sort'(event, instance) {
instance.sortOrder.set(instance.sortOrder.get() * -1)
Next i wanted to implement Search on the same page. So the best way i could found was EasySearch.
But using EasySearch, it means i must change the way my items are being displayed. And then the sort doesn't work anymore.
Template
<div class="searchBox pull-right">
{{> EasySearch.Input index=cvsIndex attributes=searchAttributes }}
</div>
{{#EasySearch.Each index=cvsIndex }}
{{> Interviu}}
{{/EasySearch.Each}}
Collection
CvsIndex = new EasySearch.Index({
collection: Cvs,
fields: ['name'],
engine: new EasySearch.Minimongo()
})
JS
cvsIndex: () => CvsIndex,
How can i have both search and sort working at the same time?
With EasySearch you can use two methods on your index, namely getComponentDict() and getComponentMethods().
With getComponentDict() you can access search definition and options:
index.getComponentDict().get('searchDefinition');
index.getComponentDict().get('searchOptions');
You also have the corresponding setters to change the search definition/option.
getComponentMethods has mehods like
index.getComponentMethods().loadMore(integer);
index.getComponentMethods().hasMoreDocuments();
index.getComponentMethods().addProps(prop, value);
index.getComponentMethods().removeProps([prop])
From that you can set your prop, say index.getComponentMethods().addProp('sort', -1) and then on the index definition, in your MongoDB engine, set the sort from that prop:
index = new EasySearch.index({
// other parameters
engine: new EasySearch.MongoDB({
sort: function(searchObject, options) {
if(options.search.props.sort) {
return parseInt(options.search.props.sort);
}
return 1;
}
})
});
See EasySearch Engines for more info.

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