From fbacc95c411216667944cecc7d81262486bac446 Mon Sep 17 00:00:00 2001 From: Kevin Schaaf Date: Tue, 31 Mar 2015 12:10:48 -0700 Subject: [PATCH 01/13] Experiment with nested template scope binding. --- src/lib/template/templatizer.html | 31 +++++++++++++++++++++++++++++++ src/lib/template/x-repeat.html | 8 ++++++++ 2 files changed, 39 insertions(+) diff --git a/src/lib/template/templatizer.html b/src/lib/template/templatizer.html index b190163d04..f4b3f32a6d 100644 --- a/src/lib/template/templatizer.html +++ b/src/lib/template/templatizer.html @@ -69,6 +69,7 @@ if (contentHost) { Polymer.Annotations.prepElement = contentHost._prepElement.bind(contentHost); + this._createForwardBindings(); } archetype._annotes = c._annotes || Polymer.Annotations.parseAnnotations(template); @@ -84,6 +85,33 @@ } }, + _createForwardBindings: function() { + if (this._forwardProp) { + // inner nested x-repeat host isn't the outer x-repeat! argh... + var tidx = this.host._nodes.lastIndexOf(this); + var bindings = this.host._annotes.filter(function(o) { + return o.bindings[0] && o.bindings[0].index == tidx; + })[0].bindings.filter(function(b) { + return b.name != 'items'; + }); + this._forwardProps = bindings.map(function(b) { + return b.name; + }); + this._forwardListenerProps = bindings.filter(function(b) { + return Polymer.Bind._shouldAddListener(b); + }).map(function(b) { + return b.name; + }); + var fx = bindings.reduce(function(f, b) { + f[b.name] = ['this._forwardProp(\'' + b.name + '\')']; + return f; + }, {}); + for (var n in fx) { + Polymer.Bind._bindPropertyEffects(this, n, fx[n]); + } + } + }, + _notifyPathImpl: function() { var pd = this.pathDelegate; if (pd) { @@ -105,6 +133,9 @@ }, stamp: function(model) { + model = Polymer.Base.extend(this._forwardProps.reduce(function(m, p) { + m[p] = this[p]; return m; + }.bind(this), {}), model); return new this.ctor(model); } diff --git a/src/lib/template/x-repeat.html b/src/lib/template/x-repeat.html index 584ffbaa3d..b4aa581a7c 100644 --- a/src/lib/template/x-repeat.html +++ b/src/lib/template/x-repeat.html @@ -448,6 +448,14 @@ return row; }, + _forwardProp: function(prop) { + if (this.rows) { + this.rows.forEach(function(row) { + row[prop] = this[prop]; + }, this); + } + }, + _notifyDelegatePath: function(row, path, value) { this.notifyPath(path.replace('item', 'items.' + row.key), value); }, From b7c1dbefd19fd5b4417a6b695d69ffeff9b89ef2 Mon Sep 17 00:00:00 2001 From: Kevin Schaaf Date: Tue, 31 Mar 2015 21:09:25 -0700 Subject: [PATCH 02/13] Use annotator to find parent.props & auto-bind to template. --- src/lib/annotations/annotations.html | 28 ++++++++++- src/lib/template/templatizer.html | 53 +++++++------------- src/lib/template/x-repeat.html | 74 ++++++++++++++++++++-------- 3 files changed, 97 insertions(+), 58 deletions(-) diff --git a/src/lib/annotations/annotations.html b/src/lib/annotations/annotations.html index fdd863fe16..6315b8571b 100644 --- a/src/lib/annotations/annotations.html +++ b/src/lib/annotations/annotations.html @@ -141,13 +141,39 @@ if (node.localName === 'template') { // TODO(sjmiles): simply altering the .content reference didn't // work (there was some confusion, might need verification) + var bindings = []; var content = document.createDocumentFragment(); + content._annotes = this.parseAnnotations(node); + // 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 parentProps = content._parentProps = {}; + content._annotes.forEach(function(n) { + n.bindings.forEach(function(b) { + var m; + if (m = b.value.match(/parent\.([^.]*)/)) { + var prop = m[1]; + if (!parentProps[prop]) { + parentProps[prop] = true; + bindings.push({ + index: i, + kind: 'property', + mode: '{', + name: '_parent_' + prop, + value: prop + }); + } + } + }); + }); content.appendChild(node.content); // 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, diff --git a/src/lib/template/templatizer.html b/src/lib/template/templatizer.html index f4b3f32a6d..6c3ee12041 100644 --- a/src/lib/template/templatizer.html +++ b/src/lib/template/templatizer.html @@ -35,6 +35,8 @@ 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); @@ -65,16 +67,8 @@ archetype._template = template; var c = template._content; if (c) { - var contentHost = archetype.contentHost; - if (contentHost) { - Polymer.Annotations.prepElement = - contentHost._prepElement.bind(contentHost); - this._createForwardBindings(); - } - 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'); @@ -85,29 +79,13 @@ } }, - _createForwardBindings: function() { - if (this._forwardProp) { - // inner nested x-repeat host isn't the outer x-repeat! argh... - var tidx = this.host._nodes.lastIndexOf(this); - var bindings = this.host._annotes.filter(function(o) { - return o.bindings[0] && o.bindings[0].index == tidx; - })[0].bindings.filter(function(b) { - return b.name != 'items'; - }); - this._forwardProps = bindings.map(function(b) { - return b.name; - }); - this._forwardListenerProps = bindings.filter(function(b) { - return Polymer.Bind._shouldAddListener(b); - }).map(function(b) { - return b.name; - }); - var fx = bindings.reduce(function(f, b) { - f[b.name] = ['this._forwardProp(\'' + b.name + '\')']; - return f; - }, {}); - for (var n in fx) { - Polymer.Bind._bindPropertyEffects(this, n, fx[n]); + _prepParentProperties: function(archetype) { + if (this._forwardParentProp) { + if (this._parentProps = archetype._parentProps) { + for (var prop in this._parentProps) { + Polymer.Bind._bindPropertyEffects(this, '_parent_' + prop, + ['this._forwardParentProp(\'' + prop + '\')']); + } } } }, @@ -133,9 +111,12 @@ }, stamp: function(model) { - model = Polymer.Base.extend(this._forwardProps.reduce(function(m, p) { - m[p] = this[p]; return m; - }.bind(this), {}), model); + if (model) { + model.parent = model.parent || {}; + for (var prop in this._parentProps) { + model.parent[prop] = this['_parent_' + prop]; + } + } return new this.ctor(model); } diff --git a/src/lib/template/x-repeat.html b/src/lib/template/x-repeat.html index b4aa581a7c..bb6b324371 100644 --- a/src/lib/template/x-repeat.html +++ b/src/lib/template/x-repeat.html @@ -102,7 +102,8 @@ * to stamp and that that each template instance should bind to. */ items: { - type: Array + type: Array, + observer: '_itemsChanged' }, /** @@ -155,10 +156,6 @@ Polymer.Templatizer ], - observers: { - 'items.*': '_itemsChanged' - }, - created: function() { this.boundCollectionObserver = this.render.bind(this); }, @@ -193,19 +190,14 @@ this.observe.replace('.*', '.').split(' '); }, - _itemsChanged: function(items, old, path) { - if (path) { - this._notifyElement(path, items); - this._checkObservedPaths(path); - } else { - if (old) { - this._unobserveCollection(old); - } - if (items) { - this._observeCollection(items); - this.debounce('render', this.render); - } - } + _itemsChanged: function(items, old) { + if (old) { + this._unobserveCollection(old); + } + if (items) { + this._observeCollection(items); + this.debounce('render', this.render); + } }, _checkObservedPaths: function(path) { @@ -265,6 +257,7 @@ } row.item = item; row.key = key; + row.index = i; rowForKey[key] = i; } // Remove extra @@ -448,16 +441,55 @@ return row; }, - _forwardProp: function(prop) { + _forwardParentProp: function(prop) { + if (this.rows) { + this.rows.forEach(function(row) { + var val = this['_parent_' + prop]; + row.parent[prop] = val; + row.notifyPath('parent.' + prop, val, true); + }, this); + } + }, + + _forwardParentPath: function(path, value) { if (this.rows) { this.rows.forEach(function(row) { - row[prop] = this[prop]; + row.notifyPath('parent.' + path, value, true); }, this); } }, _notifyDelegatePath: function(row, path, value) { - this.notifyPath(path.replace('item', 'items.' + row.key), value); + // 'parent.foo.bar.baz' + // p[1] = 'parent' + // p[2] = 'foo.bar.baz' + // p[3] = 'foo' + var p = path.match(/([^.]*)\.(([^.]*).*)/); + switch (p[1]) { + case 'item': + this.notifyPath(path.replace('item', 'items.' + row.key), value); + break; + case 'parent': + if (p[2] == p[3]) { + this.host[p[2]] = value; + } else { + this.host.notifyPath(p[2], value); + } + break; + } + }, + + notifyPath: function(path, value, fromAbove) { + var prop = path.substring(0, path.indexOf('.')); + if (prop == 'item') { + this._notifyElement(path, value); + } else { + if (prop.indexOf('_parent_') === 0) { + this._forwardParentPath(path.substring(8), value); + } else { + Polymer.Base.notifyPath.apply(this, arguments); + } + } }, _notifyElement: function(path, value) { From f906c935d0b848aa993b55ca8f910b0dff190f0c Mon Sep 17 00:00:00 2001 From: Kevin Schaaf Date: Tue, 31 Mar 2015 22:15:52 -0700 Subject: [PATCH 03/13] Fix notifyPath override impl. --- src/lib/template/x-repeat.html | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/lib/template/x-repeat.html b/src/lib/template/x-repeat.html index bb6b324371..5a08063f89 100644 --- a/src/lib/template/x-repeat.html +++ b/src/lib/template/x-repeat.html @@ -481,15 +481,13 @@ notifyPath: function(path, value, fromAbove) { var prop = path.substring(0, path.indexOf('.')); - if (prop == 'item') { + if (prop == 'items') { this._notifyElement(path, value); - } else { - if (prop.indexOf('_parent_') === 0) { - this._forwardParentPath(path.substring(8), value); - } else { - Polymer.Base.notifyPath.apply(this, arguments); - } + this._checkObservedPaths(path); + } else if (prop.indexOf('_parent_') === 0) { + this._forwardParentPath(path.substring(8), value); } + Polymer.Base.notifyPath.apply(this, arguments); }, _notifyElement: function(path, value) { From b49fc5c3cbeaab469665015e0b234f0935c27873 Mon Sep 17 00:00:00 2001 From: Kevin Schaaf Date: Fri, 3 Apr 2015 15:56:18 -0700 Subject: [PATCH 04/13] Support for forwarding deeply nested parent props. Add smoke test. --- src/lib/annotations/annotations.html | 60 ++- src/lib/template/templatizer.html | 59 ++- src/lib/template/x-if.html | 28 +- src/lib/template/x-repeat.html | 64 ++-- src/standard/notify-path.html | 524 ++++++++++++++------------- test/smoke/x-if.html | 20 +- test/smoke/x-repeat.html | 248 +++++++++++++ 7 files changed, 659 insertions(+), 344 deletions(-) create mode 100644 test/smoke/x-repeat.html diff --git a/src/lib/annotations/annotations.html b/src/lib/annotations/annotations.html index 6315b8571b..07962b63f3 100644 --- a/src/lib/annotations/annotations.html +++ b/src/lib/annotations/annotations.html @@ -141,34 +141,26 @@ if (node.localName === 'template') { // TODO(sjmiles): simply altering the .content reference didn't // work (there was some confusion, might need verification) - var bindings = []; 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 parentProps = content._parentProps = {}; - content._annotes.forEach(function(n) { - n.bindings.forEach(function(b) { - var m; - if (m = b.value.match(/parent\.([^.]*)/)) { - var prop = m[1]; - if (!parentProps[prop]) { - parentProps[prop] = true; - bindings.push({ - index: i, - kind: 'property', - mode: '{', - name: '_parent_' + prop, - value: prop - }); - } - } + var bindings = []; + this._discoverTemplateParentProps(content); + console.log(node, content._parentProps); + for (var prop in content._parentProps) { + bindings.push({ + index: i, + kind: 'property', + mode: '{', + name: '_parent_' + prop, + value: prop }); - }); - content.appendChild(node.content); + } // TODO(sjmiles): using `nar` to avoid unnecessary allocation; // in general the handling of these arrays needs some cleanup // in this module @@ -190,6 +182,34 @@ } }, + _discoverTemplateParentProps: function(content) { + var parentProps = content._parentPropChain = []; + content._annotes.forEach(function(n) { + n.bindings.forEach(function(b) { + var m; + if (m = b.value.match(/parent\.((parent\.)*)([^.]*)/)) { + var depth = m[1].length / 7; // 'parent.'.length == 7 + var pp = parentProps[depth] || (parentProps[depth] = {}); + pp[m[3]] = true; + if (depth > 0) { + pp = parentProps[0] || (parentProps[0] = {}); + pp.parent = true; + } + } + }); + if (n.templateContent) { + var tpp = n.templateContent._parentPropChain; + for (var i=1; i path change on instance + _forwardParentProp: function(prop) { + if (this._instance) { + var val = this['_parent_' + prop]; + this._instance.parent[prop] = val; + this._instance.notifyPath('parent.' + prop, val, true); + } + }, + + // Implements extension point from Templatizer + // Called as side-effect of a host path change, responsible for + // notifying parent. path change on each row + _forwardParentPath: function(path, value) { + if (this._instance) { + this._instance.notifyPath('parent.' + path, value, true); + } } }); diff --git a/src/lib/template/x-repeat.html b/src/lib/template/x-repeat.html index 5a08063f89..d2590cc5d8 100644 --- a/src/lib/template/x-repeat.html +++ b/src/lib/template/x-repeat.html @@ -425,8 +425,7 @@ _generateRow: function(idx, item) { var row = this.stamp({ - item: item, - pathDelegate: this + item: item }); // each row is a document fragment which is lost when we appendChild, // so we have to track each child individually @@ -441,6 +440,29 @@ return row; }, + // Implements extension point from Templatizer + // Called as a side effect of a template instance path change, responsible + // for notifying items.. change up to host + _handleInstancePath: function(row, root, subPath, value) { + if (root == 'item') { + this.notifyPath('items.' + row.key + '.' + subPath, value); + } + }, + + // Implements extension point from Templatizer + // Called as a side effect of a host path change, and performs actions + // required when items.* changes (notify specific row & check observed + // paths), then calls Templatizer to check for parent path changes + _handleParentPath: function(path, value) { + if (path.indexOf('items.') === 0) { + this._notifyItemChanged(path, value); + this._checkObservedPaths(path); + } + }, + + // Implements extension point from Templatizer mixin + // Called as side-effect of a host property change, responsible for + // notifying parent. path change on each row _forwardParentProp: function(prop) { if (this.rows) { this.rows.forEach(function(row) { @@ -451,6 +473,9 @@ } }, + // Implements extension point from Templatizer + // Called as side-effect of a host path change, responsible for + // notifying parent. path change on each row _forwardParentPath: function(path, value) { if (this.rows) { this.rows.forEach(function(row) { @@ -459,38 +484,9 @@ } }, - _notifyDelegatePath: function(row, path, value) { - // 'parent.foo.bar.baz' - // p[1] = 'parent' - // p[2] = 'foo.bar.baz' - // p[3] = 'foo' - var p = path.match(/([^.]*)\.(([^.]*).*)/); - switch (p[1]) { - case 'item': - this.notifyPath(path.replace('item', 'items.' + row.key), value); - break; - case 'parent': - if (p[2] == p[3]) { - this.host[p[2]] = value; - } else { - this.host.notifyPath(p[2], value); - } - break; - } - }, - - notifyPath: function(path, value, fromAbove) { - var prop = path.substring(0, path.indexOf('.')); - if (prop == 'items') { - this._notifyElement(path, value); - this._checkObservedPaths(path); - } else if (prop.indexOf('_parent_') === 0) { - this._forwardParentPath(path.substring(8), value); - } - Polymer.Base.notifyPath.apply(this, arguments); - }, - - _notifyElement: function(path, value) { + // Called as a side effect of a host items.. path change, + // responsible for notifying item. changes to row for key + _notifyItemChanged: function(path, value) { if (this._rowForKey) { // 'items.'.length == 6 var dot = path.indexOf('.', 6); diff --git a/src/standard/notify-path.html b/src/standard/notify-path.html index c6376cd708..587b42cb7e 100644 --- a/src/standard/notify-path.html +++ b/src/standard/notify-path.html @@ -1,260 +1,264 @@ - - - + + + \ No newline at end of file diff --git a/test/smoke/x-if.html b/test/smoke/x-if.html index 887f9e10b9..bdad8ee94e 100644 --- a/test/smoke/x-if.html +++ b/test/smoke/x-if.html @@ -23,19 +23,21 @@ }