Matt Guga
Matt Guga

Reputation: 33

How to add event listeners automatically?

I am working with a lot of inputs (100 - 1000). I need to create a function for each of them like this:

const range1 = document.getElementById("a1");
const range2 = document.getElementById("a2");

//label1 shows a value from range1 + 3
range1.oninput = function() {
    document.getElementById("label1").innerHTML = parseInt(this.value) + 3;
};
range2.oninput = function() {
    document.getElementById("label2").innerHTML = parseInt(this.value) + 3;
};

Can I automate this somehow?

Upvotes: 2

Views: 145

Answers (5)

customcommander
customcommander

Reputation: 18901

When the same task can be done on an unknown or big number of elements, then event delegation is the best option IMHO.

What is event delegation?

In a nutshell you let a common ancestor handle the task that you would have otherwise assigned to individual elements. e.g. instead of binding a click listener to n elements, you assign it once to a common ancestor. (Read more.)

Why is it better?

If you're not careful adding too many event listeners will eventually degrade the perceived performance of your app. It will feel sluggish to your users.

Here's what John Resig says about it:

Event delegation is an efficient way to watch for an event on a large number of elements.

Source: https://johnresig.com/apps/workshop/adv-talk/index2.html#3

Demo time!

Every 2s I will append the following elements into the DOM: (X is generated)

<div>
  <input type="range" id="inpX"/>
  <label for="inpX"><!-- SOME DYNAMIC VALUE --></label>
</div>

The <label> will be updated as soon as the user adjusts the range.

All with exactly one event listener!

// set el content to given txt
const write = (el, txt) => el.innerText = txt;

// return the <label> for given id
const label = id => document.querySelector(`label[for="${id}"]`);

// one global event listener - event delegation FTW!
document.body.addEventListener('input', ev => {
  const {id, value} = ev.target;
  write(label(id), parseInt(value) + 3); 
});
<script>
// This code is only for the demo
// It is not part of the answer

function append(n) {
  const div = document.createElement('div');
  const id = `inp${n}`;
  div.innerHTML = `
    <input type="range" id="${id}"/>
    <label for="${id}"></label>
  `;
  document.body.appendChild(div);
}

function start() {
  let n = Date.now();
  append(n++);
  start.timer = setInterval(() => append(n++), 2000);
}

function stop() {
  clearInterval(start.timer);
}
</script>

<button onclick="start()">START</button>
<button onclick="stop()">STOP</button>

<hr>

Upvotes: 1

Dmitry Reutov
Dmitry Reutov

Reputation: 3032

The best way is to assign just one listener to their common parent

document.getElementById(commonParentId).oninput = e =>
  document.getElementById("label" + e.target.id.substring(1))
    .innerHTML = parseInt(e.target.value) + 3

Upvotes: 1

Klaycon
Klaycon

Reputation: 11080

You can easily just use arrays and loops:

let inputNum = 100;
let range = [];

for(let i = 1; i <= inputNum; i++) {
    range[i] = document.getElementById(`a${i}`);
    range[i].oninput = function() {
        document.getElementById(`label${i}`).innerHTML = parseInt(this.value) + 3;
    };
}

Upvotes: 0

Moob
Moob

Reputation: 16184

Here's a simple demo:

const ranges = document.querySelectorAll("input[type=range]");
for (i = 0; i < ranges.length; ++i) { //for each input[type=range]
  ranges[i].oninput = oninput; //set on change
  oninput.call(ranges[i]); //set on load
}

function oninput() {
  this.parentElement.querySelector("span").innerHTML = parseInt(this.value) + 3;
};
<label><input type=range /><span></span><label><br />
<label><input type=range /><span></span><label>

Upvotes: 0

user2541867
user2541867

Reputation: 444

There are plenty of frameworks to automate such things. What can be done with plain html/js:

<label for='a1'></label>
<label for='a2'></label>
<input id='a1' class='range-input-output-to-label'/>
<input id='a2' class='range-input-output-to-label'/>
<script>
  const inputs = document.getElementsByClassName('number-input-output-to-label')

  const outputNumberToLabel = (e) => {
    const labels = e.target.labels
    for (let i = 0; i < labels.length; i++)
      labels[i].innerText = parseInt(e.target.value) || 0
  }

  for (let i = 0; i < inputs.length; i++)
    inputs[i].oninput = outputNumberToLabel
</script>

Here I'm using vanilla for loops because native element lists are not array and does not support forEach.

I'm suggesting agains modifying ids to find label because it might be tricky to debug it later.

Upvotes: -1

Related Questions