can-dom-mutate
Version:
Dispatch and listen for DOM mutations
171 lines (147 loc) • 3.94 kB
JavaScript
;
var getDocument = require("can-globals/document/document");
function eliminate(array, item) {
var index = array.indexOf(item);
if (index >= 0) {
array.splice(index, 1);
}
}
function wasNotInSet(item, set) {
var inSet = set.has(item);
if(inSet === false) {
set.add(item);
}
return !inSet;
}
function contains(parent, child){
if(child && child.nodeType === Node.TEXT_NODE) {
return contains(parent, child.parentNode);
}
if(parent.contains) {
return parent.contains(child);
}
if(parent.nodeType === Node.DOCUMENT_NODE && parent.documentElement) {
return contains(parent.documentElement, child);
} else {
child = child.parentNode;
if(child === parent) {
return true;
}
return false;
}
}
function isDocumentElement (node) {
return getDocument().documentElement === node;
}
function isFragment (node) {
return !!(node && node.nodeType === 11);
}
function isElementNode (node) {
return !!(node && node.nodeType === 1);
}
function getChildren (parentNode) {
var nodes = [];
var node = parentNode.firstChild;
while (node) {
nodes.push(node);
node = node.nextSibling;
}
return nodes;
}
function getParents (node) {
var nodes;
if (isFragment(node)) {
nodes = getChildren(node);
} else {
nodes = [node];
}
return nodes;
}
function getNodesLegacyB(node) {
var skip, tmp;
var depth = 0;
var items = isFragment(node) ? [] : [node];
if(node.firstChild == null) {
return items;
}
// Always start with the initial element.
do {
if ( !skip && (tmp = node.firstChild) ) {
depth++;
items.push(tmp);
} else if ( tmp = node.nextSibling ) {
skip = false;
items.push(tmp);
} else {
// Skipped or no first child and no next sibling, so traverse upwards,
tmp = node.parentNode;
// and decrement the depth.
depth--;
// Enable skipping, so that in the next loop iteration, the children of
// the now-current node (parent node) aren't processed again.
skip = true;
}
// Instead of setting node explicitly in each conditional block, use the
// tmp var and set it here.
node = tmp;
// Stop if depth comes back to 0 (or goes below zero, in conditions where
// the passed node has neither children nore next siblings).
} while ( depth > 0 );
return items;
}
// IE11 requires a filter parameter for createTreeWalker
// it also must be an object with an `acceptNode` property
function treeWalkerFilterFunction() {
return NodeFilter.FILTER_ACCEPT;
}
var treeWalkerFilter = treeWalkerFilterFunction;
treeWalkerFilter.acceptNode = treeWalkerFilterFunction;
function getNodesWithTreeWalker(rootNode) {
var result = isFragment(rootNode) ? [] : [rootNode];
// IE11 throws if createTreeWalker is called on a non-ElementNode
var walker = isElementNode(rootNode) && getDocument().createTreeWalker(
rootNode,
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT,
treeWalkerFilter,
false
);
var node;
while(node = walker && walker.nextNode()) {
result.push(node);
}
return result;
}
function getAllNodes (node) {
if( getDocument().createTreeWalker !== undefined ) {
return getNodesWithTreeWalker(node);
} else {
return getNodesLegacyB(node);
}
}
function subscription (fn) {
return function _subscription () {
var disposal = fn.apply(this, arguments);
var isDisposed = false;
return function _disposal () {
if (isDisposed) {
var fnName = fn.name || fn.displayName || 'an anonymous function';
var message = 'Disposal function returned by ' + fnName + ' called more than once.';
throw new Error(message);
}
disposal.apply(this, arguments);
isDisposed = true;
};
};
}
module.exports = {
eliminate: eliminate,
getDocument: getDocument,
isDocumentElement: isDocumentElement,
isFragment: isFragment,
getParents: getParents,
getAllNodes: getAllNodes,
getChildren: getChildren,
subscription: subscription,
wasNotInSet: wasNotInSet,
contains: contains
};