I want to detect when a node (nodeX, say) is no longer available, either because it was deleted or because its parent (or its parents parent) was deleted.
So far, all I can think of is to use Mutation Observer to see any deletions on the page, and check if the deleted nodes was nodeX or had nodeX for a descendant.
Is there an easier way?
Please note: as far as I understand, the linked question (that this question "is a duplicate of") asks "how can I detect a [direct] deletion of a node". Mine asks "How can I detect the deletion of a node or its parent (or any other ancestor)".
As far as I understand, this is not straightforward with mutation observers: You need to check every deleted node to see if it was an ancestor.
This is what I seek to confirm or deny.
As far as I understand, that is different from the linked question.
removeChild() The removeChild() method of the Node interface removes a child node from the DOM and returns the removed node. Note: As long as a reference is kept on the removed child, it still exists in memory, but is no longer part of the DOM. It can still be reused later in the code.
Child nodes can be removed from a parent with removeChild(), and a node itself can be removed with remove(). Another method to remove all child of a node is to set it's innerHTML=”” property, it is an empty string which produces the same output. This method is not preferred to use. Example-1: Using “removeChild()”.
Here is an implementation that identifies how the element was removed (either directly or because a parent was removed)
var target = document.querySelector('#to-be-removed');
var observer = new MutationObserver(function(mutations) {
// check for removed target
mutations.forEach(function(mutation) {
var nodes = Array.from(mutation.removedNodes);
var directMatch = nodes.indexOf(target) > -1
var parentMatch = nodes.some(parent => parent.contains(target));
if (directMatch) {
console.log('node', target, 'was directly removed!');
} else if (parentMatch) {
console.log('node', target, 'was removed through a removed parent!');
}
});
});
var config = {
subtree: true,
childList: true
};
observer.observe(document.body, config);
var qs = document.querySelector.bind(document);
qs('#ul').addEventListener('click', function(){qs('ul').remove();}, false)
qs('#li').addEventListener('click', function(){qs('#to-be-removed').remove();}, false)
<ul>
<li>list item 1</li>
<li>list item 2</li>
<li id="to-be-removed">list item 3</li>
<li>list item 4</li>
</ul>
<button id="ul">remove ul</button>
<button id="li">remove li</button>
The accepted answer will fail if the removed subtree is mutated after removal from the document. For example:
target.parent.remove();
target.remove();
will generate one call to the mutation observer for the parent node removal (the target node removal will not be reported to the observer as it happened when the subtree was already removed from the document).
var parentMatch = nodes.some(parent => parent.contains(target));
in the accepted answer will return false as the target is no longer a child. The problem is that mutation event reporting is batched and you cannot rely on state at the time of node removal staying the same as at the time of the call to your mutation observer.
For this reason, facing a similar issue as the questioner, I created a WeakSet of target node ancestors. Using a mutation observer attached to the document root, I compared mutations against this set and the target. If a mutation node removal event includes a node in this set or the target node, I know the target node was removed from the tree. That doesn't imply the node is still removed (it may have been added back) or that the node is still a child of the ancestors in my set. But I can be sure that the node was removed in the past.
You have to be careful to differentiate DOM state at the moment after a mutation and the state at the time you receive the mutation event.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With