Files
polymer/src/lib/dom-api-effective-nodes-observer.html
Daniel Freedman acdfc1e097 Test with ESLint enabled
Fix linter errors
2016-02-08 14:04:17 -08:00

265 lines
8.0 KiB
HTML

<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="settings.html">
<script>
(function() {
'use strict';
var DomApi = Polymer.DomApi.ctor;
var Settings = Polymer.Settings;
/**
* DomApi.EffectiveNodesObserver tracks changes to an element's
* effective child nodes, the same list returned from
* `Polymer.dom(node).getEffectiveChildNodes()`.
* It is not meant to be used directly; it is used by
* `Polymer.dom(node).observeNodes(callback)` to observe changes.
*/
DomApi.EffectiveNodesObserver = function(domApi) {
this.domApi = domApi;
this.node = this.domApi.node;
this._listeners = [];
};
DomApi.EffectiveNodesObserver.prototype = {
addListener: function(callback) {
if (!this._isSetup) {
this._setup();
this._isSetup = true;
}
var listener = {fn: callback, _nodes: []};
this._listeners.push(listener);
this._scheduleNotify();
return listener;
},
removeListener: function(handle) {
var i = this._listeners.indexOf(handle);
if (i >= 0) {
this._listeners.splice(i, 1);
handle._nodes = [];
}
if (!this._hasListeners()) {
this._cleanup();
this._isSetup = false;
}
},
_setup: function() {
this._observeContentElements(this.domApi.childNodes);
},
_cleanup: function() {
this._unobserveContentElements(this.domApi.childNodes);
},
_hasListeners: function() {
return Boolean(this._listeners.length);
},
_scheduleNotify: function() {
if (this._debouncer) {
this._debouncer.stop();
}
this._debouncer = Polymer.Debounce(this._debouncer,
this._notify);
this._debouncer.context = this;
Polymer.dom.addDebouncer(this._debouncer);
},
notify: function() {
if (this._hasListeners()) {
this._scheduleNotify();
}
},
_notify: function() {
this._beforeCallListeners();
this._callListeners();
},
_beforeCallListeners: function() {
this._updateContentElements();
},
_updateContentElements: function() {
this._observeContentElements(this.domApi.childNodes);
},
_observeContentElements: function(elements) {
for (var i=0, n; (i < elements.length) && (n=elements[i]); i++) {
if (this._isContent(n)) {
n.__observeNodesMap = n.__observeNodesMap || new WeakMap();
if (!n.__observeNodesMap.has(this)) {
n.__observeNodesMap.set(this, this._observeContent(n));
}
}
}
},
_observeContent: function(content) {
var self = this;
var h = Polymer.dom(content).observeNodes(function() {
self._scheduleNotify();
});
h._avoidChangeCalculation = true;
return h;
},
_unobserveContentElements: function(elements) {
for (var i=0, n, h; (i < elements.length) && (n=elements[i]); i++) {
if (this._isContent(n)) {
h = n.__observeNodesMap.get(this);
if (h) {
Polymer.dom(n).unobserveNodes(h);
n.__observeNodesMap.delete(this);
}
}
}
},
_isContent: function(node) {
return (node.localName === 'content');
},
_callListeners: function() {
var o$ = this._listeners;
var nodes = this._getEffectiveNodes();
for (var i=0, o; (i < o$.length) && (o=o$[i]); i++) {
var info = this._generateListenerInfo(o, nodes);
if (info || o._alwaysNotify) {
this._callListener(o, info);
}
}
},
_getEffectiveNodes: function() {
return this.domApi.getEffectiveChildNodes()
},
_generateListenerInfo: function(listener, newNodes) {
if (listener._avoidChangeCalculation) {
return true;
}
var oldNodes = listener._nodes;
var info = {
target: this.node,
addedNodes: [],
removedNodes: []
};
var splices = Polymer.ArraySplice.calculateSplices(newNodes, oldNodes);
// process removals
for (var i=0, s; (i<splices.length) && (s=splices[i]); i++) {
for (var j=0, n; (j < s.removed.length) && (n=s.removed[j]); j++) {
info.removedNodes.push(n);
}
}
// process adds
for (i=0, s; (i<splices.length) && (s=splices[i]); i++) {
for (j=s.index; j < s.index + s.addedCount; j++) {
info.addedNodes.push(newNodes[j]);
}
}
// update cache
listener._nodes = newNodes;
if (info.addedNodes.length || info.removedNodes.length) {
return info;
}
},
_callListener: function(listener, info) {
return listener.fn.call(this.node, info);
},
enableShadowAttributeTracking: function() {}
};
if (Settings.useShadow) {
var baseSetup = DomApi.EffectiveNodesObserver.prototype._setup;
var baseCleanup = DomApi.EffectiveNodesObserver.prototype._cleanup;
Polymer.Base.extend(DomApi.EffectiveNodesObserver.prototype, {
_setup: function() {
if (!this._observer) {
var self = this;
this._mutationHandler = function(mxns) {
if (mxns && mxns.length) {
self._scheduleNotify();
}
};
this._observer = new MutationObserver(this._mutationHandler);
this._boundFlush = function() {
self._flush();
}
Polymer.dom.addStaticFlush(this._boundFlush);
// NOTE: subtree true is way too aggressive, but it easily catches
// attribute changes on children. These changes otherwise require
// attribute observers on every child. Testing has shown this
// approach to be more efficient.
// TODO(sorvell): do we still need to include an option to defeat
// attribute tracking?
this._observer.observe(this.node, { childList: true });
}
baseSetup.call(this);
},
_cleanup: function() {
this._observer.disconnect();
this._observer = null;
this._mutationHandler = null;
Polymer.dom.removeStaticFlush(this._boundFlush);
baseCleanup.call(this);
},
_flush: function() {
if (this._observer) {
this._mutationHandler(this._observer.takeRecords());
}
},
enableShadowAttributeTracking: function() {
if (this._observer) {
// provoke all listeners needed for <content> observation
// to always call listeners when no-op changes occur (which may
// affect lower distributions.
this._makeContentListenersAlwaysNotify();
this._observer.disconnect();
this._observer.observe(this.node, {
childList: true,
attributes: true,
subtree: true
});
var root = this.domApi.getOwnerRoot();
var host = root && root.host;
if (host && Polymer.dom(host).observer) {
Polymer.dom(host).observer.enableShadowAttributeTracking();
}
}
},
_makeContentListenersAlwaysNotify: function() {
for (var i=0, h; i < this._listeners.length ; i++) {
h = this._listeners[i];
h._alwaysNotify = h._isContentListener;
}
}
});
}
})();
</script>