Skip to content

Commit 6499e83

Browse files
author
Steven Orvell
committed
Adds Polymer.dom(element).observeChildren(callback) api
1 parent f34fb45 commit 6499e83

3 files changed

Lines changed: 230 additions & 4 deletions

File tree

‎src/lib/dom-api.html‎

Lines changed: 141 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626

2727
var DomApi = function(node) {
2828
this.node = node;
29+
this._observers = [];
30+
this._addedNodes = [];
31+
this._removedNodes = []
2932
if (this.patch) {
3033
this.patch();
3134
}
@@ -65,6 +68,7 @@
6568
// 3. node is <content> (host of container needs distribution)
6669
appendChild: function(node) {
6770
var handled;
71+
this._recordMutationRemoveFromParent(node);
6872
this._removeNodeFromHost(node, true);
6973
if (this._nodeIsInLogicalTree(this.node)) {
7074
this._addLogicalInfo(node, this.node);
@@ -78,6 +82,7 @@
7882
addToComposedParent(container, node);
7983
nativeAppendChild.call(container, node);
8084
}
85+
this._recordMutationAdd(node);
8186
return node;
8287
},
8388

@@ -86,6 +91,7 @@
8691
return this.appendChild(node);
8792
}
8893
var handled;
94+
this._recordMutationRemoveFromParent(node);
8995
this._removeNodeFromHost(node, true);
9096
if (this._nodeIsInLogicalTree(this.node)) {
9197
saveLightChildrenIfNeeded(this.node);
@@ -109,6 +115,7 @@
109115
addToComposedParent(container, node, ref_node);
110116
nativeInsertBefore.call(container, node, ref_node);
111117
}
118+
this._recordMutationAdd(node);
112119
return node;
113120
},
114121

@@ -136,6 +143,7 @@
136143
nativeRemoveChild.call(container, node);
137144
}
138145
}
146+
this._recordMutationRemove(node);
139147
return node;
140148
},
141149

@@ -251,7 +259,7 @@
251259
root.host._elementRemove(node);
252260
hostNeedsDist = this._removeDistributedChildren(root, node);
253261
}
254-
this._removeLogicalInfo(node, node._lightParent);
262+
this._removeLogicalInfo(node, parent);
255263
}
256264
this._removeOwnerShadyRoot(node);
257265
if (root && hostNeedsDist) {
@@ -478,6 +486,69 @@
478486
}
479487
}
480488
return n;
489+
},
490+
491+
observeChildren: function(callback) {
492+
return this._observers.push(callback);
493+
},
494+
495+
unobserveChildren: function(handle) {
496+
this._observers.splice(handle - 1, 1);
497+
},
498+
499+
_hasObservers: function() {
500+
return Boolean(this._observers.length);
501+
},
502+
503+
_recordMutationAdd: function(node) {
504+
if (this._hasObservers()) {
505+
this._addedNodes.push(node);
506+
this._scheduleMutationNotify();
507+
}
508+
},
509+
510+
_recordMutationRemove: function(node) {
511+
if (this._hasObservers()) {
512+
this._removedNodes.push(node);
513+
this._scheduleMutationNotify();
514+
}
515+
},
516+
517+
_recordMutationAddAll: function() {
518+
if (this._hasObservers()) {
519+
var c$ = this.childNodes;
520+
for (var i=0, c; (i < c$.length) && (c=c$[i]); i++) {
521+
this._recordMutationAdd(c);
522+
}
523+
}
524+
},
525+
526+
_recordMutationRemoveFromParent: function(node) {
527+
var parent = node._lightParent;
528+
if (parent) {
529+
factory(parent)._recordMutationRemove(node);
530+
}
531+
},
532+
533+
_scheduleMutationNotify: function() {
534+
this._mutationDebouncer = Polymer.Debounce(this._mutationDebouncer,
535+
this._notifyObservers);
536+
this._mutationDebouncer.context = this;
537+
Polymer.dom.addDebouncer(this._mutationDebouncer);
538+
},
539+
540+
_notifyObservers: function(mxns) {
541+
var info = {
542+
target: this.node,
543+
addedNodes: this._addedNodes,
544+
removedNodes: this._removedNodes
545+
}
546+
var o$ = this._observers;
547+
for (var i=0, o; (i < o$.length) && (o=o$[i]); i++) {
548+
o.call(null, info);
549+
}
550+
this._addedNodes = [];
551+
this._removedNodes = [];
481552
}
482553

483554
};
@@ -718,7 +789,68 @@
718789
return n$ ? Array.prototype.slice.call(n$) : [];
719790
};
720791

721-
DomApi.prototype._distributeParent = function() {}
792+
DomApi.prototype._distributeParent = function() {};
793+
794+
DomApi.prototype.observeChildren = function(callback) {
795+
if (!this._mo) {
796+
this._mo = new MutationObserver(this._notifyObservers.bind(this));
797+
this._mo.observe(this.node, {childList: true});
798+
// make sure to notify initial state...
799+
this._mutationDebouncer = Polymer.Debounce(this._mutationDebouncer,
800+
function() {
801+
this._notifyObservers([{
802+
target: this.node,
803+
addedNodes: this.childNodes.slice()
804+
}]);
805+
}
806+
);
807+
this._mutationDebouncer.context = this;
808+
Polymer.dom.addDebouncer(this._mutationDebouncer);
809+
}
810+
return this._observers.push(callback);
811+
};
812+
813+
DomApi.prototype._notifyObservers = function(mxns) {
814+
var info = {
815+
target: this.node,
816+
addedNodes: [],
817+
removedNodes: []
818+
};
819+
mxns.forEach(function(m) {
820+
if (m.addedNodes) {
821+
for (var i=0; i < m.addedNodes.length; i++) {
822+
info.addedNodes.push(m.addedNodes[i]);
823+
}
824+
}
825+
if (m.removedNodes) {
826+
for (var i=0; i < m.removedNodes.length; i++) {
827+
info.removedNodes.push(m.removedNodes[i]);
828+
}
829+
}
830+
});
831+
if (info.addedNodes.length || info.removedNodes.length) {
832+
var o$ = this._observers;
833+
for (var i=0, o; (i < o$.length) && (o=o$[i]); i++) {
834+
o.call(null, info);
835+
}
836+
}
837+
};
838+
839+
DomApi.prototype.flush = function() {
840+
if (this._mo) {
841+
this._notifyObservers(this._mo.takeRecords());
842+
}
843+
Polymer.dom.flush();
844+
}
845+
846+
var nativeForwards = ['appendChild', 'insertBefore',
847+
'removeChild', 'replaceChild'];
848+
849+
nativeForwards.forEach(function(forward) {
850+
DomApi.prototype[forward] = function() {
851+
return this.node[forward].apply(this.node, arguments);
852+
};
853+
});
722854

723855
Object.defineProperties(DomApi.prototype, {
724856

@@ -776,14 +908,18 @@
776908

777909
var CONTENT = 'content';
778910

779-
var factory = function(node, patch) {
911+
function factory(node, patch) {
780912
node = node || document;
781913
if (!node.__domApi) {
782914
node.__domApi = new DomApi(node, patch);
783915
}
784916
return node.__domApi;
785917
};
786918

919+
function hasDomApi(node) {
920+
return Boolean(node.__domApi);
921+
}
922+
787923
Polymer.dom = function(obj, patch) {
788924
if (obj instanceof Event) {
789925
return Polymer.EventApi.factory(obj);
@@ -928,7 +1064,8 @@
9281064
matchesSelector: matchesSelector,
9291065
hasInsertionPoint: hasInsertionPoint,
9301066
ctor: DomApi,
931-
factory: factory
1067+
factory: factory,
1068+
hasDomApi: hasDomApi
9321069
};
9331070

9341071
})();

‎src/mini/shady.html‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
polyfill across browsers.
1919
2020
*/
21+
22+
var hasDomApi = Polymer.DomApi.hasDomApi;
23+
2124
Polymer.Base._addFeature({
2225

2326
_prepShady: function() {
@@ -149,7 +152,11 @@
149152
this._updateChildNodes(this, children);
150153
}
151154
}
155+
var hasDistributed = this.shadyRoot._hasDistributed;
152156
this.shadyRoot._hasDistributed = true;
157+
if (!hasDistributed && hasDomApi(this)) {
158+
Polymer.dom(this)._recordMutationAddAll();
159+
}
153160
}
154161
},
155162

‎test/smoke/observeChildren.html‎

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
5+
<title>observeChildren</title>
6+
7+
<meta charset="utf-8">
8+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
9+
10+
<script src="../../../webcomponentsjs/webcomponents-lite.js"></script>
11+
<link rel="import" href="../../polymer.html">
12+
13+
</head>
14+
<body>
15+
16+
<dom-module id='test-content'>
17+
<template>
18+
[<content></content>]
19+
</template>
20+
<script>
21+
Polymer({
22+
is:'test-content',
23+
created: function() {
24+
Polymer.dom(this).observeChildren(function(info) {
25+
console.log('test-content', info);
26+
});
27+
}
28+
});
29+
</script>
30+
</dom-module>
31+
32+
<dom-module id='test-static'>
33+
<template>
34+
My children do not render.
35+
</template>
36+
<script>
37+
Polymer({
38+
is:'test-static',
39+
created: function() {
40+
Polymer.dom(this).observeChildren(function(info) {
41+
console.log('test-static', info);
42+
});
43+
}
44+
});
45+
</script>
46+
</dom-module>
47+
48+
<test-content id="content">
49+
<div>content A</div>
50+
<div>content B</div>
51+
</test-content>
52+
53+
<br><br>
54+
55+
<test-static id="stat"><div>static A</div><div>static B</div></test-static>
56+
57+
<script>
58+
Polymer.dom(content).flush();
59+
Polymer.dom(stat).flush();
60+
console.group('test dynamic');
61+
function obs(mxns) {
62+
console.log('custom observer', mxns);
63+
}
64+
var hc = Polymer.dom(content).observeChildren(obs);
65+
var hs = Polymer.dom(stat).observeChildren(obs);
66+
67+
var d = document.createElement('div');
68+
d.id = 'foo';
69+
d.innerHTML = 'Dynamic!';
70+
Polymer.dom(content).appendChild(d);
71+
Polymer.dom(stat).appendChild(d);
72+
Polymer.dom(content).flush();
73+
Polymer.dom(stat).flush();
74+
Polymer.dom(content).unobserveChildren(hc);
75+
Polymer.dom(stat).unobserveChildren(hs);
76+
Polymer.dom(stat).removeChild(d);
77+
Polymer.dom(stat).flush();
78+
console.groupEnd('test dynamic');
79+
</script>
80+
81+
</body>
82+
</html>

0 commit comments

Comments
 (0)