Escape keyword in the handlebar template - ember.js

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

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

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

Ember.js: replacing simple linkTo helper with a view

I've got an app with basic functionality built out. I'm not going through and adding additional features. In this case I need to convert a simple button, currently using linkTo, to a View. Problem is that I'm not sure how to convert one to the other and still keep the link intact.
How do I do this conversion? Here's the code I have now:
<script type="text/x-handlebars" data-template-name="accountItem">
{{#each account in controller}}
{{#linkTo "account" account}}
<img {{bindAttr src="account.icon"}} />
{{/linkTo}}
{{/each}}
</script>
and here's the code I'm going to have:
<script type="text/x-handlebars" data-template-name="accountItem">
{{#each account in controller}}
{{#view "Social.AccountButtonView"}}
<img {{bindAttr src="account.icon"}} />
{{/view}}
{{/each}}
</script>
Social.AccountButtonView = Ember.View.extend({
tagName: 'a',
classNames: ['item-account'],
click: function(){
// do something
}
});
I would assume that I'd be building on top of the click handler in the View, but I'm not sure how to pass the reference to item being iterated over, nor how to reference the correct route within the View.
Assistance please?
Update 1
The first version renders an href attribute with a value of #/accounts/4 based on the Router I have set up:
Social.Router.map(function() {
this.resource('accounts', function(){
this.resource('account', { path: ':account_id'});
});
});
When I convert the current code to a view, how do I mimic the functionality that linkTo provides?
You can define a property binding for account in your handlebars template.
This binding works like this:
<script type="text/x-handlebars">
<h1>App</h1>
{{#each item in controller}}
{{#view App.AccountView accountBinding="item"}}
<a {{bindAttr href="view.account.url"}} target="_blank">
{{view.account.name}}
</a>
{{/view}}
{{/each}}
</script>
Note that I added accountBinding, so the general rule is propertyName and Binding as a suffix. And remember that when you add a property to a view, you will not be able to access it directly, instead you will have to access it with view.propertyName as shown above.
Just keep in mind that you must have a View class when using the {{view}} helper:
window.App = Em.Application.create();
App.AccountView = Em.View.extend(); // this must exist
App.ApplicationRoute = Em.Route.extend({
model: function() {
return [
{id: 1, name: 'Ember.js', url: 'http://emberjs.com'},
{id: 2, name: 'Toronto Ember.js', url: 'http://torontoemberjs.com'},
{id: 3, name: 'JS Fiddle', url: 'http://jsfiddle.com'}];
}
})
Working fiddle: http://jsfiddle.net/schawaska/PFxHx/
In Response to Update 1:
I found myself in a similar scenario, and ended up creating a child view to mimic the {{linkTo}} helper. I don't really know/think it's the best implementation tho.
You can see my previous code here: http://jsfiddle.net/schawaska/SqhJB/
At that time I had created a child view within the ApplicationView:
App.ApplicationView = Em.View.extend({
templateName: 'application',
NavbarView: Em.View.extend({
init: function() {
this._super();
this.set('controller', this.get('parentView.controller').controllerFor('navbar'))
},
selectedRouteName: 'home',
gotoRoute: function(e) {
this.set('selectedRouteName', e.routeName);
this.get('controller.target.router').transitionTo(e.routePath);
},
templateName: 'navbar',
MenuItemView: Em.View.extend({
templateName:'menu-item',
tagName: 'li',
classNameBindings: 'IsActive:active'.w(),
IsActive: function() {
return this.get('item.routeName') === this.get('parentView.selectedRouteName');
}.property('item', 'parentView.selectedRouteName')
})
})
});
and my Handlebars looks like this:
<script type="text/x-handlebars" data-template-name="menu-item">
<a {{action gotoRoute item on="click" target="view.parentView"}}>
{{item.displayText}}
</a>
</script>
<script type="text/x-handlebars" data-template-name="navbar">
<ul class="left">
{{#each item in controller}}
{{view view.MenuItemView itemBinding="item"}}
{{/each}}
</ul>
</script>
I'm sorry I can't give you a better answer. This is what I could come up with at the time and haven't touched it ever since. Like I said, I don't think this is the way to handle it. If you are willing to take a look into the {{linkTo}} helper source code, you'll see a modular and elegant implementation that could be the base of your own implementation. I guess the part you're looking for is the href property which is being defined like so:
var LinkView = Em.View.extend({
...
attributeBindings: ['href', 'title'],
...
href: Ember.computed(function() {
var router = this.get('router');
return router.generate.apply(router, args(this, router));
})
...
});
So I guess, from there you can understand how it works and implement something on your own. Let me know if that helps.