Emberjs conditional output in a template with handlebars - ember.js

I got following models:
A Community with a name, members and moderators(both are Users). Users, who have an id and a name.
In the CommunityMembers template i want to show all the users, and if that user is a moderator, i want to add some extra saying that he's a moderator
<script type="text/x-handlebars" data-template-name="communityMembers">
//model contains an array of users in a community
{{#each user in model}}
<li>{{user.name}}</li>
{{#each moderator in controllers.community.moderators}}
//here is the problem-->
{{#if moderator.id == user.id}}
<b>this is a moderator</b>
{{/if}}
{{/each}}
{{/each}}
</script>
i know that in handlebars you can't use moderator.id==user.id but it's an easy way to say what i want to do.
i tried to write a handlebars helper but when i checked in the helper what my argument was i got a string saying: "moderator.id" or "user.id" so that didn't work.
i also tried to do it with a method in my community-object:
App.Community = Ember.Object.extend({
isModerator: function(community, user_id){
return community.moderators.indexOf({"id":user_id})!=-1;
}
});
in the template:
{{#if isModerator(controllers.community,user.id)}}
<h>this is a moderator</h>
{{/if}}
but that gave me errors in the template like:
. Compiler said: Error: Parse error on line 12: .../if}}
{{#if isModerator(controll
----------------------^ Expecting 'CLOSE', 'CLOSE_UNESCAPED', 'STRING', 'INTEGER', 'BOOLEAN', 'ID', 'DATA', 'SEP', got 'INVALID'
Is there anyone who knows how to deal with this?

You can't do this in Handlebars (as you said) and you shouldn't try do mimic this behavior with a helper. This limitation is intentionally designed into the templating, because it is considered a bad practice to have too much logic in the template. Instead your goal should be to write your template like this:
<script type="text/x-handlebars" data-template-name="communityMembers">
//model contains an array of users in a community
{{#each user in controller.usersWithModeratorFlag}}
<li>{{user.name}}</li>
{{#if user.isModerator}}
<b>this is a moderator</b>
{{/if}}
{{/each}}
</script>
Now you are probably asking yourself how to implement this attribute. You could try something like this (if you can't embed this attribute into your user objects):
App.CommunityMembersController = Ember.ArrayController.extend({
needs : ["community"],
usersWithModeratorFlag : function(){
var moderators = this.get("controllers.community.moderators");
return this.get("model").map(function(user){
if(moderators.contains(user)){
user.set("isModerator", true);
}
}
}.property("model.#each", "controllers.community.moderators.#each")
});
As you can see, it is quite easy to move this logic out of the template and into the controller, where it belongs.

You can use ember-truth-helpers
{{#if (eq moderator.id user.id)}}
<b>this is a moderator</b>
{{/if}}

Related

How do I render a collection of models each with variable view and controller?

I have a collection of models in my Ember.js app, which I would like to render. The catch is that I want to be able to specify a specialized view and controller for each of the models.
The controller part seems to be easy: I would just wrap the array in an ArrayController and implement itemController method. The view part is where it gets tricky. I don't see an obvious idiomatic way of doing this.
The best way we came up with is the combination of ArrayController and CollectionView with an overridden createChildView. For instance:
createChildView: function(viewClass, attrs) {
var viewInstance,
widgetType = attrs.content.get('type');
// lookup view, if found, use it, if not, pass empty view
var viewDefined = this.container.lookup('view:' + widgetType);
var createWidgetType = viewDefined ? widgetType : 'empty';
// create view instance from widgetType name
// it causes lookup in controller
viewInstance = this._super(createWidgetType, attrs);
// if `attrs.content` is controller (item of `ComponentsController`)
// set it as controller of newly created view
if(attrs.content.get('isController')) {
viewInstance.set('controller', attrs.content);
}
return viewInstance;
}
This feels unnecessarily convoluted, I don't like that I have to connect the view with the controller manually like that. Is there a cleaner way?
You can create a component, which will act as controller and have a view associated with it:
App.XItemComponent = Ember.Component.extend({
controllerProperty: '!',
tagName: 'li'
});
Then, you can just do:
<script type="text/x-handlebars" data-template-name="index">
<ul>
{{#each model }}
{{ x-item item=this }}
{{/each}}
</ul>
</script>
http://emberjs.jsbin.com/wehevixolu/1/edit?html,js,output
I'd use the {{render}} helper. It'll create a view and controller for each instance.
{{#each item in model}}
{{render "item" item}}
{{/each}}
Example: http://emberjs.jsbin.com/vuwimu/2/edit?html,js,output
Render helper guide: http://emberjs.com/guides/templates/rendering-with-helpers/#toc_the-code-render-code-helper
Additionally:
In your comment you mentioned you want different controller/view types for particular model types. This could be done like this:
{{#each item in model}}
{{#if item.typeX}}
{{render "itemX" item}}
{{/if}}
{{#if item.typeY}}
{{render "itemY" item}}
{{/if}}
{{/each}}
or if you'd choose to go with components:
{{#each item in model}}
{{#if item.typeX}}
{{component-x item=item}}
{{/if}}
{{#if item.typeY}}
{{component-y item=item}}
{{/if}}
{{/each}}
Without knowing what you are trying to accomplish in more detail it’s hard to tell what the best solution is.

Using variable in partial name

I have a list of "summaries" in a loop:
{{#each formSummaries}}
{{/each}}
For each one I want to output a template that is named by each formSummary.name:
{{#each formSummaries}}
{{partial 'forms/summaries/' + name}}
{{/each}}
or like this:
Controller:
summaryPath: 'forms/summaries' + name
Template:
{{#each formSummaries}}
{{partial summaryPath}}
{{/each}}
Is it possible to do something like this with a partial? Is there a more "Ember" way of solving this problem? Thank you in advance.
The handlebars looks good to me, except that I would specify an itemController. This will let you get fancy-pants with each rendered model through the use of computed properties and other powerful controller mechanisms.
{{#each formSummaries itemController='summary'}}
{{partial summaryPath}}
{{/each}}
Now, summaryPath should be computed within that item controller. How about:
App.SummaryController = Ember.ObjectController.extend({
summaryPath: function () {
return 'templateName_' + this.get('name'); // You can tailor this part to suit your needs.
}.property('name')
)};
Hope that helps!

Ember.js: ArrayController undefined in template

Problem:
I am kind of struggling with the organization of my first ember app. The current issue is that the my Items ArrayController is not defined in my dashboard template:
<script type="text/x-handlebars" data-template-name="dashboard">
{{#if controllers.items}}
<p class="alert alert-error">Dashboard can access item's info - Nice!</p>
{{else}}
<p class="alert alert-error">Dashboard cannot access items... :-/</p>
{{/if}}
</script>
Likely cause: *
**EDIT: after talking with #conrad below, I'm kind of questioning this:*
I had a similar issue in an earlier post and kingpin2k suggested the cause was that I:
"never created anything that uses the options controller".
This is probably the case here as well. This quick screencast shows that a breakpoint on my ArrayController is not hit on page load - but it is hit when I inspect the Items controller in the Ember inspector tool (eg, Ember creates the ArrayController object right then for the first time).
Apparent non-solutions:
My Dashboard controller says it needs the Items controller. I guess that isn't enough to instantiate the ArrayController?
App.ItemsController = Ember.ArrayController.extend({
len: function(){
return this.get('length');
}.property('length'),
totalCost: function() {
return this.reduce( function(prevCost, item){
return parseInt(item.get('values').findBy('type', 'cost').price, 10) + prevCost;
}, 0);
}.property('#each.values')
[more computed properties...]
});
App.DashboardController = Em.Controller.extend({
needs: ['items'],
itemsLength: Ember.computed.alias('controllers.items.len'),
itemsTotalCost: Ember.computed.alias('controllers.items.totalCost'),
[more computed properties...]
});
Furthermore, each item in Items is being rendered in my items template. I guess that does not create the missing controllers.items either...
<script type="text/x-handlebars" data-template-name="items">
{{#each}}
[these render fine]
{{/each}}
</script>
<script type="text/x-handlebars" data-template-name="display">
<!-- DISPLAY TEMPLATE -->
{{!- DASHBOARD -}}
{{render dashboard}}
{{!- ITEMS -}}
{{render 'items' items}}
</script>
So then.. what?
I can imagine many possible avenues, but haven't gotten any of them to work yet:
Specify the Items ArrayController in {{render dashboard}}?
Some configuration in a Route?
Maybe my templates/routes are not correctly arranged?
You could make sure that the ItemController is instantiated in the dashboard template by calling it in the DashboardController's init function:
App.DashboardController = Em.Controller.extend({
needs: ['items'],
init: function() {
this._super();
this.get('controllers.items.length');
}
});
/edit:
removed the part that was not helpful

Ember.js itemController and model and controller confusion

Let's assume that I've set everything up correctly. I have a model App.User and I have a controller App.UsersIndexUserController.
GOOD GOOD GOOD GOOD GOOD
The following view template ...
<script type="text/x-handlebars" data-template-name="users_index_template">
{{#each user in users}}
{{log user}}
{{#linkTo users.user user}}{{user.name}}{{/linkTo}}
{{/each}}
</script>
... this outputs the following in browser's console.log ...
<App.User:ember258> { created_at="2013-03-05T01:51:15Z", id=76 ... etc ... }
BAD BAD BAD BAD BAD
However, when using itemController directive in my template, like so ...
<script type="text/x-handlebars" data-template-name="users_index_template">
{{#each user in users itemController="usersIndexUser"}}
{{log user}}
{{#linkTo users.user user}}{{user.name}}{{/linkTo}}
{{/each}}
</script>
... this outputs the following in browser's console.log ...
<App.UsersIndexUserController:ember253> { target=<Ember.ArrayController:ember245> ... etc ... }
I'm expecting {{log user}} to return an instance of App.User for both cases. But as you can see above, it returns an instance of App.UsersIndexUserController when using itemController directive, and returns and instance of App.User when not using itemController directive.
Should my App.UsersIndexUserController explicitly return some object such that in both cases above, {{log user}} will return App.User?
App.UsersIndexUserController = Ember.ObjectController.extend({
});
I'm using Ember.js v1.0.0-rc1
// Version: v1.0.0-rc.1
// Last commit: 8b061b4 (2013-02-15 12:10:22 -0800)
I think this behaviour is right. In the second case you are explicitly telling ember to wrap each of your user objects in a proxy (UsersIndexUserController). Therefore the logging of the variable user yields an instance of this proxy. From a debugging perpspective, i absolutely agree with the behaviour of the log helper. It may not be intuitive at first, but would this be not the case, you would not see the real object you are working on there. Imagine your itemController would define a computed property also called name. In this case {{user.name}} would access the property on the controller instead of the model. This could likely be an error and with this behaviour of the helper, you can spot the error much easier.

In templates in Ember.js, how do you refer to a value in the parent context when you are inside an #each block?

I have a situation in a template where I want to use an if block on a value in the parent context while inside an each block.
The code:
App = Ember.Application.create({});
App.view = Ember.View.extend({
foo: [1, 2, 3],
bar: true
});
The template:
<script type="text/x-handlebars">
{{#view App.view}}
{{#each foo}}
{{#if bar}}
{{this}}
{{/if}}
{{/each}}
{{/view}}
</script>
This does not work because names referenced inside an each loop are scoped to the element of iteration. How do you refer to things in the parent context?
Demo: http://jsfiddle.net/hekevintran/sMeyC/1/
I found a better solution.
From the Ember.js View Layer guide (http://emberjs.com/guides/understanding-ember/the-view-layer/):
Handlebars helpers in Ember may also specify variables. For example, the {{#with controller.person as tom}} form specifies a tom variable that descendent scopes can access. Even if a child context has a tom property, the tom variable will supersede it.
This form has one major benefit: it allows you to shorten long paths without losing access to the parent scope.
It is especially important in the {{#each}} helper, which provides a {{#each person in people}} form. In this form, descendent context have access to the person variable, but remain in the same scope as where the template invoked the each.
The template:
<script type="text/x-handlebars" >
{{#view App.view}}
{{#each number in view.foo}}
{{#if view.bar}}
{{number}}
{{/if}}
{{/each}}
{{/view}}
</script>​
Demo: http://jsfiddle.net/hekevintran/hpcJv/1/
What hekevintran's answer means is that you can rename any variable using #with. We have a similar problem in JavaScript with this. In JavaScript, sometimes you'll see code like this to work around it.
var self = this;
doSomething(function() {
// Here, `this` has changed.
if (self.bar) {
console.log(this);
}
});
In Ember flavored Handlebars, something similar is happening with view. Say you have App.MyOuterView and another view inside it. You can work around it like this.
{{#with view as myOuterView}}
{{#each foo}}
{{#if myOuterView.bar}}
{{this}}
{{/if}}
{{/each}}
{{/with}}
Similar to the JavaScript, you can essentially rename view to something else so it doesn't get shadowed by the inner view. {{#each person in people}} is just a special case of that. But renaming using {{#with view as myView}} is the more general solution/workaround to this problem that also works with nested calls to the view helper.
I was also stumped on this. This thread and this other thread (Using a container view in ember.js - how to access parent variables from child view) helped me with the solution. I used Jonathan's suggestion to do {#with} and also figured out that I should access my variable by calling the controller. Mine worked like this:
// I use the #which command to preserve access to the outer context once inside the #each
{{#with view as myOuterView}}
{{#each myInnerArray}}
//here, i get my 'name' property from the *controller* of myOuterView
{{myOuterView.controller.name}}
// stuff i did in inner array
{{/each}
{{/with}
No need to place the if inside each in the first place:
<script type="text/x-handlebars">
{{#view App.view}}
{{#if view.bar}}
{{#each view.foo}}
{{this}}
{{/each}}
{{/if}}
{{/view}}
</script>
Demo: http://jsfiddle.net/ppanagi/NQKvy/35/