Skip to content
This repository

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
jbrown commented June 06, 2012

:+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
jbrown commented June 06, 2012

@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
jbrown commented June 06, 2012

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
jbrown commented June 06, 2012

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
trek commented July 09, 2012

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?

Trek Glowacki
Owner
trek commented July 17, 2012

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 October 09, 2012
Jakub Nieznalski knusul referenced this pull request from a commit October 28, 2013
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
@@ -107,7 +107,7 @@ Ember.Application = Ember.Namespace.extend(
107 107
         namespace = this, controller, name;
108 108
 
109 109
     if (!router && Ember.Router.detect(namespace['Router'])) {
110  
-      router = namespace['Router'].create();
  110
+      router = namespace['Router'].create({ autoInitialState: false });
111 111
       this._createdRouter = router;
112 112
     }
113 113
 
@@ -166,6 +166,7 @@ Ember.Application = Ember.Namespace.extend(
166 166
       applicationView.appendTo(rootElement);
167 167
     }
168 168
 
  169
+    router.initialize();
169 170
     router.route(location.getURL());
170 171
     location.onUpdateURL(function(url) {
171 172
       router.route(url);
12  packages/ember-states/lib/state_manager.js
@@ -373,6 +373,8 @@ require('ember-states/state');
373 373
 Ember.StateManager = Ember.State.extend(
374 374
 /** @scope Ember.StateManager.prototype */ {
375 375
 
  376
+  autoInitialState: true,
  377
+
376 378
   /**
377 379
     When creating a new statemanager, look for a default state to transition
378 380
     into. This state can either be named `start`, or can be specified using the
@@ -383,6 +385,16 @@ Ember.StateManager = Ember.State.extend(
383 385
 
384 386
     set(this, 'stateMeta', Ember.Map.create());
385 387
 
  388
+    var initialState = get(this, 'initialState'),
  389
+        autoInitialState = get(this, 'autoInitialState');
  390
+
  391
+
  392
+    if (autoInitialState) {
  393
+      this.initialize();
  394
+    }
  395
+  },
  396
+
  397
+  initialize: function() {
386 398
     var initialState = get(this, 'initialState');
387 399
 
388 400
     if (!initialState && getPath(this, 'states.start')) {
9  packages/ember-states/tests/state_manager_test.js
@@ -240,6 +240,15 @@ test("it triggers setup on initialSubstate", function() {
240 240
   ok(grandchildSetup, "sets up grandchild");
241 241
 });
242 242
 
  243
+test("it does not automatically transition to a default state when autoInitialState is false", function() {
  244
+  stateManager = Ember.StateManager.create({
  245
+    autoInitialState: false,
  246
+    start: Ember.State.create()
  247
+  });
  248
+
  249
+  equal(get(stateManager, 'currentState'), undefined, "does not transition to initial state");
  250
+});
  251
+
243 252
 test("it throws an assertion error when the initialState does not exist", function() {
244 253
   raises(function() {
245 254
     Ember.StateManager.create({
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.