Skip to content

Commit

Permalink
Merge pull request #10703 from ef4/fix-#10602
Browse files Browse the repository at this point in the history
[BUGFIX beta] fix outlets inside render helper
  • Loading branch information
rwjblue committed Mar 23, 2015
2 parents bb66982 + d8a16a4 commit 73b4060
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 3 deletions.
46 changes: 46 additions & 0 deletions packages/ember-routing-htmlbars/lib/helpers/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import { isStream } from "ember-metal/streams/utils";
import mergeViewBindings from "ember-htmlbars/system/merge-view-bindings";
import appendTemplatedView from "ember-htmlbars/system/append-templated-view";
import create from 'ember-metal/platform/create';

/**
Calling ``{{render}}`` from within a template will insert another
Expand Down Expand Up @@ -191,6 +192,51 @@ export function renderHelper(params, hash, options, env) {
helperName: 'render "' + name + '"'
};

impersonateAnOutlet(currentView, view, name);
mergeViewBindings(currentView, props, hash);
appendTemplatedView(currentView, options.morph, view, props);
}

// Megahax to make outlets inside the render helper work, until we
// can kill that behavior at 2.0.
function impersonateAnOutlet(currentView, view, name) {
view._childOutlets = Ember.A();
view._isOutlet = true;
view._outletName = '__ember_orphans__';
view._matchOutletName = name;
view.setOutletState = function(state) {
var ownState;
if (state && (ownState = state.outlets[this._matchOutletName])) {
this._outletState = {
render: { name: 'render helper stub' },
outlets: create(null)
};
this._outletState.outlets[ownState.render.outlet] = ownState;
ownState.wasUsed = true;
} else {
this._outletState = null;
}
for (var i = 0; i < this._childOutlets.length; i++) {
var child = this._childOutlets[i];
child.setOutletState(this._outletState && this._outletState.outlets[child._outletName]);
}
};

var pointer = currentView;
var po;
while (pointer && !pointer._isOutlet) {
pointer = pointer._parentView;
}
while (pointer && (po = pointer._parentOutlet())) {
pointer = po;
}
if (pointer) {
// we've found the toplevel outlet. Subscribe to its
// __ember_orphan__ child outlet, which is our hack convention for
// stashing outlet state that may target the render helper.
pointer._childOutlets.push(view);
if (pointer._outletState) {
view.setOutletState(pointer._outletState.outlets[view._outletName]);
}
}
}
2 changes: 1 addition & 1 deletion packages/ember-routing-views/lib/views/outlet.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { get } from "ember-metal/property_get";
export var CoreOutletView = ContainerView.extend({
init() {
this._super();
this._childOutlets = [];
this._childOutlets = Ember.A();
this._outletState = null;
},

Expand Down
31 changes: 29 additions & 2 deletions packages/ember-routing/lib/system/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -1022,15 +1022,42 @@ function appendLiveRoute(liveRoutes, defaultParentState, renderOptions) {
if (target) {
set(target.outlets, renderOptions.outlet, myState);
} else {
Ember.assert("You attempted to render into '" + renderOptions.into + "' but it was not found", !renderOptions.into);
liveRoutes = myState;
if (renderOptions.into) {
// Megahax time. Post-2.0-breaking-changes, we will just assert
// right here that the user tried to target a nonexistent
// thing. But for now we still need to support the `render`
// helper, and people are allowed to target templates rendered
// by the render helper. So instead we defer doing anyting with
// these orphan renders until afterRender.
appendOrphan(liveRoutes, renderOptions.into, myState);
} else {
liveRoutes = myState;
}
}
return {
liveRoutes: liveRoutes,
ownState: myState
};
}

function appendOrphan(liveRoutes, into, myState) {
if (!liveRoutes.outlets.__ember_orphans__) {
liveRoutes.outlets.__ember_orphans__ = {
render: {
name: '__ember_orphans__'
},
outlets: create(null)
};
}
liveRoutes.outlets.__ember_orphans__.outlets[into] = myState;
Ember.run.schedule('afterRender', function() {
// `wasUsed` gets set by the render helper. See the function
// `impersonateAnOutlet`.
Ember.assert("You attempted to render into '" + into + "' but it was not found",
liveRoutes.outlets.__ember_orphans__.outlets[into].wasUsed);
});
}

function representEmptyRoute(liveRoutes, defaultParentState, route) {
// the route didn't render anything
var alreadyAppended = findLiveRoute(liveRoutes, route.routeName);
Expand Down
110 changes: 110 additions & 0 deletions packages/ember/tests/routing/basic_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3738,3 +3738,113 @@ QUnit.test("Allows any route to disconnectOutlet another route's templates", fun
Ember.run(router, 'send', 'close');
equal(Ember.$('#qunit-fixture').text().trim(), 'hi');
});

QUnit.test("Can render({into:...}) the render helper", function() {
Ember.TEMPLATES.application = compile('{{render "foo"}}');
Ember.TEMPLATES.foo = compile('<div class="foo">{{outlet}}</div>');
Ember.TEMPLATES.index = compile('other');
Ember.TEMPLATES.bar = compile('bar');

App.IndexRoute = Ember.Route.extend({
renderTemplate() {
this.render({ into: 'foo' });
},
actions: {
changeToBar: function() {
this.disconnectOutlet({
parentView: 'foo',
outlet: 'main'
});
this.render('bar', { into: 'foo' });
}
}
});

bootApplication();
equal(Ember.$('#qunit-fixture .foo').text(), 'other');
Ember.run(router, 'send', 'changeToBar');
equal(Ember.$('#qunit-fixture .foo').text(), 'bar');
});

QUnit.test("Can disconnect from the render helper", function() {
Ember.TEMPLATES.application = compile('{{render "foo"}}');
Ember.TEMPLATES.foo = compile('<div class="foo">{{outlet}}</div>');
Ember.TEMPLATES.index = compile('other');

App.IndexRoute = Ember.Route.extend({
renderTemplate() {
this.render({ into: 'foo' });
},
actions: {
disconnect: function() {
this.disconnectOutlet({
parentView: 'foo',
outlet: 'main'
});
}
}
});

bootApplication();
equal(Ember.$('#qunit-fixture .foo').text(), 'other');
Ember.run(router, 'send', 'disconnect');
equal(Ember.$('#qunit-fixture .foo').text(), '');
});


QUnit.test("Can render({into:...}) the render helper's children", function() {
Ember.TEMPLATES.application = compile('{{render "foo"}}');
Ember.TEMPLATES.foo = compile('<div class="foo">{{outlet}}</div>');
Ember.TEMPLATES.index = compile('<div class="index">{{outlet}}</div>');
Ember.TEMPLATES.other = compile('other');
Ember.TEMPLATES.bar = compile('bar');

App.IndexRoute = Ember.Route.extend({
renderTemplate() {
this.render({ into: 'foo' });
this.render('other', { into: 'index' });
},
actions: {
changeToBar: function() {
this.disconnectOutlet({
parentView: 'index',
outlet: 'main'
});
this.render('bar', { into: 'index' });
}
}
});

bootApplication();
equal(Ember.$('#qunit-fixture .foo .index').text(), 'other');
Ember.run(router, 'send', 'changeToBar');
equal(Ember.$('#qunit-fixture .foo .index').text(), 'bar');

});

QUnit.test("Can disconnect from the render helper's children", function() {
Ember.TEMPLATES.application = compile('{{render "foo"}}');
Ember.TEMPLATES.foo = compile('<div class="foo">{{outlet}}</div>');
Ember.TEMPLATES.index = compile('<div class="index">{{outlet}}</div>');
Ember.TEMPLATES.other = compile('other');

App.IndexRoute = Ember.Route.extend({
renderTemplate() {
this.render({ into: 'foo' });
this.render('other', { into: 'index' });
},
actions: {
disconnect: function() {
this.disconnectOutlet({
parentView: 'index',
outlet: 'main'
});
}
}
});

bootApplication();
equal(Ember.$('#qunit-fixture .foo .index').text(), 'other');
Ember.run(router, 'send', 'disconnect');
equal(Ember.$('#qunit-fixture .foo .index').text(), '');
});

0 comments on commit 73b4060

Please sign in to comment.