こんにちは。
この回答では、ご質問の本題である
プラスボタンとマイナスボタンのaddEventListenerを果物の品名の数だけ書かなければならない状況
を解消するために
1. ボタンのインデクスを使う例
と
2. ボタンのインデクスを使わない例
の両方のコードを挙げますが、その前に、まずご質問にある HTMLとCSSを、以下の2点で修正します。
-
<li>
の親要素は <ul>
になるようにする。
-
id
の値 plus
, num
, minus
が複数の異なる要素に付与されているので、これらをクラスにする。
上記2点について、簡単な修正をしたHTMLとCSSが以下です。
html
1<section class="items">
2 <ul>
3 <li>
4 <div class="item">
5 <span class="name">みかん</span>
6 <div class="count">
7 <div class="plus icon">+</div>
8 <div class="num">0</div>
9 <div class="minus icon">-</div>
10 </div>
11 </div>
12 </li>
13 <li>
14 <div class="item">
15 <span class="name">りんご</span>
16 <div class="count">
17 <div class="plus icon">+</div>
18 <div class="num">0</div>
19 <div class="minus icon">-</div>
20 </div>
21 </div>
22 </li>
23 <li>
24 <div class="item">
25 <span class="name">ぶどう</span>
26 <div class="count">
27 <div class="plus icon">+</div>
28 <div class="num">0</div>
29 <div class="minus icon">-</div>
30 </div>
31 </div>
32 </li>
33 </ul>
34</section>
35
css
1li {
2 height: 40px;
3 line-height: 40px;
4 text-align: center;
5 margin-right: 10px;
6}
7
8.item {
9 display: flex;
10 margin: 10px;
11}
12
13.name {
14 margin-right: 12px;
15}
16
17.count{
18 display: flex;
19 align-items: center;
20}
21
22.num {
23 width: 100px;
24 height: 30px;
25 border-radius: 5px;
26 border: 1px solid #ddd;
27 line-height: 30px;
28 text-align: center;
29 margin: 0 5px;
30}
31
32.icon {
33 width: 20px;
34 height: 20px;
35 line-height: 20px;
36 text-align: center;
37 border-radius: 50%;
38}
39
40.icon.plus {
41 background: pink;
42}
43
44.icon.minus {
45 background: skyblue;
46}
47
上記のように修正したHTMLとCSS に対して、意図している動作を実現するための javascript を以下に挙げます。
1. ボタンのインデクスを使う例
以下のコードで btnIndex
にボタンのインデクスが(0始まりで)入ってきます。
javascript
1document.querySelectorAll('.icon').forEach((btn, btnIndex) => {
2
3 btn.addEventListener('click', () => {
4 const numDiv = document.querySelectorAll('.num')[Math.floor(btnIndex / 2)];
5 numDiv.textContent = (+numDiv.textContent) + (btnIndex % 2 ? -1 : 1);
6 });
7
8});
9
以下は、上記を動作確認するために jsFiddleに上げたものです。
2. ボタンのインデクスを使わない例
.count
にクリックハンドラを設定し、 イベントターゲットが .icon
の場合に、当該の .count
に含まれる .num
のカウンターを増減する処理を書きます。
javascript
1document.querySelectorAll('.count').forEach(counter => {
2
3 counter.addEventListener('click', function(e) {
4 const { classList } = e.target;
5 if (classList.contains('icon')) {
6 const numDiv = counter.querySelector('.num');
7 if (classList.contains('plus'))
8 numDiv.textContent ++;
9 else if (classList.contains('minus'))
10 numDiv.textContent --;
11 }
12 });
13
14});
以上、参考になれば幸いです。
追記
コメントから頂きました質問に回答します。
(1) 単項加算について
・(+numDiv.textContent)
の書き方は初めてみたのですが、もし特別な表記方法なのであれば名前を教えていただけますか。
**単項加算(Unary plus) **です。
自身で調べてみたいと思います。
以下が参考になると思います。
丁寧に書けば parseInt(numDiv.textContent)
とするところですが、 (コードの読みやすさはさておき、)+numDiv.textContent
と短いコードで済ませることができます。
なお、試しにnumDiv.textContent
とに変えて実行してみたところ、ボタンを押すたびにnumDiv内に"1"というテキストが追加されました。
はい。そのように変えたりして試してみることは大変よい事です。単項加算を取ってしまって、 numDiv.textContent
とすると
javascript
1numDiv.textContent = numDiv.textContent + (btnIndex % 2 ? -1 : 1);
となるわけですが、たとえば <div class="num">0</div>
となっているとき、numDiv.textContent
は、文字列(String)の '0'
を返します。ここから + をクリックしたとすると (btnIndex % 2 ? -1 : 1)
は、 数値(Number)の 1
となります。従って上記の右辺は
という加算を行うことになりますが、この結果は(期待している)数値の 1
にはならずに、 '0' + 1
の 1
が文字列の'1'
として扱われて、 '0' + '1'
という文字列の連結が行われて、結果は '01'
という文字列になります。以下同様にして、+をクリックするたびに '1'
、 ーをクリックするたびに '-1'
という文字列が末尾に追加された文字列がnumDiv.textContent
に入ってしまいます。これを意図する動作にするためには、numDiv.textContent
に 文字列の'0'
が入っているとき、数値の0
が得られたらよいので 、 単項加算を使って +numDiv.textContent
としています。なお (+numDiv.textContent)
と書いてるところの丸カッコ (
)
は無くても問題ないので、
javascript
1numDiv.textContent = +numDiv.textContent + (btnIndex % 2 ? -1 : 1);
でも意図通りに動きます。(
)
をつけて、(+numDiv.textContent)
としたのは、カッコが無いより多少読みやすくなるかな、ぐらいの考えでした。
(2) 条件 (三項) 演算子について
・条件 (三項) 演算子の記載についてですが、btnIndex % 2 ? -1 : 1
は奇数がtrueで偶数がfalseということでしょうか。
はい。そうです。
そのモヤモヤを解消するには、 Truthy と Falsy という用語を知っておくと整理できるかもしれません。
Truthy と Falsy という言葉を使えば、 条件 (三項) 演算子 式 ? A : B
では、 式
(を評価した結果の)値が Truthy であれば A
、 Falsyであれば B
となる、と言えます。たとえば空文字列 ''
は、Falsy なので
の結果は 20
となります。また、
javascript
1[1,2,3].length ? 10 : 20
の結果は、 [1,2,3].length
は 3
で、 3
はTruthyなので 10
となります。
本題の
javascript
1btnIndex % 2 ? -1 : 1
についてですが、 この条件演算子の結果は、ボタンがクリックされたときの、現状の値に加算する値を表し、具体的には1
か -1
となります。-1
を加算するということは、つまり 1
を引くということです。
条件式の btnIndex % 2
は btnIndex
を2で割った余りですので、 0
か 1
です。btnIndex % 2
が 0
のときは、条件式としては Falsy です。そのとき、ボタンは0始まりのインデクスが偶数の位置にあるということなので、現状でのデザインでは + ボタンのほうなので、加算する数値として :
の後ろには 1
があるべきです。逆に btnIndex % 2
が1のとき、つまり条件式としてTruthy のとき、 btnIndex
は奇数で、現状のデザインでは ー ボタンのほうなので 1を引きたいわけですから、加算する数値としては、:
の前の値は -1
でなければなりません。
javascript
1btnIndex % 2 ? -1 : 1
をよく見ると、条件演算子の ?
の後ろが、-1 : 1
となっていて、これはボタンの表示上の並びである、
+ 数 ー
とは逆なので、ちょっと混乱するかもしれませんね。-1 : 1
ではなく 1 : -1
と、 ボタンの並びと同じにするには、btnIndex
が偶数のときTruthyになる条件式になればよいので
javascript
1btnIndex % 2 === 0 ? 1 : -1
と書けばよいです。つまり押さえておくべきポイントは、
x が n で割り切れるとき、 x % n は、 ( 0 なので) 条件式としては Falthy になる。
ということです。T.Takedaさんが
感覚的に2で割り切れればtrueに感じてしまうのですが。。。
とおっしゃるように確かに直感的には 割り切れるときtrueという感じがしますが、割り切れるとき Truthy な式は x % n === 0
というふうに === 0
を追加しなければなりません。 コードを短くすることを求めると、 x % n ===0
ではなく、単に x % n
と条件式に書きたいので、そうすると条件演算子の ?
の後のA : B
の A
には割り切れないときの値を書くことになります。
このあたりは好みなので、 読みやすさを優先して、btnIndex % 2 === 0 ? 1 : -1
を採用するのでもかまわないと思います。
回答のコードに書いた、
javascript
1numDiv.textContent = (+numDiv.textContent) + (btnIndex % 2 ? -1 : 1);
は、parseInt(numDiv.textContent)
とせずに単項加算を使ったり、 btnIndex % 2 === 0
としないで、単にbtnIndex % 2
を条件式にしたりと、コードを詰める方向で書きました。これを以下のように書けば少し親切かなと思います。
javascript
1numDiv.textContent = parseInt(numDiv.textContent) + (btnIndex % 2 === 0 ? 1 : -1);
コメントへの回答は以上です。