Skip to content

Commit

Permalink
Merge pull request #1514 from Polymer/0.8-nested-template-args
Browse files Browse the repository at this point in the history
Process nested templates in base, parse method args for parentProps.
  • Loading branch information
Steve Orvell committed May 12, 2015
2 parents 23190b1 + 312d50a commit ed7d25c
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 87 deletions.
46 changes: 2 additions & 44 deletions src/lib/annotations/annotations.html
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,7 @@

// 1. Parse annotations from the template and memoize them on
// content._notes (recurses into nested templates)
// 2. Parse template bindings for parent.* properties and memoize them on
// content._parentProps
// 3. Create bindings in current scope's annotation list to template for
// parent props found in template
// 4. Remove template.content and store it in annotation list, where it
// 2. Remove template.content and store it in annotation list, where it
// will be the responsibility of the host to set it back to the template
// (this is both an optimization to avoid re-stamping nested template
// children and avoids a bug in Chrome where nested template children
Expand All @@ -176,56 +172,18 @@
var content = document.createDocumentFragment();
content._notes = 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: index,
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: bindings,
bindings: Polymer.nar,
events: Polymer.nar,
templateContent: content,
parent: parent,
index: index
});
},

// Finds all bindings in template content and stores the path roots in
// the path members in content._parentProps. Each outer template merges
// inner _parentProps to propagate inner parent property needs to outer
// templates.
_discoverTemplateParentProps: function(content) {
var pp = content._parentProps = {};
content._notes.forEach(function(n) {
// Find all bindings to parent.* and spread them into _parentPropChain
n.bindings.forEach(function(b) {
var prop = b.value;
var dot = prop.indexOf('.');
prop = (dot < 0) ? prop : prop.slice(0, dot);
pp[prop] = true;
});
// Merge child _parentProps into this _parentProps
if (n.templateContent) {
var tpp = n.templateContent._parentProps;
Polymer.Base.mixin(pp, tpp);
}
});
},

// 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
32 changes: 12 additions & 20 deletions src/lib/template/templatizer.html
Original file line number Diff line number Diff line change
Expand Up @@ -124,28 +124,20 @@
},

_customPrepAnnotations: function(archetype, template) {
if (template) {
archetype._template = template;
var c = template._content;
if (c) {
var rootDataHost = archetype._rootDataHost;
if (rootDataHost) {
Polymer.Annotations.prepElement =
rootDataHost._prepElement.bind(rootDataHost);
}
archetype._notes = c._notes ||
Polymer.Annotations.parseAnnotations(template);
c._notes = archetype._notes;
Polymer.Annotations.prepElement = null;
archetype._parentProps = c._parentProps;
}
else {
console.warn('no _content');
archetype._template = template;
var c = template._content;
if (!c._notes) {
var rootDataHost = archetype._rootDataHost;
if (rootDataHost) {
Polymer.Annotations.prepElement =
rootDataHost._prepElement.bind(rootDataHost);
}
c._notes = Polymer.Annotations.parseAnnotations(template);
Polymer.Annotations.prepElement = null;
this._processAnnotations(c._notes);
}
else {
console.warn('no _template');
}
archetype._notes = c._notes;
archetype._parentProps = c._parentProps;
},

// Sets up accessors on the template to call abstract _forwardParentProp
Expand Down
60 changes: 60 additions & 0 deletions src/standard/annotations.html
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,70 @@
// TODO(sorvell): ad hoc method of plugging behavior into Annotations
Polymer.Annotations.prepElement = this._prepElement.bind(this);
this._notes = Polymer.Annotations.parseAnnotations(this._template);
this._processAnnotations(this._notes);
Polymer.Annotations.prepElement = null;
}
},

_processAnnotations: function(notes) {
for (var i=0; i<notes.length; i++) {
var note = notes[i];
// Parse bindings for methods & path roots (models)
for (var j=0; j<note.bindings.length; j++) {
var b = note.bindings[j];
b.signature = this._parseMethod(b.value);
if (!b.signature) {
b.model = this._modelForPath(b.value);
}
}
// Recurse into nested templates & bind parent props
if (note.templateContent) {
this._processAnnotations(note.templateContent._notes);
var pp = note.templateContent._parentProps =
this._discoverTemplateParentProps(note.templateContent._notes);
var bindings = [];
for (var prop in pp) {
bindings.push({
index: note.index,
kind: 'property',
mode: '{',
name: '_parent_' + prop,
model: prop,
value: prop
});
}
note.bindings = note.bindings.concat(bindings);
}
}
},

// Finds all bindings in template content and stores the path roots in
// the path members in content._parentProps. Each outer template merges
// inner _parentProps to propagate inner parent property needs to outer
// templates.
_discoverTemplateParentProps: function(notes) {
var pp = {};
notes.forEach(function(n) {
// Find all bindings to parent.* and spread them into _parentPropChain
n.bindings.forEach(function(b) {
if (b.signature) {
var args = b.signature.args;
for (var k=0; k<args.length; k++) {
pp[args[k].model] = true;
}
} else {
pp[b.model] = true;
}
});
// Merge child _parentProps into this _parentProps
if (n.templateContent) {
var tpp = n.templateContent._parentProps;
Polymer.Base.mixin(pp, tpp);
}
});
return pp;
},

_prepElement: function(element) {
Polymer.ResolveUrl.resolveAttrs(element, this._template.ownerDocument);
},
Expand Down
30 changes: 14 additions & 16 deletions src/standard/effects.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,7 @@
Polymer.Base._addFeature({

_addPropertyEffect: function(property, kind, effect) {
// TODO(sjmiles): everything to the right of the first '.' is lost, implies
// there is some duplicate information flow (not the only sign)
var model = property.split('.').shift();
Polymer.Bind.addPropertyEffect(this, model, kind, effect);
Polymer.Bind.addPropertyEffect(this, property, kind, effect);
},

// prototyping
Expand Down Expand Up @@ -79,13 +76,16 @@
if (m) {
return {
method: m[1],
args: m[2].split(/[^\w.*]+/).map(this._parseArg)
args: m[2].split(/[^\w.*]+/).map(this._parseArg, this)
};
}
},

_parseArg: function(arg) {
var a = { name: arg };
var a = {
name: arg,
model: this._modelForPath(arg)
};
a.structured = arg.indexOf('.') > 0;
if (a.structured) {
a.wildcard = (arg.slice(-2) == '.*');
Expand All @@ -99,7 +99,7 @@
_addComputedEffect: function(name, expression) {
var sig = this._parseMethod(expression);
sig.args.forEach(function(arg) {
this._addPropertyEffect(arg.name, 'compute', {
this._addPropertyEffect(arg.model, 'compute', {
method: sig.method,
args: sig.args,
arg: arg,
Expand All @@ -126,7 +126,7 @@
_addComplexObserverEffect: function(observer) {
var sig = this._parseMethod(observer);
sig.args.forEach(function(arg) {
this._addPropertyEffect(arg.name, 'complexObserver', {
this._addPropertyEffect(arg.model, 'complexObserver', {
method: sig.method,
args: sig.args,
arg: arg
Expand Down Expand Up @@ -154,22 +154,20 @@
Polymer.Bind._addAnnotatedListener(this, index,
note.name, note.value, note.event);
}
var sig = this._parseMethod(note.value);
if (sig) {
this._addAnnotatedComputationEffect(sig, note, index);
if (note.signature) {
this._addAnnotatedComputationEffect(note, index);
} else {
// capture the node index
note.index = index;
// discover top-level property (model) from path
var model = note.value.split('.').shift();
// add 'annotation' binding effect for property 'model'
this._addPropertyEffect(model, 'annotation', note);
this._addPropertyEffect(note.model, 'annotation', note);
}
},

_addAnnotatedComputationEffect: function(sig, note, index) {
_addAnnotatedComputationEffect: function(note, index) {
var sig = note.signature;
sig.args.forEach(function(arg) {
this._addPropertyEffect(arg.name, 'annotatedComputation', {
this._addPropertyEffect(arg.model, 'annotatedComputation', {
kind: note.kind,
method: sig.method,
args: sig.args,
Expand Down
3 changes: 2 additions & 1 deletion src/standard/notify-path.html
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,8 @@
},

_modelForPath: function(path) {
return path.split('.').shift();
var dot = path.indexOf('.');
return (dot < 0) ? path : path.slice(0, dot);
},

_EVENT_CHANGED: '-changed',
Expand Down
8 changes: 6 additions & 2 deletions test/smoke/dom-repeat.html
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
<x-labeled-input label="outerObjA.value" value="{{outerObjA.value}}"></x-labeled-input>
<x-labeled-input label="outerObjB.value" value="{{outerObjB.value}}"></x-labeled-input>
<x-labeled-input label="outerObjC.value" value="{{outerObjC.value}}"></x-labeled-input>
<x-labeled-input label="outerObjD.value" value="{{outerObjD.value}}"></x-labeled-input>
</div>

<div class="layout horizontal flex">
Expand All @@ -104,7 +105,7 @@
<x-labeled-input label="itemA.name" value="{{itemA.name}}"></x-labeled-input>
<x-labeled-input label="outerA" value="{{outerA}}"></x-labeled-input>
<x-labeled-input label="outerObjA.value" value="{{outerObjA.value}}"></x-labeled-input>
<x-labeled-input label="computeConcat(itemA.name, outerObjA.value)" value="{{computeConcat(itemA.name, outerObjA.value)}}" disabled></x-labeled-input>
<x-labeled-input label="computeConcat(itemA.name, outerObjD.value)" value="{{computeConcat(itemA.name, outerObjD.value)}}" disabled></x-labeled-input>
<template is="dom-repeat" id="xr1-2" idx$="{{index}}" items="{{itemA.items}}" as="itemB">
<div class="item" id="xr1-2" idx$="{{index}}">
<div class="index">idx: <span>{{index}}</span></div>
Expand Down Expand Up @@ -143,7 +144,7 @@
<x-labeled-input label="itemA.name" value="{{itemA.name}}"></x-labeled-input>
<x-labeled-input label="outerA" value="{{outerA}}"></x-labeled-input>
<x-labeled-input label="outerObjA.value" value="{{outerObjA.value}}"></x-labeled-input>
<x-labeled-input label="computeConcat(itemA.name, outerObjA.value)" value="{{computeConcat(itemA.name, outerObjA.value)}}" disabled></x-labeled-input>
<x-labeled-input label="computeConcat(itemA.name, outerObjD.value)" value="{{computeConcat(itemA.name, outerObjD.value)}}" disabled></x-labeled-input>
<template is="dom-repeat" id="xr2-2" idx$="{{index}}" items="{{itemA.items}}" as="itemB">
<div class="item" id="xr2-2" idx$="{{index}}">
<div class="index">idx: <span>{{index}}</span></div>
Expand Down Expand Up @@ -297,6 +298,9 @@
},
outerObjC: {
value: function() { return { value: 'outerObjC.value' }; }
},
outerObjD: {
value: function() { return { value: 'outerObjD.value' }; }
}
},
changeOuterA: function() {
Expand Down
26 changes: 22 additions & 4 deletions test/unit/dom-repeat-elements.html
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,10 @@
innerb-prop="{{innerbProp}}"
itemb-prop="{{itembProp}}"
innerc-prop="{{innercProp}}"
itemc-prop="{{itemcProp}}">
itemc-prop="{{itemcProp}}"
computed1="{{computed1}}"
computed2="{{computed2}}"
computed3="{{computed3}}">
</x-bar>
</template>
</dom-module>
Expand Down Expand Up @@ -267,7 +270,8 @@
itema-prop="{{itema.prop}}"
outer-prop="{{prop}}"
outer-item-prop="{{item.prop}}"
indexa="{{indexa}}">
indexa="{{indexa}}"
computeda="{{concat(itema.prop, itemForComputedA.prop)}}">
</x-foo>
<template is="dom-repeat" items="{{itema.items}}" as="itemb" index-as="indexb">
<x-foo
Expand All @@ -278,7 +282,8 @@
outer-prop="{{prop}}"
outer-item-prop="{{item.prop}}"
indexa="{{indexa}}"
indexb="{{indexb}}">
indexb="{{indexb}}"
computedb="{{concat(itemb.prop, itemForComputedB.prop)}}">
</x-foo>
<template is="dom-repeat" items="{{itemb.items}}" as="itemc" index-as="indexc">
<x-foo
Expand All @@ -292,7 +297,8 @@
outer-item-prop="{{item.prop}}"
indexa="{{indexa}}"
indexb="{{indexb}}"
indexc="{{indexc}}">
indexc="{{indexc}}"
computedc="{{concat(itemc.prop, itemForComputedC.prop)}}">
</x-foo>
</template>
</template>
Expand All @@ -312,6 +318,15 @@
item: {
value: function() { return {prop: 'outerItem'}; }
},
itemForComputedA: {
value: function() { return {prop: 'itemForComputedA'}; }
},
itemForComputedB: {
value: function() { return {prop: 'itemForComputedB'}; }
},
itemForComputedC: {
value: function() { return {prop: 'itemForComputedC'}; }
},
_testHost: {
value: function() { return this; }
}
Expand All @@ -323,6 +338,9 @@
filter2nd: function(o) {
assert.equal(this, this._testHost);
return o.prop.indexOf('2') < 0;
},
concat: function(a, b) {
return a + '+' + b;
}
});
</script>
3 changes: 3 additions & 0 deletions test/unit/dom-repeat.html
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,16 @@ <h4>inDocumentRepeater</h4>
var stamped = Polymer.dom(configured.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
assert.equal(stamped[0].itemaProp, 'prop-1');
assert.equal(stamped[0].computeda, 'prop-1+itemForComputedA');
assert.equal(stamped[0].indexa, 0);
assert.equal(stamped[0].$.bar.itemaProp, 'prop-1');
assert.equal(stamped[1].itembProp, 'prop-1-1');
assert.equal(stamped[1].computedb, 'prop-1-1+itemForComputedB');
assert.equal(stamped[1].indexa, 0);
assert.equal(stamped[1].indexb, 0);
assert.equal(stamped[1].$.bar.itembProp, 'prop-1-1');
assert.equal(stamped[2].itemcProp, 'prop-1-1-1');
assert.equal(stamped[2].computedc, 'prop-1-1-1+itemForComputedC');
assert.equal(stamped[2].indexa, 0);
assert.equal(stamped[2].indexb, 0);
assert.equal(stamped[2].indexc, 0);
Expand Down

0 comments on commit ed7d25c

Please sign in to comment.