From 6499e83ee767d93cd1d89175589ebd0bc16826d5 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Tue, 4 Aug 2015 14:06:18 -0700 Subject: [PATCH] Adds `Polymer.dom(element).observeChildren(callback)` api --- src/lib/dom-api.html | 145 +++++++++++++++++++++++++++++++- src/mini/shady.html | 7 ++ test/smoke/observeChildren.html | 82 ++++++++++++++++++ 3 files changed, 230 insertions(+), 4 deletions(-) create mode 100644 test/smoke/observeChildren.html diff --git a/src/lib/dom-api.html b/src/lib/dom-api.html index c46cb582b6..dd4443b2ae 100644 --- a/src/lib/dom-api.html +++ b/src/lib/dom-api.html @@ -26,6 +26,9 @@ var DomApi = function(node) { this.node = node; + this._observers = []; + this._addedNodes = []; + this._removedNodes = [] if (this.patch) { this.patch(); } @@ -65,6 +68,7 @@ // 3. node is (host of container needs distribution) appendChild: function(node) { var handled; + this._recordMutationRemoveFromParent(node); this._removeNodeFromHost(node, true); if (this._nodeIsInLogicalTree(this.node)) { this._addLogicalInfo(node, this.node); @@ -78,6 +82,7 @@ addToComposedParent(container, node); nativeAppendChild.call(container, node); } + this._recordMutationAdd(node); return node; }, @@ -86,6 +91,7 @@ return this.appendChild(node); } var handled; + this._recordMutationRemoveFromParent(node); this._removeNodeFromHost(node, true); if (this._nodeIsInLogicalTree(this.node)) { saveLightChildrenIfNeeded(this.node); @@ -109,6 +115,7 @@ addToComposedParent(container, node, ref_node); nativeInsertBefore.call(container, node, ref_node); } + this._recordMutationAdd(node); return node; }, @@ -136,6 +143,7 @@ nativeRemoveChild.call(container, node); } } + this._recordMutationRemove(node); return node; }, @@ -251,7 +259,7 @@ root.host._elementRemove(node); hostNeedsDist = this._removeDistributedChildren(root, node); } - this._removeLogicalInfo(node, node._lightParent); + this._removeLogicalInfo(node, parent); } this._removeOwnerShadyRoot(node); if (root && hostNeedsDist) { @@ -478,6 +486,69 @@ } } return n; + }, + + observeChildren: function(callback) { + return this._observers.push(callback); + }, + + unobserveChildren: function(handle) { + this._observers.splice(handle - 1, 1); + }, + + _hasObservers: function() { + return Boolean(this._observers.length); + }, + + _recordMutationAdd: function(node) { + if (this._hasObservers()) { + this._addedNodes.push(node); + this._scheduleMutationNotify(); + } + }, + + _recordMutationRemove: function(node) { + if (this._hasObservers()) { + this._removedNodes.push(node); + this._scheduleMutationNotify(); + } + }, + + _recordMutationAddAll: function() { + if (this._hasObservers()) { + var c$ = this.childNodes; + for (var i=0, c; (i < c$.length) && (c=c$[i]); i++) { + this._recordMutationAdd(c); + } + } + }, + + _recordMutationRemoveFromParent: function(node) { + var parent = node._lightParent; + if (parent) { + factory(parent)._recordMutationRemove(node); + } + }, + + _scheduleMutationNotify: function() { + this._mutationDebouncer = Polymer.Debounce(this._mutationDebouncer, + this._notifyObservers); + this._mutationDebouncer.context = this; + Polymer.dom.addDebouncer(this._mutationDebouncer); + }, + + _notifyObservers: function(mxns) { + var info = { + target: this.node, + addedNodes: this._addedNodes, + removedNodes: this._removedNodes + } + var o$ = this._observers; + for (var i=0, o; (i < o$.length) && (o=o$[i]); i++) { + o.call(null, info); + } + this._addedNodes = []; + this._removedNodes = []; } }; @@ -718,7 +789,68 @@ return n$ ? Array.prototype.slice.call(n$) : []; }; - DomApi.prototype._distributeParent = function() {} + DomApi.prototype._distributeParent = function() {}; + + DomApi.prototype.observeChildren = function(callback) { + if (!this._mo) { + this._mo = new MutationObserver(this._notifyObservers.bind(this)); + this._mo.observe(this.node, {childList: true}); + // make sure to notify initial state... + this._mutationDebouncer = Polymer.Debounce(this._mutationDebouncer, + function() { + this._notifyObservers([{ + target: this.node, + addedNodes: this.childNodes.slice() + }]); + } + ); + this._mutationDebouncer.context = this; + Polymer.dom.addDebouncer(this._mutationDebouncer); + } + return this._observers.push(callback); + }; + + DomApi.prototype._notifyObservers = function(mxns) { + var info = { + target: this.node, + addedNodes: [], + removedNodes: [] + }; + mxns.forEach(function(m) { + if (m.addedNodes) { + for (var i=0; i < m.addedNodes.length; i++) { + info.addedNodes.push(m.addedNodes[i]); + } + } + if (m.removedNodes) { + for (var i=0; i < m.removedNodes.length; i++) { + info.removedNodes.push(m.removedNodes[i]); + } + } + }); + if (info.addedNodes.length || info.removedNodes.length) { + var o$ = this._observers; + for (var i=0, o; (i < o$.length) && (o=o$[i]); i++) { + o.call(null, info); + } + } + }; + + DomApi.prototype.flush = function() { + if (this._mo) { + this._notifyObservers(this._mo.takeRecords()); + } + Polymer.dom.flush(); + } + + var nativeForwards = ['appendChild', 'insertBefore', + 'removeChild', 'replaceChild']; + + nativeForwards.forEach(function(forward) { + DomApi.prototype[forward] = function() { + return this.node[forward].apply(this.node, arguments); + }; + }); Object.defineProperties(DomApi.prototype, { @@ -776,7 +908,7 @@ var CONTENT = 'content'; - var factory = function(node, patch) { + function factory(node, patch) { node = node || document; if (!node.__domApi) { node.__domApi = new DomApi(node, patch); @@ -784,6 +916,10 @@ return node.__domApi; }; + function hasDomApi(node) { + return Boolean(node.__domApi); + } + Polymer.dom = function(obj, patch) { if (obj instanceof Event) { return Polymer.EventApi.factory(obj); @@ -928,7 +1064,8 @@ matchesSelector: matchesSelector, hasInsertionPoint: hasInsertionPoint, ctor: DomApi, - factory: factory + factory: factory, + hasDomApi: hasDomApi }; })(); diff --git a/src/mini/shady.html b/src/mini/shady.html index 8d96fc7b57..fff1b31951 100644 --- a/src/mini/shady.html +++ b/src/mini/shady.html @@ -18,6 +18,9 @@ polyfill across browsers. */ + + var hasDomApi = Polymer.DomApi.hasDomApi; + Polymer.Base._addFeature({ _prepShady: function() { @@ -149,7 +152,11 @@ this._updateChildNodes(this, children); } } + var hasDistributed = this.shadyRoot._hasDistributed; this.shadyRoot._hasDistributed = true; + if (!hasDistributed && hasDomApi(this)) { + Polymer.dom(this)._recordMutationAddAll(); + } } }, diff --git a/test/smoke/observeChildren.html b/test/smoke/observeChildren.html new file mode 100644 index 0000000000..0b9624f4b1 --- /dev/null +++ b/test/smoke/observeChildren.html @@ -0,0 +1,82 @@ + + + + + observeChildren + + + + + + + + + + + + + + + + + + + + + +
content A
+
content B
+
+ +

+ +
static A
static B
+ + + + +