How to get reference of View in Controller - ember.js

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

Related

Ember component call an action in a route or controller

I have a component the main purpose of which is to display a row of items.
Every row has a delete button to make it possible to delete a row. How is possible to pass an action from a template to the component which will trigger an action in a router ?
Here is the template using the component:
#templates/holiday-hours.hbs
{{#each model as |holidayHour|}}
{{holiday-hour holiday=holidayHour shouldDisplayDeleteIcon=true}}
{{/each}}
Here is the component template:
# templates/components/holiday-hour.hbs
...
div class="col-sm-1">
{{#if shouldDisplayDeleteIcon}}
<button type="button" class="btn btn-danger btn-sm mt-1" {{action 'deleteHoliday' holiday}}>
<span class="oi oi-trash"></span>
</button>
{{/if}}
</div>
I'm using the same component to display a row and to create a new item (holiday-hour).
I'm using ember 3.1.2
Thank you
You have to send the actions up from the component to the route. The main way to do this is by adding actions to your component that "send" the action to the parent. Once the action is sent you have to tell the component what action on the route to trigger by passing in the action as a parameter. Below is an example of how to do this.
Component js
# components/holiday-hour.js
...
actions: {
deleteHoliday(){
this.sendAction('deleteHoliday');
}
}
Template for route
#templates/holiday-hours.hbs
...
{{#each model as |holidayHour|}}
{{holiday-hour holiday=holidayHour shouldDisplayDeleteIcon=true deleteHoliday='deleteHoliday'}}
{{/each}}
Route js
#routes/holiday-hours.js
...
actions: {
deleteHoliday(){
//code to delete holiday
}
}
I will try to give a general answer because your question is not giving enough/all info regarding the route actions etc. Long answer short, using closure functions. Assuming this is your route js file routes/holiday-hours.js
import Route from '#ember/routing/route';
export default Route.extend({
model(){ /*... some code */ },
setupController(controller){
this._super(controller);
controller.set('actions', {
passToComponent: function(param) { //.... function logic }
})
}
});
Note: in the above snippet, I'm using setupController to create actions. Alternatively, you can put the actions inside a controller file otherwise actions directly inside the route will throw an error.
So I want the action passToComponent to be called from the component. This is what you do to make it accessible inside the component.
{{#each model as |holidayHour|}} {{holiday-hour holiday=holidayHour shouldDisplayDeleteIcon=true callAction=(action 'passToComponent')} {{/each}}
Now we have passed the action to the component and here's how to call it from the component. Note: I have added a param just to show that it can take a param when called within the component.
import Component from '#ember/component';
export default Component.extend({
actions: {
deleteHoliday: ()=> {
this.get('callAction')() /*Pass in any params in the brackets*/
}
}
});
You will also see demonstrations using sendAction which is rather old and acts more of an event bus that is not very efficient. Read more from this article

Dynamic add/remove a component to the page via action

I am creating a FlashCard app and I would like to dynamically insert a component with property into the view via the action inside the route. See screenshot below,
Click "Add Card" button
Dynamically create a card-editor component in the view
I think one possible way to achieve this is to add a conditional handlebar block inside the view and render the component based on the property state; however, I wish to keep my view as clean as possible and think it could be better if I can dynamically render a component to the view only when the action is triggered.
My solution
<div style="margin-left: 200px;">
{{#if cardEditor}}
{{app/card-editor}}
{{/if}}
</div>
In view's controller
export default Ember.Controller.extend({
cardEditor: false,
actions: {
addNewCardEditor() {
this.set('cardEditor', true));
}
}
});
What I have tried
Based on the answer How to programatically add component via controller action in ember 2.x, but it does not work for me. I get an error,
ember.debug.js:41417 Uncaught Error: Cannot instantiate a component without a renderer. Please ensure that you are creating <(subclass of Ember.Component):ember604> with a proper container/registry.
Inside the view HTML,
{{app/side-bar
addNewCardPressed='addNewCardEditor'
}}
Inside the view route,
import Ember from 'ember';
import CardEditorComponent from '../../components/app/card-editor';
export default Ember.Route.extend({
actions: {
addNewCardEditor() {
CardEditorComponent.create().appendTo($('body'));
}
}
});
Inside the component JS,
actions: {
addNewCardPressed() {
this.sendAction('addNewCardPressed');
}
}
Question
So my question is how can I use the action inside the routes/home/index.js to render the component to the view.
The View HTML,
{{side-bar
addNewCardPressed='addNewCardEditor'
}}
The Index Page route,
import Ember from 'ember';
export default Ember.Route.extend({
actions: {
addNewCardEditor(newCard){}
}
});
What should I put inside the addNewCardEditor function to generate a component in the view on the fly?
Thanks for your time.
in the global.js of your EmberCLI application:
export function initialize(application) {
window.EmberApp = application; // or window.Whatever
}
Where you want to create dynamically your component, even though it might look like a hack, there might be cleaner way to do it without relying on EmberCLI variables.
"App" below is the namespace of your global EmberCLI application that you define in application.js.
var component = App.CardEditorComponent.extend({
renderer: window.EmberApp.__container__.lookup('renderer:-dom'),
}).create();
Ember.setOwner(component , window.EmberApp);
component.append();

Calling an action within controller in Ember

I am getting some data from server and in my controller I am trying to display that in list format. What I want to do is to allow the user to click on any item of that list and call an action behind it. Here is my js code.
for(var index in posts) {
if (posts.hasOwnProperty(index)) {
console.log(1);
var attr = posts[index];
//console.log(attr);
/*$("ul#previous_recipients").append('<li class="list-group-item"><label>'+attr.name+'' +
'</label><a href="javascript:void(0)" class="close g-green" {{action "aa"}}>Add</a></li>');*/
$("ul#previous_recipients").append(Ember.Handlebars.compile('<li class="list-group-item"><label>{{attr.name}} </label>Add</li>'));
}
}
How to call action on it? {{action "test"}} is not being called.
You have to compile the handlebars template and define the test action inside actions hash in the same controller.
$("ul#previous_recipients").append(Ember.Handlebars.compile('
<li class="list-group-item"><label>{{attr.name}} </label>
Add</li>
'));
You have to move this html code from controller to a handlebars file,that will be good.
What you need to do is create an ember view and give it a
templateName
property which will contain your html and handlebars expression. Alternately, you could, inside the view say
defaultTemplate: Em.Handlebars.compile(/*your handlebars and html markup here*/)
using Jquery to do things like this is not the ember way of doing things.

Ember View not finding the controller/action

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

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);
}
});
​