Ember.Component's sendAction() to view target - ember.js

sendAction() in an Ember.Component bubbles to controller by default which is expected. But i have 2,3 actions which i rather need to send to corresponding view which is using the component. In templates we set action to view using target=view. Can we do that?.
Update: Currently as a work around I am sending my view object to component which from there calls view.send() to send the action. But i feel this is not correct.

Ok after some thinking I believe i know that you mean. If you have a component and you have a action it will be handled by the component itself. If you want to send a action outside the component you would use sendAction.
Now to target the view of which holds your component since your component is base on a view, you can probably do this.get('parentView') to get the parent view and then chain send('nameOfAction')
So it would be this.get('parentView').send('nameOfAction') from within the component action and it will then trigger the action on the parent view of which the component is embedded.
So in your component you could have:
App.DemoCompComponent = Ember.Component.extend({
actions: {
internalTrigger: function() {
//just an alert to make sure it fired
alert('Internal action was caught on component');
//this should target parent view component is in
this.get('parentView').send('viewTriggerTest');
}
}
});
Now lets say you have you component in the index template you could:
The template would be:
<script type="text/x-handlebars" data-template-name="index">
<h2>Inside Index Template</h2>
{{demo-comp}}
</script>
The Index View Code would be:
App.IndexView = Ember.View.extend({
actions: {
viewTriggerTest: function() {
alert('View Trigger Test Worked on Index!');
}
}
});
Here is a jsbin
http://emberjs.jsbin.com/reyexuko/1/edit

For latest Ember 2.9 the recommended approach is to pass a closure action to the child component. The property target and parentView are private ones.

Related

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

binding controller object to a component in ember

I am trying to build a modal box component in ember. The modal box has two standard buttons, "close" and "save". I wanted to pass controller action to this component so that when save button is clicked, it calls the controller action that was passed. I call my component as :
{{#affi-modal-box title="Test title" modalId="createNewAnalyticsRunModal" controllerBinding=controller}}some message{{/affi-modal-box}}
and my component :
AS.AffiModalBoxComponent = Ember.Component.extend({
attributeBindings: ['modelId','test'],
//this is the function that gets called when save button is clicked
onSaveButtonClick : function(){
console.log(this.controllerFor('analysisTemplates'));//fails
console.log(this.get('controller'));//returns modal box component which I don't need
}
});
Any ideas how I can pass the controller object to the component??
Thanks.
The way Ember.Component's work is to be agnostic to other parts of your application, therefore rather then passing in a controller on which you want an action to be called on when something happens in your component, you do it more like in this way:
{{#affi-modal-box
title="Test title"
modalId="createNewAnalyticsRunModal"
action="actionNameOnTheController"}}some message{{/affi-modal-box}}
As you can see you set the action attribute to the action name on your controller, and then inside your component you simply call this.sendAction('action'); which will trigger whatever action name you defined earlier:
AS.AffiModalBoxComponent = Ember.Component.extend({
attributeBindings: ['modelId','test'],
//this is the function that gets called when save button is clicked
onSaveButtonClick : function(){
this.sendAction('action');
}
});
So now, whenever onSaveButtonClick is invoked it will send the action actionNameOnTheController to whatever controller is listening to it. And best of all, without knowing nothing about the controller. This is the kind of functionality that makes Ember.Component's reusable in any way.
Please see here a simple demo of the concept explained.
Hope it helps.

ItemController does not work when we have a seperate controller for View

Part of learning Ember.js I am trying to create a Table View in Ember, based on example by Adam.
The issue I am facing is that if I create a seperate controller for the View and include an itemController for the Rows, ember gives following error: Uncaught TypeError: Cannot call method 'lookup' of null ember-1.0.0-rc.6.js:13933
When I debug this I find that in the following code :
controllerAt: function(idx, object, controllerClass) {
var container = get(this, 'container'),
subControllers = get(this, '_subControllers'),
subController = subControllers[idx];
if (!subController) {
subController = container.lookup("controller:" + controllerClass, { singleton: false });
The container is retrieved as null.
Whereas when it is run through ApplicationController, no such issue is there.
JS Fiddle Using ApplicationController for the View and another controller for itemController - Works Fine
JS Bin
in this the item Controller is specified as {{#each controller itemController="tableRow"}} and the controller is App.TableRowController
Here is a very similar JS Bin, JS Fiddle using a seperate TableViewController :
The item controller is similarly specified as {{#each controller itemController='tableRow'}.
A seperate Controller for Table View is binded using : {{view App.TableView controllerBinding="tableViewController"}} and this tableViewController is specified as a property in ApplicationController as :
App.ApplicationController = Ember.ArrayController.extend({
tableViewController: function() {
var tc = Ember.get('App.TableViewController').create();
tc.set('content',Ember.ArrayProxy.create({
content: Ember.A(tableData)})
);
return tc;
}.property()
});
But for some reason, the itemController does not work here.
Here is the JS Fiddle Using seperate Controller for View, but without any itemController - this works fine
Is there anything I am missing in the controller ?
Please help. Thanks.
With Ember most of the time you don't create objects directly, you declare the classes for things like controller, model, etc. And ember creates these objects using an IOC container. Avoid things like Controller.create. Similarly avoid directly controllerBinding instead use needs.
So, instead of providing a controllerBinding pass the content to be rendered by the App.TableView.
{{view App.TableView contentBinding=content}}
The setup of the tableData also belongs in a model() hook. It works in the sample because tableData variable is in scope.
Here's the updated jsfiddle.

binding context to action in ember textfield

I've got an ember application that needs to manage multiple chat windows. A window for each active chat is created within an {{#each}} loop. This is straightforward enough. The place that I'm having trouble is sending the chat message when the user presses enter.
The window looks like this
{{#each chats}}
... stuff to display already existing chats...
{{view Ember.TextField valueBinding="text" action="sendChat"}}
<button {{action sendChat this}}> Send </button>
{{/each}}
This works fine for the button, since I can pass this to it. By default the function defined in the textfield view action just gets the text within that textfield, which is not enough in this case. Since there can be multiple chat windows open, I need to know which window the message was typed into. Is it possible to pass this to the textfield action function? (or can you suggest a different way to solve this problem?)
Add contentBinding="this" to the definition of the view, like:
{{view Ember.TextField valueBinding="text" action=sendChat contentBinding="this"}}
EDIT
Ember master already has this change, but the official downloadable verstion still don't.. so you will need to subclass the Ember.TextField and change its insertNewline to achieve required functionality:
App.ActionTextField = Em.TextField.extend({
insertNewline: function(event) {
var controller = this.get('controller'),
action = this.get('action');
if (action) {
controller.send(action, this.get('value'), this);
if (!this.get('bubbles')) {
event.stopPropagation();
}
}
}
});
After that, the action handler will receive additional argument, the view:
{{view App.ActionTextField valueBinding="text" action=sendChat myfieldBinding="this"}}
and in controller:
sendChat: function (text, view) {
var myField = view.get('myfield');
//do stuff with my field
}
You may use ember master instead of subclassing Ember.TextField..
I hope the ember guys will release the next version soon..
I know this question has been answered but I said let me add some information that may help out someone in the situation of actions and TextField. One word "Component". TextField in Ember is a Component so if you think of TextField from that perspective it may help when it comes to sending actions and using TextField in an application.
So when you say App.SomeTextField = Ember.TexField.extend({...});App.SomeTextField is subclassing Ember.TextField (remember which is a component). You could add your logic inside and that works and you could access it from your template such as {{view App.SomeTextField}}
You may be thinking I see the word 'view' this guy sucks, TextField is a View. Well, it is sort of a View because Ember Components are a subclass of Ember.View so they have all that Views have. But there are some important things to keep in mind Components un-like Views do not absorb their surrounding context(information/data), they lock out everything and if you want to send something from the outside surrounding context you must explicitly do so.
So to pass things into App.SomeTextField in your template where you have it you would do something like {{view App.SomeTextField value=foo action="sendChat"}} where you are passing in two things value, and action in this case. You may be able to ride the fine line between View/Component for a bit but things come crashing why is your action not sending?
Now this is where things get a little trippy. Remember TextField is a Component which is subclassed from View but a View is not a Component. Since Components are their own encapsulated element when you are trying to do this.get('controller').send('someAction', someParam), "this" is referring to the Component its self, and the controller is once again the component its self in regards to this code. The action that you are hoping will go to the outside surrounding context and your application will not.
In order to fix this you have to follow the protocol for sending actions from a Component. It would be something like
App.SomeTextField = Ember.TextField.extend({
//this will fire when enter is pressed
insertNewline: function() {
//this is how you send actions from components
//we passed sendChat action in
//Your logic......then send...
this.sendAction('sendChat');
}
});
Now in the controller that is associated with where your SomeTextField component/view element is you would do
App.SomeController = Ember.Controller.extend({
//In actions hash capture action sent from SomeTextField component/view element
actions: {
sendChat: function() {
//Your logic well go here...
}
}
});
Now I said to think of TextField as a Component but I have been riding the tail of the view and declaring {{view AppSomeTextField...}}. Lets do it like a component.
So you would have in your template where you want to use it
//inside some template
`{{some-text-field}}`
Then you get a specfic template for the component with the name:
//template associated with component
<script type="text/x-handlebars" data-template-name="components/some-text-field">
Add what you want
</script>
In your JS declare your component:
//important word 'Component' must be at end
App.SomeTextFieldComponent = Ember.TextField.extend({
//same stuff as above example
});
Since we on a role you could probably get the same functionality using Ember input helpers. They are pretty powerful.
{{input action="sendChat" onEvent="enter"}}
Welp hopefully this information will help someone if they get stuck wondering why is my action not sending from this textField.
This jsBin is a sandBox for Components/Views sending actions etc....Nothing too fancy but it may help someone..
http://emberjs.jsbin.com/suwaqobo/3/
Peace, Im off this...

How can I bind the element ID for an Ember View?

My model "content.id" contains a string, e,g "123":
{{view Em.TextArea idBinding="content.id"}}
Instead of just setting the id of this view to "123", I'd like it to be "message-123", basically customizing the string being used. Sadly, Ember does not allow bindings to be functions, which would solve my problem (I could define such a function on the controller).
What's the best way to achieve this?
You could define a computed property in the controller (or elsewhere):
The controller
MyApp.ApplicationController = Ember.Controller.extend({
content: "a-content",
editedContent: function() {
return "message-" + this.get('content');
}.property('content')
});
The view
MyApp.FooView = Ember.View.extend({
    tagName: 'p'
});
The template (where content is a String, here)
{{#view MyApp.FooView elementIdBinding="editedContent"}}
{{content}}
{{/view}}
And the JSFiddle is here.
EDIT
How can the view see the property editedContent since it belongs on the ApplicationController controller?
The router, after started, automatically render the ApplicationView, or its template when there is no ApplicationView defined. If you want more detail, I suggest you to read the Ember guide: Understanding the Ember.js Router: A Primer.
And {{editedContent}} directly get the controller editedContent property, because the default view context is its controller, as you can read in Ember Blog - 1.0 Prerelease:
The {{#view}} helper no longer changes the context, instead maintaining the parent context by default. Alternatively, we will use the controller property if provided. You may also choose to directly override the context property. The order is as follows:
Specified controller
Supplied context (usually by Handlebars)
parentView's context (for a child of a ContainerView)