如何对具有相同 <select> 选项的 <input> 求和?

Posted

技术标签:

【中文标题】如何对具有相同 <select> 选项的 <input> 求和?【英文标题】:How to sum <input>s that have the same <select> options? 【发布时间】:2020-03-25 03:55:13 【问题描述】:

问题是我有这个用于多个捐赠入口的表格,我几乎完成了,我只需要计算我的捐赠的小计。基本上每个条目都有一个输入和一个带有 5 个选项的选择 - 每个选项都将作为我的捐赠输入的小计。我想将属于相同小计类别(相同选择选项)的捐款相加并列出。

我在想出解决方案时遇到了很多麻烦。现在我正在寻找香草js或jquery的解决方案。有人知道怎么做吗?

这是简化的 html

 <label>donation value</label>
 <input type="number" class="donation">

 <select class="categories">
   <option id="cat1">category 1</option>
   <option id="cat2">category 2</option>
   <option id="cat3">category 3</option>
 </select>

编辑:如果它是通过 onClick 事件制作的,我会更喜欢。提前致谢!

【问题讨论】:

为了确保我理解:上面的 HTML 结构重复了?每个实例都有一个类别和一个值?你想按类别小计? 不需要重复,因为我已经处理好了,但我不介意你的。几乎,只要我需要一个实例,我就会创建一个实例,每个实例都有一个类别和一个值的输入文本,是的。确切地说,按类别小计。我忘了包括:我希望它是通过 onClick 事件,鉴于我的经验有限,跟踪和管理事件会更容易。 【参考方案1】:

您可以将 onchange 侦听器添加到输入并更新数据结构以跟踪每个实例的值,然后根据该数据结构计算小计。这个演示需要稍微调整一下——例如,它不处理清除金额字段——但它展示了基本思想:

// 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>

【讨论】:

我非常喜欢你的代码,非常感谢。你能让它符合我最后的要求吗?我在这里尝试您的代码,但在 onChange 事件上遇到了一点问题。 你最后的要求是什么? 使用 onClick 事件代替 onChange 事件。我尝试过,但运气不佳。有可能吗? 你的代码非常好。我有问题。这可能是我的错,但是当我用我的代码尝试它时,我无法使小计工作,也许是因为我正在动态地创建每个实例。我在没有动态创建任何实例的情况下尝试了您的代码,并且效果很好。我绝对接受你的回答。你能帮我解决这个问题吗? My demo 演示中小计的问题是,当您创建新捐赠时,您会为输入的数字提供一个唯一的名称(例如amount_1)。 subtotal 函数将'amount' 字段相加,因此它忽略了新的输入。如果去掉加名字后缀的那一行(第28行)it works.

以上是关于如何对具有相同 <select> 选项的 <input> 求和?的主要内容,如果未能解决你的问题,请参考以下文章

更改 <select> 的选项并使用 JavaScript 触发事件

Ember JS:选择具有所选值的列表

如何禁用具有相同名称的 2 个输入之一

两个浏览器选项卡中的相同 Vue.js 组件使 <select> 镜像其选定值

如何使 dijit.form.Select 与具有相同值的多个项目一起使用?

如何初始化具有相同类的页面上的所有 Select2 下拉菜单