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

0.8 nested template #1398

Merged
merged 13 commits into from
Apr 10, 2015
1 change: 1 addition & 0 deletions polymer.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,4 @@
<link rel="import" href="src/lib/template/x-template.html">
<link rel="import" href="src/lib/template/x-repeat.html">
<link rel="import" href="src/lib/template/x-array-selector.html">
<link rel="import" href="src/lib/template/x-if.html">
55 changes: 54 additions & 1 deletion src/lib/annotations/annotations.html
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,29 @@
// TODO(sjmiles): simply altering the .content reference didn't
// work (there was some confusion, might need verification)
var content = document.createDocumentFragment();
content._annotes = this.parseAnnotations(node);
content.appendChild(node.content);
// Special-case treatment of 'parent.*' props for nested templates
// Automatically bind `prop` on host to `_parent_prop` on template
// for any `parent.prop`'s encountered in template binding; it is
// responsibility of the template implementation to forward
// these properties as appropriate
var bindings = [];
this._discoverTemplateParentProps(content);
for (var prop in content._parentProps) {
bindings.push({
index: i,
kind: 'property',
mode: '{',
name: '_parent_' + prop,
value: prop
});
}
// TODO(sjmiles): using `nar` to avoid unnecessary allocation;
// in general the handling of these arrays needs some cleanup
// in this module
list.push({
bindings: Polymer.nar,
bindings: bindings,
events: Polymer.nar,
templateContent: content,
parent: annote,
Expand All @@ -164,6 +181,42 @@
}
},

// Finds all parent.* properties in template content and stores
// the path members in content._parentPropChain, which is an array
// of maps listing the properties of parent templates required at
// each level. Each outer template merges inner _parentPropChains to
// propagate inner parent property needs to outer templates.
// The top-level parent props from the chain (corresponding to this
// template) are stored in content._parentProps.
_discoverTemplateParentProps: function(content) {
var chain = content._parentPropChain = [];
content._annotes.forEach(function(n) {
// Find all bindings to parent.* and spread them into _parentPropChain
n.bindings.forEach(function(b) {
var m;
if (m = b.value.match(/parent\.((parent\.)*[^.]*)/)) {
var parts = m[1].split('.');
for (var i=0; i<parts.length; i++) {
var pp = chain[i] || (chain[i] = {});
pp[parts[i]] = true;
}
}
});
// Merge child _parentPropChain[n+1] into this _parentPropChain[n]
if (n.templateContent) {
var tpp = n.templateContent._parentPropChain;
for (var i=1; i<tpp.length; i++) {
if (tpp[i]) {
var pp = chain[i-1] || (chain[i-1] = {});
Polymer.Base.simpleMixin(pp, tpp[i]);
}
}
}
});
// Store this template's parentProps map
content._parentProps = chain[0];
},

// add annotation data from attributes to the `annotation` for node `node`
// TODO(sjmiles): the distinction between an `annotation` and
// `annotation data` is not as clear as it could be
Expand Down
11 changes: 9 additions & 2 deletions src/lib/bind/effects.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@
this._notifyChange(source);
},

// Raw effect for extension; info.function is an actual function
functionEffect: function(source, value, info, old) {
info.function.call(this, source, value, info, old);
},

observerEffect: function(source, value, info, old) {
//console.log(value, info);
if (info.property) {
Expand All @@ -58,8 +63,10 @@
annotatedComputationEffect: function(source, value, info) {
var args = Polymer.Bind._marshalArgs(this._data, info.args);
if (args) {
var value = this[info.methodName].apply(this, args);
this._applyEffectValue(value, info);
var computedHost = this._rootDataHost || this;
var computedvalue =
computedHost[info.methodName].apply(computedHost, args);
this._applyEffectValue(computedvalue, info);
}
},

Expand Down
148 changes: 121 additions & 27 deletions src/lib/template/templatizer.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
Polymer.Templatizer = {

templatize: function(template) {
this._templatized = template;
// TODO(sjmiles): supply _alternate_ content reference missing from root
// templates (not nested). `_content` exists to provide content sharing
// for nested templates.
Expand All @@ -23,29 +24,31 @@
if (template._content._ctor) {
this.ctor = template._content._ctor;
//console.log('Templatizer.templatize: using memoized archetype');
// forward parent properties to archetype
this._prepParentProperties(this.ctor.prototype);
return;
}
// `archetype` is the prototype of the anonymous
// class created by the templatizer
var archetype = Object.create(Polymer.Base);
archetype.host = this;
archetype.contentHost = this._getContentHost();
var rootDataHost = this._getRootDataHost();
archetype._rootDataHost = rootDataHost;
// normally Annotations.parseAnnotations(template) but
// archetypes do special caching
this.customPrepAnnotations(archetype, template);
// setup accessors
archetype._prepEffects();
// forward parent properties to archetype
this._prepParentProperties(archetype);
// late-binds archetype.listen to host.listen; h.l doesn't exist yet
archetype.listen = function() {
this.listen.apply(this, arguments);
}.bind(this.host);
archetype.listen = rootDataHost.listen.bind(rootDataHost);

// boilerplate code
archetype._notifyPath = this._notifyPathImpl;
// boilerplate code
var _constructor = this._constructorImpl;
var ctor = function TemplateInstance(model) {
_constructor.call(this, model);
var ctor = function TemplateInstance(model, host) {
_constructor.call(this, model, host);
};
// standard references
ctor.prototype = archetype;
Expand All @@ -56,24 +59,31 @@
this.ctor = ctor;
},

_getContentHost: function() {
return (this.host && this.host.contentHost) || this.host;
_getRootDataHost: function() {
return (this.dataHost && this.dataHost._rootDataHost) || this.dataHost;
},

_getAllStampedChildren: function(children) {
children = children || [];
if (this._getStampedChildren) {
var c$ = this._getStampedChildren();
for (var i=0, c; c = c$[i]; i++) {
children.push(c);
if (c._getAllStampedChildren) {
c._getAllStampedChildren(children);
}
}
}
return children;
},

customPrepAnnotations: function(archetype, template) {
if (template) {
archetype._template = template;
var c = template._content;
if (c) {
var contentHost = archetype.contentHost;
if (contentHost) {
Polymer.Annotations.prepElement =
contentHost._prepElement.bind(contentHost);
}
archetype._annotes = c._annotes ||
Polymer.Annotations.parseAnnotations(template);
c._annotes = archetype._annotes;
Polymer.Annotations.prepElement = null;
archetype._annotes = c._annotes;
archetype._parentProps = c._parentProps;
}
else {
console.warn('no _content');
Expand All @@ -84,19 +94,94 @@
}
},

_notifyPathImpl: function() {
var pd = this.pathDelegate;
if (pd) {
var args = Array.prototype.slice.call(arguments);
args.unshift(this);
pd._notifyDelegatePath.apply(pd, args);
// Sets up accessors on the template to call abstract _forwardParentProp
// API that should be implemented by Templatizer users to get parent
// properties to their template instances. These accessors are memoized
// on the archetype and copied to instances.
_prepParentProperties: function(archetype) {
var parentProps = this._parentProps = archetype._parentProps;
if (this._forwardParentProp && parentProps) {
// Prototype setup (memoized on archetype)
var proto = archetype._parentPropProto;
if (!proto) {
proto = archetype._parentPropProto = Object.create(null);
if (this._templatized != this) {
// Assumption: if `this` isn't the template being templatized,
// assume that the template is not a Poylmer.Base, so prep it
// for binding
Polymer.Bind.prepareModel(proto);
}
// Create accessors for each parent prop that forward the property
// to template instances through abstract _forwardParentProp API
// that should be implemented by Templatizer users
for (var prop in parentProps) {
var parentProp = '_parent_' + prop;
var effects = [{
kind: 'function',
effect: { function: function(prop, source, value) {
this._forwardParentProp(prop, value);
}.bind(this, prop)}
}];
Polymer.Bind._createAccessors(proto, parentProp, effects);
}
}
// Instance setup
if (this._templatized != this) {
Polymer.Bind.prepareInstance(this._templatized);
}
this._extendTemplate(this._templatized, proto);
}
},

// Similar to Polymer.Base.extend, but retains any previously set instance
// values (_propertySet back on instance once accessor is installed)
_extendTemplate: function(template, proto) {
Object.getOwnPropertyNames(proto).forEach(function(n) {
var val = template[n];
var pd = Object.getOwnPropertyDescriptor(proto, n);
Object.defineProperty(template, n, pd);
if (val !== undefined) {
template._propertySet(n, val);
}
});
},

_notifyPathImpl: function(path, value) {
var p = path.match(/([^.]*)\.(([^.]*).*)/);
// 'root.sub.path'
var root = p[1]; // 'root'
var sub = p[3]; // 'sub'
var subPath = p[2]; // 'sub.path'
// Notify host of parent.* path/property changes
var dataHost = this.dataHost;
if (root == 'parent') {
if (sub == subPath) {
dataHost.dataHost[sub] = value;
} else {
dataHost.notifyPath('_parent_' + subPath, value);
}
}
// Extension point for Templatizer sub-classes
if (dataHost._forwardInstancePath) {
dataHost._forwardInstancePath.call(dataHost, this, root, subPath, value);
}
},

_constructorImpl: function(model) {
// Overrides Base notify-path module
_pathEffector: function(path, value, fromAbove) {
if (this._forwardParentPath) {
if (path.indexOf('_parent_') === 0) {
this._forwardParentPath(path.substring(8), value);
}
}
Polymer.Base._pathEffector.apply(this, arguments);
},

_constructorImpl: function(model, host) {
this._setupConfigure(model);
this._pushHost(this.host);
this._pushHost(host);
this.root = this.instanceTemplate(this._template);
this.root.__styleScoped = true;
this._popHost();
this._marshalAnnotatedNodes();
this._marshalInstanceEffects();
Expand All @@ -105,7 +190,16 @@
},

stamp: function(model) {
return new this.ctor(model);
model = model || {};
if (this._parentProps) {
// TODO(kschaaf): Maybe this is okay
// model.parent = this.dataHost;
model.parent = model.parent || {};
for (var prop in this._parentProps) {
model.parent[prop] = this['_parent_' + prop];
}
}
return new this.ctor(model, this);
}

// TODO(sorvell): note, using the template as host is ~5-10% faster if
Expand Down
Loading