Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Controller instances not available in connectOutlets of root state. #941

Closed
wants to merge 2 commits into from

8 participants

Joshua Borton Peter Wagenet Justin Brown Don't Add Me To Your Organization a.k.a The Travis Bot Tom Dale Trek Glowacki Alex Matchneer Yehuda Katz
Joshua Borton

I'm not sure if this is intended, but controller instances are not available in the root state when the connectOutlets event is fired. This fiddle demonstrates.

http://jsfiddle.net/LuWqj/1/

Joshua Borton

The cause of this is due to Ember.StateManager automatically transitioning to the initialState upon creation. This triggers connectOutlets before injections happen.

Peter Wagenet
Owner

This seems like it's worth fixing but I would like to see what @tomdale and @wycats think.

Justin Brown

:+1: I worked around this by using a binding...

// initialState
connectOutlets: (router) ->
  rootView = Ember.ContainerView.create
    currentViewBinding: 'App.stateManager.applicationController.view'

  rootView.append()
Justin Brown

@digitaltoad What are you trying to do in connectOutlets of the root state? None of the test cases show connectOutlets on the root state. I'm not so sure that's the convention even though the original gist shows it done that way.

Joshua Borton

Sorry, I should have added a test for that as well. I've been going by the original gist. In current apps, instead of placing setup code in the root connectOutlets, I've created a single state inside root that would house the rest of my app states so that I would have access to the controllers there. This seemed like the way root was supposed to work. I'm not sure of a better way to defer the start of the router without the flag that I've inserted that wouldn't break any apps that assumed the router would automatically transition to the root state.

Justin Brown

Okay, disregard my first comment. I figured all of this out. I made you a gist. Let me know if that helps.

Joshua Borton

My problem arises from an example where the page is loaded at a state. For instance, http://jsfiddle.net/C7LrM/8/. I've extracted the navbar area. Where would the setup go for this. If the app loads at the home state, everything is fine. If the app loads at the profile state, the navbar is never loaded. Would the navbar have to be connected in every state or is there just a better way of handling this? This might of course just be a convention issue.

Justin Brown

You're not gaining anything by using connectOutlets for that. The real power of connectOutlet is passing a context in. A navbar likely has no context. If any part of it is dynamic, you probably want to just bind to something on applicationController, or have your own navbarController. Just use the Handlebars view helper. see here

Does that make sense?

Joshua Borton

This does indeed make sense. I had gone down the route of using a view helper before I learned about outlets. However, the question remains, if connectOutlets is still fired in the root state, should everything be wired and ready at that point?

Peter Wagenet
Owner

@digitaltoad This doesn't merge cleanly. Do you mind rebasing?

Joshua Borton
Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request passes (merged 20d17d2 into d71677b).

Peter Wagenet
Owner

This looks good and would solve some annoyances for me as well. @wycats, @tomdale, what do you guys think?

Tom Dale
Owner

I agree that this is an important fix, but I think this hacks around the problem instead of addressing the core issue. Instead of special-casing the router to not automatically transition into the initialState, we should just make sure that the initialState's connectOutlets call back doesn't happen until the state manager has finished initializing.

Peter Wagenet
Owner

@tomdale So are you suggesting we delay the transitionEvent? This seems like you run into the same problem.

Joshua Borton

I had tried to figure out a way to do this without a hack. Maybe if I revisit I can come up with something else. My first thought was indeed to only fire the initial transitionEvent after the state manager was completely initialized (including injections).

Peter Wagenet
Owner

To clarify, both the initialState and the transitionEvent (connectOutlets) are handled by the StateManager class. Wherever we make the change, we're going to have to "hack" around it in the router. Delaying the initialState makes sense to me since the router really doesn't get kicked off until the first route call.

Trek Glowacki
Owner

I wanted to use connectOutlets on root state for an identical reason: setting up other main areas of the app that get their own controllers. For now, I'm doing

 {{view App.SearchView controllerBinding="App.router.searchController"}}

But I'd like to see this usage of connectOutlets in 1.0. @tomdale you seem OK statemanger being changed so the transition can be delayed?

Yehuda Katz wycats was assigned
Trek Glowacki
Owner

Sorry to pester, but were we going to pull this PR or wait to implement @tomdale's suggestion of making a statemanager's root state transitionEvent not trigger until the manager is done being initialized?

Alex Matchneer
Owner

What's the state of this? I'm using trek's hack in the meantime but would be nice to reference at least applicationController in the root connectOutlets

Peter Wagenet
Owner

Since routing is something that's been design by @wycats and @tomdale I'd really like to hear their opinion on the correct solution for this.

Yehuda Katz
Owner

A solution to this is definitely desired and is targeted for Router v2.

Yehuda Katz wycats closed this
Jakub Nieznalski knusul referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
3  packages/ember-application/lib/system/application.js
View
@@ -107,7 +107,7 @@ Ember.Application = Ember.Namespace.extend(
namespace = this, controller, name;
if (!router && Ember.Router.detect(namespace['Router'])) {
- router = namespace['Router'].create();
+ router = namespace['Router'].create({ autoInitialState: false });
this._createdRouter = router;
}
@@ -166,6 +166,7 @@ Ember.Application = Ember.Namespace.extend(
applicationView.appendTo(rootElement);
}
+ router.initialize();
router.route(location.getURL());
location.onUpdateURL(function(url) {
router.route(url);
12 packages/ember-states/lib/state_manager.js
View
@@ -373,6 +373,8 @@ require('ember-states/state');
Ember.StateManager = Ember.State.extend(
/** @scope Ember.StateManager.prototype */ {
+ autoInitialState: true,
+
/**
When creating a new statemanager, look for a default state to transition
into. This state can either be named `start`, or can be specified using the
@@ -383,6 +385,16 @@ Ember.StateManager = Ember.State.extend(
set(this, 'stateMeta', Ember.Map.create());
+ var initialState = get(this, 'initialState'),
+ autoInitialState = get(this, 'autoInitialState');
+
+
+ if (autoInitialState) {
+ this.initialize();
+ }
+ },
+
+ initialize: function() {
var initialState = get(this, 'initialState');
if (!initialState && getPath(this, 'states.start')) {
9 packages/ember-states/tests/state_manager_test.js
View
@@ -240,6 +240,15 @@ test("it triggers setup on initialSubstate", function() {
ok(grandchildSetup, "sets up grandchild");
});
+test("it does not automatically transition to a default state when autoInitialState is false", function() {
+ stateManager = Ember.StateManager.create({
+ autoInitialState: false,
+ start: Ember.State.create()
+ });
+
+ equal(get(stateManager, 'currentState'), undefined, "does not transition to initial state");
+});
+
test("it throws an assertion error when the initialState does not exist", function() {
raises(function() {
Ember.StateManager.create({
Something went wrong with that request. Please try again.