don't understand emberjs {{view.x}} - ember.js

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

Related

Custom view helper in Ember.js, "You can't use appendChild outside of the rendering process"

I want to bind my custom view's class to a controller property.
[javascript]
App.IndexController = Ember.Controller.extend({
headerClass: "a"
});
App.TestHeaderView = Ember.View.extend({
classNames: ["test-header"],
classNameBindings: ["headerClass"],
headerClass: null,
templateName: "views/test-header"
});
[templates]
<script type="text/x-handlebars" data-template-name="index">
{{view App.TestHeaderView text="view helper" headerClass=controller.headerClass }}
<hr />
{{input value=headerClass}}
</script>
<script type="text/x-handlebars" data-template-name="views/test-header">
<small>{{view.text}}</small>
</script>
The result is predictable: everything works. I can enter the class name in the text box and see it reflected in the view.
So now I want to extend this and add my own helper that wraps the {{view}} call.
[javascript]
Ember.Handlebars.helper("test-header", function (options) {
return Ember.Handlebars.helpers.view.call(this, App.TestHeaderView, options);
});
[templates]
<script type="text/x-handlebars" data-template-name="index">
{{test-header text="custom helper" headerClass=controller.headerClass}}
</script>
Nothing special right? Except, I keep getting this:
Uncaught Error: You can't use appendChild outside of the rendering process
For full working jsbin, click here.
It seems this should work. I'm just wrapping the ember's view helper pretty much exactly. What am I missing?
I figured it out.
The trick is in the contexts array in the options hash.
When you call {{view App.MyView}} from handlebars, Ember's view helper gets in its options.contexts array the "context" in which it should search for "App.MyView" property - usually the current controller. In this case, "App.MyView" will be resolved regardless of the context, but I guess Ember keeps the context around and uses it to resolve bound properties.
When I called:
{{test-header text="custom helper" headerClass=controller.headerClass}}
there was no first argument from which to draw the context. Therefore, when I passed the call along to the view helper:
return Ember.Handlebars.helpers.view.call(this, App.TestHeaderView, options);
... there was no context passed along in the options.contexts array.
The way I fixed this is:
Ember.Handlebars.helper("test-header", function (options) {
options.contexts = [this].concat(options.contexts);
return Ember.Handlebars.helpers.view.call(this, App.TestHeaderView, options);
});
IMO Ember should do a better job here. They should either figure out a context from reference, or throw an error (a preferred option).

templateName as computed property

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

How to render a nested view (relative view)

I have the following (ember-1.4.0):
App.DateRangeSelectorView = Ember.View.extend({
templateName: 'date-range-selector',
selectedBinding: 'controller.selected',
dateRangeSelectorItemView: Ember.View.extend({
tagName: 'li',
classNameBindings: ['isActive:active'],
isActive: function() {
return this.get('item') === this.get('parentView.selected');
}.property('item', 'parentView.selected')
})
});
And the template:
<script type="text/x-handlebars" data-template-name="date-range-selector">
<ul class="nav nav-pills" style="margin-bottom: 10px;">
{{#view view.dateRangeSelectorItemView item="today"}}
<a href="#" {{action gotoToday}} >{{controller.content.today.label}}</a>
{{/view}}
....
</ul>
</script>
I have followed the guidelines specified here, specially:
When nesting a view class like this, make sure to use a lowercase
letter, as Ember will interpret a property with a capital letter as a
global property.
Thanks, but no thanks: ember is stubbornly saying:
Uncaught Error: Assertion Failed: Unable to find view at path 'view.dateRangeSelectorItemView'
I have tried with and without the view. prefix, but no luck. How can I render the nested view?
EDIT
The problem seems to be that the lookup performed by the container is failing. Maybe there are some capitalization or name coercion rules that I am not getting right. I would like to list all available views, so that I can recognize if my view is there, maybe with a slightly different name.
How can I list all available (registered?) views, including nested views? That would include dateRangeSelectorItemView, which is a view nested inside App.DateRangeSelectorView, and is not defined in the application itself.
I guess what I am looking for is a way of listing all objects (with their lookup names!) which are extensions of Ember.View: Ember.View.extend()
The problem is that I was using an outlet for this, and the outlet does not allow to specify a view: it generates the view according to the template name, so that my DateRangeSelectorView was not used. I have raised an issue about this.

Accessing Ember controller attributes from a controller

I have the following controller:
App.ShowController = Ember.Controller.expend({
buttonTitle: 'Create'
});
And the following template show.handlebars
<a href='#'>{{buttonTitle}}</a>
but the text is not rendering. Is there a special call to access the attribute?
Normally, when a view is displayed (via the Router), the context of the view is automatically set to the controller, so there should be nothing to do special.
Here is an example, where the MyApp.IndexController is automatically set as the context of the IndexView (and its template is the index template):
MyApp = Ember.Application.create({});
MyApp.Router = Ember.Router.extend();
MyApp.Router.map(function(match) {
match('/').to('index');
});
MyApp.IndexController = Ember.Controller.extend({
buttonTitle: "create"
});
The template:
<script type="text/x-handlebars" data-template-name="index">
{{buttonTitle}}
</script>
And you could try it on this JSFiddle.
N.B.: I'm using Ember v1.0.0-pre.2-239 here. There are some changes to do for upgrading this example to master

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