Is it possible to loop over a computed Array in Emberjs? - ember.js

I'm trying to loop over a computed property array in handlebars. In this example, I can do it for an ordinary array, but not a computed array:
http://jsfiddle.net/gh7Qr/
What should the right syntax be to loop over a computed property in handlebars?

Yes, it is possible. But you forgot to return your computed array and you have to add cacheable() to computed properties, which return an object and not a primitive. Otherwise you'll run into an infinite loop (see discussion https://github.com/emberjs/ember.js/issues/38) Also have a look at Gordon Hempton's excellent blog post about current Ember.js gotchas, among others regarding computed properties. However since commit 626d23f the issue with cacheable has been solved.
A corrected example of your code is here: http://jsfiddle.net/gh7Qr/4/
Handlebars:
<script type="text/x-handlebars" >
{{#each App.games}}
{{this}}
{{/each}}
{{#each App.gamesA}}
{{this}}
{{/each}}
</script>
JavaScript:
App = Ember.Application.create({
games: [1, 2, 3],
gamesA: Em.computed(function() {
return this.get('games').map(function(game) {
return game + 'a';
})
}).property('games').cacheable()
});​
​

Related

Ember.js: Dynamic width based on model attribute value

First-time Ember user here. In the app, my model objects are each represented by a rectangular-shaped <div> element. The width of each div is determined by its model's size property. The catch is that the possible values for Model.size are 1-10, not simply pixel values. The div's width is then calculated based on the size. For example, a size of 1 might equal a width of 100px, and a size of 2 would equal 200px, and so on. Thus, these CSS width values need to be calculated and bound to the template. Being new to Ember, I don't yet know where this logic should live. Helper? Controller? Because it's really just presentation logic, it doesn't seem right to have it in the model.
<script type="text/x-handlebars" id="things">
{{#each model}}
<div>
{{description}}
</div>
{{/each}}
</script>
Also, will binding it to the template allow the calculated width to be updated automatically in the template whenever the Model.size value is changed (say, from 1 to 3, thus the div would grow wider)?
While it is a good idea to keep presentation and logic separate, sometimes they need to be mixed. I've certainly had use cases. I used this helper when I had a similar issue. Assuming you had this property in your model:
divWidth: function() {
return this.get('size') * 100;
}.property('size')
You could use this template (which is bound to the property value):
<script type="text/x-handlebars" id="things">
{{#each model}}
<div {{bindStyle width="divWidth" width-unit="px"}}>
{{description}}
</div>
{{/each}}
</script>
I don't think this is the right way to do this, but you should be able to do it this way. You can add a function to your controller (or, I believe your model as well) that listens to the size property for changes, and then calls a jQuery function.
i.e.
changeSize: function() {
return $('#things').css( 'width', this.get('size') * 100);
}.property('size')
Also, you could create a computed property in your model the calculates the size for you:
divSize: function() {
return this.get('size') * 100;
}.property('size')
And you'd reference the divSize in changeSize function, which would be useful if your conversion was more complicated than just multiplying by 100. The helper in the other answer looks useful and more Ember-esque, but here's another way it could be done.

How to make linkTo dynamic in ember?

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

Ember.js - Using {{#if}} around {{bindAttr}} with shared condition?

I'm on Ember-1.0.0-pre2 and I seem to be having trouble using an {{#if}} statement around an element which has a {{bindAttr class="..."}} and the binding conditions are the same. I.E. the if statment and class binding are to the same controller attribute. See code:
<button {{action "toggleShow" target="controller"}}>Toggle Visibility</button>
{{#if show}}
<div {{bindAttr class="show:red:green"}}>test</div>
{{/if}}
http://jsfiddle.net/y49ch/10/
If you click the "Toggle Visibility" button several times you'll notice you get a the common error that says: "Something you did caused a view to re-render after it rendered but before it was inserted into the DOM. Because this is avoidable and the cause of significant performance issues in applications, this behavior is deprecated ..."
At first look that seems stupid, but that's a very primitive example of my problem. In my case, there is a computed property on the end of both bindings (if and class attribute). In both cases the computed properties share a common dependent key. When that common dependent key changes it causes both helpers to be update and thus the error.
Is this a bug? I can guess what's happening here, but it seems like I should be able to do this safely.
EDIT: The above is a primitive example of the problem I'm having. It's meant to show it in a very simple way. Below is a more complex example.
Template:
<button {{action "toggleValue" target="controller"}}>Toggle Value</button><br>
{{#if greaterThanTen}}
<div {{bindAttr class="isOdd:red:green"}}>test</div>
{{/if}}
Javascript:
App.myController = Ember.Controller.create({
value: 10,
greaterThanTen: function() {
return this.get('value') > 10;
}.property('value'),
isOdd: function() {
return this.get('value') % 2 === 1;
}.property('value'),
toggleValue: function() {
this.set('value', (this.get('isOdd') ? 10 : 11));
}
});
http://jsfiddle.net/y49ch/16/
I see it now. Your original code had both points watching the same property which got me a little confused, but now it makes more sense. I can't really get what's going on, but I suspect it might have something to do with the runloop.
I've changed your code a little (see this jsfiddle) so that div is now a child view. Some of your properties were moved from the controller to the view (does your spec allow these guys to be at the view or does it have to be at the controller? unless I missed something only the view should be concerned about isOdd and toggleValue at this point) and the css is bound through classNameBindings watching for the value property that is bound to the parent view.
App.myController = Ember.Controller.create({
value: 10,
greaterThanTen: function() {
return this.get('value') > 10;
}.property('value')
});
App.MyView = Ember.View.extend({
templateName: 'my-view',
valueBinding: 'controller.value',
toggleValue: function() {
this.set('value', (this.get('isOdd') ? 10 : 11));
},
isOdd: function() {
return this.get('value') % 2 === 1;
}.property('value'),
ChildView: Em.View.extend({
classNameBindings: 'parentView.isOdd:red:green'
})
});
Now, the template looks like this:
<script type="text/x-handlebars" data-template-name="my-view">
<button {{action "toggleValue"}}>Toggle Value</button><br>
{{#if greaterThanTen}}
{{#view view.ChildView}}
test
{{/view}}
{{/if}}
</script>
Since the default tag for the View is div, it renders the same html, and it totally acts as a different view and prevents unecessary re-render.
Edit: Just as proof of concept, I've added a button to add to the value instead of just toggle so you can actually see the color changing after it gets visible. Here's the fiddle
Let me know if this is good for you

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/

Reversing a content array

I'm storing a list of search terms in my ArrayController. I'd like the search terms to be displayed newest to oldest. By default Ember outputs them in order.
You can see my current implementation here: http://andymatthews.net/code/emberTweets/
And here's the pertinent code.
{{#each App.recentUsersArray.reverse}}
<li>
<a href="#" title="view again" {{action "searchAgain" target="App.recentUsersArray"}}>{{this}}</a>
</li>
{{/each}}
App.recentUsersArray = Em.ArrayController.create({
content: [],
reverse: function(){
return this.content.reverse();
}.property(),
});
You can see that I'm trying to reverse it using a property() method but it's not working. Am I doing something wrong?
You should always use get and set to access properties. Also if a computed property depends on other ones, you have to add these in the property declaration. The use of cacheable can be omitted in the next release of ember, see discussion. You can see a working example here.
reverse: function(){
return this.get('content').toArray().reverse();
}.property('content.#each').cacheable()
You could also use the unshiftObject method on an array and hereby circumvent creating a computed property, see http://jsfiddle.net/ez7bV/.