Skip to content

Commit b9fafb7

Browse files
committed
Address feedback from review:
* Refactor `_bindTemplate` to remove problematic `hasCreatedAccessors` * Remove vestigial `dom` from `_bindTemplate` call * Rename `_unstampTemplate` to `_removeBoundDom` * Add `infoIndex` to `nodeInfo` (and renamed parent & index) * Add test to ensure runtime accessors created for new props in runtime stamped template * Changed custom binding test to use different prop names * Added test for #first count after removing bound dom
1 parent dff5f2b commit b9fafb7

3 files changed

Lines changed: 80 additions & 56 deletions

File tree

lib/mixins/property-effects.html

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -645,7 +645,6 @@
645645
addNotifyListener(node, inst, binding);
646646
}
647647
}
648-
addNotifyListener
649648
node.__dataHost = inst;
650649
}
651650
}
@@ -1999,35 +1998,40 @@
19991998
* templates can be efficiently stamped and unstamped.
20001999
*
20012000
* This method may be called on the prototype (for prototypical template
2002-
* binding) once per prototype, or on the instance for either the
2003-
* prototypically bound template and/or one or more templates to stamp
2004-
* bound to the instance.
2005-
*
2006-
* Note that a template may only be stamped by one element; stamping
2007-
* the same template by multiple element types is not currently supported.
2001+
* binding, to avoid creating accessors every instance) once per prototype,
2002+
* and will be called with `runtimeBinding: true` by `_stampTemplate` to
2003+
* create and link an instance of the template metadata associated with a
2004+
* particular stamping.
20082005
*
20092006
* @param {HTMLTemplateElement} template Template containing binding
20102007
* bindings
2011-
* @param {DocumentFragment=} dom Stamped DOM for this template to be
2012-
* bound to the instance (leave undefined for prototypical template
2013-
* binding)
2014-
* @return {Object} Instanced template metadata object
2008+
* @param {boolean=} runtimeBinding When false (default), performs
2009+
* "prototypical" binding of the template and overwrites any previously
2010+
* bound template for the class. When true (as passed from
2011+
* `_stampTemplate`), the template info is instanced and linked into
2012+
* the list of bound templates.
2013+
* @return {Object} Template metadata object; for `runtimeBinding`,
2014+
* this is an instance of the prototypical template info
20152015
* @protected
20162016
*/
2017-
_bindTemplate(template) {
2017+
_bindTemplate(template, instanceBinding) {
20182018
let templateInfo = this.constructor._parseTemplate(template);
2019-
if (!templateInfo.hasCreatedAccessors) {
2020-
// Create accessors for any template effects
2019+
let wasPreBound = this.__templateInfo == templateInfo;
2020+
// Optimization: since this is called twice for proto-bound templates,
2021+
// don't attempt to recreate accessors if this template was pre-bound
2022+
if (!wasPreBound) {
20212023
for (let prop in templateInfo.propertyEffects) {
20222024
this._createPropertyAccessor(prop);
20232025
}
2024-
templateInfo.hasCreatedAccessors = true;
20252026
}
2026-
// Create instance of template metadata and link into list of templates
2027-
templateInfo = Object.create(templateInfo);
2028-
if (this.hasOwnProperty('__templateInfo')) {
2029-
templateInfo.nextTemplateInfo = this.__templateInfo;
2030-
this.__templateInfo.previousTemplateInfo = templateInfo;
2027+
if (instanceBinding) {
2028+
// For instance-time binding, create instance of template metadata
2029+
// and link into list of templates if necessary
2030+
templateInfo = Object.create(templateInfo);
2031+
if (!wasPreBound && this.__templateInfo) {
2032+
templateInfo.nextTemplateInfo = this.__templateInfo;
2033+
this.__templateInfo.previousTemplateInfo = templateInfo;
2034+
}
20312035
}
20322036
return this.__templateInfo = templateInfo;
20332037
}
@@ -2072,8 +2076,7 @@
20722076
*/
20732077
_stampTemplate(template) {
20742078
let dom = super._stampTemplate(template);
2075-
// Link template info & create accessors
2076-
let templateInfo = this._bindTemplate(template, dom);
2079+
let templateInfo = this._bindTemplate(template, true);
20772080
// Add template-instance-specific data to instanced templateInfo
20782081
templateInfo.nodeList = dom.nodeList;
20792082
templateInfo.childNodes = Array.from(dom.childNodes);
@@ -2096,7 +2099,7 @@
20962099
* from `_stampTemplate` associated with the nodes to be removed
20972100
* @protected
20982101
*/
2099-
_unstampTemplate(dom) {
2102+
_removeBoundDom(dom) {
21002103
// Unlink template info
21012104
let templateInfo = dom.templateInfo;
21022105
if (templateInfo.previousTemplateInfo) {

lib/mixins/template-stamp.html

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,13 @@
4545

4646
function findTemplateNode(root, nodeInfo) {
4747
// recursively ascend tree until we hit root
48-
let parent = nodeInfo.parent && findTemplateNode(root, nodeInfo.parent);
48+
let parent = nodeInfo.parentInfo && findTemplateNode(root, nodeInfo.parentInfo);
4949
// unwind the stack, returning the indexed node at each level
5050
if (parent) {
5151
// note: marginally faster than indexing via childNodes
5252
// (http://jsperf.com/childnodes-lookup)
5353
for (let n=parent.firstChild, i=0; n; n=n.nextSibling) {
54-
if (nodeInfo.index === i++) {
54+
if (nodeInfo.parentIndex === i++) {
5555
return n;
5656
}
5757
}
@@ -152,8 +152,8 @@
152152
* templateInfo: <nested template metadata>,
153153
* // Metadata to allow efficient retrieval of instanced node
154154
* // corresponding to this metadata
155-
* parent: <reference to parent nodeInfo>,
156-
* index: <integer index in parent's `childNodes` collection>
155+
* parentInfo: <reference to parent nodeInfo>,
156+
* parentIndex: <integer index in parent's `childNodes` collection>
157157
* },
158158
* ...
159159
* ],
@@ -248,7 +248,7 @@
248248
* @param {Object} nodeInfo Node metadata for current template.
249249
*/
250250
static _parseTemplateChildNodes(root, templateInfo, nodeInfo) {
251-
for (let node=root.firstChild, index=0, next; node; node=next) {
251+
for (let node=root.firstChild, parentIndex=0, next; node; node=next) {
252252
// Wrap templates
253253
if (node.localName == 'template') {
254254
node = wrapTemplateExtension(node);
@@ -272,11 +272,11 @@
272272
continue;
273273
}
274274
}
275-
let childInfo = { index, parent: nodeInfo };
275+
let childInfo = { parentIndex, parentInfo: nodeInfo };
276276
if (this._parseTemplateNode(node, templateInfo, childInfo)) {
277-
templateInfo.nodeInfoList.push(childInfo);
277+
childInfo.infoIndex = templateInfo.nodeInfoList.push(childInfo) - 1;
278278
}
279-
index++;
279+
parentIndex++;
280280
}
281281
}
282282

test/unit/property-effects-template.html

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@
103103
<x-element id="ifElementLD" prop="{{prop}}" path="{{obj.path}}"></x-element>
104104
</template>
105105
</template>
106+
<template id="templateWithDifferentProps">
107+
<div id="bound">[[otherProp]]</div>
108+
</template>
106109
<script>
107110
HTMLImports.whenReady(() => {
108111
class XRuntime extends Polymer.GestureEventListeners(Polymer.Element) {
@@ -114,7 +117,6 @@
114117
this.pathChanged = sinon.spy();
115118
this.prop = 'prop';
116119
this.obj = {path: 'obj.path'};
117-
this.shadowTemplates = [];
118120
}
119121
ready() {
120122
super.setAttribute('log', '');
@@ -134,6 +136,11 @@
134136
this.shadowRoot.appendChild(dom);
135137
return dom;
136138
}
139+
stampTemplateWithDifferentProps() {
140+
let dom = this._stampTemplate(Polymer.DomModule.import(this.localName, '#templateWithDifferentProps'));
141+
this.shadowRoot.appendChild(dom);
142+
return dom;
143+
}
137144
}
138145
customElements.define('x-runtime', XRuntime);
139146
});
@@ -162,7 +169,6 @@
162169
static _parseTemplateNodeAttribute(node, templateInfo, nodeInfo, name, value) {
163170
if (name == 'special') {
164171
nodeInfo.specialAttr = value;
165-
nodeInfo.infoIndex = templateInfo.nodeInfoList.length;
166172
node.removeAttribute('special');
167173
node.setAttribute('had-special', '');
168174
return true;
@@ -173,7 +179,6 @@
173179
static _parseTemplateNode(node, templateInfo, nodeInfo) {
174180
let noted = super._parseTemplateNode(node, templateInfo, nodeInfo);
175181
if (node.localName == 'x-special') {
176-
nodeInfo.infoIndex = templateInfo.nodeInfoList.length;
177182
noted = nodeInfo.specialNode = true;
178183
}
179184
return noted;
@@ -215,14 +220,14 @@
215220
<dom-module id="x-binding">
216221
<template>
217222
<x-element id="standard1" prop="[[prop]]" path="[[obj.path]]"></x-element>
218-
<x-element id="custom1" prop='[{"prop": "prop", "prop2": "prop2"}]'></x-element>
223+
<x-element id="custom1" prop='[{"a": "prop", "b": "prop2"}]'></x-element>
219224
<div>
220225
<x-element id="standard2" prop="[[prop]]" path="[[obj.path]]"></x-element>
221-
<x-element id="custom2" prop='[{"prop": "prop", "prop2": "prop2"}]'></x-element>
226+
<x-element id="custom2" prop='[{"a": "prop", "b": "prop2"}]'></x-element>
222227
</div>
223-
<template id="domIf" is="dom-if" if="[[prop]]" restamp>
228+
<template id="domIf" is="dom-if" if="[[prop2]]" restamp>
224229
<x-element id="standard3" prop="[[prop]]" path="[[obj.path]]"></x-element>
225-
<x-element id="custom3" prop='[{"prop": "prop", "prop2": "prop2"}]'></x-element>
230+
<x-element id="custom3" prop='[{"a": "prop", "b": "prop2"}]'></x-element>
226231
</template>
227232
</template>
228233
<script>
@@ -367,10 +372,13 @@
367372
assert.equal(stamped[0], el.$.first);
368373
assert.equal(stamped[1], dom1.$.first);
369374
// Unstamp
370-
el._unstampTemplate(dom1);
375+
el._removeBoundDom(dom1);
371376
for (let n in dom1.$) {
372377
assert.notOk(dom1.$[n].parentNode, null);
373378
}
379+
stamped = el.shadowRoot.querySelectorAll('x-element#first');
380+
assert.equal(stamped.length, 1);
381+
assert.equal(stamped[0], el.$.first);
374382
// Stamp again
375383
let dom2 = el.stampTemplateFromShadow();
376384
assertStampingCorrect(el, el.$);
@@ -405,8 +413,8 @@
405413
'x-runtime|x-element#shadow'
406414
]);
407415
// Unstamp
408-
el._unstampTemplate(dom2);
409-
el._unstampTemplate(dom3);
416+
el._removeBoundDom(dom2);
417+
el._removeBoundDom(dom3);
410418
for (let n in dom2.$) {
411419
assert.notOk(dom1.$[n].parentNode, null);
412420
}
@@ -448,7 +456,7 @@
448456
assert.equal(stamped[0], el.$.first);
449457
assert.equal(stamped[1], dom1.$.first);
450458
// Unstamp
451-
el._unstampTemplate(dom1);
459+
el._removeBoundDom(dom1);
452460
for (let n in dom1.$) {
453461
assert.notOk(dom1.$[n].parentNode, null);
454462
}
@@ -486,8 +494,8 @@
486494
'x-runtime|x-element#light'
487495
]);
488496
// Unstamp
489-
el._unstampTemplate(dom2);
490-
el._unstampTemplate(dom3);
497+
el._removeBoundDom(dom2);
498+
el._removeBoundDom(dom3);
491499
for (let n in dom2.$) {
492500
assert.notOk(dom1.$[n].parentNode, null);
493501
}
@@ -554,6 +562,20 @@
554562
assertAllPropValues(el, sd, ld, 'path', 'obj.path+++', 4);
555563
});
556564

565+
test('accessors for non-prototypically bound properties created', () => {
566+
// First element
567+
let dom = el.stampTemplateWithDifferentProps();
568+
el.otherProp = 'otherProp';
569+
assert.equal(dom.$.bound.textContent, 'otherProp');
570+
// Second element
571+
let el2 = document.createElement('x-runtime');
572+
document.body.appendChild(el2);
573+
let dom2 = el2.stampTemplateWithDifferentProps();
574+
el2.otherProp = 'otherProp';
575+
assert.equal(dom2.$.bound.textContent, 'otherProp');
576+
document.body.removeChild(el2);
577+
});
578+
557579
test('prototypical stamping not affected by runtime stamping', () => {
558580
assertStampingCorrect(el, el.$);
559581
let stamped = el.shadowRoot.querySelectorAll('x-element#first');
@@ -665,30 +687,29 @@
665687
assert.equal(el.$.standard1.path, 'obj.path');
666688
assert.equal(el.$.standard2.path, 'obj.path');
667689
assert.equal(el.shadowRoot.querySelector('#standard3').path, 'obj.path');
668-
assert.equal(el.$.custom1.prop, 'prop prop2');
669-
assert.equal(el.$.custom2.prop, 'prop prop2');
670-
assert.equal(el.shadowRoot.querySelector('#custom3').prop, 'prop prop2');
690+
assert.equal(el.$.custom1.prop, 'a b');
691+
assert.equal(el.$.custom2.prop, 'a b');
692+
assert.equal(el.shadowRoot.querySelector('#custom3').prop, 'a b');
671693
el.prop = false;
672-
el.$.domIf.render();
673694
assert.equal(el.$.standard1.prop, false);
674695
assert.equal(el.$.standard2.prop, false);
696+
assert.equal(el.shadowRoot.querySelector('#standard3').prop, false);
675697
assert.equal(el.$.standard1.path, 'obj.path');
676698
assert.equal(el.$.standard2.path, 'obj.path');
677-
assert.notOk(el.shadowRoot.querySelector('#standard3'));
678-
assert.equal(el.$.custom1.prop, 'prop2');
679-
assert.equal(el.$.custom2.prop, 'prop2');
680-
assert.equal(el.shadowRoot.querySelector('#custom3'));
699+
assert.equal(el.shadowRoot.querySelector('#standard3').path, 'obj.path');
700+
assert.equal(el.$.custom1.prop, 'b');
701+
assert.equal(el.$.custom2.prop, 'b');
702+
assert.equal(el.shadowRoot.querySelector('#custom3').prop, 'b');
681703
el.prop = true;
682-
el.$.domIf.render();
683704
assert.equal(el.$.standard1.prop, true);
684705
assert.equal(el.$.standard2.prop, true);
685706
assert.equal(el.shadowRoot.querySelector('#standard3').prop, true);
686707
assert.equal(el.$.standard1.path, 'obj.path');
687708
assert.equal(el.$.standard2.path, 'obj.path');
688709
assert.equal(el.shadowRoot.querySelector('#standard3').path, 'obj.path');
689-
assert.equal(el.$.custom1.prop, 'prop prop2');
690-
assert.equal(el.$.custom2.prop, 'prop prop2');
691-
assert.equal(el.shadowRoot.querySelector('#custom3').prop, 'prop prop2');
710+
assert.equal(el.$.custom1.prop, 'a b');
711+
assert.equal(el.$.custom2.prop, 'a b');
712+
assert.equal(el.shadowRoot.querySelector('#custom3').prop, 'a b');
692713
document.body.removeChild(el);
693714
});
694715
});

0 commit comments

Comments
 (0)