Вы можете добавить слушателей onchange к входам и обновить структуру данных, чтобы отслеживать значения каждого экземпляра, а затем вычислить промежуточные итоги из этой структуры данных. Эта демонстрация требует небольшой настройки - например, она не обрабатывает очистку поля количества - но она показывает основную идею c:
// an array to keep track of each donation instance
const donations = [];
// change handler for both the number input and the category select
// The first argument is the change event, from which we
// extract the target's name and value, where 'name' will be
// either 'category' for the select or 'amount' for the number input
function onDonationChange({target: {name, value}}, index) {
// update the entry at [index] to reflect the change
donations[index] = {
// keep the existing fields for this entry if it exists,
// otherwise create it using default values
...(donations[index] ?? { category: 'category 1', amount: 0}),
// update with the event target's field name and value
[name]: value
};
// update the subtotal display to reflect the change
updateSubtotal();
}
function updateSubtotal () {
// find the subtotal dom element we want to update
const elem = document.querySelector('.subtotal');
// compute the subtotals for each category, e.g.
// { 'category 1': 100, 'category 2': 200 } etc.
const subtotals = donations.reduce((acc, {category, amount}) => ({ ...acc, [category]: ((acc[category] ?? 0) + parseFloat(amount)) }), {});
// update the display.
// for each entry in subtotals, create an html snippet,
// then join them into a single string.
elem.innerHTML = Object.entries(subtotals)
.map(([category, subtotal]) => (
`<div><span class="cat">${category}</span>: ${subtotal}</div>`
)).join('');
}
// find the items so we can add the event listeners
const items = document.querySelectorAll('li');
// iterate over each item and...
items.forEach((item, index) => {
// find the input and select for this instance
const inputs = item.querySelectorAll('input, select');
// add a change listener that passes both the change
// event and the item's index
inputs.forEach(i => i.addEventListener('change', e => onDonationChange(e, index)));
});
body {
font-family: sans-serif;
}
ul {
list-style: none;
padding: 0;
}
.cat {
opacity: 0.5;
}
<ul>
<li>
<label>donation value</label>
<input name="amount" type="number" class="donation">
<select name="category" class="categories">
<option>category 1</option>
<option>category 2</option>
<option>category 3</option>
</select>
</li>
<li>
<label>donation value</label>
<input name="amount" type="number" class="donation">
<select name="category" class="categories">
<option>category 1</option>
<option>category 2</option>
<option>category 3</option>
</select>
</li>
<li>
<label>donation value</label>
<input name="amount" type="number" class="donation">
<select name="category" class="categories">
<option>category 1</option>
<option>category 2</option>
<option>category 3</option>
</select>
</li>
<li>
<label>donation value</label>
<input name="amount" type="number" class="donation">
<select name="category" class="categories">
<option>category 1</option>
<option>category 2</option>
<option>category 3</option>
</select>
</li>
<li>
<label>donation value</label>
<input name="amount" type="number" class="donation">
<select name="category" class="categories">
<option>category 1</option>
<option>category 2</option>
<option>category 3</option>
</select>
</li>
</ul>
<hr />
<div class="subtotal"></div>
Или, если вам действительно нужно запустить все сразу после факта:
// Get a donation object for the given LI
// returns an object representing the LI's inputs, e.g.
// { category: 'category 1', amount: 122 }
function getDonation (listItem) {
// find all the inputs and selects in this LI
const inputs = listItem.querySelectorAll('input, select');
// construct and return the object based on the inputs' names and values
return [...inputs]
.reduce((acc, {name, type, value}) => ({...acc, [name]: type === 'number' ? parseFloat(value) : value}), {})
}
function updateSubtotal() {
// Get all of the LI's
const listItems = document.querySelectorAll('li');
// Get a donation object for each LI
// Filter out items where amount is not a number
const donations = [...listItems].map(getDonation).filter(({amount}) => !isNaN(amount));
// Compute the subtotals for each category
const subtotals = donations.reduce((acc, {category, amount}) => ({ ...acc, [category]: ((acc[category] ?? 0) + parseFloat(amount)) }), {});
// Find the subtotal display element
const displayElem = document.querySelector('.subtotal');
// Generate and insert HTML from the computed subtotals
displayElem.innerHTML = Object.entries(subtotals)
.map(([category, amount]) => (
`<div><span class="category">${category}</span>: <span class="amount">${amount}</span></div>`
)).join('');
}
body {
font-family: sans-serif;
}
ul {
list-style: none;
padding: 0;
}
.category {
opacity: 0.5;
}
.amount {
color: tomato;
}
<ul>
<li>
<label>donation value</label>
<input name="amount" type="number" class="donation">
<select name="category" class="categories">
<option>category 1</option>
<option>category 2</option>
<option>category 3</option>
</select>
</li>
<li>
<label>donation value</label>
<input name="amount" type="number" class="donation">
<select name="category" class="categories">
<option>category 1</option>
<option>category 2</option>
<option>category 3</option>
</select>
</li>
<li>
<label>donation value</label>
<input name="amount" type="number" class="donation">
<select name="category" class="categories">
<option>category 1</option>
<option>category 2</option>
<option>category 3</option>
</select>
</li>
<li>
<label>donation value</label>
<input name="amount" type="number" class="donation">
<select name="category" class="categories">
<option>category 1</option>
<option>category 2</option>
<option>category 3</option>
</select>
</li>
<li>
<label>donation value</label>
<input name="amount" type="number" class="donation">
<select name="category" class="categories">
<option>category 1</option>
<option>category 2</option>
<option>category 3</option>
</select>
</li>
</ul>
<button onclick="updateSubtotal()">Update Subtotal</button>
<hr />
<div class="subtotal"></div>