Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUGFIX beta] fix outlets inside render helper #10703

Merged
merged 1 commit into from
Mar 23, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(), '');
});