I need to target all elements with class="kendoTooltip"
and run a jQuery function on any element with that class.
An example of the html is;
<div class="document_wrapper">
<div>
<div class="kendoTooltip">Tooltip!</div>
</div>
</div>
I have been trying to use the mutation observer but have't been able to target the elements deep within the page.
With the following code, I have gotten the closest but the issue seems to be that the mutation observer only monitors the note itself and its immediate child elements. I have also tried calling it recursively but with no success.
var mutationObserver = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (!mutation.target.classList.contains('isRendered') && mutation.target.classList.contains('kendoTooltip')) {
renderTooltip(mutation.target);
mutation.target.classList.add('isRendered');
}
});
});
mutationObserver.observe(document.documentElement || document.body, {
childList: true,
subtree: true
});
I need to target all elements with class="kendoTooltip"
and run a jQuery function on any element with that class.
An example of the html is;
<div class="document_wrapper">
<div>
<div class="kendoTooltip">Tooltip!</div>
</div>
</div>
I have been trying to use the mutation observer but have't been able to target the elements deep within the page.
With the following code, I have gotten the closest but the issue seems to be that the mutation observer only monitors the note itself and its immediate child elements. I have also tried calling it recursively but with no success.
var mutationObserver = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (!mutation.target.classList.contains('isRendered') && mutation.target.classList.contains('kendoTooltip')) {
renderTooltip(mutation.target);
mutation.target.classList.add('isRendered');
}
});
});
mutationObserver.observe(document.documentElement || document.body, {
childList: true,
subtree: true
});
Share
Improve this question
asked Sep 21, 2018 at 13:51
Oliver KOliver K
551 silver badge12 bronze badges
5
- 2 what mutation are you not observing? i.e. what you've posted has no mutation, so of course nothing happens – Jaromanda X Commented Sep 21, 2018 at 13:57
-
@JaromandaX I need to track when an element with the class
kendoTooltip
is created. As far as I was aware,childElements
would track when a node is added. – Oliver K Commented Sep 21, 2018 at 14:06 - 1 you have not shown any mutations, just static HTML - which does not mutate in and of itself – Jaromanda X Commented Sep 21, 2018 at 14:08
-
1
Your code checks
target
which is always the immediate parent element. You need to enumerate addedNodes array within each mutation, and check if the element you're looking for is nested inside any of those nodes! See this or that. – woxxom Commented Sep 21, 2018 at 14:09 - @wOxxOm That makes sense, thank you! – Oliver K Commented Sep 21, 2018 at 14:13
4 Answers
Reset to default 1This is an old post but I thought I would add my thoughts, I too have had a similar issue with trying to detect changes to an element that is inside the body
element.
I am not saying that my solution is good by any means but seeing as this question has no accepted answer perhaps my thoughts can help to find a solution.
My issue is that I want to create a JavaScript solution to detecting insertions inside of the <div id="root"></div>
element used by ReactJS applications, the reasons as to why I want to do this are not important.
I found that attaching my MutationObserver
to document
, document.documentElement
or document.body
didn't provide any solutions. I ended up having to place my MutationObserver
inside of a loop and attach it to each child element within body
so I could detect the changes.
Here is my code, I am sure there is a better way to do this but I have not found it yet:
// Public function
export function listenForDynamicComponents() {
// Create the observer
const observer = new MutationObserver((mutations) => {
// Loop each mutation
for (let i = 0; i < mutations.length; i++) {
const mutation = mutations[i];
// Check the mutation has added nodes
if (mutation.addedNodes.length > 0) {
const newNodes = mutation.addedNodes;
// Loop though each of the added nodes
for (let j = 0; j < newNodes.length; j++) {
const node = newNodes[j];
// Check if current node is a ponent
if (node.nodeType === 1 && node.hasAttribute('data-module')) {
// Do something here
}
// Loop though the children of each of the added nodes
for (let k = 0; k < node.childNodes.length; k++) {
const childNode = node.childNodes[k];
// Check if current child node is a poennt
if (childNode.nodeType === 1 && childNode.hasAttribute('data-module')) {
// Do something here
}
}
}
}
}
});
// Get all nodes within body
const bodyNodes = document.body.childNodes;
// Loop though all child nodes of body
for (let i = 0; i < bodyNodes.length; i++) {
const node = bodyNodes[i];
// Observe on each child node of body
observer.observe(node, {
attributes: false,
characterData: false,
childList: true,
subtree: true,
attributeOldValue: false,
characterDataOldValue: false
});
}
}
I hope this helps.
For the record, contrary to what have been said in some ments subtree
option will target all descendants : children, children of children... of the observed element but only those directly added or removed from the DOM. See here and here. There is a pitfall by the way. Imagine you want to observe a video
tag and its is not directly attached to the DOM but attached to a detached DOM, say a div.video-container
and that it is that container which is added to the DOM.
const mutationInstance = new MutationObserver((mutations) => {
// The <video> is not directly added to the DOM, so wee need to query it on each added nodes
const videoContainer = mutations
.reduce((addedNodes, mutationRecord) => {
return [
...addedNodes,
...(Array.from(mutationRecord.addedNodes)),
];
}, [])
.find((element) => {
return element.querySelector("video") !== null;
});
const videoElement = videoContainer.querySelector("video");
console.log('Video element is', videoElement);
});
mutationInstance.observe(document, {
subtree: true,
childList: true,
});
Code triggering the mutation
const div = document.createElement('div');
div.classList.add('video-container');
const video = document.createElement('video');
// This **does not trigger** a mutation on the document
div.appendChild(video);
// This **triggers** a mutation on the document
document.body.appendChild(div);
Note mutation.target
is going to be one of three values
https://developer.mozilla/en-US/docs/Web/API/MutationRecord#Properties
For attributes, it is the element whose attribute changed.
For characterData, it is the CharacterData node.
For childList, it is the node whose children changed.
So in your case target
is either going to be body
or if your html is actually being added to some other element it will be that other element. Due to this your current code of checking target
's classList won't work since it is not the elements you added but the element you added them to.
If you want to check elements that have been added use the addedNodes property and a search method like querySelectorAll, getElementsByClassName, or etc on each of the elements in the addedNodes list for the elements you want to target.
var addedNodes = Array.from(mutation.addedNodes);
addedNodes.forEach(function(node) {
//skip textnodes
if (node.nodeType == 3) return;
//findall kendoTooltips that are not
//isRendered
var tooltips = node.querySelectorAll('.kendoTooltip:not(.isRendered)')
.forEach(renderTooltip);
});
function renderTooltip(target) {
console.log("tooltiping: ", target.textContent);
target.classList.add("isRendered");
}
var mutationObserver = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
var addedNodes = Array.from(mutation.addedNodes);
addedNodes.forEach(function(node) {
//skip textnodes
if (node.nodeType == 3) return;
//findall kendoTooltips that are not
//isRendered
var tooltips = node.querySelectorAll('.kendoTooltip:not(.isRendered)')
.forEach(renderTooltip);
});
});
});
mutationObserver.observe(document.documentElement || document.body, {
childList: true,
subtree: true
});
setTimeout(() => {
document.body.insertAdjacentHTML('beforeend', `
<div class="document_wrapper">
<div>
<div class="kendoTooltip">Tooltip!</div>
</div>
</div>`);
}, 1000);
setTimeout(() => {
document.body.insertAdjacentHTML('beforeend', `
<div class="anotherclass">
<div>
<div class="kendoTooltip">More Tooltip!</div>
</div>
</div>`);
}, 3000);
I'm pretty sure you can't delegate a MutationObserver
like you can with an event (although, to be fair, I've never actually tried it). I think you need to create a new one for every element.
document.querySelectorAll(".kendoTooltip").forEach(function (tooltip) {
var mutationObserver = new MutationObserver(function (mutations) {
// ...
});
mutationObserver.observe(tooltip, {
// ...
});
});
For what it's worth, it would be a lot easier to trigger that renderTooltip
function as you add the isRendered
class without having to worry about the MutationObserver