Subscribe to and process change to multiple inputs

Viewed 143

I want to add an event listener to input elements with element id's I have stored in a list and then take the product of them. For example:

a = ['id1', 'id2', 'id3']

I would like to do something like:

input element id1 = 0.5
input element id2 = 0.6
input element id3 = 1.0
return 0.5*0.6*1

and I would like this to update as the user changes the values in the input box.

So I have started with something like:

function calcProduct(values){
    var product = 1;
    for(i=0; values.length; i++){
        product = product*values[i];
    }
    return product
}

id_list = ['id1', 'id2', 'id3']
function calcProduct(id_list){
    for(i=0; id_list.length; i++){
        //how do I get the values as they change so I can feed it to calcProduct?
        document.getElementById(id_list[i]).addEventListener("onchange", calcProduct(values) )
    }
}
    
2 Answers

Using an event listener and reduce() you can accumulate the final product. Note: in an event listener, omit the 'on', so use change rather than onchange. To make it easier, I used a className for inputs that will be calculated

window.addEventListener('load', () => {
  document.querySelectorAll('.to-calc').forEach(el => el.addEventListener('change', calcProduct))
  calcProduct()
})

function calcProduct(e) {
  let output = [...document.querySelectorAll('.to-calc')].reduce((b, a) => b * +a.value, 1)
  console.log(output.toFixed(2))
  return output.toFixed(2)
}
<input class='to-calc' type='number' value='0.5'> <input class='to-calc' type='number' value='0.6'> <input class='to-calc' type='number' value='0.1'>

I would combine event delegation and reactive programming for this.

Attach your event listener to a parent node and filter the events as they come in:

  1. When an input event is dispatched the parent <div> catches it.
  2. The filter operator makes sure we consume the right events.
  3. The scan operator writes new values to the map.
  4. The map operator multiplies the values in the map.
  5. Finally the subscriber receives and displays the product.

This way we can work with multiple sets of numbers.
(Note how the map corresponds to ids and values in the DOM.)

const mult = nums =>
  fromEvent(document.querySelector('div'), 'input')                       //1
  .pipe( filter(e => e.target.id in nums)                                 //2
       , scan((acc, {target: {id, value}}) => (acc[id]=value, acc), nums) //3
       , map(ns => Object.values(ns).reduce((p, n) => p * n, 1)));        //4

mult({n1: 1, n2: 1, n3: 1}).subscribe(display('#p1'));                    //5
mult({n4: 1, n5: 1, n6: 1}).subscribe(display('#p2'));                    //5
mult({n7: 1, n8: 1, n9: 1}).subscribe(display('#p3'));                    //5
input{width:30px}
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.2.0/rxjs.umd.min.js" integrity="sha512-MlqMFvHwgWJ1vfts5fdC2WzxDaIXWfYuAd9Tb2lobtF61Gk+HIRDrbtxgasBSM9lZgOK9ilwK9LqFIYEV+k0IA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script>
const {fromEvent} = rxjs;
const {filter, map, scan} = rxjs.operators;
const display = sel => val => document.querySelector(sel).textContent = val;
</script>
<div>
  <input id="n1" type="number" value="1"/> ×
  <input id="n2" type="number" value="1"/> ×
  <input id="n3" type="number" value="1"/> = <span id="p1">1</span>
  <hr/>
  <input id="n4" type="number" value="1"/> ×
  <input id="n5" type="number" value="1"/> ×
  <input id="n6" type="number" value="1"/> = <span id="p2">1</span>
  <hr/>
  <input id="n7" type="number" value="1"/> ×
  <input id="n8" type="number" value="1"/> ×
  <input id="n9" type="number" value="1"/> = <span id="p3">1</span>
</div>

Related