five-bells-visualization
Version:
Tool to visualize Five Bells payments
472 lines (411 loc) • 16.1 kB
HTML
<!--
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">
<link rel="import" href="event-api.html">
<script>
Polymer.DomApi = (function() {
var Debounce = Polymer.Debounce;
var Settings = Polymer.Settings;
var nativeInsertBefore = Element.prototype.insertBefore;
var nativeRemoveChild = Element.prototype.removeChild;
var nativeAppendChild = Element.prototype.appendChild;
var dirtyRoots = [];
var DomApi = function(node, patch) {
this.node = node;
if (patch) {
this.patch();
}
};
DomApi.prototype = {
// experimental: support patching selected native api.
patch: function() {
var self = this;
this.node.appendChild = function(node) {
return self.appendChild(node);
};
this.node.insertBefore = function(node, ref_node) {
return self.insertBefore(node, ref_node);
};
this.node.removeChild = function(node) {
return self.removeChild(node);
};
},
get childNodes() {
var c$ = getLightChildren(this.node);
return Array.isArray(c$) ? c$ : Array.prototype.slice.call(c$);
},
get children() {
return Array.prototype.filter.call(this.childNodes, function(n) {
return (n.nodeType === Node.ELEMENT_NODE);
});
},
get parentNode() {
return this.node.lightParent || this.node.parentNode;
},
flush: function() {
for (var i=0, host; i<dirtyRoots.length; i++) {
host = dirtyRoots[i];
host.flushDebouncer('_distribute');
}
dirtyRoots = [];
},
_lazyDistribute: function(host) {
if (host.shadyRoot) {
host.shadyRoot._distributionClean = false;
}
// TODO(sorvell): optimize debounce so it does less work by default
// and then remove these checks...
// need to dirty distribution once.
if (!host.isDebouncerActive('_distribute')) {
host.debounce('_distribute', host._distributeContent);
dirtyRoots.push(host);
}
},
// cases in which we may not be able to just do standard appendChild
// 1. container has a shadyRoot (needsDistribution IFF the shadyRoot
// has an insertion point)
// 2. container is a shadyRoot (don't distribute, instead set
// container to container.host.
// 3. node is <content> (host of container needs distribution)
appendChild: function(node) {
var distributed;
this._removeNodeFromHost(node);
if (this._nodeIsInLogicalTree(this.node)) {
var host = this._hostForNode(this.node);
this._addLogicalInfo(node, this.node, host && host.shadyRoot);
this._addNodeToHost(node);
if (host) {
distributed = this._maybeDistribute(node, this.node, host);
}
}
if (!distributed) {
// if adding to a shadyRoot, add to host instead
var container = this.node._isShadyRoot ? this.node.host : this.node;
nativeAppendChild.call(container, node);
}
return node;
},
insertBefore: function(node, ref_node) {
if (!ref_node) {
return this.appendChild(node);
}
var distributed;
this._removeNodeFromHost(node);
if (this._nodeIsInLogicalTree(this.node)) {
saveLightChildrenIfNeeded(this.node);
var children = this.childNodes;
var index = children.indexOf(ref_node);
if (index < 0) {
throw Error('The ref_node to be inserted before is not a child ' +
'of this node');
}
var host = this._hostForNode(this.node);
this._addLogicalInfo(node, this.node, host && host.shadyRoot, index);
this._addNodeToHost(node);
if (host) {
distributed = this._maybeDistribute(node, this.node, host);
}
}
if (!distributed) {
// if ref_node is <content> replace with first distributed node
ref_node = ref_node.localName === CONTENT ?
this._firstComposedNode(ref_node) : ref_node;
// if adding to a shadyRoot, add to host instead
var container = this.node._isShadyRoot ? this.node.host : this.node;
nativeInsertBefore.call(container, node, ref_node);
}
return node;
},
/**
Removes the given `node` from the element's `lightChildren`.
This method also performs dom composition.
*/
removeChild: function(node) {
var distributed;
if (this._nodeIsInLogicalTree(this.node)) {
var host = this._hostForNode(this.node);
distributed = this._maybeDistribute(node, this.node, host);
this._removeNodeFromHost(node);
}
if (!distributed) {
// if removing from a shadyRoot, remove form host instead
var container = this.node._isShadyRoot ? this.node.host : this.node;
nativeRemoveChild.call(container, node);
}
return node;
},
replaceChild: function(node, ref_node) {
this.insertBefore(node, ref_node);
this.removeChild(ref_node);
return node;
},
getOwnerRoot: function() {
return this._ownerShadyRootForNode(this.node);
},
_ownerShadyRootForNode: function(node) {
if (node._ownerShadyRoot === undefined) {
var root;
if (node._isShadyRoot) {
root = node;
} else {
var parent = Polymer.dom(node).parentNode;
if (parent) {
root = parent._isShadyRoot ? parent :
this._ownerShadyRootForNode(parent);
} else {
root = null;
}
}
node._ownerShadyRoot = root;
}
return node._ownerShadyRoot;
},
_maybeDistribute: function(node, parent, host) {
var nodeNeedsDistribute = this._nodeNeedsDistribution(node);
var distribute = this._parentNeedsDistribution(parent) ||
nodeNeedsDistribute;
if (nodeNeedsDistribute) {
this._updateInsertionPoints(host);
}
if (distribute) {
this._lazyDistribute(host);
}
return distribute;
},
_updateInsertionPoints: function(host) {
host.shadyRoot._insertionPoints =
factory(host.shadyRoot).querySelectorAll(CONTENT);
},
_nodeIsInLogicalTree: function(node) {
return Boolean(node._isShadyRoot ||
this._ownerShadyRootForNode(node) ||
node.shadyRoot);
},
// note: a node is its own host
_hostForNode: function(node) {
var root = node.shadyRoot || (node._isShadyRoot ?
node : this._ownerShadyRootForNode(node));
return root && root.host;
},
_parentNeedsDistribution: function(parent) {
return parent.shadyRoot && hasInsertionPoint(parent.shadyRoot);
},
// TODO(sorvell): technically we should check non-fragment nodes for
// <content> children but since this case is assumed to be exceedingly
// rare, we avoid the cost and will address with some specific api
// when the need arises.
_nodeNeedsDistribution: function(node) {
return (node.localName === CONTENT) ||
((node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) &&
node.querySelector(CONTENT));
},
_removeNodeFromHost: function(node) {
if (node.lightParent) {
var root = this._ownerShadyRootForNode(node);
if (root) {
root.host._elementRemove(node);
}
this._removeLogicalInfo(node, node.lightParent);
}
this._removeOwnerShadyRoot(node);
},
_addNodeToHost: function(node) {
var checkNode = node.nodeType === Node.DOCUMENT_FRAGMENT_NODE ?
node.firstChild : node;
var root = this._ownerShadyRootForNode(checkNode);
if (root) {
root.host._elementAdd(node);
}
},
_addLogicalInfo: function(node, container, root, index) {
saveLightChildrenIfNeeded(container);
var children = factory(container).childNodes;
index = index === undefined ? children.length : index;
// handle document fragments
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
var n = node.firstChild;
while (n) {
children.splice(index++, 0, n);
n.lightParent = container;
n = n.nextSibling;
}
} else {
children.splice(index, 0, node);
node.lightParent = container;
}
},
// NOTE: in general, we expect contents of the lists here to be small-ish
// and therefore indexOf to be nbd. Other optimizations can be made
// for larger lists (linked list)
_removeLogicalInfo: function(node, container) {
var children = factory(container).childNodes;
var index = children.indexOf(node);
if ((index < 0) || (container !== node.lightParent)) {
throw Error('The node to be removed is not a child of this node');
}
children.splice(index, 1);
node.lightParent = null;
},
_removeOwnerShadyRoot: function(node) {
// TODO(sorvell): need to clear any children of element?
node._ownerShadyRoot = undefined;
},
// TODO(sorvell): This will fail if distribution that affects this
// question is pending; this is expected to be exceedingly rare, but if
// the issue comes up, we can force a flush in this case.
_firstComposedNode: function(content) {
var n$ = factory(content).getDistributedNodes();
for (var i=0, l=n$.length, n, p$; (i<l) && (n=n$[i]); i++) {
p$ = factory(n).getDestinationInsertionPoints();
// means that we're composed to this spot.
if (p$[p$.length-1] === content) {
return n;
}
}
},
// TODO(sorvell): consider doing native QSA and filtering results.
querySelector: function(selector) {
return this.querySelectorAll(selector)[0];
},
querySelectorAll: function(selector) {
return this._query(function(n) {
return matchesSelector.call(n, selector);
}, this.node);
},
_query: function(matcher, node) {
var list = [];
this._queryElements(factory(node).childNodes, matcher, list);
return list;
},
_queryElements: function(elements, matcher, list) {
for (var i=0, l=elements.length, c; (i<l) && (c=elements[i]); i++) {
if (c.nodeType === Node.ELEMENT_NODE) {
this._queryElement(c, matcher, list);
}
}
},
_queryElement: function(node, matcher, list) {
if (matcher(node)) {
list.push(node);
}
this._queryElements(factory(node).childNodes, matcher, list);
},
getDestinationInsertionPoints: function() {
return this.node._destinationInsertionPoints || [];
},
getDistributedNodes: function() {
return this.node._distributedNodes || [];
},
/*
Returns a list of nodes distributed within this element. These can be
dom children or elements distributed to children that are insertion
points.
*/
queryDistributedElements: function(selector) {
var c$ = this.childNodes;
var list = [];
this._distributedFilter(selector, c$, list);
for (var i=0, l=c$.length, c; (i<l) && (c=c$[i]); i++) {
if (c.localName === CONTENT) {
this._distributedFilter(selector, factory(c).getDistributedNodes(),
list);
}
}
return list;
},
_distributedFilter: function(selector, list, results) {
results = results || [];
for (var i=0, l=list.length, d; (i<l) && (d=list[i]); i++) {
if ((d.nodeType === Node.ELEMENT_NODE) &&
(d.localName !== CONTENT) &&
matchesSelector.call(d, selector)) {
results.push(d);
}
}
return results;
}
};
if (Settings.useShadow) {
DomApi.prototype.querySelectorAll = function(selector) {
return Array.prototype.slice.call(this.node.querySelectorAll(selector));
};
DomApi.prototype.patch = function() {};
DomApi.prototype.getOwnerRoot = function() {
var n = this.node;
while (n) {
if (n.nodeType === Node.DOCUMENT_FRAGMENT_NODE && n.host) {
return n;
}
n = n.parentNode;
}
};
DomApi.prototype.getDestinationInsertionPoints = function() {
var n$ = this.node.getDestinationInsertionPoints();
return n$ ? Array.prototype.slice.call(n$) : [];
};
DomApi.prototype.getDistributedNodes = function() {
var n$ = this.node.getDistributedNodes();
return n$ ? Array.prototype.slice.call(n$) : [];
};
}
var CONTENT = 'content';
var factory = function(node, patch) {
node = node || document;
if (!node.__domApi) {
node.__domApi = new DomApi(node, patch);
}
return node.__domApi;
};
Polymer.dom = function(obj, patch) {
if (obj instanceof Event) {
return Polymer.EventApi.factory(obj);
} else {
return factory(obj, patch);
}
};
// make flush available directly.
Polymer.dom.flush = DomApi.prototype.flush;
function getLightChildren(node) {
var children = node.lightChildren;
return children ? children : node.childNodes;
}
function saveLightChildrenIfNeeded(node) {
// Capture the list of light children. It's important to do this before we
// start transforming the DOM into "rendered" state.
//
// Children may be added to this list dynamically. It will be treated as the
// source of truth for the light children of the element. This element's
// actual children will be treated as the rendered state once lightChildren
// is populated.
if (!node.lightChildren) {
var children = [];
for (var child = node.firstChild; child; child = child.nextSibling) {
children.push(child);
child.lightParent = child.lightParent || node;
}
node.lightChildren = children;
}
}
function hasInsertionPoint(root) {
return Boolean(root._insertionPoints.length);
}
var p = Element.prototype;
var matchesSelector = p.matches || p.matchesSelector ||
p.mozMatchesSelector || p.msMatchesSelector ||
p.oMatchesSelector || p.webkitMatchesSelector;
return {
getLightChildren: getLightChildren,
saveLightChildrenIfNeeded: saveLightChildrenIfNeeded,
matchesSelector: matchesSelector,
hasInsertionPoint: hasInsertionPoint,
factory: factory
};
})();
</script>