diff --git a/layout-test.html b/layout-test.html
index c9f3f26..13588a6 100644
--- a/layout-test.html
+++ b/layout-test.html
@@ -26,6 +26,15 @@
{{> yield region="footer"}}
+
+ {{#with childData}}
+ {{> yield}}
+ {{/with}}
+ {{#with childData}}
+ {{> yield region="footer"}}
+ {{/with}}
+
+
child {{title}}
@@ -54,3 +63,27 @@
footer
{{/contentFor}}
+
+
+ {{#Layout template='LayoutWithTwoYields' footer='One'}}
+ inside
+ {{/Layout}}
+
+
+
+ {{#Layout template='LayoutWithTwoYields' footer=footerHelper}}
+ inside
+ {{/Layout}}
+
+
+
+ callback
+
+
+
+ {{#with title="ok"}}
+ {{#Layout template="LayoutWithOneYield"}}
+ inner{{title}}
+ {{/Layout}}
+ {{/with}}
+
diff --git a/layout-test.js b/layout-test.js
index 24daac8..386ebf5 100644
--- a/layout-test.js
+++ b/layout-test.js
@@ -98,6 +98,12 @@ Tinytest.add('layout - default main region using Layout template', function (tes
});
});
+Tinytest.add('layout - default data context using Layout template', function (test) {
+ withRenderedComponent(Template.DefaultDataForLayout, function (cmp, screen) {
+ test.equal(screen.innerHTML.compact(), 'layoutinnerok', 'default data context should be outer data context');
+ });
+});
+
Tinytest.add('layout - dynamic yield regions', function (test) {
withRenderedLayout({template: 'LayoutWithTwoYields'}, function (layout, screen) {
var renderedCount = 1;
@@ -222,3 +228,66 @@ Tinytest.add('layout - region templates not found in lookup', function (test) {
document.body.removeChild(div);
}
});
+
+
+Tinytest.add('layout - set regions via arguments - static', function (test) {
+ withRenderedLayout({template: 'RegionArgumentsTestStatic'}, function (layout, screen) {
+ test.equal(screen.innerHTML.compact(), 'insideone', 'One template should render into footer region');
+ });
+});
+
+Tinytest.add('layout - set regions via arguments - dynamic', function (test) {
+ var footerTemplate = new ReactiveVar('One');
+ Template.RegionArgumentsTestDynamic.footerHelper = function() { return footerTemplate.get(); }
+
+ withRenderedLayout({template: 'RegionArgumentsTestDynamic'}, function (layout, screen) {
+ test.equal(screen.innerHTML.compact(), 'insideone', 'One template should render into footer region');
+
+ footerTemplate.set('Two');
+ Deps.flush()
+ test.equal(screen.innerHTML.compact(), 'insidetwo', 'Two template should render into footer region');
+ });
+});
+
+// SEE IR#276 for detailed discussion
+Tinytest.add('layout - Templates render with correct data even if setData is called after setRegion', function (test) {
+ withRenderedLayout({template: 'LayoutWithOneYield'}, function (layout, screen) {
+ Template.TemplateWithHelper = function() {};
+ layout.setData(false);
+ layout.setRegion('One');
+ Deps.flush();
+ test.equal(screen.innerHTML.compact(), 'layoutone');
+
+ Template.TemplateWithCreatedCallback.created = function() {
+ test.equal(this.data, true);
+ }
+
+ layout.setRegion('TemplateWithCreatedCallback');
+ layout.setData(true);
+ Deps.flush();
+ test.equal(screen.innerHTML.compact(), 'layoutcallback');
+ });
+});
+
+// XXX: This test doesn't work.
+// To be totally honest, I'm not sure how it *should* work -- should
+// the yield be getting the data context of the with block? Maybe..
+// perhaps yield.data() should look at parent's data (modolo __isTemplateWith)
+// just like layout does.
+//
+// Tinytest.add('layout - set data via with', function (test) {
+// withRenderedLayout({template: 'LayoutThatSetsData'}, function (layout, screen) {
+// layout.setRegion('main', 'ChildWithData');
+// layout.setRegion('footer', 'FooterWithData');
+//
+// layout.setData({
+// title: 'parentTitle',
+// childData: {
+// title: 'childTitle'
+// }
+// });
+//
+// Deps.flush();
+// test.equal(screen.innerHTML.compact(), 'childchildTitlefooterchildTitle');
+// });
+// });
diff --git a/layout.js b/layout.js
index 454f7c0..76ad03a 100644
--- a/layout.js
+++ b/layout.js
@@ -104,11 +104,21 @@ Layout = UI.Component.extend({
var tmplDep = new Deps.Dependency;
// get the initial data value
- var data = Deps.nonreactive(function () { return self.get(); });
+ var data, dataSet = false;
var dataDep = new Deps.Dependency;
var regions = this._regions = new ReactiveDict;
var content = this.__content;
-
+
+ // look first in regions that have been explicitly set, then data
+ var regionCaches = {};
+ var getRegion = function(region) {
+ regionCaches[region] = regionCaches[region] || Deps.cache(function () {
+ return self._regions.get(region) || self.get(region);
+ });
+
+ return regionCaches[region].get()
+ }
+
// a place to put content defined like this:
// {{#contentFor region="footer"}}content{{/contentFor}}
// this will be searched in the lookup chain.
@@ -134,13 +144,28 @@ Layout = UI.Component.extend({
};
var cachedData = Deps.cache(function () {
- log('return data()');
dataDep.depend();
- return data;
+ if (dataSet) {
+ return data;
+ } else {
+ // find the closest parent with a data context.
+ // If it's the direct parent, and it has `__isTemplateWith` set,
+ // then it's because we have `{{#Layout foo=bar}}` and we should ignore
+ var parent = self.parent;
+ if (parent) {
+ if (parent.__isTemplateWith)
+ parent = parent.parent;
+ return getComponentData(parent);
+ } else {
+ // the only time we don't have a parent is when we are in tests really
+ return null
+ }
+ }
});
this.setData = function (value) {
- log('setData', value);
+ dataSet = true;
+ log('setData', EJSON.stringify(value, 2));
if (!EJSON.equals(value, data)) {
data = value;
dataDep.changed();
@@ -149,13 +174,10 @@ Layout = UI.Component.extend({
this.getData = function () {
var val = cachedData.get();
+ log('return data()', EJSON.stringify(val, 2));
return val;
};
- this.data = function () {
- return self.getData();
- };
-
/**
* Set a region template.
*
@@ -164,6 +186,7 @@ Layout = UI.Component.extend({
*
*/
this.setRegion = function (key, value) {
+ log('setRegion', key, value);
if (arguments.length < 2) {
value = key;
key = 'main';
@@ -200,10 +223,17 @@ Layout = UI.Component.extend({
region = 'main';
self.region = region;
+ self.text = !! (data && data.text);
// reset the data function to use the layout's
// data
this.data = function () {
+ // XXX: should we be instead trying to sensibly get parent's
+ // data -- much like layout.getData() does?
+ // then we'd expect to inherit layout.getData() (via layout.render)
+ // unless we wrapped our {{> yield}} in a with.
+ // is this what users would expect?
+ // see 'layout - set data via with' test
return layout.getData();
};
},
@@ -217,9 +247,12 @@ Layout = UI.Component.extend({
// changes, this comp will be rerun and the new template
// will get put on the screen.
return function () {
- var regions = layout._regions;
// create a reactive dep
- var tmpl = regions.get(region);
+ var tmpl = getRegion(region);
+ log('rendering yield', region, tmpl)
+
+ if (self.text)
+ return tmpl;
if (tmpl)
return lookupTemplate.call(layout, tmpl);
@@ -231,6 +264,10 @@ Layout = UI.Component.extend({
};
}
});
+
+ this.hasYield = function(region) {
+ return !! getRegion(region);
+ };
// render content into a yield region using markup. when you call setRegion
// manually, you specify a string, not a content block. And the
@@ -289,38 +326,40 @@ Layout = UI.Component.extend({
render: function () {
var self = this;
- // return a function to create a reactive
- // computation. so if the template changes
- // the layout is re-endered.
- return function () {
- // reactive
- var tmplName = self.template();
-
- //XXX hack to make work with null/false values.
- //see this.template = in ctor function.
- if (tmplName === '_defaultLayout')
- return self._defaultLayout;
- else if (tmplName) {
- var tmpl = lookupTemplate.call(self, tmplName);
- // it's a component
- if (typeof tmpl.instantiate === 'function')
- // See how __pasthrough is used in overrides.js
- // findComponentWithHelper. If __passthrough is true
- // then we'll continue past this component in looking
- // up a helper method. This allows this use case:
- //
- // {{#Layout template="SomeLayout"}}
- // I want a helper method on SomeParent
- // called {{someHelperMethod}}
- // {{/Layout}}
- //
- tmpl.__passthrough = true;
- return tmpl;
- }
- else {
- return self['yield'];
+ return UI.With(_.bind(self.getData, self), UI.block(function () {
+ // return a function to create a reactive
+ // computation. so if the template changes
+ // the layout is re-endered.
+ return function() {
+ // reactive
+ var tmplName = self.template();
+
+ //XXX hack to make work with null/false values.
+ //see this.template = in ctor function.
+ if (tmplName === '_defaultLayout')
+ return self._defaultLayout;
+ else if (tmplName) {
+ var tmpl = lookupTemplate.call(self, tmplName);
+ // it's a component
+ if (typeof tmpl.instantiate === 'function')
+ // See how __pasthrough is used in overrides.js
+ // findComponentWithHelper. If __passthrough is true
+ // then we'll continue past this component in looking
+ // up a helper method. This allows this use case:
+ //
+ // {{#Layout template="SomeLayout"}}
+ // I want a helper method on SomeParent
+ // called {{someHelperMethod}}
+ // {{/Layout}}
+ //
+ tmpl.__passthrough = true;
+ return tmpl;
+ }
+ else {
+ return self['yield'];
+ }
}
- };
+ }));
}
});
diff --git a/overrides.js b/overrides.js
index 319bcb8..6e26e87 100644
--- a/overrides.js
+++ b/overrides.js
@@ -53,7 +53,7 @@ UI.Component.lookup = function (id, opts) {
if (id === 'yield') {
throw new Error("Sorry, would you mind using {{> yield}} instead of {{yield}}? It helps the Blaze engine.");
- } else if (id === 'contentFor') {
+ } else if (id === 'contentFor' || id === 'hasYield') {
var layout = findComponentOfKind('Layout', this);
if (!layout)
throw new Error("Couldn't find a Layout component in the rendered component tree");