Skip to content

Commit

Permalink
Adds Polymer.dom(element).observeChildren(callback) api
Browse files Browse the repository at this point in the history
  • Loading branch information
Steven Orvell committed Aug 4, 2015
1 parent f34fb45 commit 6499e83
Show file tree
Hide file tree
Showing 3 changed files with 230 additions and 4 deletions.
145 changes: 141 additions & 4 deletions src/lib/dom-api.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@

var DomApi = function(node) {
this.node = node;
this._observers = [];
this._addedNodes = [];
this._removedNodes = []
if (this.patch) {
this.patch();
}
Expand Down Expand Up @@ -65,6 +68,7 @@
// 3. node is <content> (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);
Expand All @@ -78,6 +82,7 @@
addToComposedParent(container, node);
nativeAppendChild.call(container, node);
}
this._recordMutationAdd(node);
return node;
},

Expand All @@ -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);
Expand All @@ -109,6 +115,7 @@
addToComposedParent(container, node, ref_node);
nativeInsertBefore.call(container, node, ref_node);
}
this._recordMutationAdd(node);
return node;
},

Expand Down Expand Up @@ -136,6 +143,7 @@
nativeRemoveChild.call(container, node);
}
}
this._recordMutationRemove(node);
return node;
},

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 = [];
}

};
Expand Down Expand Up @@ -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, {

Expand Down Expand Up @@ -776,14 +908,18 @@

var CONTENT = 'content';

var factory = function(node, patch) {
function factory(node, patch) {
node = node || document;
if (!node.__domApi) {
node.__domApi = new DomApi(node, patch);
}
return node.__domApi;
};

function hasDomApi(node) {
return Boolean(node.__domApi);
}

Polymer.dom = function(obj, patch) {
if (obj instanceof Event) {
return Polymer.EventApi.factory(obj);
Expand Down Expand Up @@ -928,7 +1064,8 @@
matchesSelector: matchesSelector,
hasInsertionPoint: hasInsertionPoint,
ctor: DomApi,
factory: factory
factory: factory,
hasDomApi: hasDomApi
};

})();
Expand Down
7 changes: 7 additions & 0 deletions src/mini/shady.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
polyfill across browsers.
*/

var hasDomApi = Polymer.DomApi.hasDomApi;

Polymer.Base._addFeature({

_prepShady: function() {
Expand Down Expand Up @@ -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();
}
}
},

Expand Down
82 changes: 82 additions & 0 deletions test/smoke/observeChildren.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<!doctype html>
<html>
<head>

<title>observeChildren</title>

<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<script src="../../../webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="../../polymer.html">

</head>
<body>

<dom-module id='test-content'>
<template>
[<content></content>]
</template>
<script>
Polymer({
is:'test-content',
created: function() {
Polymer.dom(this).observeChildren(function(info) {
console.log('test-content', info);
});
}
});
</script>
</dom-module>

<dom-module id='test-static'>
<template>
My children do not render.
</template>
<script>
Polymer({
is:'test-static',
created: function() {
Polymer.dom(this).observeChildren(function(info) {
console.log('test-static', info);
});
}
});
</script>
</dom-module>

<test-content id="content">
<div>content A</div>
<div>content B</div>
</test-content>

<br><br>

<test-static id="stat"><div>static A</div><div>static B</div></test-static>

<script>
Polymer.dom(content).flush();
Polymer.dom(stat).flush();
console.group('test dynamic');
function obs(mxns) {
console.log('custom observer', mxns);
}
var hc = Polymer.dom(content).observeChildren(obs);
var hs = Polymer.dom(stat).observeChildren(obs);

var d = document.createElement('div');
d.id = 'foo';
d.innerHTML = 'Dynamic!';
Polymer.dom(content).appendChild(d);
Polymer.dom(stat).appendChild(d);
Polymer.dom(content).flush();
Polymer.dom(stat).flush();
Polymer.dom(content).unobserveChildren(hc);
Polymer.dom(stat).unobserveChildren(hs);
Polymer.dom(stat).removeChild(d);
Polymer.dom(stat).flush();
console.groupEnd('test dynamic');
</script>

</body>
</html>

0 comments on commit 6499e83

Please sign in to comment.