Parent Route Not Asked to Handle Event? - ember.js

The guide says that when an action is triggered, Ember first looks for a handler in the current controller, then if it can't find it in the controller it looks in the current route, then the parent route, etc. I'm not seeing that happen.
My routes:
App.Router.map(function() {
// Creates 'products' and 'products.index' routes
this.resource('products', function(){
// ...
});
});
My super trivial products.index template;
<span {{action fooBar}}>Run fooBar</span>
To test this, I'm currently at /#/products in the browser, and Ember logs "Transitioned into 'products.index'" saying I'm currently in the products.index route, as I expect. Now if click on the action, Ember should look for a handler in:
ProductsIndexController
ProductsIndexRoute
ProductsRoute
My observations:
If I put the handler in ProductsIndexController, it works.
If I put the handler in ProductsIndexRoute, it works.
However, if I put the handler in ProductsRoute, it's never called:
.
App.ProductsRoute = Ember.Route.extend({
events: {
fooBar: function(){
alert("alarm!");
}
}
});
Instead I see the error:
*Error: Nothing handled the event 'fooBar'.*
What am I missing?

One of my other javascript files was also setting/creating App.ProductsRoute (and doing nothing with it), which was causing a conflict. Silly mistake.

Related

Ember - Hitting refresh on browser redirects to previous controller?

I have an ember application.
The routes are setup like.
customer
customer/view/:customerId
customer/dashboard
customer/device-sharing
Customer/dashboard page displays a component, a table, which has a #linkto helper with route customer.device-sharing
This is working as expected.
The issue is, once on device-sharing route, if I am hitting refresh button on browser, the app is being redirected to dashboard route.
EDITED:
router.js:
this.route('customers', function() {
this.route('index', {path: '/'});
this.route('view', {path: '/view/:device_id'});
this.route('dashboard', {path: '/dashboard/:customer_id'});
this.route('device-sharing', {path: '/device-sharing'});
});
The thing going wrong is, in the customer.js route file's afterModel function, we are using transitionTo to go to dashboard.
afterModel(model) {
model = model || [];
if (model.length === 1) {
console.log('Route parent');
this.transitionTo('customers.dashboard', this.get('paramValue'));
}
}
Is there any way to check if the device-sharing subroute is active so that we can prevent the transitionTo call.
Regards,
Ishan
You probably want to move your model and afterModel to your customers/index route to get the behavior that you're really after. Without seeing more of the code, it's a bit of a guessing game though.
You could use the currentRouteName method on the RouterService to check decide whether to transition or not, but I suspect that would be working around an incorrect design and storing up trouble for yourself down the line.

Observes other childcontroller?

I am trying to observe another childcontroller.
I have the following router:
this.resource('generics', {path: '/gens'}, function() {
this.resource('generic', {path: '/:generic_id'}, function() {
this.route('prepare'); // Objectcontroller
this.route('sent'); // Objectcontroller
});
});
I have an observer in the sent controller, however it does not work. I have currently: 'controllers.sent.id' to get the id prepared in the prepare controller.
If I do a needs property with generic.prepare. It shows this error:
#needs must not specify dependencies with periods in their names (generic.sent)
I have also tried to use setupController to add the id to the sent controller properties, however the observer is worthless then.
it would be genericPrepare, but prepare/sent should never exist at the same time. You'd probably be better off sending the object to generic and then having sent grab the property off of it. Why are you against setting it up during setupController? The route will always hit setupController of sent before it's visible to the end user.

How to get reference to current route from controller?

I have a controller (KbRelatedGroupController) that is loaded via a {{render}} helper in a template.
In the controller's action, if I do this.get('target'), it returns a reference to the parent controller for the page (KbShowController).
If I call .target on that, I get a reference to Discourse.Router, which is no good to me.
What I want is a reference to a KbShowRoute, and that is what I expected since .target is supposed to produce the Route when called from a controller is it not?
Really confused here. Why is it so hard to get a reference to the current route from a controller?
The way I see it, you're not supposed to. You can let the action bubble up to the route:
App.KbShowRoute = Ember.Route.extend({
...
actions: {
something: function() {
console.log('called second');
}
}
});
App.KbShowController = Ember.Controller.extend({
...
actions: {
something: function() {
console.log('called first');
}
}
});
see docs
You could:
Handle one part of the action in the controller, and let it bubble to the route by not returning anything in the controller's action handler
Let the route handle the action (by not adding the action to your controller's action hash) and from the route use this.controllerFor(this.routeName).sendAction('..', ...) to call a different action (or part of the action) in the controller.
I hope this helps you!

Ember.js: Action Propagation

I have the following setup:
A ListRoute which has an action doNext.
An ItemRoute (Detail) which also has an event doNext.
App.Router.map(function () {
this.resource('list', function() {
this.route('item');
})
});
App.ListRoute = Ember.Route.extend({
actions:{
doNext:function() {
alert("doNext from List Route!");
}
}
})
App.ListItemRoute = Ember.Route.extend({
actions:{
doNext:function() {
alert("doNext from List Item Route");
}
}
})
When clicking on an {{action doNext}} inside the List it fires the correct action in the List Route.
But when I click on the same link, after I have transitioned int the item, the action of the List Item Route gets fired. I would have expected, that the action would still get sent to the ListRoute.
Is this by design? And is there a way to force my expected behavior?
I've created a fiddle which where you can see this:
http://jsfiddle.net/AyKarsi/HA93a/4/
it's how actions occur, it doesn't matter which template you click the action from, just what route you're in, it starts at the very top, then if it doesn't find it, it goes up the chain. This allows you to send the same action from random places in your application and have them propagate up, or be overridden by the highest/deepest available route.
Your jsfiddle's template name was wrong btw, it should be resource/route
http://jsfiddle.net/HA93a/6/
the best way around it would be to use different names in your actions if you don't want the same action name to have that issue. Honestly it's a little weird in my opinion, and maybe someone should complain, but it was by design.

What's the right way to enter and exit modal states with Ember router v2?

I can't figure out the correct way to handle modal states/views with the new Ember router. More generally, how do you handle states that you can enter and exit without affecting the "main" state (the URL)?
For example, a "New Message" button that is always available regardless of the current leaf state. Clicking "New Message" should open the new message modal over the current view, without affecting the URL.
Currently, I'm using an approach like this:
Routes:
App.Router.map(function() {
this.route('inbox');
this.route('archive');
});
App.IndexRoute = Em.Route.extend({
...
events: {
newMessage: function() {
this.render('new_message', { into: 'application', outlet: 'modalView' });
},
// Clicking 'Save' or 'Cancel' in the new message modal triggers this event to remove the view:
hideModal: function() {
// BAD - using private API
this.router._lookupActiveView('application').disconnectOutlet('modalView');
}
}
});
App.InboxRoute = Em.Route.extend({
...
renderTemplate: function(controller, model) {
// BAD - need to specify the application template, instead of using default implementation
this.render('inbox', { into: 'application' });
}
});
App.ArchiveRoute = ... // basically the same as InboxRoute
application.handlebars:
<button {{action newMessage}}>New Message</button>
{{outlet}}
{{outlet modalView}}
I've obviously left out some code for brevity.
This approach 'works' but has the two problems identified above:
I'm using a private API to remove the modal view in the hideModal event handler.
I need to specify the application template in all of my subroutes, because if I don't, the default implementation of renderTemplate will attempt to render into the modal's template instead of into application if you open the modal, close it, and then navigate between the inbox and archive states (because the modal's template has become the lastRenderedTemplate for the IndexRoute).
Obviously, neither of these problems are dealbreakers but it would be nice to know if there is a better approach that I'm missing or if this is just a gap in the current router API.
We do kind of the same thing but without accessing the private API.
I don't know if our solution is a best practice, but it works.
In the events of our RootRoute I have an event (same as your newMessage), where we create the view we need to render, and then append it.
events: {
showNewSomething: function(){
var newSomethingView = app.NewSomethingView.create({
controller: this.controllerFor('newSomething')
});
newSomethingView.append();
}
}
This appends the modal view into our app.
On cancel or save in the newSomethingView we call this.remove() to destroy the view and removing it from the app again.
Again, this doesn't feel like a best practice, but it works. Feel free to comment on this if someone have a better solution.
Don't know if you are using the Bootstrap Modal script or which one, but if you are, this question has a proposed solution. Haven't figured out all the pieces myself yet, but is looking for a similar type of solution myself to be able to use Colorbox in an "Ember best practices"-compliant way.