javascriptmutation-observershtmlcollection

What is the best way to handle events on dynamically generated input fields


I'm looking for the simplest way to handle different events on dynamically generated input fields.

Using MutationObserver looks complicated because requires processing each MutationRecord from the list, additional search for input fields and adding or removing listener.

Is there an alternative to MutationObserver like some hack with HTMLCollection:

const inputsHTMLCollection = document.getElementsByTagName('inputs');
// some way for detecting inputsHTMLCollection changing

UPD

I am looking for alternative solutions in pure JS, without using any libraries.

// emulate code on the page
addinput.addEventListener('click', () => {
    const i = document.createElement('input');
    i.type = "text";
    inputs.append(i);
});

const log = (e) => {
  const event = document.createElement('div');
  event.innerText = `${e.type}: on ${e.target.tagName}`;
  events.prepend(event);
};


const inputsHTMLCollection = document.getElementsByTagName('input');
for (let i of inputsHTMLCollection) {
  i.addEventListener('focus', log);
};

// After addinput click - the inputsHTMLCollection length will be changed
// some code, for detect length change here
body {
  display: flex;
}

body > div {
  flex: 0 0 50%;
}

input {
  display: block;
}
<div id="inputs">
  <button id='addinput'>Add input field</button>
  <input type="text" />
</div>
<div id="events"></div>

There is no problems if an event bubbles (for click or input events), but what is the best way to handle focus events in dynamically added inputs?

I don't have access to the code that adds the fields to the page. All I have is the ability to add JS code after the page is loaded.


Solution

  • What you need is event delegation.

    This is similar to this and this but those questions do not touch on events that do not bubble, like the focus event.

    To implement event delegation on events that do not bubble, you will need to catch it during event capturing phase. This can be done by using the third argument of the .addEventListener method:

    document.addEventListener('focus', function(event){ ... }, true);
    /* OR */
    document.addEventListener('focus', function(event){ ... }, { capture: true });
    

    For the focus event specifically, you can also use the focusin event. One of the differences between focus and focusin is that focusin bubbles:

    document.addEventListener('focusin', function(event){ ... });
    

    Event delegation for focus event is discussed in this MDN article.

    document.addEventListener('focus', (e) => console.log('focus', 'useCapture'), true);
    document.addEventListener('focus', (e) => console.log('focus', '{ capture: true }'), { capture: true });
    document.addEventListener('focusin', (e) => console.log('focusin'));
    <input>