templateName as computed property - ember.js

According to the documentation it is possible to specify a template for a view with templateName:
App.ShowEntryView = Ember.View.extend({
templateName: 'my-template',
});
And we can use it like:
<script type="text/x-handlebars">
<div>
{{view App.ShowEntryView}}
</div>
</script>
Could we bind the templateName to another property? Something like:
{{view App.ShowEntryView templateNameBinding="myComputedTemplateName"}}
So that in the controller we have:
myComputedTemplateName: function() {
return "this-is-my-template-name";
}.property()
The reason why I want to do this is that I have several models which I am displaying as an heterogeneous table. I want that, whenever the user selects one of the entries in the table, a detailed view is shown, using the right template according to the underlying model.

I guess you could do this:
{{view App.ShowEntryView templateName=myComputedTemplateName}}
JS Bin example

Related

Inheriting singular controller with render helper

I am trying to render a set of tabs for a set of objects (conversations) using the render helper for each. This is not part of a route as it is a persistent part of the interface. I have run into a problem where only the view with the same name as the model gets the intended controller (i.e. the panel contents and not the tab headers).
I have a Chat model, object controller and array controller (deliberately simplified here):
App.Chat = DS.Model.extend({ });
App.ChatsController = Ember.ArrayController.extend({
needs: 'application',
content: Ember.computed.alias('controllers.application.currentChats'),
});
App.ChatController = Ember.ObjectController.extend({ });
The ArrayController needed the needs/content properties because the chats are loaded in the application controller. I used the currentChats name as other routes may load non-current chats.
App.ApplicationController = Ember.Controller.extend({
init: function(){
this.store.find('chat', {"current": true});
this.set('currentChats', this.store.all('chat'));
}
});
I have no difficulty rendering the chat contents with the appropriate controller (into the 'chat' template). However, the chat tabs are given the default ObjectController, and therefore can't fire actions.
<script type="text/x-handlebars" id="application">
<!--application template-->
{{outlet chats}}
</script>
<script type="text/x-handlebars" id="chats">
<div id="chats">
<ul id="chat-tabs">
{{#each}}
{{render 'chatTab' this}}
{{/each}}
</ul>
{{#each}}
{{render 'chat' this}}
{{/each}}
</div>
</script>
<script type="text/x-handlebars" id="chatTab">
<!--tab template-->
</script>
<script type="text/x-handlebars" id="chat">
<!--chat template-->
</script>
The application router is as follows:
App.ApplicationRoute = Ember.Route.extend({
model: function(){ },
renderTemplate: function(){
this.render('application', { });
this.render('chats', {
into: 'application',
outlet: 'chats',
controller: 'chats'
});
}
});
This seems to come solely down to naming of the templates. The template called 'chat' inherits the correct controller, but chatTab doesn't despite receiving a chat as the model. Is there any way to force the view to inherit the correct controller? Or am I going about this in an idiosyncratic way.
Many thanks for your help to this Ember novice.
Andrew
It goes solely off the name provided to the render. The easiest way is to just create the other controller and extend the chat controller.
App.ChatTabController = App.ChatController.extend();

Using Ember, how do I dynamically bind a property in a view helper contained in an each block?

I have a template with code similar to:
{{#each item in items}}
<label>{{item.name}}</label>
{{view Ember.Select content=selectableValues value={{item.name}}}}
{{/each}}
I understand the code above won't work, but it illustrates my problem and what I want to achieve. I don't want to bind what's selected in the Ember.Select to the property of the iterated item, but rather to a controller property of the name of the iterated item's name property.
How might I do this?
You're going to have to dynamically create a computed property, since you are binding the value of the value, unfortunately, this presents problems if you then decide to change the value of the value, but, I'll leave that up to you to play with.
<script type="text/x-handlebars" data-template-name="index">
{{input value=foo}}
{{view App.BlahView host=controller propName=chacha}}
</script>
<script type="text/x-handlebars" data-template-name="blah">
{{view.propValue}}
</script>
Code
App.IndexController = Em.Controller.extend({
chacha:'foo',
foo:'bar'
});
App.BlahView = Em.View.extend({
templateName:'blah',
setupDyna: function(){
var propName = this.get('propName');
Ember.defineProperty(this, 'propValue', Ember.computed(function() {
return this.get('host').get(propName);
}).property("host." + propName));
}.on('init')
});
http://emberjs.jsbin.com/vunureno/1/edit

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

don't understand emberjs {{view.x}}

In the documentation the first view example looks like:
HTML:
<html>
<head>
<script type="text/x-handlebars" data-template-name="say-hello">
Hello, <b>{{view.name}}</b>
</script>
</head>
</html>
JS:
var view = Ember.View.create({
templateName: 'say-hello',
name: "Bob"
});
and maybe I am being a muppet but I really don't understand what is going on. Could someone help.
I kind of understand the cases of {{view}}{{view}} around some html/handlebars where the actions and events will apply from the view definition in javascript. Also I appreciate that you can have a blockless single {{view MyApp.thingView}} which will render the template specified in the view into the the place the view helper is used (as well as making available properties in the view definition).
Is the {{view.x}} instantiating a view and if so why does the example use create rather than extend. Or is the view referring to the global var view (I'm assuming not since this is handlebars.) Could extend be used. Is this form just trying to say that you can access a view's properties inside a template where the view definition has templateName set to the template?
Thanks for any clarification
Update:
After looking at the example again it looks like the var is used for the programmatic append in the other snippets. So we can assume that this is like having the template within two {{view App.aView}}{{/view}} elements and the view. form allows you to get at properties inside App.aView.
<Update>
In response to the update in the question:
You should use {{#view App.SomeView}} ... {{/view}} if that view does not have any template associated to it.
On the other hand, you should use {{view App.SomeView}} if a template has been created for this view via naming conventions or templateName property. Example:
{{view Ember.TextField valueBinding="view.userName"}}
</Update>
When you see {{view.propertyName}} in a Handlebars template, that means you're consuming/rendering a property from the View you are in, so your initial assumption is kind of right. For example:
App = Em.Application.create();
App.HelloView = Em.View.extend({
welcome: 'Willkommen',
userName: 'DHH',
greeting: function() {
return this.get('welcome') + ' ' + this.get('userName');
}.property('welcome', 'userName')
});
Then in your application template:
<script type="text/handlebars">
<h1>App</h1>
{{#view App.HelloView}}
{{view.greeting}}
{{/view}}
</script>
In this case, the {{view.greeting}} part will look into the scope of that View (HelloView) for a property named greeting (it would be the same for any of those properties), and not in the parent view (ApplicationView which is implied). You have to use {{view.propertyName}} whenever calling properties defined in the View.
Properties defined in the controller can be accessed directly without a prefix.
One of the reasons for this, is to make sure you're calling the correct property. Consider the following:
App = Em.Application.create();
App.ApplicationView = Em.View.extend({
userName: 'David'
});
App.HelloView = Em.View.extend({
welcome: 'Willkommen',
userName: 'DHH',
greeting: function() {
return this.get('welcome') + ' ' + this.get('userName');
}.property('welcome', 'userName')
});
Now, both the application view and the inner view have been defined with a property named userName to represent slightly different things. In order to separate which one is which, you can use the view and parentView keywords to access the properties:
<script type="text/handlebars">
<h1>App</h1>
<!-- this comes from the ApplicationView -->
<h3>{{view.userName}}'s Profile</h3>
{{#view App.HelloView}}
<!-- this comes from the HelloView -->
{{view.welcome}} {{view.userName}}
{{/view}}
</script>
And if you want/need to use the real name and nickname in this example, you'd have to:
<script type="text/handlebars">
<h1>App</h1>
{{#view App.HelloView}}
<!-- this comes from the ApplicationView -->
<h3>{{parentView.userName}}'s Profile</h3>
<!-- this comes from the HelloView -->
{{view.welcome}} {{view.userName}}
{{/view}}
</script>
Relevant reference:
http://emberjs.com/api/classes/Ember.View.html
http://emberjs.com/api/classes/Ember.TextField.html

Escape keyword in the handlebar template

I have a collection of objects (MyApp.instrsController, an ArrayController) and each object has a property called 'action' (a float number), how can I display it using the handlebars template?
Below is the code I've tried:
<ul>
{{#each MyApp.instrsController}}
<li>{{action}}</li>
{{/each}}
</ul>
But since 'action' is a 'keyword', it throws javascript runtime error 'options is undefined'.
You can manually specify that you're looking for a property in the current context by preceding the property name with this.:
{{this.action}}
If you can't change the property name you can use a computed property in your model object, see http://jsfiddle.net/pangratz666/4ZQM8/:
Handlebars:
<script type="text/x-handlebars" >
<ul>
{{#each App.controller}}
<li>{{actionProp}}</li>
{{/each}}
</ul>
</script>
JavaScript:
App.Object = Ember.Object.extend({
actionProp: function() {
return this.get('action');
}.property('action')
});
App.controller = Ember.ArrayController.create({
content: [],
addObj: function(number) {
this.pushObject(App.Object.create({
action: number
}));
}
});
If you don't have a custom model object, you can use a computed property on a CollectionView, see http://jsfiddle.net/pangratz666/r6XAc/:
Handlebars:
<script type="text/x-handlebars" data-template-name="item" >
{{actionProp}}
</script>​
JavaScript:
Ember.CollectionView.create({
contentBinding: 'App.controller',
itemViewClass: Ember.View.extend({
templateName: 'item',
actionProp: function(){
return this.getPath('content.action');
}.property()
})
}).append();
I seriously suggest that you just change the name of the property.
You are spending time working on a problem that is not core to your domain.
YAGNI. KISS.
The biggest lesson to learn in programming is how to get things made and done rather than how to manipulate javascript into not throwing a snit over your use of a reserved keyword. You will be happier later if you don't have a fragile solution that looks tricky to understanc
Since this is a float, can I suggest you use "actionLevel"?
just use model.property,such as:
{{input class="input-xlarge" value=model.content }}