javascriptを使って簡単な計算機を作るpart.last 入門者向け

#計算機を作る

##完成物
<p class="codepen" data-height="265" data-theme-id="light" data-default-tab="js,result" data-user="raimumk2" data-slug-hash="jOWgaGJ" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="jOWgaGJ">
  <span>See the Pen <a href="https://codepen.io/raimumk2/pen/jOWgaGJ">
  jOWgaGJ</a> by ライム (<a href="https://codepen.io/raimumk2">@raimumk2</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

##サンプルコード

<details><summary>HTML</summary><div>

```html:caluculate.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="css/caluculate.css">
  <title>計算機</title>
</head>
<body>
  <div class="caluculate">
    <div class="cal-body">
      <div class="wrapper">
        <div id="number-text">0</div>
      </div>
      <div class="other-btns">
        <button id="clear-btn">CLEAR</button>
        <button id="record-btn">RECORD</button>
      </div>
      <div id="btns">
        <div id="num-btns">
          <button onclick="number(7)">7</button>
          <button onclick="number(8)">8</button>
          <button onclick="number(9)">9</button>
          <button onclick="number(4)">4</button>
          <button onclick="number(5)">5</button>
          <button onclick="number(6)">6</button>
          <button onclick="number(1)">1</button>
          <button onclick="number(2)">2</button>
          <button onclick="number(3)">3</button>
          <button id="zero" onclick="zero(0)">0</button>
          <button name="point" onclick="point('.')">.</button>
          <button id="equal" onclick="calc('=')">=</button>
        </div>
        
        <div id="symbol-btns">
          <button class="symbol-btn" onclick="calc('/')">/</button>
          <button class="symbol-btn" onclick="calc('*')">*</button>
          <button class="symbol-btn" onclick="calc('-')">-</button>
          <button class="symbol-btn" onclick="calc('+')">+</button>
        </div>
      </div>
    </div>
    <div class="cal-lists">
      <div class="lists-name">-MEMO-</div>
      <div class="lists-btn">
        <button id="clear-all">Clear All</button>
      </div>
      <ul id="result-lists"></ul>
    </div>
  </div>


  <script src="js/caluculate.js"></script>
</body>
</html>
```

</div></details>

<details><summary>CSS</summary><div>

```css:caluculate.css
* {
  margin: 0;
  padding: 0;
}

.caluculate {
  margin: 100px auto;
  display: table;
  border-spacing: 5px 0;
}

.cal-body {
  display: table-cell;
  background-color: #6289a4;
  padding: 30px 15px;
  border-radius: 5px;
}

.wrapper {
  width: 300px;
  margin: 0 auto;
}

.wrapper > #number-text {
  background-color: #fff;
  width: 285px;
  height: 54px;
  line-height: 54px;
  margin-left: 5px;
  margin-bottom: 15px;
  font-size: 48px;
  border: 1px solid black;
  /* 右から左へ入力するためのスタイル */
  text-align: right;
}

.other-btns {
  margin: 0 auto;
  width: 300px;
}

#clear-btn {
  width: 135px;
  margin-left: 5px;
  font-size: 24px;
  color: #fff;
  background-color: #8aa3b9;
  /* 上下のズレを直すためのスタイル */
  vertical-align: middle;
}

#record-btn {
  width: 135px;
  font-size: 24px;
  margin-left: 13px;
  color: #fff56c;
  background-color: #8aa3b9;
  /* 上下のズレを直すためのスタイル */
  vertical-align: middle;
}

#btns {
  width: 300px;
  display: flex;
  margin: auto;
}

button {
  width: 65px;
  height: 57px;
}

#num-btns {
  margin: 5px;
}

#num-btns > button {
  margin-bottom: 5px;
  font-size: 24px;
  color: #fff;
  background-color: #8aa3b9;
}

#symbol-btns {
  height: 228px;
  display: flex;
  flex-direction: column;
  display: inline-block;
  margin-top: 5px;
}

#symbol-btns > .symbol-btn {
  margin-bottom: 5px;
  height: 57px;
  font-size: 24px;
  text-align: center;
  background-color: #f68b58;
}

#symbol-btns > .active {
  background-color: #fecf8d;
}

/* 計算結果リスト */
.cal-lists {
  display: table-cell;
  vertical-align: top;
  width: 200px;
  background-color: #fff8dc;
  overflow-y: scroll;
}

.lists-name {
  text-align: center;
  color: #f37053;
}

.lists-btn{
  text-align: left;
  margin-bottom: 5px;
}

#clear-all {
  height: 24px;
  width: 64px;
  margin-left: 5px;
  background-color: #f37053;
  border: 1px solid;
  border-radius: 5px;
  /* position: fixed; */
}

ul {
  margin: 0 auto;
  height: 400px;
}

#result-lists > li {
  font-size: 20px;
  list-style: none;
  text-align: right;
  color: #333;
}

#formula {
  background-color: #fff799;
}

#answer {
  background-color: #fecf8d;
  font-weight: bold; 
}
```

</div></details>

<details><summary>JavaScript</summary><div>

```js:caluculate.js
//計算用
var show = document.getElementById('number-text');
var total = ''; //合計
var operator = ''; //演算子
var currentValue = ''; //現在の値
var flag = 0; //ボタンが押されたあとに効かなくする※flag(フラグ)を立てる。最初は0を代入。

// 計算結果リスト用
var formula1 = 0;
var formula2 = 0;



//数字入力の関数
var number = data => { //押されたボタンの値 data()をアロー関数にしている
  if (currentValue === '0') {
    return; //「0.」と入力しなければ数字を打てなくする
  } else if (currentValue.length <= 8) {
    flag = 0;
    currentValue += data;
    show.textContent = currentValue;
    // console.log(show.textContent); //確認用
  }
};

//0についての関数
var zero = data => {
  if(currentValue === '0') {
    return; //最初の文字が0のとき0を押せなくする
  } else if (currentValue.length <= 8){
    flag = 0;
    currentValue += data;
    show.textContent = currentValue;
    // console.log(show.textContent);
  }
}

//小数点(.)の関数
var point = data => {
  if (currentValue === '') {
    return; //最初に小数点を押せなくする
  } else if (!currentValue.includes('.')) {
    currentValue += data;
    show.textContent = currentValue;
  }
}

//計算の関数
var calc = data => {
  if (flag === 0 && data !== "=") {
    flag = 1;
    
    var formula = total + operator + currentValue;
    total = eval(formula);
    
    operator = data;

    formula1 = currentValue; //計算結果リスト用

    currentValue = '';
    show.textContent = total;
    console.log(operator); //確認用
  } else if (flag === 1 && data === "=") {
    var formula = total + operator + total;
    total = limitNum(eval(formula));
    console.log(formula);

    formula2 = currentValue; //計算結果リスト用

    currentValue = "";
    show.textContent = total;
  } else if (data === "=") {
    flag = 1;
    
    var formula = total + operator + currentValue;
    total = limitNum(eval(formula));
    console.log(formula);

    formula2 = currentValue; //計算結果リスト用

    currentValue = "";
    show.textContent = total;
  } else {
    operator = data; //演算子入力を変更できる
    console.log(operator);
  }
};

//ボタン切り替え機能の関数

var btns = document.getElementsByClassName('symbol-btn');
for (var i = btns.length - 1; i >= 0; i--) {
  btnAction(btns[i],i);
}

function btnAction(btnDOM, btnId) {
  btnDOM.addEventListener('click', function() {
    this.classList.add('active');

    for(let i = btns.length - 1; i >= 0; i--) {
      if(btnId !== i) {
        if(btns[i].classList.contains('active')) {
          btns[i].classList.remove('active');
        }
      }
    }
  });
}

//小数点以下の桁数を揃える関数
function limitNum(num) {
  return Math.round(num*10000000)/10000000;
}

// RECORDボタンの関数
//計算結果を表示
function resultAddAnswer() {
  const li = document.createElement('li');
  li.id = 'answer';
  const answer = show.textContent;
  const lists = document.createTextNode(answer);

  li.appendChild(lists);

  const result = document.getElementById('result-lists');
  result.appendChild(li);
};

//計算式を表示
function resultAddFormula() {
  const li = document.createElement('li');
  li.id = 'formula';
  
  const formula = formula1 + operator + formula2;
  const lists = document.createTextNode(formula);

  li.appendChild(lists);

  const result = document.getElementById('result-lists');
  result.appendChild(li);
}

var record = document.getElementById('record-btn');
record.addEventListener('click', () => {
  if (operator != false) { //演算子ボタンが押された時にだけ、計算式を結果に出すようにしている
    resultAddFormula();
    formula1 = total; //計算結果を使って続けて計算した場合に、前の計算結果をformula1として反映させるため
  }
  resultAddAnswer();
});

//CLEARボタンの関数
function reset() {
  operator = '';
  total = '';
  currentValue = '';
  flag = 0;
  show.textContent = '0';
};

function resetBackColor() {
  [].forEach.call(btns, function(e) {
    e.classList.remove('active');
  });
}

var clear = document.getElementById('clear-btn');
clear.addEventListener('click', () => {
  reset();
  resetBackColor();
});

//計算結果リストの削除ボタンの関数
var allClear = document.getElementById('clear-all');
allClear.addEventListener('click', allClearBtn);

function allClearBtn() {
  const result = document.getElementById('result-lists');
  while(result.lastChild) {
    result.removeChild(result.lastChild);
  }
}
```

</div></details>

##今回作る機能
###計算結果をリストに追加する機能

##目次
**1.計算結果をリストに追加する。**

**2.計算結果を出す過程の計算式もリストに追加する。**
(今後の構想にはなかったが、突如作ることを決めた)

**3.「1.」と「2.」の関数をRECORDボタンを押した時に呼び出す**

**4.不具合の修正**

**5.計算結果リストの編集ができる機能**
削除ボタンのみ

**6.スタイルの変更**
計算機の見た目や計算結果リストの見た目

**7.参考サイト**

**8.感想**


###1.計算結果をリストに追加する。

####該当するコード

```js:caluculate.js
//計算結果をリストに追加する
function resultAddAnswer() {
  const li = document.createElement('li');
  li.id = 'answer';
  const answer = show.textContent;
  const lists = document.createTextNode(answer);

  li.appendChild(lists);

  const result = document.getElementById('result-lists');
  result.appendChild(li);
};
```

3行目.`const li = document.createElement('li');`
liという変数に、li要素を生成するオブジェクトを入れる。

5行目.`const answer = show.textContent;`
answerという変数に、計算結果の内容を入れる。

6行目.`const lists = document.createTextNode(answer);`
listsという変数に、変数answerの内容でTextノードを生成するオブジェクトを入れる。

8行目.`li.appendChild(lists);`
変数listsの生成したTextノードを、li要素としてDOMツリーに挿入する。

10行目.`const result = document.getElementById('result-lists');`
resultという変数に、ID名（result-lists）がついた要素を取得。

11行目.`result.appendChild(li);`
変数resultで取得した要素の最後に、li要素を子ノードとして追加する。

###2.計算結果を出す過程の計算式もリストに追加する。

####該当するコード

```js:caluculate.js
var formula1 = 0; //１つ目の数値を保持するための関数
var formula2 = 0; //2つ目の数値を保持するための関数

//計算を行う関数
var calc = data = > {
  if (flag === 0 && data !== '=') {
    formula1 = currentValue; //演算子ボタン押すまでに入力した数値をformula1に保持
  } else if (flag === 1 && data === '=') {
    formula2 = currentValue; //=ボタンを押すまでに入力した数値をformula2に保持
  } else if (data === '=') {
    formula2 = currentValue; //上記と同様。
  }
}

```

```js:caluculate.js
//計算式をリストに追加する
function resultAddFormula() {
  const li = document.createElement('li');
  li.id = 'formula';
  
  const formula = formula1 + operator + formula2;
  const lists = document.createTextNode(formula);

  li.appendChild(lists);

  const result = document.getElementById('result-lists');
  result.appendChild(li);
```

6行目.`const formula = formula1 + operator + formula2;`
計算結果を求めるために入力した値を、変数formulaに入れる。

7行目.`const lists = document.createTextNode(formula);`
listsという変数に、変数formulaの内容でTextノードを生成するオブジェクトを入れる。



###3.「1.」と「2.」の関数をRECORDボタンを押した時に呼び出す

####該当するコード

```js:caluculate.js
var record = document.getElementById('record-btn');
record.addEventListener('click', () => {
  resultAddFormula();
  resultAddAnswer();
});
```

ID名:record-btnがついた要素をクリックした時に、計算式と計算結果をリストに追加する。



###4.不具合の修正
####1.計算式をリストに追加する際の不具合

計算式と計算結果をリストに追加することができたが、

計算結果をそのまま次の計算に使う場合に起きた。

例:「1+1=2」→「2+1=3」という計算の場合、

計算結果リストでは、

1+1
2
1+1　**←この部分がおかしい**
3

と表示された。

これは`const formula = formula1 + operator + formula2`のformula1が前回の計算から変わってないから、このように表示されてしまっているからだと推測した。

最初は、計算を行う関数や数字入力の関数にいろいろ試していたが、

最終的には`record.addEventListener`に、

```js:caluculate.js
var record = document.getElementById('record-btn');
record.addEventListener('click', () => {
  if (operator != false) { //演算子ボタンが押された時にだけ、計算式を結果を出すようにしている
    resultAddFormula();
    formula1 = total; //計算結果を使って続けて計算した時、前の計算結果をformula1として扱う
  }
  resultAddAnswer();
});
```
と加えてみたところ、この問題は自己解決することができた。


####2.「０１」と入力できてしまう

```js:caluculate.js
//数字入力の関数
var number = data => { //押されたボタンの値 data()をアロー関数にしている
  if (currentValue === '0') {
    return; //「0.」と入力しなければ数字を打てなくする
  } else if (currentValue.length <= 8) {
    flag = 0;
    currentValue += data;
    show.textContent = currentValue;
    console.log(show.textContent); //確認用
  }
};
```

これは、数字入力の関数に`if (currentValue === '0') { return;}`を追加するだけで解決。

###5.計算結果リストの編集ができる機能

####該当するコード

```js:caluculate.js
//計算結果リストのボタンの関数
var allClear = document.getElementById('clear-all');
allClear.addEventListener('click', allClearBtn);

function allClearBtn() {
  const result = document.getElementById('result-lists');
  while(result.lastChild) {
    result.removeChild(result.lastChild);
  }
}
```

`removeChild()`は、子要素を**一つだけしか削除ができない**ので、
**対象の子要素がなくなるまでループ処理**するように記述する必要があった。

###6.スタイルの変更
####該当するコード

```js:caluculate.js
//計算式と計算結果を区別するためのコード
function resultAddAnswer() {
  li.id = 'answer';
};

function resultAddFormula() {
  li.id = 'formula';
};
```
実際にあるような計算機の見た目と、見やすい計算結果リストの見た目になるようスタイルを変更した。

####スクリーンショット
初期画面
<img width="692" alt="スクリーンショット 2020-08-01 17.59.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/662094/9aad7f6b-27a5-e444-da66-19c82ffdc922.png">

**リストに追加機能を使うと...**

<img width="710" alt="スクリーンショット 2020-08-01 18.12.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/662094/0ca04140-f58e-2302-c70e-6c23d2dff005.png">


##7.参考サイト
###CSSに関する

[qiita:よく使うCSSで要素を横並びにする方法と使い分け](https://qiita.com/yukinaka/items/c4fcb7f0878702485f70)

[tableに謎のpadding](https://teratail.com/questions/165410)

[【CSS】display:table-cellにmarginを指定して間隔を作りたい時は](https://www.j-cool.co.jp/blog/?p=2564)

[HTMLでスクロールバーを表示・非表示する方法を現役エンジニアが解説【初心者向け】](https://techacademy.jp/magazine/25158)

###JSに関する

[document.createElement - 指定タグでのエレメント作成](http://javascriptist.net/ref/document.createElement.html)

[JavaScriptやjQueryでの変数が「空かどうか」のチェック方法](https://webukatu.com/wordpress/blog/821)

####リスト編集機能に関する
[qiita:【DOM基礎】要素内容の取得・設定／ノードの作成・挿入・削除](https://qiita.com/KDE_SPACE/items/3c4b52d077b8eabbe670)

[qiita:[JavaScript]複数の子要素を削除する。](https://qiita.com/kwk1211ken/items/5995a38e6577dee12767)

[【JavaScript】特定の子要素（子ノード）を削除する【removeChild】](https://into-the-program.com/removechild/)

[HTML5 入れ子チートシート](https://yoshikawaweb.com/element/)

##8.感想
この計算機作り自体、最初は**インプットした内容をちょっと試しにアウトプットしてみるか程度の思いで取り組んでいた**のだが、

思いの外、熱中してしまい、作っているうちにいろいろなアイデアが思い浮び、そのアイデアを実際に実現できたときにはとても楽しかった。

ほとんどはインターネット上の情報通りに実装していたが、調べた内容についてまた調べるとやっていたので、かなり時間を掛けていたと自覚しているが、**調べることで問題解決に繋がっていた**ので調べる癖は大事。

一通り**インプットが済んだ後に行うアウトプットはとても有意義**だと実感したし、これからも積極的に取り組んで行こうと決めました。

後、アウトプットした内容をこうやってqiitaのような投稿サイト(人目に触れるようなサイト)で、記事にするのも**「しっかりと書こう」**という気になって中途半端になりづらいし、こちらも続けて行こうと思いました。