Access Controller in a View in a Render - ember.js

I have a view like this:
App.AbilityFilter = Ember.TextField.extend({
classNames: ['span3'],
keyUp: function(evt) {
this.get('controller').send('filterAbilities','text');
},
placeholder:'Search abilities'
});
It's part of a render like this:
<script type="text/x-handlebars" data-template-name="abilities">
{{view App.AbilityFilter}}
<div class="accordion" id="abilities">
{{#each ability in model}}
<div class="accordion-group">
{{ability.name}}
</div>
{{/each}}
</div>
</script>
Which is being rendered in my application like this:
{{render 'abilities'}}
The problem I'm having is with the event or, rather, the action. The keyUp event fires perfectly well, but for some reason it won't go to a controller.
I've tried adding the filterAbilities to the actions hash on both the App.AbilitiesController and the App.IndexRoute according to this. According to this, the view should be part of the abilities controller since that's the context of it's parent, but it's not working.
I've done some testing and it almost seems like this.get('controller') isn't fetching a controller at all. I'm a bit lost as to what's causing the problem. This code worked a few RCs ago, but as soon as I upgraded to 1.0 it broke.
What I'm trying to do here is filter the list of abilities. If this isn't the way to this anymore, please let me know! Any help would be appreciated. Thanks!!

Ember.TextField and Ember.TextArea are no longer simple views but rather subclasses of Ember.Component which means that this.get('controller') does not refer anymore to the views controller.
But there is a different variable which indeed holds a reference to the surrounding controller and this is this.get('targetObject'). Therefore you should send your action to the targetObject:
App.AbilityFilter = Ember.TextField.extend({
classNames: ['span3'],
keyUp: function(evt) {
this.get('targetObject').send('filterAbilities','text');
},
placeholder:'Search abilities'
});
Hope it helps.

Related

Evaluating controller property everytime the template is rendered in Ember

I have a template called sample and I am changing the property show in the controller based on a button press. Now I want the controller to reset the property every time the template is rendered. Currently even if I go to 'next' template and come back, the property show remains true if the button on sample was pressed. I want to change this behaviour and the property show should be false by default. I know I can do this by defining a view and using the didInsertElement hook but is that the only way to do this?? Ember.js website says that Views in Ember.js are typically only created for the following reasons:
When you need sophisticated handling of user events
When you want to create a re-usable component
and I am doing none of the above. Here is some sample code:
<script type="text/x-handlebars" data-template-name="sample">
{{#if show}}
Showing stuff
{{/if}}
<button {{action changeShow}}>Change</button>
{{#link-to 'next'}} Next {{/link-to}}
</script>
<script type="text/x-handlebars" data-template-name="next">
Hello
{{#link-to 'sample'}}Back{{/link-to}}
</script>
App.SampleController=Ember.Controllers.Extend{(
show:false,
actions:{
changeShow:function(){
this.controllerFor('sample').set('show',true);
}
}
)};
you can use didTransition action which will trigger automatically once the transition happened. didTransition action
App.SampleController=Ember.Controllers.Extend{(
show:false,
actions:{
didTransition:function(){
this.controllerFor('sample').set('show',false);
},
changeShow:function(){
this.controllerFor('sample').set('show',true);
}
}
)};
You can use the renderTemplate hook for the route you're doing this in, and change the controller variable in there.
http://emberjs.com/api/classes/Ember.Route.html#method_renderTemplate
I'd do something like this:
App.PostsRoute = Ember.Route.extend({
renderTemplate: function(controller, model) {
var favController = this.controllerFor('favoritePost');
favController.set("toggle", false)
this._super()
}
});

toastr and ember.js

Is the popup library toastr not going to work with Ember because of direct dom manipulation that ember doesn't like?
Are there any other libraries like this one that work nicely with ember?
Edit
Even through the working example posted below I could not get this to work locally. I finally used Pine Notify which worked straight away.
This works fine in Ember, you just have to handle the event in the right place. The "right place" depends on your implementation. If you want this to be fired from a button within your view, you'll need to use the {{action}} helper passing the action name. Example:
<script type="text/x-handlebars" >
<button class="btn btn-info" {{action showInfo}}>Info</button>
</script>
In the template above, I'm saying that the button should fire the showInfo event, so the Controller responsible for this view should have a function with the same name:
App.ApplicationController = Em.ArrayController.extend({
showInfo: function() {
toastr.info('This is some sample information');
}
});
You can also have the view handle the event; the code below defines a click event, so if you click anywhere in the view, it would run your function:
App.OtherView = Em.View.extend({
click: function(e) {
toastr.error('This is some sample error');
}
});
and in your Handlebars template, you don't have do tell the action since you are already saying in the view class that you want to handle the click event for that view, so you can simple render the view and style it:
{{#view App.OtherView class="btn btn-danger"}}
Error
{{/view}}
Here's a sample in JSFiddle: http://jsfiddle.net/schawaska/YZwDh/
I recommend that you read the Ember Guide about the {{action}} helper

Selected item in a template, is there any solution for a context aware bindAttr?

The problem is as follows:
In our application we have several buttons, navigation icons etc., which we want to be 'selected' when they have been clicked. We can have multiple elements marked at the same time.
The secondary reason for me wanting to do this is that when I read the new Guides on emberjs.com I get the feeling that templates should be used more than stated before and that templates should have the responsibility of rendering the DOM, while the views should be used to handle sophisticated events (if any) or to create common/shared components to be reused in the application.
Currently the view is handling this:
app.NavView = Ember.CollectionView.extend({
...
itemViewClass: Ember.View.extend({
...
classNameBindings: ['isSelected:selected']
isSelected: function () {
return this.get('controller.selected') === this.get('content');
}.property('controller.selected')
})
});
But that is all the View basically is doing, I would like to drop the entire View and just use a template for this
I have tried with a template approach, and dropped the entire View concept.
<div id="main-menu">
{{#each content}}
<div {{bindAttr class="controller.isSelected:selected"}}>
{{{iconsvg}}}
{{name}}
</div>
{{/each}}
</div>
But my problem here of course is that bindAttr doesn't know about the context it’s in, and cannot 'send' this to the isSelected property on the controller to evaluate if it is this element that is selected or not.
Is there a good solution to do this without a view, or am I forced to use a view?
Or am I thinking the design part and responsibility of Templates/views/controllers wrong?
Any response is appreciated!
In the current documentation: http://emberjs.com/guides/templates/displaying-a-list-of-items/ there is a mention explaining how to use the {{each}} helper which doesn't override the current context.
In your case, this would be something like:
<div id="main-menu">
{{#each item in controller}}
<div {{bindAttr class="isSelected:selected"}}>
{{{item.iconsvg}}}
{{item.name}}
</div>
{{/each}}
</div>
Note I have remove the reference to 'controller' in the {{bindAttr}} since I assume it's an ember controller, then it's the current context, so basically isSelected is equivalent to controller.isSelected

Controller action to create child view?

Using latest ember.js build, I'm trying to use a controller action to create a childView and push it into the current view. But I can't figure out how to talk to the associated view.
In my search.handlebars:
<p>Results:</p>
{{#each animal in someResults}}
<li><a {{action showAnimal animal}}>{{animal.species.commonName}}</a></li>
{{/each}}
In App.SearchController, I have:
showAnimal: function(animal) {
// Now what?? The below is obviously wrong
this.animalView = App.AnimalView.create({controller: animal});
var childView = App.SearchView.createChildView(this.animalView);
App.SearchView.get('childViews').pushObject(childView);
}
The objects visible from within showAnimal are:
animal - ok fine
this.container - don't see anything helpful in here
this.target - seemingly the router?
Anyway, I'm baffled. Any help appreciated.
Is there a reason you're using the {{action}} instead of the {{linkTo}} helper? {{linkTo}} would generate a link to the animal passed with the helper. http://emberjs.com/guides/templates/links/.
You can then define a route as described in the Router API Guides.
Recognizing my question was a bit arcane / situation specific, here is what I ultimately did in case anyone cares. Note that some APIs have subsequently changed.
Recall that I'm trying to get a jQuery UI dialog to popup in my current view. Luke Melia's great article on this topic was the basis for my original approach.
In handlebars:
{{#each animal in someResults}}
<li><a {{action showAnimal animal target="view" href=true}}>{{animal.species.commonName}} </a></li>
{{/each}}
Then in App.SearchView:
App.SearchView = Ember.View.extend({
templateName: 'my-appname/~templates/search',
showAnimal: function(animal) {
animalDialog = App.AnimalView.create()
animalDialog.set('context', animal);
animalDialog.append();
}
});
And just for completeness, here's the AnimalView.
App.AnimalView = JQ.Dialog.extend({
templateName: 'my-appname/~templates/animal',
autoOpen: true,
modal: true,
closeText: "Ok"
});

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