|
26 | 26 |
|
27 | 27 | var DomApi = function(node) { |
28 | 28 | this.node = node; |
| 29 | + this._observers = []; |
| 30 | + this._addedNodes = []; |
| 31 | + this._removedNodes = [] |
29 | 32 | if (this.patch) { |
30 | 33 | this.patch(); |
31 | 34 | } |
|
65 | 68 | // 3. node is <content> (host of container needs distribution) |
66 | 69 | appendChild: function(node) { |
67 | 70 | var handled; |
| 71 | + this._recordMutationRemoveFromParent(node); |
68 | 72 | this._removeNodeFromHost(node, true); |
69 | 73 | if (this._nodeIsInLogicalTree(this.node)) { |
70 | 74 | this._addLogicalInfo(node, this.node); |
|
78 | 82 | addToComposedParent(container, node); |
79 | 83 | nativeAppendChild.call(container, node); |
80 | 84 | } |
| 85 | + this._recordMutationAdd(node); |
81 | 86 | return node; |
82 | 87 | }, |
83 | 88 |
|
|
86 | 91 | return this.appendChild(node); |
87 | 92 | } |
88 | 93 | var handled; |
| 94 | + this._recordMutationRemoveFromParent(node); |
89 | 95 | this._removeNodeFromHost(node, true); |
90 | 96 | if (this._nodeIsInLogicalTree(this.node)) { |
91 | 97 | saveLightChildrenIfNeeded(this.node); |
|
109 | 115 | addToComposedParent(container, node, ref_node); |
110 | 116 | nativeInsertBefore.call(container, node, ref_node); |
111 | 117 | } |
| 118 | + this._recordMutationAdd(node); |
112 | 119 | return node; |
113 | 120 | }, |
114 | 121 |
|
|
136 | 143 | nativeRemoveChild.call(container, node); |
137 | 144 | } |
138 | 145 | } |
| 146 | + this._recordMutationRemove(node); |
139 | 147 | return node; |
140 | 148 | }, |
141 | 149 |
|
|
251 | 259 | root.host._elementRemove(node); |
252 | 260 | hostNeedsDist = this._removeDistributedChildren(root, node); |
253 | 261 | } |
254 | | - this._removeLogicalInfo(node, node._lightParent); |
| 262 | + this._removeLogicalInfo(node, parent); |
255 | 263 | } |
256 | 264 | this._removeOwnerShadyRoot(node); |
257 | 265 | if (root && hostNeedsDist) { |
|
478 | 486 | } |
479 | 487 | } |
480 | 488 | 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 = []; |
481 | 552 | } |
482 | 553 |
|
483 | 554 | }; |
|
718 | 789 | return n$ ? Array.prototype.slice.call(n$) : []; |
719 | 790 | }; |
720 | 791 |
|
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 | + }); |
722 | 854 |
|
723 | 855 | Object.defineProperties(DomApi.prototype, { |
724 | 856 |
|
|
776 | 908 |
|
777 | 909 | var CONTENT = 'content'; |
778 | 910 |
|
779 | | - var factory = function(node, patch) { |
| 911 | + function factory(node, patch) { |
780 | 912 | node = node || document; |
781 | 913 | if (!node.__domApi) { |
782 | 914 | node.__domApi = new DomApi(node, patch); |
783 | 915 | } |
784 | 916 | return node.__domApi; |
785 | 917 | }; |
786 | 918 |
|
| 919 | + function hasDomApi(node) { |
| 920 | + return Boolean(node.__domApi); |
| 921 | + } |
| 922 | + |
787 | 923 | Polymer.dom = function(obj, patch) { |
788 | 924 | if (obj instanceof Event) { |
789 | 925 | return Polymer.EventApi.factory(obj); |
|
928 | 1064 | matchesSelector: matchesSelector, |
929 | 1065 | hasInsertionPoint: hasInsertionPoint, |
930 | 1066 | ctor: DomApi, |
931 | | - factory: factory |
| 1067 | + factory: factory, |
| 1068 | + hasDomApi: hasDomApi |
932 | 1069 | }; |
933 | 1070 |
|
934 | 1071 | })(); |
|
0 commit comments