Skip to content

Commit

Permalink
Merge pull request #1398 from Polymer/0.8-nested-template
Browse files Browse the repository at this point in the history
0.8 nested template
  • Loading branch information
Steve Orvell committed Apr 10, 2015
2 parents 62c1bd2 + 8b114fe commit 9092b55
Show file tree
Hide file tree
Showing 10 changed files with 1,025 additions and 512 deletions.
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

0 comments on commit 9092b55

Please sign in to comment.