Reputation: 310907
Is there a DOM event that fires when an element's parentElement
changes? If not, is there any way better than polling with a timeout?
I'm specifically interesting in knowing when the parentElement
changes from null
to some defined element. That is, when a DOM element is attached to the document tree somewhere.
EDIT
Given the questions in the comments, here is an example that shows how to create an element with a null
parentElement
:
var element = document.createElement('div');
console.assert(element.parentElement == null);
The parent is only set once it's added to the DOM:
document.body.appendChild(element);
console.assert(element.parentElement != null);
Note too that elements created using jQuery will also have a null
parent when created:
console.assert($('<div></div>').get(0).parentElement == null);
Upvotes: 2
Views: 3747
Reputation: 428
You can use a MutationObserver like this:
const trackedElement = document.getElementById('tracked-element');
const parent1 = document.getElementById('parent1');
const parent2 = document.getElementById('parent2');
startObserver();
function changeParent() {
if (trackedElement.parentElement == parent1) {
parent1.removeChild(trackedElement);
parent2.appendChild(trackedElement);
} else {
parent2.removeChild(trackedElement);
parent1.appendChild(trackedElement);
}
}
function startObserver() {
let parentElement = trackedElement.parentElement
new MutationObserver(function(mutations) {
if (!parentElement.contains(trackedElement)) {
trackedElement.textContent = "Parent changed";
setTimeout(() => {
trackedElement.textContent = "Parent not changed";
}, 500);
startObserver();
this.disconnect();
}
}).observe(parentElement, {
childList: true
});
}
<!doctype html>
<html lang="en">
<body>
<div id="parent1">
<p id="tracked-element">Parent not changed</p>
</div>
<div id="parent2"></div>
<button onclick="changeParent()">ChangeParent</button>
</body>
</html>
You can right click on the Parent not changed
text and click inspect to make sure that it is actually changing parents
If you want to know how the .observer
function works, there's VERY good documentation on it here: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe
Upvotes: 3
Reputation: 3279
1) Such a parentElementHasChanged event
doesn't exist.
2) The workaround PISquared pointed to would work but looks very strange to me.
3) In practise there is no need for such an event. A parentChange would only appear to an element if it's position in the DOM changes.To make this happen you have to run some code on the element doing this, and all that code has to use native parent.removeChild()
,
parent.appendChild
, parent.insertBefore()
or parent.replaceChild()
somewhere. The same code could run a callback afterwards so the callback would be the event.
4) You are building library code. The library could provide a single function for all DOM-insertions/removals, which wraps the four native functions and "triggers the event". That's the last and only what comes in my mind to avoid a frequently lookup for parentElement.
5) If there's a need to include the native Event API
, you may create a parentChanged event with CustomEvent
element.addEventListener('parentChanged', handler); // only when Event API needed
function manipulateElementsDOMPosition(element, target, childIndex, callback, detail) {
if (!target.nodeType) {
if (arguments.length > 4) return element;
detail = callback; callback = childIndex; childIndex = target; target = null;
}
if (typeof childIndex === 'function') detail = callback, callback = childIndex;
var oldParent = element.parentElement,
newParent = target,
sameParent = oldParent === newParent,
children = newParent.children,
cl = children.length,
ix = sameParent && cl && [].indexOf.call(children, element),
validPos = typeof childIndex === 'number' && cl <= childIndex;
if (childIndex === 'replace') {
(newParent = target.parentElement).replaceChild(element, target);
if (sameParent) return element;
} else {
if (samePar) {
if (!oldParent || ix == childIndex ||
childIndex === 'first' && ix === 0 ||
childIndex === 'last' && ix === (cl - 1)) return element;
oldParent.removeChild(element);
} else if (oldParent) oldParent.removeChild(element);
if (!cl || childIndex === 'last') {
newParent.appendChild(element);
} else if (childIndex === 'first') {
newParent.insertBefore(element, children[0])
} else if (validPos) {
newParent.insertBefore(element, children[childIndex]);
} else return element;
}
console.log(element, 'parentElement has changed from: ', oldParent, 'to: ', newParent);
element.dispatchEvent(new CustomEvent('parentChanged', detail)); // only when Event API needed
if (typeof callback === 'function') callback.call(element, oldParent, newParent, detail);
return element;
}
some example usage (detail may be anything you want to pass to the event/callback). Function always return element.
// remove element
manipulateElementsDOMPosition(element /*optional:*/, callback, detail);
// prepend element in target
manipulateElementsDOMPosition(element, target, 'first' /*optional:*/, callback, detail);
// append element in target
manipulateElementsDOMPosition(element, target, 'last' /*optional:*/, callback, detail);
// add element as third child of target, do nothing when less than two children there
manipulateElementsDOMPosition(element, target, 3 /*optional:*/, callback, detail);
// replace a target-element with element
manipulateElementsDOMPosition(element, target, 'replace' /*optional:*/, callback, detail);
Upvotes: 1
Reputation: 29
Afaik there's no such "parent listener".
Yet, I found a hack that might be helpful. At least it's worth reading, since the idea is clever.
http://www.backalleycoder.com/2012/04/25/i-want-a-damnodeinserted/
He uses CSS @keyframes during the insertion and listens for the resulting animation event which tells him, that the element got inserted.
Upvotes: 2