can-dom-mutate
Advanced tools
Comparing version 0.1.0 to 0.2.1
'use strict'; | ||
var each = require('can-util/js/each/each'); | ||
var domData = require('can-util/dom/data/data'); | ||
var CIDMap = require('can-util/js/cid-map/cid-map'); | ||
var setImmediate = require('can-util/js/set-immediate/set-immediate'); | ||
var observer = require('./-observer'); | ||
var globals = require('can-globals'); | ||
var getRoot = require('can-globals/global/global'); | ||
var getMutationObserver = require('can-globals/mutation-observer/mutation-observer'); | ||
var setImmediate = getRoot().setImmediate || function (cb) { | ||
return setTimeout(cb, 0); | ||
}; | ||
var util = require('./-util'); | ||
var getDocument = util.getDocument; | ||
var eliminate = util.eliminate; | ||
var subscription = util.subscription; | ||
var isDocumentElement = util.isDocumentElement; | ||
var getAllNodes = util.getAllNodes; | ||
var push = Array.prototype.push; | ||
var slice = Array.prototype.slice; | ||
var domMutate; | ||
var dataStore = new WeakMap(); | ||
function eliminate(array, item) { | ||
var index = array.indexOf(item); | ||
if (index >= 0) { | ||
array.splice(index, 1); | ||
function getRelatedData(node, key) { | ||
var data = dataStore.get(node); | ||
if (data) { | ||
return data[key]; | ||
} | ||
} | ||
function batch(processBatchItems) { | ||
function setRelatedData(node, key, targetListenersMap) { | ||
var data = dataStore.get(node) || dataStore.set(node, {}).get(node); | ||
data[key] = targetListenersMap; | ||
} | ||
function deleteRelatedData(node, key) { | ||
var data = dataStore.get(node); | ||
return delete data[key]; | ||
} | ||
function batch(processBatchItems, shouldDeduplicate) { | ||
var waitingBatch = []; | ||
var waitingCalls = []; | ||
var dispatchSet = new Set(); | ||
var isPrimed = false; | ||
return function batchAdd(items, callback) { | ||
waitingBatch = waitingBatch.concat(items); | ||
if (shouldDeduplicate) { | ||
for (var i = 0; i < items.length; i++) { | ||
var item = items[i]; | ||
var target = item.target; | ||
if (!dispatchSet.has(target)) { | ||
waitingBatch.push(item); | ||
dispatchSet.add(target); | ||
} | ||
} | ||
} else { | ||
push.apply(waitingBatch, items); | ||
} | ||
if (callback) { | ||
@@ -36,7 +70,11 @@ waitingCalls.push(callback); | ||
waitingCalls = []; | ||
if (shouldDeduplicate) { | ||
dispatchSet = new Set(); | ||
} | ||
isPrimed = false; | ||
processBatchItems(currentBatch); | ||
currentCalls.forEach(function (callback) { | ||
callback(); | ||
}); | ||
var callCount = currentCalls.length; | ||
for (var c = 0; c < callCount; c++) { | ||
currentCalls[c](); | ||
} | ||
}); | ||
@@ -47,13 +85,5 @@ } | ||
function getDocument(target) { | ||
return target.ownerDocument || target.document || target; | ||
} | ||
function isDocumentElement (node) { | ||
return getDocument(node).documentElement === node; | ||
} | ||
function getDocumentListeners (target, key) { | ||
var doc = getDocument(target); | ||
var data = domData.get.call(doc, key); | ||
var data = getRelatedData(doc, key); | ||
if (data) { | ||
@@ -66,3 +96,3 @@ return data.listeners; | ||
var doc = getDocument(target); | ||
var targetListenersMap = domData.get.call(doc, key); | ||
var targetListenersMap = getRelatedData(doc, key); | ||
if (!targetListenersMap) { | ||
@@ -77,6 +107,6 @@ return; | ||
var doc = getDocument(target); | ||
var targetListenersMap = domData.get.call(doc, key); | ||
var targetListenersMap = getRelatedData(doc, key); | ||
if (!targetListenersMap) { | ||
targetListenersMap = new CIDMap(); | ||
domData.set.call(doc, key, targetListenersMap); | ||
targetListenersMap = new Map(); | ||
setRelatedData(doc, key, targetListenersMap); | ||
} | ||
@@ -93,3 +123,3 @@ var targetListeners = targetListenersMap.get(target); | ||
var doc = getDocument(target); | ||
var targetListenersMap = domData.get.call(doc, key); | ||
var targetListenersMap = getRelatedData(doc, key); | ||
if (!targetListenersMap) { | ||
@@ -103,6 +133,6 @@ return; | ||
eliminate(targetListeners, listener); | ||
if (targetListeners.size === 0) { | ||
if (targetListeners.length === 0) { | ||
targetListenersMap['delete'](target); | ||
if (targetListenersMap.size === 0) { | ||
domData.clean.call(doc, key); | ||
deleteRelatedData(doc, key); | ||
} | ||
@@ -112,18 +142,23 @@ } | ||
function dispatch(listenerKey, documentDataKey, isAttributes) { | ||
function fire (callbacks, arg) { | ||
var safeCallbacks = slice.call(callbacks, 0); | ||
var safeCallbackCount = safeCallbacks.length; | ||
for (var i = 0; i < safeCallbackCount; i++) { | ||
safeCallbacks[i](arg); | ||
} | ||
} | ||
function dispatch(listenerKey, documentDataKey) { | ||
return function dispatchEvents(events) { | ||
for (var e = 0; e < events.length; e++) { | ||
var event = events[e]; | ||
var target = isAttributes ? event.node : event; | ||
var target = event.target; | ||
var targetListeners = getTargetListeners(target, listenerKey); | ||
if (targetListeners) { | ||
for (var t = 0; t < targetListeners.length; t++) { | ||
targetListeners[t](event); | ||
} | ||
fire(targetListeners, event); | ||
} | ||
if (!documentDataKey) { | ||
return; | ||
continue; | ||
} | ||
@@ -133,5 +168,3 @@ | ||
if (documentListeners) { | ||
for (var l = 0; l < documentListeners.length; l++) { | ||
documentListeners[l](event); | ||
} | ||
fire(documentListeners, event); | ||
} | ||
@@ -143,27 +176,44 @@ } | ||
function observeMutations(target, observerKey, config, handler) { | ||
var MutationObserver = observer.getValue(); | ||
if (!MutationObserver) { | ||
return function () { | ||
}; | ||
} | ||
var observerData = domData.get.call(target, observerKey); | ||
var observerData = getRelatedData(target, observerKey); | ||
if (!observerData) { | ||
var targetObserver = new MutationObserver(handler); | ||
targetObserver.observe(target, config); | ||
observerData = { | ||
observer: targetObserver, | ||
observingCount: 0 | ||
}; | ||
domData.set.call(target, observerKey, observerData); | ||
setRelatedData(target, observerKey, observerData); | ||
} | ||
var setupObserver = function () { | ||
var MutationObserver = getMutationObserver(); | ||
if (MutationObserver) { | ||
var Node = getRoot().Node; | ||
var isRealNode = !!(Node && target instanceof Node); | ||
if (isRealNode) { | ||
var targetObserver = new MutationObserver(handler); | ||
targetObserver.observe(target, config); | ||
observerData.observer = targetObserver; | ||
} | ||
} else { | ||
if (observerData.observer) { | ||
observerData.observer.disconnect(); | ||
observerData.observer = null; | ||
} | ||
} | ||
}; | ||
if (observerData.observingCount === 0) { | ||
globals.onKeyValue('MutationObserver', setupObserver); | ||
setupObserver(); | ||
} | ||
observerData.observingCount++; | ||
return function stopObservingMutations() { | ||
var observerData = domData.get.call(target, observerKey); | ||
var observerData = getRelatedData(target, observerKey); | ||
if (observerData) { | ||
observerData.observingCount--; | ||
if (observerData.observingCount <= 0) { | ||
observerData.observer.disconnect(); | ||
domData.clean.call(target, observerKey); | ||
if (observerData.observer) { | ||
observerData.observer.disconnect(); | ||
} | ||
deleteRelatedData(target, observerKey); | ||
globals.offKeyValue('MutationObserver', setupObserver); | ||
} | ||
@@ -175,14 +225,24 @@ } | ||
function handleTreeMutations(mutations) { | ||
mutations.forEach(function (mutation) { | ||
each(mutation.addedNodes, function (node) { | ||
domMutate.dispatchNodeInsertion(node); | ||
}); | ||
each(mutation.removedNodes, function (node) { | ||
domMutate.dispatchNodeRemoval(node); | ||
}); | ||
}); | ||
var mutationCount = mutations.length; | ||
for (var m = 0; m < mutationCount; m++) { | ||
var mutation = mutations[m]; | ||
var addedNodes = mutation.addedNodes; | ||
var addedCount = addedNodes.length; | ||
for (var a = 0; a < addedCount; a++) { | ||
domMutate.dispatchNodeInsertion(addedNodes[a]); | ||
} | ||
var removedNodes = mutation.removedNodes; | ||
var removedCount = removedNodes.length; | ||
for (var r = 0; r < removedCount; r++) { | ||
domMutate.dispatchNodeRemoval(removedNodes[r]); | ||
} | ||
} | ||
} | ||
function handleAttributeMutations(mutations) { | ||
mutations.forEach(function (mutation) { | ||
var mutationCount = mutations.length; | ||
for (var m = 0; m < mutationCount; m++) { | ||
var mutation = mutations[m]; | ||
if (mutation.type === 'attributes') { | ||
@@ -194,3 +254,3 @@ var node = mutation.target; | ||
} | ||
}); | ||
} | ||
} | ||
@@ -208,18 +268,2 @@ | ||
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; | ||
}; | ||
}; | ||
} | ||
function addNodeListener(listenerKey, observerKey, isAttributes) { | ||
@@ -231,3 +275,3 @@ return subscription(function _addNodeListener(target, listener) { | ||
} else { | ||
stopObserving = observeMutations(getDocument(target).documentElement, observerKey, treeMutationConfig, handleTreeMutations); | ||
stopObserving = observeMutations(getDocument(target), observerKey, treeMutationConfig, handleTreeMutations); | ||
} | ||
@@ -238,3 +282,3 @@ | ||
stopObserving(); | ||
removeTargetListener(target, listenerKey, listenerKey); | ||
removeTargetListener(target, listenerKey, listener); | ||
}; | ||
@@ -251,6 +295,6 @@ }); | ||
var doc = getDocument(documentElement); | ||
var documentData = domData.get.call(doc, globalDataKey); | ||
var documentData = getRelatedData(doc, globalDataKey); | ||
if (!documentData) { | ||
documentData = {listeners: []}; | ||
domData.set.call(doc, globalDataKey, documentData); | ||
setRelatedData(doc, globalDataKey, documentData); | ||
} | ||
@@ -267,3 +311,3 @@ | ||
return function removeGlobalGroupListener() { | ||
var documentData = domData.get.call(doc, globalDataKey); | ||
var documentData = getRelatedData(doc, globalDataKey); | ||
if (!documentData) { | ||
@@ -277,3 +321,3 @@ return; | ||
documentData.removeListener(); | ||
domData.clean.call(doc, globalDataKey); | ||
deleteRelatedData(doc, globalDataKey); | ||
} | ||
@@ -284,19 +328,8 @@ }; | ||
function toNodes(child) { | ||
var isFragment = child.nodeType === Node.DOCUMENT_FRAGMENT_NODE; | ||
if (!isFragment) { | ||
return [child]; | ||
function toMutationEvents (nodes) { | ||
var events = []; | ||
for (var i = 0; i < nodes.length; i++) { | ||
events.push({target: nodes[i]}); | ||
} | ||
var children = []; | ||
var node = child.firstChild; | ||
while (node) { | ||
var nodes = toNodes(child); | ||
for (var i = 0; i < nodes.length; i++) { | ||
children.push(nodes[i]); | ||
} | ||
node = node.nextSibling; | ||
} | ||
return children; | ||
return events; | ||
} | ||
@@ -312,3 +345,5 @@ | ||
// document listener keys | ||
var documentInsertionDataKey = domMutationPrefix + 'DocumentInsertionData'; | ||
var documentRemovalDataKey = domMutationPrefix + 'DocumentRemovalData'; | ||
var documentAttributeChangeDataKey = domMutationPrefix + 'DocumentAttributeChangeData'; | ||
@@ -319,5 +354,5 @@ // observer keys | ||
var dispatchInsertion = batch(dispatch(insertionDataKey)); | ||
var dispatchRemoval = batch(dispatch(removalDataKey, documentRemovalDataKey)); | ||
var dispatchAttributeChange = batch(dispatch(attributeChangeDataKey, null, true)); | ||
var dispatchInsertion = batch(dispatch(insertionDataKey, documentInsertionDataKey), true); | ||
var dispatchRemoval = batch(dispatch(removalDataKey, documentRemovalDataKey), true); | ||
var dispatchAttributeChange = batch(dispatch(attributeChangeDataKey, documentAttributeChangeDataKey)); | ||
@@ -330,2 +365,6 @@ // node listeners | ||
// global listeners | ||
var addInsertionListener = addGlobalListener( | ||
documentInsertionDataKey, | ||
addNodeInsertionListener | ||
); | ||
var addRemovalListener = addGlobalListener( | ||
@@ -335,2 +374,6 @@ documentRemovalDataKey, | ||
); | ||
var addAttributeChangeListener = addGlobalListener( | ||
documentAttributeChangeDataKey, | ||
addNodeAttributeChangeListener | ||
); | ||
@@ -357,3 +400,4 @@ /** | ||
dispatchNodeInsertion: function (node, callback) { | ||
dispatchInsertion(toNodes(node), callback); | ||
var events = toMutationEvents(getAllNodes(node)); | ||
dispatchInsertion(events, callback); | ||
}, | ||
@@ -372,3 +416,4 @@ | ||
dispatchNodeRemoval: function (node, callback) { | ||
dispatchRemoval(toNodes(node), callback); | ||
var events = toMutationEvents(getAllNodes(node)); | ||
dispatchRemoval(events, callback); | ||
}, | ||
@@ -383,3 +428,3 @@ | ||
* @parent can-dom-mutate.static | ||
* @param {Node} node The node on which to dispatch an attribute change mutation. | ||
* @param {Node} target The node on which to dispatch an attribute change mutation. | ||
* @param {String} attributeName The attribute name whose value has changed. | ||
@@ -389,8 +434,8 @@ * @param {String} oldValue The attribute value before the change. | ||
*/ | ||
dispatchNodeAttributeChange: function (node, attributeName, oldValue, callback) { | ||
dispatchAttributeChange({ | ||
node: node, | ||
dispatchNodeAttributeChange: function (target, attributeName, oldValue, callback) { | ||
dispatchAttributeChange([{ | ||
target: target, | ||
attributeName: attributeName, | ||
oldValue: oldValue | ||
}, callback); | ||
}], callback); | ||
}, | ||
@@ -442,11 +487,37 @@ | ||
* | ||
* @signature `onRemoval( node, callback )` | ||
* @signature `onRemoval( documentElement, callback )` | ||
* @parent can-dom-mutate.static | ||
* @param {Node} documentElement The doucmentElement on which to listen for removal mutations. | ||
* @param {Node} documentElement The documentElement on which to listen for removal mutations. | ||
* @param {function} callback The callback called when a removal mutation is dispatched. | ||
* @return {function} The callback to remove the mutation listener. | ||
*/ | ||
onRemoval: addRemovalListener | ||
onRemoval: addRemovalListener, | ||
/** | ||
* @function can-dom-mutate.onInsertion onInsertion | ||
* | ||
* Listen for insertion mutations on any node within the documentElement. | ||
* | ||
* @signature `onInsertion( documentElement, callback )` | ||
* @parent can-dom-mutate.static | ||
* @param {Node} documentElement The documentElement on which to listen for removal mutations. | ||
* @param {function} callback The callback called when a insertion mutation is dispatched. | ||
* @return {function} The callback to remove the mutation listener. | ||
*/ | ||
onInsertion: addInsertionListener, | ||
/** | ||
* @function can-dom-mutate.onAttributeChange onAttributeChange | ||
* | ||
* Listen for attribute change mutations on any node within the documentElement. | ||
* | ||
* @signature `onAttributeChange( documentElement, callback )` | ||
* @parent can-dom-mutate.static | ||
* @param {Node} documentElement The documentElement on which to listen for removal mutations. | ||
* @param {function} callback The callback called when an attribute change mutation is dispatched. | ||
* @return {function} The callback to remove the mutation listener. | ||
*/ | ||
onAttributeChange: addAttributeChangeListener | ||
}; | ||
module.exports = domMutate; |
58
node.js
'use strict'; | ||
var assign = require('can-util/js/assign/assign'); | ||
var observer = require('./-observer'); | ||
var globals = require('can-globals'); | ||
var domMutate = require('./can-dom-mutate'); | ||
var util = require('./-util'); | ||
function isInDocument (node) { | ||
var root = node.ownerDocument.documentElement; | ||
if (root === node) { | ||
return true; | ||
} | ||
return root.contains(node); | ||
} | ||
var isInDocument = util.isInDocument; | ||
var getParents = util.getParents; | ||
@@ -30,5 +25,8 @@ var synthetic = { | ||
replaceChild: function (newChild, oldChild) { | ||
var newChildren = getParents(newChild); | ||
var result = this.replaceChild(newChild, oldChild); | ||
synthetic.dispatchNodeRemoval(this, oldChild); | ||
synthetic.dispatchNodeInsertion(this, newChild); | ||
for (var i = 0; i < newChildren.length; i++) { | ||
synthetic.dispatchNodeInsertion(this, newChildren[i]); | ||
} | ||
return result; | ||
@@ -39,4 +37,15 @@ }, | ||
var result = this.setAttribute(name, value); | ||
domMutate.dispatchNodeAttributeChange(this, name, oldAttributeValue); | ||
var newAttributeValue = this.getAttribute(name); | ||
if (oldAttributeValue !== newAttributeValue) { | ||
domMutate.dispatchNodeAttributeChange(this, name, oldAttributeValue); | ||
} | ||
return result; | ||
}, | ||
removeAttribute: function (name) { | ||
var oldAttributeValue = this.getAttribute(name); | ||
var result = this.removeAttribute(name); | ||
if (oldAttributeValue) { | ||
domMutate.dispatchNodeAttributeChange(this, name, oldAttributeValue); | ||
} | ||
return result; | ||
} | ||
@@ -54,4 +63,7 @@ }; | ||
compat[nodeMethod] = function (node) { | ||
var nodes = getParents(node); | ||
var result = this[nodeMethod].apply(this, arguments); | ||
synthetic[dispatchMethod](this, node); | ||
for (var i = 0; i < nodes.length; i++) { | ||
synthetic[dispatchMethod](this, nodes[i]); | ||
} | ||
return result; | ||
@@ -62,3 +74,3 @@ }; | ||
var normal = {}; | ||
var nodeMethods = ['appendChild', 'insertBefore', 'removeChild', 'replaceChild', 'setAttribute']; | ||
var nodeMethods = ['appendChild', 'insertBefore', 'removeChild', 'replaceChild', 'setAttribute', 'removeAttribute']; | ||
nodeMethods.forEach(function (methodName) { | ||
@@ -148,10 +160,24 @@ normal[methodName] = function () { | ||
/** | ||
* @function can-dom-mutate/node.removeAttribute removeAttribute | ||
* | ||
* Removes an attribute from an element, effectively `Element.prototype.removeAttribute`. | ||
* | ||
* @signature `mutate.removeAttribute.call(element, name, value)` | ||
* @parent can-dom-mutate.node | ||
* @param {Element} element The element from which to remove the attribute. | ||
* @param {String} name The name of the attribute to remove. | ||
*/ | ||
function setMutateStrategy(observer) { | ||
var strategy = observer ? normal : compat; | ||
assign(mutate, strategy); | ||
for (var key in strategy) { | ||
mutate[key] = strategy[key]; | ||
} | ||
} | ||
setMutateStrategy(observer.getValue()); | ||
observer.onValue(setMutateStrategy); | ||
var mutationObserverKey = 'MutationObserver'; | ||
setMutateStrategy(globals.getKeyValue(mutationObserverKey)); | ||
globals.onKeyValue(mutationObserverKey, setMutateStrategy); | ||
module.exports = mutate; |
{ | ||
"name": "can-dom-mutate", | ||
"description": "Dispatch and listen for DOM mutations", | ||
"version": "0.1.0", | ||
"version": "0.2.1", | ||
"author": { | ||
@@ -14,5 +14,3 @@ "name": "DoneJS Team", | ||
"dependencies": { | ||
"can-dom-events": "^1.0.2", | ||
"can-symbol": "^1.0.0", | ||
"can-util": "^3.8.4" | ||
"can-globals": "<2.0.0" | ||
}, | ||
@@ -41,7 +39,9 @@ "devDependencies": { | ||
"scripts": { | ||
"install-canary": "npm install --no-shrinkwrap", | ||
"install-locked": "npm install", | ||
"jshint": "jshint ./*.js ./events/*.js ./test/**/*.js --config", | ||
"jshint": "jshint ./*.js ./test/*.js --config", | ||
"lint": "fixpack && npm run jshint", | ||
"preversion": "npm test", | ||
"release:major": "npm version major && npm publish", | ||
"release:minor": "npm version minor && npm publish", | ||
"release:patch": "npm version patch && npm publish", | ||
"release:pre": "npm version prerelease && npm publish --tag pre", | ||
"test": "npm run lint && npm run testee", | ||
@@ -48,0 +48,0 @@ "testee": "testee test.html --browsers firefox" |
import './test/can-dom-mutate-test'; | ||
import './test/events/attributes-test'; | ||
import './test/events/inserted-test'; | ||
import './test/events/removed-test'; | ||
import './test/node-test'; |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
50250
1
1162
2
+ Addedcan-globals@<2.0.0
- Removedcan-dom-events@^1.0.2
- Removedcan-symbol@^1.0.0
- Removedcan-util@^3.8.4
- Removedcan-ajax@1.4.1(transitive)
- Removedcan-assign@1.3.3(transitive)
- Removedcan-cid@1.3.1(transitive)
- Removedcan-deparam@1.2.3(transitive)
- Removedcan-dom-data-state@0.2.0(transitive)
- Removedcan-dom-events@1.3.13(transitive)
- Removedcan-event-dom-enter@1.0.4(transitive)
- Removedcan-event-dom-radiochange@1.0.5(transitive)
- Removedcan-key-tree@1.2.2(transitive)
- Removedcan-log@1.0.2(transitive)
- Removedcan-param@1.2.0(transitive)
- Removedcan-parse-uri@1.2.2(transitive)
- Removedcan-types@1.4.0(transitive)
- Removedcan-util@3.14.0(transitive)