Ember View not finding the controller/action - ember.js

I'm trying to call my controller's action from my view with Ember, but it says:
Uncaught TypeError: Cannot call method 'send' of null
I just can't find the right way to work with views in ember.
My view layout has a call like:
<button type="button" {{action modalConfirmation target="view"}} class="btn btn-primary">Save changes</button>
And my View class tries to call the controller in this fashion:
this.get('controller').modalConfirmation();
My Controller has something like this:
ProjEmber.BananasIndexController = Ember.ArrayController.extend({
actions: {
showModal: function() {
modalinaView.title = "My Title";
modalinaView.templateName = "any_template_you_wish";
modalinaView.append();
},
modalConfirmation: function() {
console.debug('Action modalConfirmation');
}
}
});
OBS: it works if I append my view using the helper like this:
{{#view ProjEmber.ModalinaView title='A title'}}
A not so good application of a modal view. Just for the sake of illustration.
{{/view}}
You can see the full source on Github, especifically this part of the commit:
https://github.com/lucaspottersky/ember-lab/commit/4862426b39adc0bbcce0b4cc3fd0099439f8dd55#commitcomment-4421854

There is a good likelihood it's failing to be appended within the body, or the scope of your ember app which would be why the events aren't propagating to your actions hash.
You might try appendTo('body')

You shouldn't access view like this
var modalinaView = this.container.lookup('view:modalina');
This PR can give you more insights.
You are doing the same as Stefanpenner has done in this commit.
And this is Wycats reply.
Alternatively, This answer may help you in instantiating modals

Related

Access Controller in a View in a Render

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.

How to trigger an action on a controller from a template

I have this jsbin. My problem is that I am trying to trigger an action:
<a {{action controllers.nodesIndex.destroyAllRecords this}}><i class="icon-remove-circle"></i><a/>
But I get an:
Uncaught Error: Nothing handled the event 'controllers.nodesIndex.destroyAllRecords'
(You can trigger that by pressing the little icon icon-remove-circle on the top-right, and checking the error on the js console)
But my controller is properly set-up:
App.NodesIndexController = Ember.ArrayController.extend({
destroyAllRecords: function () {
console.log('destroyAllRecords called');
},
});
What am I missing here?
Since the controller for the nodes/index template is the App.NodesIndexController, You need to mention it as controllers.nodesIndex.destroyAllRecords, the default target will be App.NodesIndexController, and so you can just say <a {{action destroyAllRecords}}> as #Thomas told.
Also for getting the length of records, just say {{this.length}} instead of {{controllers.nodesIndex.length}}.
I've updated your jsbin,
You'll need to say as 'controllers.controllername.methodname' only if you are referring to some other controller than the controller for the template, and you've to give the controller's name in the needs list,
say, if you want to call a method of your 'profile' route from your 'nodes/index' template,
then
App.NodesIndexController = Ember.ArrayController.extend({
needs: ['profile'],
});
and in your template,
<a {{action controllers.profile.methodname}}>
Hope it helps.
UPDATE: Refer the solution and the bin in the comment

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

How to get reference of View in Controller

I am using Ember 1.0pre and following Ember suggested application structure (Using Router).
For form validation, I want to call $('form').valid() method on Button click.
So I have following method in my view
validate: function(){
return this.$('form').valid()
}
Action in template file:
<button type="submit" class="btn" {{action doSaveSettings this}}>Save Changes</button>
and doSaveSettings method is in Controller.
How can I get instance of view in controller, for calling validate method?
EDIT:
In controller, this.view is null. I have put {{debugger}} in template and this refers to
<App.XyzController:ember1062> and this.view is null.
The default target of actions have been changed from the view to the router in ember 0.9.8.1 (I believe). To set the target to the view you need to override it like this
<button type="submit" class="btn" {{action doSaveSettings target="view"}}>Save Changes</button>
edit: Your controllers should not know about the view.
In ember the purpose of a view is only i repeat is only to handle events or to create reusable components
i would not suggest this since there is always a reason why views are not meant to be accessed from controller and its good to follow it, however if you really do want to use it you could do the below two ways:
I dont know if u r using ember-cli or ember but the logic is same. The answer however is for ember-cli
//Inside appname/controller/your-conroller.js
import reqdView from 'appname/views/your-view';
//Lets assume u want to call a function called validate inside view
//Add this statement inside the controller to run the validate function
reqdView.prototype.validate();
OR
var reqdViewInst = new reqdView();
reqdViewInst.validate();
If you want to validate the view then do the validation inside didInsertElement
export default Ember.View.extend({
didInsertElement:function()
{
this.validate();
},
validate:function()
{
//do your validation
}
});
OR
export default Ember.View.extend({
eventManager: Ember.Object.create({
didInsertElement:function(event, view)
{
view.validate();
}
}),
validate:function()
{
//do your validation
}
});

Controller Strategy / Garbage Collection (destroy)

Trying to figure out the "ember best practices" for my app, regarding MVC. also for reference, I'm using ember-data, ember-layout, and ember-route-manager.
I'll use User as an example:
what I feel like I want to do is to get a User model from the database... then wrap it in a UserController, and set the model on a "content" property... then in a View, I want to bind to the controller for some functionality, and to the controller.content for model-level data. so a controller might look something like:
App.UserViewController = Em.Object.create({
content: userRecord,
isFollowingBinding : 'content.you_follow',
toggleFollow: function() {
make server call to change following flag
}
});
then the view could bind to the {{controller.content.name}}, or {{#if controller.isFollowing}}, or {{action "toggleFollowing" target="controller"}}
but say I get a list of User models back from the database... I feel like what should happen is that each of those models should be wrapped with a controller, and that should be returned as a list... so the view would have a list of UserControllers
Incidentally, I've done this... and it is working nicely.... except that everytime I reload the list, I wrap all of the new model objects with new controllers... and over time, the # of controllers in memory get larger and larger. on my base Controller class, I'm logging calls to "destroy", and I dont see it ever happening
when it comes to Em.View... I know that everytime it is removed from the screen, .destroy() gets calls (I am logging those as well). so if I were to move my code into a view, i know it will get destroyed and recreated everytime... but I dont feel like the functionality like toggleFollow() is supposed to be in view...
SO QUESTIONS:
is this how MVC is supposed to work? every instance of a model wrapped in a controller for that model? where there could be lots of controller instances created for one screen?
if I go down this approach, then I'm responsible for destroy()ing all of the controllers I create?
or is the functionality I've described above really meant for a View, and them Ember would create/destroy them as they are added/removed from the screen? also allowing template designers to decide what functionality they need (if they just need the {{user.name}}, theres no need to instantiate other controller/view classes... but if they need a "toggle" button, then they could wrap that part of the template in {{#view App.UserViewController contentBinding="this"}} )
I re-wrote this a few times... hopefully it makes sense....
I wouldn't wrap every user into an own controller.
Instead I would bind the user to a view, say App.UserView and handle the action toggleFollow on that view. This action will then delegate it's action to a controller which will handle the server call, see http://jsfiddle.net/pangratz666/hSwEZ/
Handlebars:
<script type="text/x-handlebars" >
{{#each App.usersController}}
{{#view App.UserView userBinding="this" controllerBinding="App.usersController"}}
{{user.name}}
{{#if isFollowing}}
<a {{action "toggleFollowing"}} class="clickable" >stop following</a>
{{else}}
<a {{action "toggleFollowing"}} class="clickable" >start following</a>
{{/if}}
{{#if user.isSaving}}saving ...{{/if}}
{{/view}}
{{/each}}
</script>​
JavaScript:
App.usersController = Ember.ArrayProxy.create({
content: [],
toggleFollowing: function(user) {
user.set('isSaving', true);
Ember.run.later(function() {
user.toggleProperty('you_follow');
user.set('isSaving', false);
}, 1000);
}
});
App.UserView = Ember.View.extend({
isFollowingBinding: 'user.you_follow',
toggleFollowing: function() {
var user = this.get('user');
var controller = this.get('controller');
controller.toggleFollowing(user);
}
});
​