2

I'm looking for a better way to handle events for multiple elements on a page that can appear dynamically. Currently I'm using this code:

var myToolbar = document.createElement('div');
setInterval(function () {
    for (let textField of document.querySelectorAll('textarea[name="text"]:not(.modified)')) {
        textField.classList.add('modified');
        textField.addEventListener('focus', function () {
            textField.insertAdjacentElement('beforebegin', myToolbar);
        });
    }
}, 1000);

This accomplishes my task fine, but having an interval running all the time seems clunky.

therks
  • 499
  • 3
  • 11
  • 1
    Maybe event delegation is useful here, see this https://stackoverflow.com/questions/9914587/javascript-event-delegation-handling-parents-of-clicked-elements. – Tasos K. Mar 25 '18 at 07:36
  • Another post about event delegation is what actually lead me to ask about this, but they only handled the `click` event there as well and I was specifically looking for the `focus` event (which didn't seem to work the same when substituted for `click`). – therks Mar 26 '18 at 15:02

2 Answers2

1

You should be aware of the components you are rendering to the webpage and their state within your application. If you are not, then your design pattern is wrong and needs to be thought through more. What you have going on right now is a recipe for disaster. Adding event listeners willy nilly inside a setInterval without ever cleaning them up -- smells of memory leak.

The components you render to your webpage initially should likely have their own event listeners to handle what you are calling "dynamic" elements appearing. These dynamic elements are appearing either due to your application putting them there or the user doing something to make them appear there. Both of these things can be tracked and maintained in your application state.

With that being said to avoid even having the problem you've described, you need to invert the control of your application state to your code rather than the state of the DOM. This will make setting up event listeners and removing event listeners when required a lot easier.

So unless you are doing something fancy with ContentEditable regions, you should reconsider the design of your application.

Aaron Rumery
  • 552
  • 2
  • 9
  • It's not my design actually. I'm creating Tampermonkey scripts to augment webpages I visit, so I have no control over the webpage rendering itself. This particular script was to create a small formatting toolbar for reddit posts (I constantly forget the markdown syntax). – therks Mar 26 '18 at 14:56
  • 1
    I see, something like MutationObserver may be the route you want to go then. – Aaron Rumery Mar 26 '18 at 15:59
1

You can use MutationObserver. Example:

setInterval(() => {
  const allDivs = document.querySelectorAll('div');
  const randomDiv = allDivs[Math.floor(Math.random() * allDivs.length)];
  randomDiv.appendChild(document.createElement('div'));
}, 1000);

const obs = new MutationObserver(track);
obs.observe(document.body.children[0], { childList: true, subtree: true });

function track(mutations) {
  mutations.forEach((mutation) => {
    mutation.addedNodes.forEach(addedNode => {
      if (addedNode.tagName === 'DIV') addedNode.classList.add('found');
    });
  });
}
div {
  border: 1px solid black;
  background-color: white;
  height: 20px;
  width: 20px;
}
.found {
  background-color: yellow;
}
<div>
</div>

For your particular case, you want to find new textarea[name="text"]s, so you would instead test:

if (addedNode.tagName === 'TEXTAREA' && addedNode.name === 'text') {
  // ...
}
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • This looks interesting. Not getting it to work yet, but I'll be toying with it for sure. _Edit: I think I figured it out._ – therks Mar 26 '18 at 15:36