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

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

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 router with transient states

Inspired by Emberjs: Conditional redirect in router I thought I could use transient states in Ember router, as that is what the 'index' route in that question is - it is entered and immediately transitions to another state without a new event triggering it.
I can get the desired effect using the following code (action triggering transitionTwice), however the URL is updated in reverse order leaving it at the first state even though the App traverses through the states in the correct order ending in the expected last state.
App.Router = Ember.Router.extend({
enableLogging: true,
root: Ember.Route.extend({
transitionTwice: Ember.Route.transitionTo('twiceStep1'),
index: Ember.Route.extend({
route: '/',
}),
twiceStep1: Ember.Route.extend({
route: '/twiceStep1',
connectOutlets: function (router) {
router.transitionTo('twiceStep2');
}
}),
twiceStep2: Ember.Route.extend({
route: '/twiceStep2',
})
})
}),
App.initialize();
The console output shows the correct state changes:
STATEMANAGER: Sending event 'transitionTwice' to state root.
STATEMANAGER: Entering root.twiceStep1
STATEMANAGER: Entering root.twiceStep2
However the URL ends up in:
...test2.html#/twiceStep1
Can anyone spot anything I am doing wrong?
Thanks,
Jon.
I have been able to get this to work successfully by wrapping the second transitionTo() call in Ember.run.next().
Ember.run.next(function () {
router.transitionTo('twiceStep2');
});
The problem is that for any transition the URL is updated after the connectOutlets() call completes. This means that chained calls to transitionTo from connectOutlets nest and the URL is updated in reverse order as the calls complete.
While this fixes my immediate problem I'm not sure this really constitutes a final answer to the question as this implies that the final state needs to have the knowledge that it will be entered from a previous, transient state - which isn't good separation. But I thought I should record it in case anyone else struggles with this anomaly.

Ember Router - Load content from different controller on same level

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.

Ember.Router loses context for dynamic segments on page reload

I've been trying to debug this for a while now, and I may have figured out what the problem is, let me explain what is happening by use of a fiddle
Navigate to http://fiddle.jshell.net/ivanvanderbyl/mfqEB/show/#/projects/1, you'll notice the dynamic segment in the url is replaced with null — it should stay as 1
From what I can gather, after routing once, Ember Router then makes a call to update the URL, replacing the dynamic segments with the values it gets from the respective instantiated objects, in this case App.Project.
The problem is that App.Project is not loaded at this point so id is null
Now whether this is a bug or an implementation failure on my part, has anyone else seen this?
Moving connectOutlets from projects.index directly into projects seems to do the trick. Don't ask me why though.
jsFiddle
Router: Ember.Router.extend(
location: 'hash'
enableLogging: true
root: Ember.Route.extend(
listProjects: Ember.Route.transitionTo("projects.index")
showProject: Ember.Route.transitionTo("projects.show")
index: Ember.Route.extend(
route: "/"
redirectsTo: 'projects.index'
)
projects: Ember.Route.extend(
route: '/projects'
connectOutlets: ((router) ->
router.get('applicationController').connectOutlet('projects', App.Project.find())
)
index: Ember.Route.extend(
route: '/'
)
add: Ember.Route.extend(
route: '/new'
)
show: Ember.Route.extend(
route: '/:project_id'
connectOutlets: (router, project) ->
console.log project.get('name')
# router.get('applicationController').connectOutlet('project', project)
)
)
)
)
So after much investigation the correct solution to this is to simply not specify the primary key attribute on the model, in this case I should not have been specifying the ID attribute on the Project model. Ember Data handles this internally.
Thanks to Peter Wagenet for pointing this out on Github.

Why does Ember Router only allow navigating to leaf routes?

Something I have noticed recently with Ember Router is it only allows navigating to leaf routes — routes without child routes.
Now unless I'm doing things incorrectly then this seems like a bug/mistake in design.
Let's take for example something like this:
I have a collection of projects, each project has many collaborators, with this I want to build a UI with a 3 column layout (something like your standard desktop email client) where on the left I have a list of projects, when clicking on a project the middle column shows a list of collaborators, and clicking on a collaborator loads its details into the righthand column.
Now with the routing I want to navigate to /projects/1 when clicking on a project and onto /projects/1/collaborators/23 when clicking on a collaborator.
Here is a router illustrating the first part of nested route:
App.reopen(
Router: Ember.Router.extend(
enableLogging: true
location: 'hash'
root: Ember.Route.extend(
index: Ember.Route.extend(
route: '/'
redirectsTo: 'projects'
)
projects: Ember.Route.extend(
# This route is not routable because it is not a leaf route.
route: '/projects'
connectOutlets: (router) ->
# List projects in left column
router.get('applicationController').connectOutlet('projects', App.projects)
show: Ember.Route.extend(
# This route is routable because it is a leaf route.
route: '/:project_id'
connectOutlets: (router, project) ->
# Render the project into the second column, which actually renders
# a list of collaborators.
router.get('projectsController').connectOutlet('project', project)
)
)
)
)
)
As you'll see Ember doesn't call updateRoute (set the URL) until transitioning to root.projects.show because of this line https://github.com/emberjs/ember.js/blob/master/packages/ember-routing/lib/routable.js#L81
Has anyone else done anything like this? Is there a better way to design this?
The best way I've found to do this is to have a root.projects.index state with a route of "/" and nothing else. This way every page has it's own specific state.
projects: Ember.Route.extend(
# This route is not routable because it is not a leaf route.
route: '/projects'
connectOutlets: (router) ->
# List projects in left column
router.get('applicationController').connectOutlet('projects', App.projects)
index: Ember.Route.extend(
route: "/"
)
show: Ember.Route.extend(
# This route is routable because it is a leaf route.
route: '/:project_id'
connectOutlets: (router, project) ->
# Render the project into the second column, which actually renders
# a list of collaborators.
router.get('projectsController').connectOutlet('project', project)
)
)
N.B. That being said I'm doing a similar thing with a 3 column layout, and am matching the middle and right column to the routes as above and adding the left column in a shared layout to each of the possible middle views.
I think this issue is resolved. I am using ember routes without an index and do not face any leaf state issues. I was browsing why we need an index at all and I landed up here. Is there any other reason for using the index state too?