Strange results with nested handlebar blocks in Meteor - nested

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

Related

Problem accessing specific elements of all objects in an array using Handlebars

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

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

How to check type of object in Handlebars?

I am currently working on a search bar that lists three types of objects: users, records and locations. Each has its own model and such defined, with corresponding controllers. What I need to do is to check which type of object it is because I need the HTML that renders with it to be different.
{{#each mainUserSearchResults}}
{{#link-to 'user' this.userID}}
<div class="row mainListSeperator" {{action "getapplicantUserID" this target="view"}}>
<img class="applicantsIcon" src="">
<div class="applicantsName">
{{unbound this.firstName}} {{unbound this.lastName}}
</div>
<div class="applicantsTitle">
User
</div>
</div>
{{/link-to}}
{{/each}}
The only issue I am having is that I need it to print this.firstName & this.lastName if it is a user, but I cannot do that for records. For records, I would have to render another property - this.recordID - in the same manner as I did this.firstName. The way to do this would be an if conditional, but I cannot find anything in HandleBars that allows me to check whether the data coming in from mainUserSearchResults is a user or a record.
The mainUserSearchResults is a property in my controller that returns an array of objects: currently it return a concatenated array of user objects and record objects.
Handlebars is a logicless template, so you really can't perform computations, like type-checking. There are some pretty good reasons for this. If it were me, I'd push that responsibility elsewhere, probably all the way to your model, for two reasons:
Any conditionals you put in the DOM are SLOW. The problem only gets compounded when you're on slow devices (most notably, mobile).
Every time you add a new searchable thing, you're going to have to update your templates. Over time, you'll end up with:
{{#if ...}}
...
{{/if}}
{{#if ...}}
...
{{/if}}
{{#if ...}}
...
{{/if}}
{{#if ...}}
...
{{/if}}
Delegate instead. For example, in your User model, add
formattedName: (function() {
return [this.get('firstName'), this.get('lastName')].join(' ');
}).property('firstName', 'lastName')
Do the same for your records and locations.
Then, change your template from
{{unbound this.firstName}} {{unbound this.lastName}}
to
{{unbound this.formattedName}}
Now, if you add new types to your searches, you have a completely generic solution that only needs the model to implement the formattedName property.
Some would would say my suggestion is a bad one, because it mixes the business-logic responsibility of a model with display logic. In this case, I'm guessing you're displaying {{firstName}} {{lastName}} in more than one place, so I think the benefits of keeping your code DRY outweigh moving this to, e.g., a controller. Your call.
Assuming there is some way to easily distinguish between the 2+ types of objects, you can use an if statement and render a different template (or even use a different partial, http://emberjs.com/guides/templates/rendering-with-helpers/)
<script type="text/x-handlebars" data-template-name="index">
<ul>
{{#each item in model}}
<li>
{{#if item.foo}}
{{render 'foo' item}}
{{/if}}
{{#if item.cow}}
{{render 'cow' item}}
{{/if}}
</li>
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" data-template-name="foo">
I'm a foo: {{foo}}
</script>
<script type="text/x-handlebars" data-template-name="cow">
I'm a cow: {{cow}}
</script>
http://emberjs.jsbin.com/qinilege/1/edit
Or if you need more advanced checking you can use an item controller and add the logic in there
{{#each item in model itemController='checker'}}
<li>
{{#if item.isFooBar}}
{{render 'foo' item.model}}
{{/if}}
{{#if item.isFooBaz}}
{{render 'fooz' item.model}}
{{/if}}
{{#if item.isCow}}
{{render 'cow' item.model}}
{{/if}}
</li>
{{/each}}
App.CheckerController = Em.ObjectController.extend({
isFooBar: Ember.computed.equal('foo', 'bar'),
isCow: Ember.computed.notEmpty('cow'),
isFooBaz: Ember.computed.equal('foo', 'baz')
});
http://emberjs.jsbin.com/qinilege/2/edit

Rendering new div created dynamically with Meteor

I have a the classic "Thread->Posts" model.
In the application, I have a left sidebar with the "Thread" collections. When the user click in a Thread, I want to create another div with the Thread->Posts elements.
Is there any way to do this, conservating the reactivity?
For now, I've got this:
// In the client
Template.threadlist.events({
'click tr': function(event){
Session.set("selectedThread",this);
$("#posts").html( Meteor.render(Template["datathread"]) );
}
})
[...]
Template.datathread.events({
'click input.add-post' : function(event){
var t = Session.get("selectedThread");
Meteor.call("addPost", {"thread":t,"text":"foo","user":"var"},callback})
}
})
[...]
// In the server
addPost: function(param){
var id = Threads.update(param.thread,{ $addToSet: {"posts": {"text":param.text, "user": param.user}}});
return id;
}
The template with the posts is something like this:
<template name="datathread">
{{#each thread.posts}}
{{user}} says: {{text}}
<br />
{{/each}}
</template>
(The "user" and "text" propertis are from the "thread.posts" elements)
With this code, I only get the new values refreshing (F5) the webpage (or executing the 'click tr'event). What I'm doing wrong?
Thank you!
== Edit ==
Ok... Now, with the recomendation of Chistian Fritz, my code its something like this:
// In the client
Template.datathread.thread = function(){
return Session.get("selectedThread");
}
[...]
Template.threadlist.events({
'click tr': function(event){
Session.set("selectedThread",this);
}
});
//In the html
<div class="span5" id="threads">
{{> datathread}}
</div>
<template name="datathread">
{{#if thread}}
<hr />
{{#each thread.posts}}
{{user}} says: {{text}}
<br />
{{/each}}
{{/if}}
</template>
The changes are great (it's so simple!), but the reactivity still doesn't work :(....
The problem is that you are using jQuery to fill your DOM:
$("#posts").html( Meteor.render(Template["datathread"]) );
This breaks the reactivity chain.
You are only showing part of your code, so I can't give you the full solution, but it seems that the datathread template is already using the selectedThread session variable -- which is good. Hence, it might be as easy as using {{> datathread}} in place of the #posts element in your HTML.
EDIT:
One thing that you need to change is the scope you are assuming datathread: it's already the thread itself, if I understand correctly:
<template name="datathread">
<hr />
{{#each posts}}
{{user}} says: {{text}}
<br />
{{/each}}
</template>
Also, the this in the threadlist event handler most certainly won't be the thread. I don't know the data of the tr in the threadlist (can you show that code?), but you will most probably do something like the following:
Session.set("selectedThread", this.id);
And for datathread:
Template.datathread.thread = function(){
return Threads.find({_id: Session.get("selectedThread")});
}
or similar.

Resources