Ember Router - Load content from different controller on same level - ember.js

Having two routes (comments, trackbacks) nested within post I try to access content of commentsController from trackbacksController after entering the App directly through /posts/1/trackbacks. Unfortunately it seems like the controller is not fully initialized and the content doesn't get loaded.
This is how the Router looks like:
Router = Ember.Router.create
root: Ember.Route.extend
index: Ember.Route.extend
route: '/'
post: Ember.Route.extend
route: '/posts/:post_id'
index: Ember.Route.extend
route: '/'
redirectsTo: 'comments'
comments: Ember.Route.extend
route: '/comments'
connectOutlets: (router) ->
controller = router.get('postController')
controller.connectOutlet 'comments', controller.get('comments')
trackbacks: Ember.Route.extend
route: '/trackbacks'
connectOutlets: (router) ->
controller = router.get('postController')
controller.connectOutlet 'trackbacks', controller.get('trackbacks')
And here is the TrackbacksController:
App.TrackbacksController = App.ArrayController.extend
init: ->
console.log App.router.get('attributesController.content') # : []
#_super()
Is there a best practice to initialize router.commentsController manually to get it's content from trackbacksController? Is there anything wrong concerning my approach?

You can access all controllers from a controller, using its controllers property.
For example, from trackbacksController, you can use this.get('controllers.commentsController')
EDIT: I realize I did'nt get your problem. I think there is some dependencies between the two controllers, and from my point of view, you can't rely on the application initialization order. So in the init method, you will not be able to access other controllers.
But I think you can put an observer in the trackbacksController, which observes controllers.commentsController.content or controller.commentsController.#each, so when the commentsController is populated, you will be notified.

Related

Hooking into "root" route in new Ember rc1 router

I'm looking for a way to hook into the router as it transitions to any given leaf resource during app init. In other words, I want to run a little init routine every single time an app is loaded, no matter what the "incoming" url state is.
Given a router such as this:
App.Router.map ->
#resource "donkeys"
#resource "camels"
When the app is loaded (for the first time) with a dangling url such as this:
localhost:9000/#/donkeys
-- or --
localhost:9000/#/camels
I want the same router-level code to be called.
My first guess was to try something like this (this doesn't work):
App.IndexRoute = Ember.Route.extend
activate: ->
App.callTheBeastsOfBurden()
... but it turns out that IndexRoute is not being traversed. I only see the log message Transitioned into 'donkeys'
In ye olde days, there was this "root route" concept that you could hook in to. If I recall, it looked something like this (this is old and "wrong"):
App.Router = Em.Router.extend
root: Em.Route.extend
connectOutlets: ->
App.callTheBeastsOfBurden()
So what's the Router v2 approved method of accomplishing the same?
App.ApplicationRoute = Ember.Route.extend
activate: ->
App.callTheBeastsOfBurden()

Ember.js connectOutlets runs on root the moment Router is created

I have the following ember router definition:
WZ.Router = Em.Router.extend
enableLogging: true
location: 'hash'
showHome: Ember.Route.transitionTo 'root.index'
root: Em.Route.extend
initialState: 'index'
connectOutlets: (router, event) ->
unless router.get 'initialized'
router.get('applicationController').connectOutlet 'nav', 'navbar'
router.get('homeController').connectOutlet 'bottombar', 'bottombar'
router.set 'initialized', true
index: Em.Route.extend
route: '/'
connectOutlets: (router, event) ->
router.get('applicationController').connectOutlet 'home'
I am using connectOutlets of the root route because I want the navigation outlets to be connected no matter which url the user enters the application.
The problem is that as soon as the router is created, the root connectOutlets fires and this is before the router has had the controllers injected via runInjections.
Everything works if I connect these outlets in a leaf route but that is not what I am after.
If I cannot use the root connectOutlets, how can I best ensure that the navigation outlets are connected no matter which url or route the user enters the app on?
Should we also disallowing connectOutlets to be overriden on a leaf route as it is fairly useless if there are no controllers etc. to connect?
EDIT: I got round this problem by using Ember.run.next:
WZ.Router = Em.Router.extend
enableLogging: true
location: 'hash'
showHome: Ember.Route.transitionTo 'root.index'
root: Em.Route.extend
connectOutlets: (router, event) ->
Ember.run.next #, ->
unless router.get 'initialized'
router.get('applicationController').connectOutlet 'nav', 'navbar'
router.get('homeController').connectOutlet 'bottombar', 'bottombar'
router.set 'initialized', true
index: Em.Route.extend
route: '/'
But this still seems less than ideal. Is this a hole in the Em logic or by design?
I generally consider the root state to be special, so I dont use connectOutlets for this state. In practice this means that the root state (which responds to URL '/'), redirects to a "home" state, inside which I would put your connectOutlets.
The upside of this is that you don't run in to the controllers-not-injected yet problem, but it also means that your "default" URL becomes something like yoursite.com/#/home if not using the History API, and yoursite.com/home if you are.
Not really ideal in either case, but at least you aren't shoehorning Ember.run.next into your connectOutlets, which seems like bad form which might bring up a slew of other issues moving forward. I suppose you wouldn't, but you are now expecting that your controllers will be injected between now and when the next run loop fires... That may or may not be the case...

How to get an instance of a controller in Ember.js?

I'm trying to access an instance of a controller that has been wired automatically using App.initialize();
I've tried the below but it returns a Class not an instance.
Ember.get('App.router.invitesController')
I have a quick post about this exact subject on my Blog. It's a little big of a different method, but seems to work well for Ember.js RC1.
Check it out at: http://emersonlackey.com/article/emberjs-instance-of-controller-and-views
The basic idea is to do something like:
var myController = window.App.__container__.lookup('controller:Posts');
This answer works with RC1/RC2.
Now you can use the needs declaration in order to make the desired controller accessible. Here's an example:
Suppose I want to get something from my SettingsController from within my ApplicationController. I can do the following:
App.SettingsController = Ember.Controller.extend({
isPublic: true
});
App.ApplicationController = Ember.Controller.extend({
needs: 'settings',
isPublicBinding: 'controllers.settings.isPublic'
});
Now in the context of my ApplicationController, I can just do this.get('isPublic')
You can access a controller instance inside an action in the router via router.get('invitesController'), see http://jsfiddle.net/pangratz666/Pk4k2/:
App.InvitesController = Ember.ArrayController.extend();
App.Router = Ember.Router.extend({
root: Ember.Route.extend({
route: '/',
index: Ember.Route.extend({
route: '/',
connectOutlets: function(router, context) {
var invitesController = router.get('invitesController');
},
anAction: function(router) {
var invitesController = router.get('invitesController');
}
})
})
});​
You can access any controller instance by name using lookup method of Application instance.
To get Application instance you can use getOwner from any route or controller.
const controllerName = 'invites';
Ember.getOwner(this).lookup(`controller:${controllerName}`));
Works for me in Ember 2.4 - 3.4.

Proper way to use parent route parameters with ember-data?

In my app, I am trying to keep the routing structure as close to the API structure as possible, which ember facilitates in the basic case but I am still confused about the following case:
(In reference to the example of http://emberjs.com/guides/outlets/#toc_nesting)
What is the best way to go about retrieving the /posts/:post_id/comments data (assuming it isn't given to me by /posts/:post_id)?
Should I be passing the post ID to App.Comment.find(...) somehow, in the comments.deserialize(...) method? Is there a better way to get the post ID than router.getPath('postController.content._id')? I am using a modified DS.RESTAdapter.
Parent router parameters are no more accessible as parameters in children routes, but should have been used to retrieve and populate intermediate data structure.
Given your models are defined as follow:
App.Post = DS.Model.extend({
text: DS.attr('string'),
// ...
comments: DS.hasMany('App.Comment')
});
App.Comment = DS.Model.extend({
// You may also have: "post: DS.belongsTo('App.Post')", but we do not care for this exemple
text: DS.attr('string'),
// ...
});
This should be something working:
posts: Ember.Route.extend({
route: 'posts',
member: Ember.Route.extend({
route: '/:post_id', // [A]
connectOutlets: function (router, post) {
var applicationController = router.get('applicationController');
applicationController.connectOutlet('post', post); // [B]
},
show: Ember.Route.extend({
route: '/'
}),
comments: Ember.Route.extend({
route: 'comments',
connectOutlets: function (router) {
var postController = router.get('postController'),
comments = postController.get('comments'); // [C]
postController.connectOutlet('comments', comments);
},
}),
})
})
[A]: The post model instance will be retrieved automagically by the router, according to convention: post_id refers to Post model instance with the given id (see this comment).
[B]: Here, PostController will be populated by the router with the passed context: post, which is the Post instance retrieved upper (see [A]).
[C]: PostController is an ObjectController (i.e Proxy) on the post model instance, so it directly holds comments.

Ember.Router's transitionTo not updating URL

I implemented the following using latest ember.js (0.9.8.1) by referring to Ember Router not updating url in Chrome and Safari. When I try switch b/w routes via router.transitionTo('route path') method (last 3 lines of the attached snippet), browser url is not updated correctly, but I do see the view markup being updated confirming that state change do happen. Could some help in identifying whether am I missing something here?
BTW: I tested this in Chrome 20.0.1132.27 beta-m
App = Ember.Application.create({});
App.IndexView = Ember.View.extend({
template: Ember.Handlebars.compile(
'hello world from index'
)
});
App.ShowView = Ember.View.extend({
template: Ember.Handlebars.compile(
'hello world from show'
)
});
App.Router = Ember.Router.extend({
location: 'hash',
enableLogging: true,
root: Ember.State.extend({
index: Ember.State.extend({
route: '/',
redirectsTo: 'tasks'
}),
tasks: Ember.State.extend({
route: '/tasks',
index: Ember.ViewState.extend({
route: '/',
view: App.IndexView
}),
show: Ember.ViewState.extend({
route: '/show',
view: App.ShowView
})
})
})
});
var router = App.Router.create({});
App.initialize(router);
router.transitionTo('root');
router.transitionTo('root.tasks');
router.transitionTo('root.tasks.show');
I ran your code, and in the console, I have the following error "Object hash has no method 'setURL'". By debugging a bit, I found that you have to define the location of the Router with:
location = Ember.Location.create({ style: 'hash' })
or
location = Ember.Location.create({implementation: 'hash'})
I don't know why exactly, but it seems to work. Perhaps it's only due to ember version.
You are mixing up 2 initialization methods.
When you define App.Router all you need to do is call App.initialize(). It automatically creates an instance of Ember.Router and assigns it to the variable App.stateManager. You can then use App.stateManager to call transitionTo.
You can define the variable router to extend Ember.Router then call App.initialize(router). This method also creates an instance of Ember.router and assigns it to App.stateManager. You can then use App.stateManager to call transitionTo.
Either of the methods will work but I prefer method 1. To manipulate the route we always use App.stateManager.