diff --git a/packages/ember-routing/lib/system/route.js b/packages/ember-routing/lib/system/route.js index 38a58d02ca6..c5ac7ef2e03 100644 --- a/packages/ember-routing/lib/system/route.js +++ b/packages/ember-routing/lib/system/route.js @@ -1882,27 +1882,45 @@ var Route = EmberObject.extend(ActionHandler, Evented, { disconnectOutlet(options) { var outletName; var parentView; - var parent; if (!options || typeof options === "string") { outletName = options; } else { outletName = options.outlet; parentView = options.parentView; } - parentView = parentView && parentView.replace(/\//g, '.'); - parent = parentRoute(this); + outletName = outletName || 'main'; + this._disconnectOutlet(outletName, parentView); + for (var i = 0; i < this.router.router.currentHandlerInfos.length; i++) { + // This non-local state munging is sadly necessary to maintain + // backward compatibility with our existing semantics, which allow + // any route to disconnectOutlet things originally rendered by any + // other route. This should all get cut in 2.0. + this.router.router. + currentHandlerInfos[i].handler._disconnectOutlet(outletName, parentView); + } + }, + + _disconnectOutlet(outletName, parentView) { + var parent = parentRoute(this); if (parent && parentView === parent.routeName) { parentView = undefined; } - outletName = outletName || 'main'; - for (var i = 0; i < this.connections.length; i++) { var connection = this.connections[i]; if (connection.outlet === outletName && connection.into === parentView) { - this.connections.splice(i, 1); + // This neuters the disconnected outlet such that it doesn't + // render anything, but it leaves an entry in the outlet + // hierarchy so that any existing other renders that target it + // don't suddenly blow up. They will still stick themselves + // into its outlets, which won't render anywhere. All of this + // statefulness should get the machete in 2.0. + this.connections[i] = { + into: connection.into, + outlet: connection.outlet, + name: connection.name + }; run.once(this.router, '_setOutlets'); - return; } } }, diff --git a/packages/ember-routing/lib/system/router.js b/packages/ember-routing/lib/system/router.js index 22a0e70fe36..d0aeb3f718e 100644 --- a/packages/ember-routing/lib/system/router.js +++ b/packages/ember-routing/lib/system/router.js @@ -199,15 +199,18 @@ var EmberRouter = EmberObject.extend(Evented, { for (var i = 0; i < handlerInfos.length; i++) { route = handlerInfos[i].handler; - var connections = normalizedConnections(route); + var connections = route.connections; var ownState; for (var j = 0; j < connections.length; j++) { var appended = appendLiveRoute(liveRoutes, defaultParentState, connections[j]); liveRoutes = appended.liveRoutes; - if (appended.ownState.render.name === route.routeName) { + if (appended.ownState.render.name === route.routeName || appended.ownState.render.outlet === 'main') { ownState = appended.ownState; } } + if (connections.length === 0) { + ownState = representEmptyRoute(liveRoutes, defaultParentState, route); + } defaultParentState = ownState; } if (!this._toplevelView) { @@ -1028,34 +1031,27 @@ function appendLiveRoute(liveRoutes, defaultParentState, renderOptions) { }; } -function normalizedConnections(route) { - var connections = route.connections; - var mainConnections = []; - var otherConnections = []; - - for (var i = 0; i < connections.length; i++) { - var connection = connections[i]; - if (connection.outlet === 'main') { - mainConnections.push(connection); - } else { - otherConnections.push(connection); - } - } - - if (mainConnections.length === 0) { - // There's always an entry to represent the route, even if it - // doesn't actually render anything into its own - // template. This gives other routes a place to target. - mainConnections.push({ - name: route.routeName, - outlet: 'main' - }); +function representEmptyRoute(liveRoutes, defaultParentState, route) { + // the route didn't render anything + var alreadyAppended = findLiveRoute(liveRoutes, route.routeName); + if (alreadyAppended) { + // But some other route has already rendered our default + // template, so that becomes the default target for any + // children we may have. + return alreadyAppended; + } else { + // Create an entry to represent our default template name, + // just so other routes can target it and inherit its place + // in the outlet hierarchy. + defaultParentState.outlets.main = { + render: { + name: route.routeName, + outlet: 'main' + }, + outlets: {} + }; + return defaultParentState; } - - // We process main connections first, because a main connection may - // be targeted by other connections. - return mainConnections.concat(otherConnections); } - export default EmberRouter; diff --git a/packages/ember/tests/routing/basic_test.js b/packages/ember/tests/routing/basic_test.js index a92cd6e213f..829e820db43 100644 --- a/packages/ember/tests/routing/basic_test.js +++ b/packages/ember/tests/routing/basic_test.js @@ -3607,3 +3607,134 @@ QUnit.test("Can render into a named outlet at the top level, later", function() //router._setOutlets(); equal(Ember.$('#qunit-fixture').text(), "A-The index-B-Hello world-C", "second render"); }); + +QUnit.test("Can render routes with no 'main' outlet and their children", function() { + Ember.TEMPLATES.application = compile('