Ember JS: How can I practice 'Data Down, Actions Up' with composable helpers? - ember.js

New to ember and practicing 'Data Down, Actions Up' with composable helpers. Is this possible? This is what I'm attempting to do:
//parent template
{{pizza-toppings toggleToppings=(action (toggle 'toppings' this 'mushrooms' 'anchovies'))}}
//child component template
<div {{action "toggleToppings"}}>
But I get a 'no action handler for: toggleToppings' error.
So then I tried making an action on the child, like so:
//child component template
<div {{action "togglePizza"}}>
//child component JS
actions: {
togglePizza() {
this.get('toggleToppings')();
}
}
But when I click on that, nothing happens at all. :( How can I call my parent action from within my component template?

Change the child component template to the following:
<div {{action toggleToppings}}>
When you use quotes, you are telling handlebars to lookup an action by that name on the actions hash of the current context (and bubble that action up if it is not found). However, when you pass an action (an action is really just a bound function) into this component from the parent you don't add it to the actions hash, you've just added it as a property on the component's context.
As for why the latter second attempt did not work for you, I suspect it actually does work but that the action handler has some other non-related issue. Adding a debugger to the "toggle" helper will let you know whether and when it is being called.

Related

Ember, No action handler when passing action from template to component

I'm trying to pass an action from a route to a template and then a component.
app/routes/application.js
actions: {
showModal(context) {
console.log("This needs to be triggered" + context)
},
}
app/templates/application.hbs
{{some-component
showModal=showModal
}}
app/components/some-component/template.hbs
<button {{action "showModal" "The context for the action"}}>Press Me</button>
When running this. I get an error saying
"had no action handler for: showModal"
Although, when I include the action inside templates/application.hbs without passing it to a component everything works fine. It's just when passing the action to a component.
app/templates/application.hbs
<button {{action "showModal" "The context for the action"}}>Press Me</button>
This works. I want to call this action in a component though. How can I pass this action to the component?
Firing route actions is a little bit different than firing actions coming from a controller in this context. When you pass in an action to a component from a controller or another component you wrap it in the action helper like so:
{{some-component showModal=(action "showModal")}}
Since the action you're attempting to pass in lives in a route, you need to have to utilize the send method from the controller to call the action in the route. You pass it into the component like so:
{{some-component showModal=(action send "showModal")}}
Here's a twiddle that helps piece it all together.

How would I write a sendAction helper?

In ember, we have the action helper;
{{action "actionName" arg1 arg2}}
But, when I want to send actions to a parent component, I still have to explicitly write an action on the child that calls sendAction. Even if I'm ONLY bubbling an action. I'd like the avoid this with a send-action helper;
{{send-action "actionKey" arg1 arg2}}
Does anyone know how I'd implement this helper?
You should use closure actions instead, as shown in Triggering Changes with Actions.
So if you have an action in a parent component named actionKey, you do the following.
Pass it to the child component as a closure action:
{{my-component myName=(action 'actionKey')}}
And then in the template of your child component:
<button {{action myName arg1 arg2}}>click me</button>
I used myName for illustrative purposes, it can be whatever you want as long as it matches the name you use inside the component.
I have reproduced the above in a Twiddle: https://ember-twiddle.com/fd56b0b9d017968fc334b3f109760806.

How to pass event objects as parameters (from handlebars template to an action in a controller)

I am new to ember, thus I would appreciate your assistance. I want to pass an focus-out event (see bold marked text below) from my handlebars template:
{{input type="text" class="form-control" **focus-out= (action "ccFocusLost" event**) }}
To my action in my controller:
ccFocusLost : function(**event**) {
alert(event.relatedTarget.tagName);
},
However, i get an undefined when I do as above. I need a way to obtain the focus-out event in order to find out which element will receive the focus after my main element loses it.
Thanks in advance!
It was tricky, but here is the solution. I have the following code:
template (no need to have an event argument):
{{input type="text" class="form-control" **focus-out= (action "ccFocusLost") }}
Controller:
ccFocusLost : function() {
var targetId= event.relatedTarget.id;
alert(targetId);
},
So it seems that handlebars can access the event, without the need of sending it as an argument. In this case if I press a button with id = button1, the alert will display button1.
You can define the focusOut action handler in your controller and check if the event came from your input field with the class "form-control". E.g.
focusOut(event) {
/* if event did not come from desired input field, return here */
/* else call the action as desired to process the focusOut event */
}
Alternatively, you could create a component that wraps your input field so you could define the focusOut event at the component level instead of the controller. This would remove the need to check if the event came from the input field.
For more information on handling events in Ember, here is the section of the Guides that provides more detail: Handling Events
Two things
If you use focusOut instead of focus-out, the action will automatically include the jQuery event as the argument, no need to specify it in the template.
{{input focusOut=(action "ccFocusLost") }}
In your code, the event is already being passed to your action, it's just that the jQuery event's relatedTarget property is null. This is a jQuery/Javascript event thing, unrelated to Ember. See also here.
There's a lot more information out there on relatedTargets, but it seems it would be better to just use document.activeElement

ember.js | How to bind an event of a sub-component to an action of an outer component

unfortunately i am not able to figure out, how to receive an event of a component i use from within a component.
What i mean actually sounds harder than it is, consider the following toy example, with a component my-outer and another component my-inner (a short explanation follows the code, at the end i link to jsbin).
The templates:
<script type='text/x-handlebars' id='components/my-outer'>
<div {{bind-attr class="isRed:red"}}>Buttons should toggle my background color</div>
<button {{action "toggleRed"}}>It works from my-outer</button>
{{my-inner action="toggleRed"}}
</script>
<script type='text/x-handlebars' id='components/my-inner'>
<button {{action "action"}}>It doesn't work from my-inner</button>
</script>
The javascript:
App.MyOuterComponent = Ember.Component.extend({
isRed: false,
actions: {
toggleRed: function() {
this.toggleProperty("isRed");
}
}
});
my-outer contains a short text, with a background-color, which can be toggled from and to red by invoking the toggleRed action. the first button demonstrates that this works in principle.
now i would like to bind the default action of the second component to this same toggleRed action, that's the point of the following line.
{{my-inner action="toggleRed"}}
But on clicking the second button (which is part of my-inner) an error is thrown and the action is not fired.
How do I fix this example?
http://emberjs.jsbin.com/cabasuru/2/edit?html,js,console,output
Thanks so much in advance
(and this is my first question on so, i am happy about any meta-critics)
Since Components work just like views, easiest way is to get the parentView and forward the action. You may have to handle the action in my-inner like following.
App.MyInnerComponent = Ember.Component.extend({
isRed: false,
actions: {
toggleRed: function() {
this.get('parentView').send('toggleRed');
}
}
});
You can see outer component can be accessed as parentView in inner component. Here is the working jsbin link
http://emberjs.jsbin.com/cabasuru/5/edit
My question actually missed the main point. What goes wrong in the example above, is that the action helper in the inner component
<button {{action "action"}}>It doesn't work from my-inner</button>
does not trigger the default action associated with the component. Instead it invokes a new event named action, which is not allowed to bubble (due to the component confinement).
It turns out, there are two ways to solve that:
Properly reroute the event in an actions block on the my-inner component
<button {{action "my-action"}}>...</button>
together with a definition of the my-action action for my-inner:
App.MyInnerComponent = Ember.Component.extend({
actions: {
myaction: function(){
this.sendAction();
}
}
});
This is basically, the idea #CodeJack proposes, with the difference,
that here we rely on the wiring, which is set-up in the template of my-outer.
http://emberjs.jsbin.com/cabasuru/3/edit
As #torazaburo hinted at, setting the target property on the my-inner component to the my-outer component allows the event triggered from the action helper to bypass the component isolation.
{{my-inner target=controller}} in the my-outer template and a <button {{action "toggleRed"}}>...</button> in the my-inner template.

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