【问题标题】:How does one handle DOM events from various buttons by a single handler function如何通过单个处理函数处理来自各种按钮的 DOM 事件
【发布时间】:2020-12-19 23:46:33
【问题描述】:

我不确定这是否可能,但我想使用一个独特的功能来触发 4 个不同的按钮来计算一个值(+ 和 -)。但是有四种不同的跨度值,例如,如果我触发森林,它只会添加或从森林中删除,如果我为城镇执行它只会触发城镇,等等。

// set inital value to zero
let count = 0;
// select value and buttons
const valueForest = document.querySelector("#valueForest");
const btns = document.querySelectorAll(".btn");

btns.forEach(function (btn) {
  btn.addEventListener("click", function (e) {
    const styles = e.currentTarget.classList;
    if (styles.contains("decrease")) {
      count--;
    } else if (styles.contains("increase")) {
      count++;
    } else {
      count = 0;
    }

    if (count > 0) {
      valueForest.style.color = "green";
    }
    if (count < 0) {
      valueForest.style.color = "red";
    }
    if (count === 0) {
      valueForest.style.color = "#222";
    }
    valueForest.textContent = count;
  });
 });
<div class="scoreDiv">
  <h3>Input below the quantity of each tile in the end of the game:</h3>
  <div class="scoreItem">
      <h4>Forest</h4>
      <button class="btn decrease">-</button>
      <span class="value" id="valueForest">0</span>
      <button class="btn increase">+</button>
      <h4>SOMA</h4>
  </div>
  <div class="scoreItem">
      <h4>Town</h4>
      <button class="btn decrease">-</button>
      <span class="value" id="valueTown">0</span>
      <button class="btn increase">+</button>
      <h4>SOMA</h4>
  </div>
  <div class="scoreItem">
      <h4>Production</h4>
      <button class="btn decrease">-</button>
      <span class="value" id="valueProduction">0</span>
      <button class="btn increase">+</button>
      <h4>SOMA</h4>
  </div>
  <div class="scoreItem">
      <h4>Factory</h4>
      <button class="btn decrease">-</button>
      <span class="value" id="valueFactory">0</span>
      <button class="btn increase">+</button>
      <h4>SOMA</h4>
  </div>
</div>

【问题讨论】:

  • 请记得实际提出问题。现在,你的帖子没有。阅读"how to ask a good question" 并相应地更新您的帖子(并记得在之后查看您的帖子,以便您可以发现并修复您的代码块现在存在的标记问题)

标签: javascript event-handling dom-events event-delegation


【解决方案1】:

由于 Mister Jojo 已经 perfectly answered event delegation based approach 没有什么可添加的,我将重点介绍一种查看和处理重复使用的 DOM 结构的方法具有特定行为作为组件

对于 OP 的示例,将只有一个 Score Item 组件,该组件仅实现一次其特定行为,并且独立于底层 HTML/CSS 的语义。

与这种 Score Item 组件的实际能力相比,实现/使用的 JavaScript 代码量仍然足够少。

识别组件结构依赖于data 属性,该属性将该任务与所提供的任何 HTML 和 CSS 代码/环境分离。

每个组件在初始化/创建时封装其状态;因此它不会从 DOM 读取数据(它只写入后者),但它会从/向其封装状态读取和写入数据。

还可以通过组件特定的data 属性为组件配置初始显示的值以及不同的递增/递减值。

正值、负值或 值的颜色模式由组件特定和基于 data 属性的 CSS 规则描述;没有理由产生与布局相关的脚本开销...

function incrementBoundItemScore() {
  const { outputControl: ctrl, currentValue, incrementValue } = this;
  ctrl.textContent = ctrl.dataset.currentValue = this.currentValue = (currentValue + incrementValue);
}
function decrementBoundItemScore() {
  const { outputControl: ctrl, currentValue, decrementValue } = this;
  ctrl.textContent = ctrl.dataset.currentValue = this.currentValue = (currentValue + decrementValue);
}

function initializeScoreItem(rootNode) {
  const incrementControl = rootNode.querySelector('[data-increase]');
  const decrementControl = rootNode.querySelector('[data-decrease]');
  const outputControl = rootNode.querySelector('[data-output]');

  const incrementValue = parseFloat(incrementControl.dataset.value, 10);
  const decrementValue = parseFloat(decrementControl.dataset.value, 10);
  const initialValue = parseFloat(outputControl.dataset.initialValue, 10);

  const scoreItem = {
    outputControl,
    currentValue: initialValue,
    incrementValue,
    decrementValue,
  }
  outputControl.textContent = outputControl.dataset.currentValue = initialValue;

  incrementControl
    .addEventListener('click', incrementBoundItemScore.bind(scoreItem));
  decrementControl
    .addEventListener('click', decrementBoundItemScore.bind(scoreItem));
}
function initialize() {
  document
    .querySelectorAll('[data-score-item-component]')
    .forEach(initializeScoreItem);
}
initialize();
body {
  zoom: .8;
  margin: 0;
}
ul, li {
  list-style: none;
}
ul {
  margin: 0;
}
fieldset {
  padding: 0px 10px;
}
.score-group {
  margin: 4px 0;
}
.score-item {
  margin: 0 0 5px 0;
}
.score-item legend {
  font-weight: bold;
}
.score-item strong {
  position: relative;
  top: -2px;
  font-weight: normal;
  font-size: small;
  text-transform: uppercase
}

[data-output][data-current-value] {
  display: inline-block;
  width: 3em;
  text-align: center;
  font-weight: bold;
  color: green;
}
[data-output][data-current-value="0"] {
  color: #222;
}
[data-output][data-current-value^="-"] {
  color: red;
}
<section class="score-board">
  <!--
  <h3>Input below the quantity of each tile in the end of the game:</h3>
  //-->
  <ul>
    <li class="score-item">
      <fieldset  data-score-item-component>
        <legend>Forest</legend>

        <div class="score-group">
          <button
            type="button"
            data-decrease
            data-value='-1'
            class="btn decrease"
          >-</button>
          <output
            name="forest-score"
            data-output
            data-initial-value="9"
          ></output>
          <button
            type="button"
            data-increase
            data-value='1'
            class="btn increase"
          >+</button>
        </div>

        <strong>Soma</strong>
      </fieldset>
    </li>
    <li class="score-item">
      <fieldset data-score-item-component>
        <legend>Town</legend>

        <div class="score-group">
          <button
            type="button"
            data-decrease
            data-value='-2'
            class="btn decrease"
          >-</button>
          <output
            name="town-score"
            data-output
            data-initial-value="0"
          ></output>
          <button
            type="button"
            data-increase
            data-value='2'
            class="btn increase"
          >+</button>
       </div>

        <strong>Soma</strong>
      </fieldset>
    </li>
    <li class="score-item">
      <fieldset data-score-item-component>
        <legend>Production</legend>

        <div class="score-group">
          <button
            type="button"
            data-decrease
            data-value='-5'
            class="btn decrease"
          >-</button>
          <output
            name="production-score"
            data-output
            data-initial-value="-10"
          ></output>
          <button
            type="button"
            data-increase
            data-value='5'
            class="btn increase"
          >+</button>
        </div>

        <strong>Soma</strong>
      </fieldset>
    </li>
    <li class="score-item">
      <fieldset data-score-item-component>
        <legend>Factory</legend>

        <div class="score-group">
          <button
            type="button"
            data-decrease
            data-value='-2'
            class="btn decrease"
          >-</button>
          <output
            name="factory-score"
            data-output
            data-initial-value="-5"
          ></output>
          <button
            type="button"
            data-increase
            data-value='1'
            class="btn increase"
          >+</button>
        </div>

        <strong>Soma</strong>
      </fieldset>
    </li>
  </ul>
</section>

【讨论】:

    【解决方案2】:

    是的,有事件委托

    这样:

    const scoreDiv = document.querySelector('div.scoreDiv') // the parent Div
    
    scoreDiv.onclick = e => // get all clicks everywhere upon this parent Div
      {
      if (!e.target.matches('div.scoreItem > button.btn ')) return  // ignore other clicks
    
      let countEl =  e.target.closest('div.scoreItem').querySelector('span.value')
        , newVal  = +countEl.textContent + (e.target.matches('.decrease') ? -1 : +1)
        ;
      countEl.style.color = (newVal > 0) ? 'green' :  (newVal < 0) ? 'red' : '#222'
      countEl.textContent = newVal;
      }
    span.value {
      display       : inline-block; 
      width         : 5em; 
      text-align    : right; 
      padding-right : .5em;
      font-weight   : bold;
    }
    <div class="scoreDiv">
      <h3>Input below the quantity of each tile in the end of the game:</h3>
      <div class="scoreItem">
        <h4>Forest</h4>
        <button class="btn decrease">-</button>
        <span class="value" id="valueForest">0</span>
        <button class="btn increase">+</button>
        <h4>SOMA</h4>
    </div>
      <div class="scoreItem">
        <h4>Town</h4>
        <button class="btn decrease">-</button>
        <span class="value" id="valueTown">0</span>
        <button class="btn increase">+</button>
        <h4>SOMA</h4>
      </div>
      <div class="scoreItem">
        <h4>Production</h4>
        <button class="btn decrease">-</button>
        <span class="value" id="valueProduction">0</span>
        <button class="btn increase">+</button>
        <h4>SOMA</h4>
      </div>
      <div class="scoreItem">
        <h4>Factory</h4>
        <button class="btn decrease">-</button>
        <span class="value" id="valueFactory">0</span>
        <button class="btn increase">+</button>
        <h4>SOMA</h4>
      </div>
    </div>

    说明

    if (!e.target.matches('div.scoreItem > button.btn')) return  
    

    首先事件处理程序scoreDiv.onclick = e =&gt; 关注里面的一切

    <div class="scoreDiv"> 
      // everything inside
    </div>
    

    所以这个空间中的任何点击事件都由这个箭头函数处理。
    可能是点击:
    在 H3 元素上
    ,或跨度元素之一
    , 或任何 H4 元素
    ,一切!
    ,甚至任何元素之间的空格。

    事件 [e] 有不同的属性
    e.currentTarget --> 是对调用者元素的引用(这里是 scoreDiv [div.scoreItem]) e.target --> 是对发生点击的元素的引用

    对于这项工作,我们只需要执行递增/递减操作。
    这意味着我们必须忽略任何不在加号或减号按钮上的点击事件。
    8 个按钮是:&lt;button class="btn decrease"&gt;-&lt;/button&gt;&lt;button class="btn increase"&gt;-&lt;/button&gt;

    所有这些按钮都对应于 CSS = div.scoreItem &gt; button.btn

    在 javascript 中用于测试的代码是

    e.target.matches('div.scoreItem > button.btn')
    

    将返回一个布尔值(真或假)

    现在有一个策略:而不是做大事

    if ( e.target.matches('div.scoreItem > button.btn') ) 
      { 
      //...
      // with many lines of code 
      //until the closing 
      }
    // and then quit the function
    

    因为这是一个函数,我们使用 Logical NOT (!)
    从函数直接生成return,编码如下:

    if (!e.target.matches('div.scoreItem > button.btn')) return  
    

    主要兴趣是在另一个元素(存在于 scoreDiv 中)拥有自己的点击事件处理程序的情况下快速释放事件管理器。

    【讨论】:

    • 非常感谢乔乔先生!抱歉我的无知,但是“!e”是做什么的?
    • 抱歉,但是表达式“if (!e.target.matches('div.scoreItem > button.btn '))”如何忽略其他点击? scoreItem 怎么可能 > btn?这对我来说很模糊。
    • @LucasMarodindepaiva 我在我的回答中添加了一个解释
    【解决方案3】:

    我查找了如何直接通过 css 更改颜色,幸运的是 Peter Seliger 展示了这一点。

    我还在 css 中添加了 output::before {content: attr(data-value)} 允许直接在显示器上属性此值,无需 JS 代码

    这进一步简化了 javascript 代码。

    (我还冒昧地稍微改变了界面以使其完全变亮,这对本演示没有兴趣)

    const scoreBoard = document.querySelector('#score-board')
    
    scoreBoard.onclick = e =>
      {
      if (!e.target.matches('#score-board button')) return
    
      let countEl = e.target.closest('fieldset')
                            .querySelector('output[data-value]')
      countEl.dataset.value = +countEl.dataset.value
                            + (+e.target.dataset.increase)
      }
    body, textarea, input  {
      font-family : Helvetica, Arial sans-serif;
      font-size   : 12px;
      }
    #score-board fieldset {
      width  : 20em;
      margin : .5em 1em;
      }
    #score-board legend {
      font-size : 1.4em;
      padding   : 0 .7em;
      }
    #score-board output {
      display       : inline-block; 
      font-size     : 1.4em;
      width         : 5em; 
      text-align    : right; 
      padding-right : .5em;
      color         : green;
      font-weight   : bold;
      border-bottom : 1px solid grey;
      margin        : 0 .8em 0 .2em;
      }
    #score-board output::before {
      content : attr(data-value)
      }
    #score-board output[data-value="0"] {
      color: #222;
      }
    #score-board output[data-value^="-"] {
      color: red;
      }
    <section id="score-board">
      <fieldset>
        <legend>Forest</legend>
        <output data-value="-10"></output>
        <button data-increase="+1">&#69717;</button>
        <button data-increase="-1">&#69714;</button>
      </fieldset>
      <fieldset>
        <legend>Town</legend>
        <output data-value="0"></output>
        <button data-increase="+1">&#69717;</button>
        <button data-increase="-1">&#69714;</button>
      </fieldset>
      <fieldset>
        <legend>Production</legend>
        <output data-value="-7"></output>
        <button data-increase="+1">&#69717;</button>
        <button data-increase="-1">&#69714;</button>
      </fieldset>
      <fieldset>
        <legend>Factory</legend>
        <output data-value="5"></output>
        <button data-increase="+1">&#69717;</button>
        <button data-increase="-1">&#69714;</button>
      </fieldset>
    </section>

    【讨论】:

    • at all ...我投票赞成这个是例外的答案,因为它确实结合了迄今为止提供的所有方法中最好的/techniques ... 一种 基于事件委托的方法,它满足 OP 的要求,此外还带有 最精简的实现之一 得益于更好的结构化标记高级css功能
    【解决方案4】:

    // set inital value to zero
    //let count = 0;
    // select value and buttons
    const btns = document.querySelectorAll(".btn");
    
    btns.forEach(function (btn) {
      btn.addEventListener("click", function (e) {
        const styles = e.currentTarget.classList;
        const SectionValue = e.currentTarget.parentNode.querySelector('span');
        var count = Number( SectionValue.innerHTML );
        if (styles.contains("decrease")) {
          count--;
        } else {
          count++;
        }
    
        if (count > 0) {
            SectionValue.style.color = "green";
        } else if (count < 0) {
            SectionValue.style.color = "red";
        } else {
            SectionValue.style.color = "#222";
        }
        SectionValue.innerHTML = count;
      });
     });
        <div class="scoreDiv">
            <h3>Input below the quantity of each tile in the end of the game:</h3>
            <div class="scoreItem">
                <h4>Forest</h4>
                <button class="btn decrease">-</button>
                <span class="value" id="valueForest">0</span>
                <button class="btn increase">+</button>
                <h4>SOMA</h4>
            </div>
            <div class="scoreItem">
                <h4>Town</h4>
                <button class="btn decrease">-</button>
                <span class="value" id="valueTown">0</span>
                <button class="btn increase">+</button>
                <h4>SOMA</h4>
            </div>
            <div class="scoreItem">
                <h4>Production</h4>
                <button class="btn decrease">-</button>
                <span class="value" id="valueProduction">0</span>
                <button class="btn increase">+</button>
                <h4>SOMA</h4>
            </div>
            <div class="scoreItem">
                <h4>Factory</h4>
                <button class="btn decrease">-</button>
                <span class="value" id="valueFactory">0</span>
                <button class="btn increase">+</button>
                <h4>SOMA</h4>
            </div>
          </div>

    【讨论】:

    • 发布答案时,还请描述您的代码的作用以及它如何回答问题。
    • 该代码通过将 valueForest 替换为 SectionValue 解决了指针问题。我还将条件固定为我称之为标准的条件。
    猜你喜欢
    • 1970-01-01
    • 2016-05-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-16
    • 2021-11-07
    • 1970-01-01
    相关资源
    最近更新 更多