2

I'm not sure if it's possible, but I'd like to use one unique function to trigger 4 different buttons to count a value (+ and -). But there are four different span values, for example, if I trigger forest it will only add or remove from forest, if I do it for town it will only trigger for town, and so on.

// 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>
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
  • Please remember to actually ask a question, too. Right now, your post doesn't. Read over ["how to ask a good question"](/help/how-to-ask) and update your post accordingly (and remember to look at your post afterwards so you can spot and fix markup problems like your codeblock has right now) – Mike 'Pomax' Kamermans Dec 19 '20 at 23:56

4 Answers4

3

Since there is nothing to add to Mister Jojo's already perfectly answered event delegation based approach, I will focus on an approach that sees and treats repeatedly used DOM structures which feature a specific behavior as components

As for the OP's example there would be just a Score Item component which implements its specific behavior exactly once and independent from the semantics of the underlying HTML/CSS.

The amount of implemented/used JavaScript code is still small enough in comparison to what such a Score Item component actually is capable of.

Identifying component structures relies on data attributes which decouples this task from whatever provided HTML- and CSS- code/environment.

Each component encapsulates its state at initialization/creation time; thus it does not read the data from the DOM (it only does write to the latter), but it does read and write the data from/to its encapsulated state.

A component also can be configured via component specific data attributes for an initially displayed value as well as for distinct incrementing/decrementing values.

The color-schema for positive, negative or Zero values are described by component specific and data attribute based CSS rules; there is no reason for layout related scripting overhead ...

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>
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
2

yes, with event delegation

this way:

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>

Explanations about

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

First of all the event handler scoreDiv.onclick = e => concern everything inside

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

So this get any click event in this space is processed by this arrow function.
It could be a click:
on the H3 element
, or one of the span elements
, or any the H4 elements
, everything !
, even the spaces between any elements.

the event [e] have diffrents properties
e.currentTarget --> is a reference to the caller element (here it is scoreDiv [div.scoreItem]) e.target --> is a reference to the element where the click happen

for this job we need to do only increment / decrement operations.
that's mean we have to ignore any click event witch is not on the plus or minus buttons.
This 8 buttons are : <button class="btn decrease">-</button> or <button class="btn increase">-</button>

All this buttons correspond to CSS = div.scoreItem > button.btn

In javascript the code for testing that is

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

will return a boolean value (true or false)

There is now a strategy: Instead of making a big

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

and because this is a function we use a Logical NOT (!)
to make a direct return from function, coded like that:

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

The main interest is to quickly free the event manager in case of another element (present in scoreDiv) have is own click eventHandler.

Mister Jojo
  • 20,093
  • 6
  • 21
  • 40
2

I looked up how to change the color directly via css and luckily Peter Seliger showed that.

I also added in css an output::before {content: attr(data-value)} allowing to directly attribute this value on the display, without JS code

This further simplifies the javascript code.

(I also took the liberty of changing the interface a little to lighten it up completely, which is of no interest for this demonstration)

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>
Mister Jojo
  • 20,093
  • 6
  • 21
  • 40
  • ***at all*** ... I vote for this one to be the excepted answer, since it does combine the best of all until now offered approaches/techniques ... an ***event delegation based approach*** which fulfills the OP's requirements and in addition comes with ***one of the leanest possible implementations*** due to a ***much better structured markup*** in combination with ***advanced css features***. – Peter Seliger Dec 20 '20 at 18:27
0

// 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>
  • When posting answer, please also describe what your code does and how it answers the question. – rby Dec 20 '20 at 00:46
  • The code resolves the pointer problem by replacing valueForest with SectionValue. I also fixed the conditions to what I would call standard. –  Dec 20 '20 at 00:51