Fire a function when innerHTML of element changes?
Asked Answered
B

3

12

I want to fire a function when the innerHTML of a div element changes.

Been testing with element.addEventListener('DOMCharacterDataModified', myFunction()); but it does not seem to work properly. It fires once when the page is loaded but never again after? See example code below.

Anyone have a solution to my task? Any help is greatly appreciated!

The task:

  1. I have an empty div.
  2. At random it will be fed with data from an external source (that I do not have control of).
  3. Everytime when the innerHTML changes, I want to fire myFunction().

My current example code:

var element = document.getElementById('div');

element.addEventListener('DOMCharacterDataModified', myFunction());

function myFunction() {
    console.log(element.innerHTML);
}

setTimeout(function(){
    element.innerHTML = 'Hello World!';
}, 1000);

setTimeout(function(){
    element.innerHTML = 'Hello Space!';
}, 2000);

The setTimeouts visualize the data feeding from an external source.

If possible it needs to be JS only, but jQuery could be used if really needed.


Thanks in advance!

// wirrew

Bosson answered 19/11, 2017 at 14:50 Comment(6)
it would be helpful if you can show us the codeViva
You generally want a MutationObserver, beyond that, you'll need to try something and ask a specific question.Necktie
@JegadeshBS I've now added some example code and refined my question.Bosson
.change event?Expectation
@RonRoyston AFAK, it only works for input type elements. Not for divs that I need it for.Bosson
ahh, got it. i see. #7284879Expectation
J
28

Use DOMSubtreeModified event:

var element = document.getElementById('div');

element.addEventListener('DOMSubtreeModified', myFunction);

function myFunction(e) {
    console.log(element.innerHTML);
}

setTimeout(function(){
    element.innerHTML = 'Hello World!';
}, 1000);

setTimeout(function(){
    element.innerHTML = 'Hello Space!';
}, 2000);
<div id="div"></div>

BTW, DOM mutation events were deprecated. Use MutationObserver:

window.addEventListener('load', function () {
  var element = document.getElementById('div');

  var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
  var observer = new MutationObserver(myFunction);
  observer.observe(element, {
	  childList: true
  });

  function myFunction() {
    console.log(element);
    console.log(element.innerHTML);
  }

  setTimeout(function(){
    element.innerHTML = 'Hello World!';
  }, 1000);

  setTimeout(function(){
    element.innerHTML = 'Hello Space!';
  }, 2000);
});
<div id="div">

Raw snippet on GitHub

Jervis answered 19/11, 2017 at 16:16 Comment(7)
Unfortunately, I believe I must use the old DOM Mutation Events because this code is being run on a custom graphics server that uses an old version of Chromium. Because when I test your code in the in the graphics server it does not work, but it does work in my own browser (Chrome). So for example, cross browser support is not an issue, I only need it to work for this specific case. Do you know how to get my example code to work every time a change happen? Thanks for your help! :)Bosson
@Bosson what version of Chromium does your server have?Jervis
Version 33 it seems.Bosson
@Bosson changed my answer, please look at itJervis
Running the code snippet seems to make the page crash. But it doesn't work with the graphics server (only if I run the file in my browser). What might be causing it to not work on the server? Is there any other method I could use to fire myFunction when the innerHTML changes? W/o using the DOM Mutation Events or Observer? Could I do something with the .length? In the end, I need this to work for 32 uniqe div elements.Bosson
@Bosson Updated fiddle, now it works. Btw, both mutation events and MutationObserver should work in Chromium 33 according to caniuse.com. Try to run GitHub snippet. Will it work? If not, that the problem should be in something else, not in mutation handling.Jervis
Apparently, after I restarted the server, the DOM Mutation Event seems to work now! :) Possible that the Observer also works like you said, but since I got the other one to work now, I'm not going to bother testing (for now at least). One Q tho, why does the DOMCharacterDataModified that I tried using at the start not work? What is it used for if not when "character data" is changed?Bosson
C
3

NOTE: May not be the best of explanations, please add your knowledge to make it more clear.

If you are willing to use element attributes then there is a way to create custom elements and add Observers as you please to the attributes. attributeChangedCallback is where you can add your custom code for listener.

Custom Elements is of the Web Components with support on Chrome, Opera and Safari. Here is the link which explains everything about them. https://developer.mozilla.org/en-US/docs/Web/Web_Components/Custom_Elements

There is a chance you can modify observers for your div elements but I am not aware of such, you probably need to dig a little deeper should you choose to do it this way.

Since you are trying to listen to a single element's innerHTML, it might be better to just create a custom element like the one in the code snippet.

class ObservableDiv extends HTMLElement {
  // Monitor the 'name' attribute for changes.
  static get observedAttributes() {return ['name']; }

  // Respond to attribute changes.
  attributeChangedCallback(attr, oldValue, newValue) {
    if (attr == 'name') {
      this.textContent = `HELLO, ${newValue}`;
    }
  }
}

// Define the new element
customElements.define('observable-div', ObservableDiv);

setTimeout(() => { document.getElementById("change").setAttribute("name", "I CHANGED A LOTTTTTTTTTTT") }, 1000)
<observable-div id="change" name="BEFORE CHANGING"></observable-div>

PS : This may not be a proper answer at all but I am posting this because this strategy served me better, than relying on the sub tree listeners which used to work sporadically but never consistently. The only down fall to this is that this does not have good browser support. There might be some good polyfills available already.

Casual answered 19/11, 2017 at 21:29 Comment(0)
F
0

While the accepted answer about using MutationObserver with childList: true is right, I think it's missing some important information:

MutationObserver's callback won't be invoked for every single statement that's executed that updates .innerHTML (as they might cause a single paint, like the ones inside the for in the example below), but you can get the list of updated elements from mutationsList.

However, if new elements are added at different points in time (like the ones inside setTimeouts), the callback will be invoked for each update.

const content = document.querySelector('#content');

const observer = new MutationObserver((mutationsList, observer) => {
  console.log(`innerHTML updated, ${ mutationsList.length } mutation${ mutationsList.length > 1 ? 's' : '' }`);
});

observer.observe(content, { childList: true });

for (let i = 0; i < 10; ++i){
   content.innerHTML += `<p>New element ${ i }</p>`;
};

setTimeout(() => {
  content.innerHTML += `<p>Added from setTimeout()</p>`;
})

setTimeout(() => {
  content.innerHTML += `<p>Added after 5 seconds</p>`;
}, 5000)
#content {
  padding-bottom: 67px;
}

#content > p {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100px;
  margin: 0;
  background: cyan;
  font-family: monospace;
  font-size: 32px;
}

#content > p + p {
  margin: 8px 0 0;
}
<div id="content">
  <p>0</p>
  <p>1</p>
  <p>2</p>
  <p>3</p>
  <p>4</p>
  <p>5</p>
  <p>6</p>
  <p>7</p>
  <p>8</p>
  <p>9</p>
</div>
Finisterre answered 6/8, 2023 at 1:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.