Ember component action not bubbling up to template router - ember.js

I have a component that should bubble an action up to its template router.
I pass the name of the action to the component:
{{project-table projects=model viewProject="viewProject"}}
Inside my component (project-table), I have:
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
viewProject: function (project) {
this.sendAction('viewProject', project);
}
}
});
Inside the component template, I have:
<button type="button" {{action "viewProject" project}}>
My Button
</button>
Last but not least, I have my router:
actions: {
viewProject: function (project) {
this.transitionToRoute('project', project);
}
}
The component's action gets invoked correctly. However from there on, the action does not bubble up. Any ideas as to what I might be going wrong?

#JB2, your code is almost perfect, and I'm sure that the action is bubble up to the Route level.
However, please note, that Route has transitionTo method only. http://emberjs.com/api/classes/Ember.Route.html#method_transitionTo
In a Controller, you can use transitionToRoute method.
http://emberjs.com/api/classes/Ember.Controller.html#method_transitionToRoute
It is easy to mix. :) I check the API doc also quite often, which one could I use and where. :)

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

Ember integration test fails after click event

I have this integration test:
test('can change chord text', function(assert) {
this.render(hbs`{{chart-editor-chord chord=chord}}`);
this.$().click();
assert.ok(!!this.$('.chord-input').length);
});
but the assertion fails, the component template looks like this:
<div {{action 'changeChord'}} class="measure-chord chord-big">
{{#if chord.editing}}
<input type="text" value="{{chord.name}}" class="chord-input">
{{else}}
{{chord.name}}
{{/if}}
</div>
and the component code:
import Ember from 'ember';
export default Ember.Component.extend({
store: Ember.inject.service(),
actions: {
changeChord() {
this.chord.set('editing', true);
}
}
});
I'm updating the chord model in the changeChord() action and it does work if I test in the browser, but the integration test fails. So, does this change in the model have to be rendered synchronously to the template? I tried using wait() in the test but that doesn't make a difference. So how should I test this?
While I'm trying to create a twiddle for you, I found three things:
Where do you create chord mock in your test?
You are not sending event to the correct html component. Use this.$('.measure-chord') or this.$('.chord-big').
Instead of this.chord.set you should use this.get('chord').set. Actually Ember.set(this, 'chord.isEditing', ...) is even better.
And bonus: You don't need a div wrapper, component does this for you.
twiddles:
working copy
without div
It looks like your click helper is clicking the div that your component.js controls instead of the initial div in your template. If you specify the div in your click helper it should work:
this.$('.measure-chord').click();

Ember2.8: Sending an action from a component to the controller

Reading up on the documentation for Ember, I was under the impression that when an action is triggered by a component, it will go up the hierarchy until it hits an action with that name. But here's what's happening right now. I have a game-card component written like so:
game-card.hbs
<div class="flipper">
<div class="front"></div>
<div class="back">
<img {{action "proveImAlive"}} src={{symbol}} />
</div>
</div>
game-card.js
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['flip-container'],
actions: {
//blank for now because testing for bubbling up
}
});
Now according to what I've read, since game-card.js does not have a 'proveImAlive' action, it will try to bubble up the hierarchy i.e. the controller for the particular route.
play.js (the route /play)
import Ember from 'ember';
export default Ember.Controller.extend({
actions: {
proveImAlive() {
console.log('Im aliiiiveeee');
}
}
});
But when I finally run my application, I get this error:
Uncaught Error: Assertion Failed: <testground#component:game-card::ember483> had no action handler for: proveImAlive
Now my question is twofold:
Why is this error happening?
I want some of my component's actions to bubble up to the route's controller. For example, when a game-card is clicked, i'd like to send the id value (to be implemented) of that card up to the controller so it can store it on an array.
game-card is clicked --> sends value of 1 --> arrayinController.push(1)
How can I achieve this?
First, I'd like to point out that you linked to the documentation of Ember v1.10.0. You should consult the documentation for the version of Ember you are utilizing, which you mention is v2.8.0.
Now according to what I've read, since game-card.js does not have a 'proveImAlive' action, it will try to bubble up the hierarchy i.e. the controller for the particular route.
This isn't quite what happens because components are isolated, so there is no implicit bubbling. When the Guides say "actions sent from components first go to the template's controller" and "it will bubble to the template's route, and then up the route hierarchy" they mean that you have to explicitly send an action up from the Component. If the component is nested inside another component, you have to do this for each layer, until you reach the Controller.
Why is this error happening?
You need to bind the action in the template: {{game-card proveImAlive="proveImAlive"}}
i'd like to send the id value (to be implemented) of that card up to the controller so it can store it on an array.
I am going to be using closure actions for this part of the answer. As mentioned by #kumkanillam, they have better ergonomics, and they are the current proposed way to use actions if you consult the Guides.
I have prepared a Twiddle for you.
a) Initialize array in the controller
export default Ember.Controller.extend({
appName: 'Ember Twiddle',
gameCards: null,
init() {
this.set('gameCards', []);
}
}
b) Implement the action that pushed to the array
export default Ember.Controller.extend({
appName: 'Ember Twiddle',
gameCards: null,
init() {
this.set('gameCards', []);
},
actions: {
proveImAlive(cardNo) {
this.get('gameCards').pushObject(cardNo);
console.log('Im aliiiiveeee - cardNo', cardNo);
}
}
});
c) Bind the closure action down
{{game-card proveImAlive=(action 'proveImAlive')}}
d) Trigger the action passing the arguments
<div class="flipper">
<div class="front"></div>
<div class="back">
<button {{action proveImAlive 1}}> ProveIamAlive</button>
</div>
</div>
You need to explicitly set the action handler:
{{component-name fooAction=fooHandler}}
This is required because it helps keep components modular and reusable. Implicit links could result in a component triggering unintended behavior.
Your code should work, only if you have included game-card component into play.hbs. I doubt the controller for the particular route is not play in your case.
Here is the working-twiddle
Instead of bubbling actions, use closure actions. For better understanding you can go through the below links,
https://dockyard.com/blog/2015/10/29/ember-best-practice-stop-bubbling-and-use-closure-actions
http://miguelcamba.com/blog/2016/01/24/ember-closure-actions-in-depth/
https://emberigniter.com/send-action-does-not-fire/

Can we route from a component in ember js?

I have a component which has a button with an action like
{{action 'create'}}
and inside the action create i wrote like this.transitionTo('page.new');
But i am getting an exception like Cannot read property 'enter' of undefined can anyone answer please?Just want to know is that possible to route from a component?
The way to do that is to use this.sendAction() from your component and bubble it up to the router. The router can then call this.transitionTo().
The way link-to does it is by injecting routing _routing: inject.service('-routing'),
https://github.com/emberjs/ember.js/blob/v2.1.0/packages/ember-routing-views/lib/components/link-to.js#L530
Ember.Component is extended from Ember.View and you cant use this.transitionTo in a view. It can be done only through a controller/router.
If you want a transition inside the component on clicking, you could use the link-to helper, but if you still want to be able to handle that action, read: http://emberjs.com/guides/components/handling-user-interaction-with-actions/ and the guide after it.
I found out the answer it is possible.we can use simply use the following code from our components action
App.Router.router.transitionTo('new route');
and we will get a call back for this,in which we can set the new route's model.Use the following code for that.
App.Router.router.transitionTo('your route here').then(function(newRoute){
newRoute.currentModel.set('property','value');
});
Injection is the last thing you wanna do. The way you communicate actions between routes and components is to use the sendAction Method Send Action
template.hbs
{{your-component action="nameOfYourRouteAction" }}
route.js
export default Ember.Route.extend({
ratesService: Ember.inject.service(),
model() {
//return yourdata
},
actions: {
nameOfYourRouteAction(...args){
this.transitionTo(...args);
}
}
});
in your component.js
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
toggleTransition: function(...args) {
this.sendAction('action', ...args);
}
}
});
component.hbs
<button {{action "toggleTransition" 'your route'}}>Change Route</button>

transitionToRoute('route') From Inside Component

How do I transition to a route pragmatically from inside a component action?
I tried to use #get('controller').transitionToRoute('images'), but the controller refers to the component itself. I understand that components should be self contained, so should I be using a view instead to interact with controllers/routes better?
Example
App.ImageEditorComponent = Ember.Component.extend
...
actions:
delete: ->
App.Files.removeObject(object)
#transitionToRoute('images') # This throws an exception
...
You could pass the controller in via a binding and then access it inside your component like so:
{{image-editor currentControllerBinding="controller"}}
App.ImageEditorComponent = Ember.Component.extend
...
actions:
delete: ->
App.Files.removeObject(object)
#get('currentController').transitionToRoute('images')
...
Create action on parent controller.
export default Ember.Controller.extend({
actions: {
transInController() {
this.transitionToRoute('home')
}
}
});
Specify this action on component call.
{{some-component transInComponent=(action "transInController")}}
AFTER v3.4.0 (August 27, 2018)
some-component.js
export default Component.extend({
actions: {
componentAction1() {
this.transInComponent();
}
}
});
OR simpler in some-component.hbs
<button onclick={{#transInComponent}}>GO HOME</button>
BEFORE v3.4.0
Ember.component.sendAction
"Send Action" from component up to controller
export default Ember.Component.extend({
actions: {
componentAction1() {
this.sendAction('transInComponent');
}
}
});
A component is supposed to be isolated from its context, so while you could pass in a reference to the controller, that's probably outside the scope of what a component is for. You might want to just stick with using a view with its own controller instead. Check out Views Over Components - An Ember Refactoring Story.
From Ember.js, Sending Actions from Components to Your Application, there's discussion about sending actions from a component up the route hierarchy.
A lot of things changed in Ember since the original post. So maybe today the best option would be to pass down to the component a route action that takes care of the transition (maybe using the fancy addon ember-cli-route-action.
Otherwise you can create an initializer with ember g initializer router and inside put in there a code like this one
export function initialize (application) {
application.inject('route', 'router', 'router:main')
application.inject('component', 'router', 'router:main')
}
export default {
name: 'router',
initialize
}
This way you can access the router in your component with this.get('router') and, for instance, perform a transition
this.get('router').transitionTo('images')
At component.HBS component file make a
{{#link-to "routeDestinationYouWant" }}
For Example:
<section class="component">
{{#link-to "menu.shops.category.content" "param"}}
<figure id="{{Id}}" class="list-block yellow-bg text-center" {{action "showList" "parameter"}}>
<section class="list-block-icon white-text my-icons {{WebFont}}"></section>
<figcaption class="list-block-title black-text">{{{Title}}}</figcaption>
</figure>
{{/link-to}}
</section>
I've written this answer for another similar question.
If you want to use the router only in a specific component or service or controller, you may try this:
Initialize an attribute with the private service -routing. The - because it's not a public API yet.
router: service('-routing'),
And then inside any action method or other function inside the service or component:
this.get('router').transitionTo(routeName, optionalParams);
Note: It'll be transitionToRoute in a controller.
Link to question: Ember transitionToRoute cleanly in a component without sendAction
Link to answer: https://stackoverflow.com/a/41972854/2968465