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!
Related
I'd like to dynamically render a template, like this:
{{#each layout in layouts}}
{{render layout.get('type') layout}}
{{/each}}
The problem is that render doesn't expect a variable as its first arguments, so in the older EmberJS-versions, a workaround was possible by registering a helper:
Ember.Handlebars.registerBoundHelper('render_layout',
function(callingContext, layout, options) {
return Ember.Handlebars.helpers.render.call(callingContext,
layout.get('type'), 'layout', options);
});
and in the template:
{{#each layout in layouts}}
{{render_layout this layout}}
{{/each}}
Unfortunately the thing doesn't work in newer ember versions.
Ember.Handlebars.helpers.render.call expects 3 arguments but I can not get them right. Any ideas?
I've tried:
(this, layout.get('type'), options)
and in the template:
{{#each layout in layouts}}
{{render_layout layout}}
{{/each}}
I get Uncaught TypeError: Cannot read property 'pushChildView' of null
... and many others.
Any suggestions would be greatly appreciated.
Components allow you to specify layoutName. And you can dynamically compute the layout name based on a parameter passed into the component.
App.XRenderComponent = Ember.Component.extend({
tagName: "",
layoutName: function(){
return this.get('displayLayout.type');
}.property('displayLayout'),
});
Then you can just do
<script type="text/x-handlebars" id="index">
{{#each layout in model}}
{{ x-render displayLayout=layout }}
{{/each}}
</script>
See working example here
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.
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}}
<script type="text/x-handlebars" data-template-name="patient">
<ul class="nav">
{{#each menuItem in menuItems}}
<li>{{#linkTo "dashboard.summary" menuItem}}{{menuItem.name}}{{/linkTo}}</li>
{{/each}}
</ul>
{{outlet}}
</script>
In the above code, how do I make linkTo a dynamic link instead of the hardcoded "dashboard.summary"? For example, "dashboard."+menuItem.name.
In the current Ember (1.10 as of this post), helpers now accept both quoted arguments or arguments that will be looked up as attributes in the current context. I believe this was changed in Ember 1.2 ( change log ).
If quoted, the argument will be used as a string:
{{#link-to 'post'}}Posts{{/link-to}}
If not quoted, the argument will be looked up in the current context:
{{#link-to routeName}}Go To '{{routeName}}'{{/link-to}}
This will be a link-to that points to whatever the routeName property currently is set to. This can be updated dynamically.
Here is an example JSBin showing this in action: http://emberjs.jsbin.com/nelafep/1/edit?html,css,js,output
You could register a simple Handlebars helper that wraps the linkTo helper.
var linkTo = Ember.Handlebars.helpers.linkTo;
Ember.Handlebars.registerHelper('myLinkTo', function(name, suffixPath) {
var suffix = Ember.Handlebars.get(this, suffixPath);
arguments = [].slice.call(arguments, 2);
arguments.unshift(name + '.' + suffix);
return linkTo.apply(this, arguments);
});
Then in your template you could write:
{{#each menuItems}}
<li>{{#myLinkTo "dashboard" name this}}{{name}}{{/myLinkTo}}</li>
{{/each}}
The helper will resolve the second argument and append it to the first, preceded by a dot.
Edit: this behaviour can now be achieved without a custom helper. See c4p's answer for the contemporary solution to this problem. The solution above was last tested with Ember 1.0.0-rc.1.
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/