Problem accessing specific elements of all objects in an array using Handlebars - node.js

I'm relatively new to Handlebars so this might some something simple that I'm missing, but I can't seem to output specific elements of every object in an array. I've searched through multiple posts and read the handlebars documentation, but so far haven't found a solution. Basically I'm trying to output the availability for a series of time slots and their statuses that are assigned to one item. Here's my code...
Data
Pitch: {
"name": "test",
"price": 11,
"city": "testCity",
"availability": [
{
"status": "reserved",
"dt": "2018-11-16T20:00:00Z"
},
{
"status": "available",
"dt": "2018-11-16T19:00:00Z"
}
]
}
index.js
router.get('/availability/:id', function(req, res, next) {
Pitch.findById(req.params.id, function (err, docs){
var indPitch = [];
indPitch.push(docs);
res.render('pitch/availability', { title: 'Availability', indPitch: indPitch });
});
availability.hbs
{{#each indPitch}}
<h3>{{ name }}</h3>
<p class = "description">{{ city }}</p>
{{#each availability}}
{{status}}
{{dt}}
{{/each}}
<p class = "description">Price per hour : €{{ price }}</p>
Select
{{/each}}
With the handlebars code above, only the city and price are output to the screen. If I change {{#each availability}} to {{#each this}} and then access the object as {{availability.[0].status}} or {{availability.[1].dt}}, etc. I can then output that item to the screen.
But, what I need to do is loop through 'Availability' for the item and display all date/times and their status. Any ideas what I'm doing wrong? If you need more information, let me know.

just use this before each element this should work
{{#each indPitch}}
<h3>{{this.name }}</h3>
<p class = "description">{{ this.city }}</p>
{{#each availability}}
{{this.status}}
{{this.dt}}
{{/each}}
<p class = "description">Price per hour : €{{ this.price }}</p>
Select
{{/each}}

Ok, so after a few hours of trying every combination of #each this and #each availability, eventually one worked. Posting the answer here incase anyone else has the same problem...
I had to nest {{#each availabilty}} inside an {{#each this}} as follows...
{{#each indPitch}}
<h3>{{this.name }}</h3>
<p class = "description">{{ this.city }}</p>
{{#each this}}
{{#each availability}}
{{status}}
{{dt}}
{{/each}}
{{/each}}
<p class = "description">Price per hour : €{{ this.price }}</p>
Select
{{/each}}

Related

if statement handlebars with passed 2 values

I'm making a site with handlebars and nodejs and I want to show a "message" (you can best compare them to tweets) only if the "message" is from a specific user.
For the if statement I used the following question asked on stackoverflow in the past: Logical operator in a handlebars.js {{#if}} conditional I'm passing through 2 variables: messages and username where username is a string and messages an array. I use message as |item| to get a specific place from the array. The username is stored in item.[0]. As I change the username in the value to "test" in line 3 of the hbs code so it would be {{#ifCond item.[0] "test"}} then it works, but not with the username passed through with the value.
Here's my handlebars code:
{{#if user}}
{{#each messages as |item|}}
{{#ifCond item.[0] username}}
<div class="card" style="width: 100%;">
<div class="card-body">
<h5 class="cardtitle">{{#key}}</h5>
<h6 class="card-subtitle mb-2 text-muted">{{item.[0]}}</h6>
<h6 class="card-subtitle mb-2 text-muted">{{item.[2]}}</h6>
<p class="card-text">{{item.[1]}}</p>
</div>
</div>
<p></p>
{{else}}
<p>{{item.[0]}}</p>
{{/ifCond}}
{{/each}}
{{/if}}
The node.js code to load the page:
router.get('/users',loggedIn,messages,(req, res) => {
var user = res.user
var username = req.query.user
var message = res.message
if (user != undefined){
res.render("publicprofile", {user:user, username:username, messages:message, usernamestr:String(username)})
} else{
res.redirect("/")
}
});
and the handlebars helper:
hbs.registerHelper('ifCond', function(v1, v2, options) {
if(v1 === v2) {
return options.fn(this);
}
return options.inverse(this);
});

Infinite loop when using value to call function to get data from REST Nodejs

I'm pretty new in the world of angular 2 & nodejs.
Currently I created a service that allows me to make a query through NodeJs that brings information from MySQL.
On that information I want to use a value that allows me to call a function that brings me the name associated with that id to be able to show it.
The idea is to find the name of the company associated with that ID and that is hosted in another table in the database.
When I make the call from the template, I can connect to the REST API but it starts an infinite loop that blocks my browser memory.
What would be the correct way to make this call and get this information.
customers.service.ts
export class customerService {
private serviceUrl = 'http://localhost:3000/customers';
constructor(private http: HttpClient) { }
getCustomers(): Observable<CustomerList[]> {
return this.http.get<CustomerList[]>(this.serviceUrl)
}
getCustomer(id): Observable<CustomerList[]> {
return this.http.get<CustomerList[]>(this.serviceUrl+'/'+id)
}
}
view-budget.component.ts
getCustomerName(_id) {
console.log('ID C:', _id);
this.customerService.getCustomer(_id).subscribe(datos => {
return this.getCustomer = datos;
})
}
view-budget.component.html
<ul>
<li *ngFor="let budget of viewbudget" id="{{budget.id}}">
<h2>{{ budget.date | date:'dd-MM-yyyy' }}</h2>
<p *ngFor="let customer of getCustomerName()">{{(customer.name)}}</p>
<p>Fecha Inicio: {{ budget.startdate | date:'dd-MM-yyyy' }}</p>
<hr>
<div class="price">$ {{budget.statusid}}</div>
</li>
</ul>
While It is indeed possible to solve the issue from the frontend, I think what you're trying to achieve here is more efficiently done with an SQL join on the back end. Then have the data you need with the company name ready for visualization from another API endpoint.
Once you have that, add a new method to your service, call it from the component's ngOnInit, store the result in the component, and vizualize it with :
<ul>
<li *ngFor="let budget of viewbudget" id="{{budget.id}}">
<h2>{{ budget.date | date:'dd-MM-yyyy' }}</h2>
<p *ngFor="let customer of budget.customers">{{(customer.name)}}</p>
<p>Fecha Inicio: {{ budget.startdate | date:'dd-MM-yyyy' }}</p>
<hr>
<div class="price">$ {{budget.statusid}}</div>
</li>
</ul>
If there are many companies per budget,
or :
<ul>
<li *ngFor="let budget of viewbudget" id="{{budget.id}}">
<h2>{{ budget.date | date:'dd-MM-yyyy' }}</h2>
<p>{{(budget.customer.name)}}</p>
<p>Fecha Inicio: {{ budget.startdate | date:'dd-MM-yyyy' }}</p>
<hr>
<div class="price">$ {{budget.statusid}}</div>
</li>
</ul>
If there's only one company (depending on your data models)
It's generally a bad idea to call functions from the template. Each time change detection runs, the function will be called again, which can cause errors like you're seeing.
Instead, create an ngOnInit function, and call it within there:
ngOnInit() {
this.getCustomerName();
}
And amend your template to use the getCustomer variable you've created:
<p *ngFor="let customer of getCustomer">{{(customer.name)}}</p>
This way, the API call will only be made once (when the component is initialised)
view-budget.component.ts
export class ViewBudget implements OnInit {
...
customerMap: any = {};
...
getCustomerName(_id) {
console.log('ID C:', _id);
this.customerService.getCustomer(_id).subscribe(data => {
return this.customerMap[_id] = data;
})
}
}
view-budget.component.html
<ul>
<li *ngFor="let budget of viewbudget; getCustomerName(budget.id)" id="{{budget.id}}">
<h2>{{ budget.date | date:'dd-MM-yyyy' }}</h2>
<p *ngFor="let customer of customerMap[budget.id]">{{(customer.name)}}</p>
<p>Fecha Inicio: {{ budget.startdate | date:'dd-MM-yyyy' }}</p>
<hr>
<div class="price">$ {{budget.statusid}}</div>
</li>
</ul>
this method is not good solution.
you need to handle your backend.
on your backend, you can populate customers using sql join.
so you don't need request to get customers of each budget.

Handlebars disable attribute if id exist

I have list of electoralUnits and I need to disable some in that list if that id of electoralUnit is added in other collection called StateResult.
Route:
router.get('/', (req, res) => {
StateResult.find({})
.then(stateResults => {
ElectoralUnit.find({})
.then(electoralUnits => {
StateList.find({})
.then(stateLists => {
res.render('home/results/stateResults', {
stateResults: stateResults,
electoralUnits: electoralUnits,
stateLists: stateLists
});
});
});
});
});
Now, I tried this in handlebars with if helper:
<select name="electoralUnit" multiple class="form-control" id="exampleFormControlSelect2" size="40">
{{#each electoralUnits}}
<option value="{{id}}" {{#each stateResults}} {{#if this}} disabled {{/if}} {{/each}}>{{name}}</option>
{{/each}}
</select>
and a lot of variations of this, like:
<option value="{{id}}" {{#each stateResults}} {{#if electoralUnit}} disabled {{/if}} {{/each}}>{{name}}</option>
but nothing works. Where am I wrong?
This is data from mongodb:
electoralunits collection
{"_id":"5ab906612f30fe23dc592591","town":"5ab903952e9dc70408a81e32","name":"1. МЗ Аеродром - Дом Здравља","__v":0,"electoralNumber":4200,"safeVoter":360,"date":"2018-04-25T15:19:37.900Z"}
stateresults collection
{"_id":"5ac4e01d46fa2b21280bd981","electoralUnit":"5ab906612f30fe23dc592591","allVotes":100,"validVotes":90,"invalidVotes":10,"partyVotes":[50,10,10,10,5,5],"__v":0}
I tried as #doowb explain me with custom handlebars helper:
includes: function(arr, prop, val, options) {
const matches = arr.map(item => item[prop]).includes(val);
if (matches) {
return options.fn(this);
} else {
return options.inverse(this);
}
}
<select name="electoralUnit" multiple class="form-control" id="exampleFormControlSelect2" size="40">
{{#each electoralUnits}}
<option value="{{this._id}}" {{#includes ../stateResults "electoralUnit" this._id}} hidden {{/includes}}>{{name}}</option>
{{/each}}
</select>
but this won't work either.
It would help if you show that the stateResults, electoralUnits, and stateLists objects look like, but I think your issue is with knowing which "depth" you're currently trying to access data from. In the express middleware you're passing all of those objects to the template at the root of the object:
{
stateResults: [], // assuming this is an array
electoralUnits: [], // assuming this is an array
stateLists: [] // assuming this is an array
}
In the {{#each stateResults}} you'll need to use the parent specifier: {{#each ../stateResults}} since the first {{#each}} block created a new depth. Also, the logic of the data objects doesn't seem correct since you don't need to add multiple disabled attributes to the option tag. If you post what the actual data objects look like, I'll edit this answer with additional information.
Edit: After getting more information, I think that using the {{pluck}} and {{inArray}} helpers and subexpressions like the following will achieve your goal. If the ../stateResults does work, then try #root.stateResults (I'm still assuming that the collections returned are arrays of those mongodb objects):
<select name="electoralUnit" multiple class="form-control" id="exampleFormControlSelect2" size="40">
{{#each electoralUnits}}
<option value="{{this._id}}" {{#inArray (pluck ../stateResults "electoralUnit") this._id}} disabled {{/inArray}}>{{name}}</option>
{{/each}}
</select>
If you don't want to use the handlebars-helpers library (indicated in a comment below) you can create your own helper:
Handlebars.registerHelper('includes', function(arr, prop, val, options) {
const matches = arr.map(item => item[prop]).includes(val);
if (matches) {
return options.fn(this);
} else {
return options.inverse(this);
});
Then you can use that helper like this:
<select name="electoralUnit" multiple class="form-control" id="exampleFormControlSelect2" size="40">
{{#each electoralUnits}}
<option value="{{this._id}}" {{#includes ../stateResults "electoralUnit" this._id}} disabled {{/includes}}>{{name}}</option>
{{/each}}
</select>

Compare iteration values using handlebars

I want to compare iteration values using handlebars. This is my code
{{#each accounts}}
{{#each projects}}
{{#if}} (compare accounts.project_id with projects._id)
// display the project name
{{else}}
// display not found
{{/if}}
{{/each}}
{{/each}}
Please help. I'm new to handlebars/
Use the {{compare}} helper from the handlebars-helpers module.
{{#each accounts}}
{{#each projects}}
{{#compare accounts.project_id "==" projects._id)
// display the project name
{{else}}
// display not found
{{/compare}}
{{/each}}
{{/each}}
Refer to the documentation on how to install and use the helpers.
You can do so with a simple helpers in Handlebars like so:
Handlebars.registerHelper('if_eq', function(a, b, opts) {
if(a == b)
return opts.fn(this);
else
return opts.inverse(this);
});
and in your code...
{{#each accounts}}
{{#each projects}}
{{#if_eq accounts.project_id projects._id}}
// display the project name
{{else}}
// display not found
{{/if_eq}}
{{/each}}
{{/each}}

Strange results with nested handlebar blocks in Meteor

I am trying to set default value in an html select, but without sucess.
I'm doing the initial populating like this:
<template name="demo">
<select>
{{#each foo}}
<option>{{this}}</option>
{{/each}}
</select>
</template>
And i set the possible options in the model like this:
Template.demo.foo = ["aaa","bbb","ccc"];
So far, everything work as intended.
Now i'm trying to display row of the collection collec, populating the select with the defaut recorded foo value (aaa or bbb or ccc).
My understanding is that you must add "selected" to the tag.
So i do something like this with multiple nested blocks:
<template name="demo">
{{#each collecs}}
{{_id}}
<select>
{{#each foos}}
<option{{#if isSelected this ../this}}selected{{/if}}>{{this}}</option>
{{/each}}
</select>
{{/each}}
</template>
And on the model front:
Template.demo.foos = ["aaa","bbb","ccc"];
Template.demo.collecs = function(){
return Collec.find({});
};
Template.demo.isSelected = function(fooToCheck, record){
var rid= record._id;
var currentRecord = Collec.findOne({_id:rid});
return (fooToCheck==currentRecord.foo);
};
The problem is that it does not work.
The dropdown stays empty, and the generated html code show something like this:
" >aaa "
I have checked in the js part, all seems to work correctly, true/false are adequately returned.
Thank in advance for your help.
Handlebars conditionals don't do well inline. In fact, I'm not sure if they work inline anywhere. What was happening was that the Handlebars templating engine didn't understand your html, so it skipped over it, which is why you saw '>aaa'.
The following code works. I took the liberty of simplifying your isSelected function.
Template:
<template name="demo">
{{#each collecs}}
{{_id}}
<select>
{{#each foos}}
{{#if isSelected this ../foo}}
<option selected>{{this}}</option>
{{else}}
<option>{{this}}</option>
{{/if}}
{{/each}}
</select>
{{/each}}
</template>
JavaScript:
Template.demo.foos = ["aaa","bbb","ccc"];
Template.demo.collecs = function(){
return Collec.find({});
};
Template.demo.isSelected = function(fooToCheck, recordFoo){
return (fooToCheck === recordFoo);
};

Resources