If I have a route to a state deep in the application, how do I ensure that the proper controller set up has been done although I'm going to enter the inner state directly?
For example,
- state A
- state a, representRoute: 'a'
- state B
- state b, representRoute: 'a/b'
- state C
- state c, representRoute: 'a/b/c'
- state D
- state d, representRoute: 'a/b/c/d
- state E
As you can see you can route directly to states 'a', 'b', 'c' or 'd', but what you can't see is that normally you would go between these states by selecting an item in a controller, which would then trigger a state transition to the deeper state. The problem then is that when you go directly to state 'd' none of your controllers' selections are set up.
So far I've used enterStateByRoute
in state 'a' to set the selection of the first controller and then had to use enterStateByRoute
in state 'b' to do the selection of the first and second controllers, etc. all the way to enterStateByRoute
in state 'd' to do the selection of each controller all the way along. This is quite wasteful, because I end up repeating the same code in each enterStateByRoute
.
What is the best way to set the controller selection to match the directly routed state?
I was able to improve the situation greatly once I realized that enterStateByRoute
is called on all parent states in the chain when routing. This means that if state 'c' matches the route, state 'A' will be entered, followed by state 'B' and state 'C' before finally entering state 'c' last. What I didn't realize before was that each of these states is passed the SC.StateRouteHandlerContext
object as it is entered allowing you to either check the context in enterState
or implement enterStateByRoute
in any of the states.
My solution then was to add enterStateByRoute
to state 'A' to set the first controller, add enterStateByRoute
to state 'B' to set the second controller, etc. For example, in this way, any state past state 'A' is guaranteed to have the first controller selection set and I don't have any duplicated code down the chain.
For example,
// …
state_A: SC.State.extend({
initialSubstate: 'state_a',
enterStateByRoute: function (context) {
// select object on controller 1 since we are routing
},
state_a: SC.State.extend({
representRoute: 'a',
enterStateByRoute: function (context) {
// do setup for state 'a' specific to routing
}
}),
state_B: SC.State.extend({
initialSubstate: 'state_b',
enterStateByRoute: function (context) {
// select object on controller 2 since we are routing
},
state_b: SC.State.extend({
enterStateByRoute: function (context) {
// do set up for state 'b' specific to routing
},
// …
The only problem I encountered was that because I had bound all my controllers together, the selection change doesn't propagate immediately and so I would select an object on a controller in the first state, enter the next state and find that the bound controllers' content would not yet have updated.
So I could have waited for bindings to flush by returning an SC.Async
object in enterStateByRoute
and used this.invokeLast(function () { this.resumeGotoState(); })
to go to the next state at the end of the run loop, but instead I took a declarative approach and simply set/unset each controller's content as I enter/exit the appropriate state.