MonacaとNCMBで通貨換算アプリを作る（その2: 通貨換算処理と履歴の保存）

2023年現在、円安が止まりません。そのため、海外旅行していて気になるのは、表記されている金額が円にすると幾らになるかではないでしょうか。昔は気にならなかった金額換算も、今ではびっくりするくらいの金額になっているかも知れません。

スマホアプリとして通貨換算アプリは幾つかありますが、今回はその簡易版をMonacaアプリで開発してみます。換算履歴はNCMBのデータストアに保存し、後で確認できるようにします。

[前回の記事では画面の説明とSDKの導入までを進めました](https://qiita.com/goofmint/items/9839b08a4b324e2acadc)ので、今回は通貨換算処理と、結果の保存を行います。

## コードについて

今回のコードは[currency-converter-monaca](https://github.com/NCMBMania/currency-converter-monaca) にアップロードしてあります。実装時の参考にしてください。

## www/pages/convert.htmlの処理

では画面を作成します。割と長いので、コメントを参考にしてください。長いのは、数値を入力するキーパッド部分があるからです。通貨の選択はFramework7のスマートセレクトを使い、検索で絞り込めるようにしています。

```html
<template>
  <!-- 通貨変換ページのルート要素 -->
  <div class="page" data-name="convert">
    <!-- ページのコンテンツを保持する要素 -->
    <div class="page-content">
      <!-- 通貨選択と入力リストのコンテナ -->
      <div class="list">
        <ul>
          <!-- 変換元通貨の選択 -->
          <li>
            <a
              class="item-link smart-select smart-select-init origin-currency"
              data-open-in="popup"
              data-searchbar="true"
              data-searchbar-placeholder="通貨を選択"
            >
              <!-- 変換元通貨のドロップダウン選択 -->
              <select name="originCurrency" @change=${change}>
                ${ currencies.value.map (c => $h`
                  <option value="${c.code}">${toEmoji(c.code)} ${c.name}</option>
                `) }
              </select>
              <div class="item-content">
                <div class="item-inner">
                  <div class="item-title">変換元</div>
                </div>
              </div>
            </a>
          </li>
          <!-- 変換先通貨の選択 -->
          <li>
            <a
              class="item-link smart-select smart-select-init target-currency"
              data-open-in="popup"
              data-searchbar="true"
              data-searchbar-placeholder="通貨を選択"
            >
              <!-- 変換先通貨のドロップダウン選択 -->
              <select name="targetCurrency" @change=${change}>
                ${ currencies.value.map (c => $h`
                  <option value="${c.code}">
                    ${toEmoji(c.code)} ${c.name}
                  </option>
                `)}
              </select>
              <div class="item-content">
                <div class="item-inner">
                  <div class="item-title">変換先</div>
                </div>
              </div>
            </a>
          </li>
        </ul>
        <!-- 通貨入力フィールド -->
        <div class="grid grid-cols-2">
          <!-- 変換元通貨の入力フィールド -->
          <div>
            <ul>
              <li class="item-content item-input">
                <div class="item-inner">
                  <div class="item-title item-label">${toEmoji(originCurrency.value)} ${originCurrency.value}</div>
                  <div class="item-input-wrap">
                    <input type="number" class="currency" name="originCurrency" placeholder="${showNumber()}" readonly />
                  </div>
                </div>
              </li>
            </ul>
          </div>
          <!-- 変換先通貨の入力フィールド -->
          <div>
            <ul>
              <li class="item-content item-input">
                <div class="item-inner">
                  <div class="item-title item-label">${toEmoji(targetCurrency.value)} ${targetCurrency.value}</div>
                  <div class="item-input-wrap">
                    <input type="number" class="currency" name="targetCurrency" placeholder="${converted}" readonly />
                  </div>
                </div>
              </li>
            </ul>
          </div>
        </div>
      </div>
      <!-- 数字キーパッド -->
      <div class="text-align-center display-block board">
        <div class="calc">
          <!-- キーパッドの各行 -->
          <div class="grid grid-cols-3">
            <!-- キーパッドの1行目 -->
            <div class="key text-align-center" @click=${calc}>7</div>
            <div class="key text-align-center" @click=${calc}>8</div>
            <div class="key text-align-center" @click=${calc}>9</div>
          </div>
          <!-- キーパッドの2行目 -->
          <div class="grid grid-cols-3">
            <div class="key text-align-center" @click=${calc}>4</div>
            <div class="key text-align-center" @click=${calc}>5</div>
            <div class="key text-align-center" @click=${calc}>6</div>
          </div>
          <!-- キーパッドの3行目 -->
          <div class="grid grid-cols-3">
            <div class="key text-align-center" @click=${calc}>1</div>
            <div class="key text-align-center" @click=${calc}>2</div>
            <div class="key text-align-center" @click=${calc}>3</div>
          </div>
          <!-- キーパッドの4行目 -->
          <div class="grid grid-cols-3">
            <div class="key text-align-center" @click=${calc}>0</div>
            <div class="key text-align-center" @click=${calc}>.</div>
            <div class="key text-align-center" @click=${calc}>C</div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<style>
  .board {
    width: 100%;
  }
  .calc {
    width: 70vw;
    margin-left: auto;
    margin-right: auto;
  }
  .currency {
    font-size: xx-large !important;
  }
  .key {
    font-size: xx-large;
    border: 1px solid #000;
    padding: 1vw;
    margin-top: -1px;
    height: 21vw;
    width: 21vw;
    vertical-align: middle;
    display: inline-block;
    line-height: 2;
  }
</style>
```

`toEmoji` 関数は `USD` や `JPY` といった通貨を国の旗（絵文字）に変換する関数です。[Country Code to Flag Emoji \- DEV Community](https://dev.to/jorik/country-code-to-flag-emoji-a21)を参考に、足りない部分を追加しています。この関数は `js/app.js` に定義しています。

```js
const toEmoji = (name) => {
  switch (name) {
    case 'ANG':
      return '🇨🇼';
    case 'XAF':
      return '🇨🇫';
    case 'XCD':
      return '🇨🇩';
    case 'XDR':
      return '🇩🇪';
    case 'XOF':
      return '🇫🇷';
    case 'XPF':
      return '🇫🇷';
  }
  const code = name.substr(0, 2).toUpperCase();
  const codePoints = code.split('').map(char =>  127397 + char.charCodeAt());
  return String.fromCodePoint(...codePoints);
};
```

ここからは `pages/convert.html` のJavaScriptコードです。全体は以下のコードで囲みます（Framework7のお作法です）。この画面で利用する定数である[ExchangeRate-API](https://exchangerate-api.com/)のキーおよびURLを定義しておきます。

```html
<script>
  /* 
  この部分はJavaScriptで、通貨の操作や表示に関する詳細な設定を行っています。
  */
  const EXCHANGERATE_API_KEY = 'EXCHANGERATE_API_KEY';
  const EXCHANGERATE_API_URL = `https://v6.exchangerate-api.com/v6/${EXCHANGERATE_API_KEY}/latest/`;
  export default (props, { $store, $onMounted, $tick, $update }) => {
		// ここに実装していきます
    return $render; // 最終的にレンダリングを行います。
  };
</script>
```

### ストアの取得

ストアから変換する通貨のリスト、現在の設定（変換元、変換先）を取得します。

```ts
const { currencies, originCurrency, targetCurrency } = $store.getters;
```

この中で、 `currencies` はドロップダウン生成に利用されます。

```html
<select name="originCurrency" @change=${change}>
	${ currencies.value.map (c => $h`
		<option value="${c.code}">${toEmoji(c.code)} ${c.name}</option>
	`) }
</select>
```

![1658 localhost - 1009010131.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/b51a5f82-5b90-a936-5fab-f7f5e5e36910.jpeg)

### 他の変数の定義

また、この画面で利用する変数を定義します。

```js
const nums = [];      // キーパッドで入力された内容
let converted = 0.00; // 通貨換算後の数値が入る
```


### 画面が表示された際の処理

画面がマウントされた際には `$onMounted` が呼ばれます。ここではドロップダウン生成に利用する通貨のリストを取得します。ストアを使っている場合、特に何も行わずとも `currencies` が更新され、画面描画に利用されます。しかし、描画は非同期処理で行われるので、デフォルト値の設定が先に実行されてしまいます。

そこで `$tick` を使って、画面描画を待ちます。その上で、スマートセレクトの `setValue` メソッドで値をセットします。

```js
// マウントされた際の処理
$onMounted(async () => {
	// `js/currencies.json` を取得
	await $store.dispatch('getCurrencies');
	// 通貨のドロップダウンが生成されるのを待機
	await $tick();
	// 入力のデフォルトを設定
	app.smartSelect.get('.origin-currency').setValue(originCurrency.value);
	app.smartSelect.get('.target-currency').setValue(targetCurrency.value);
});
```

### 通貨が選択された際の処理

通貨を選択すると `change` メソッドが呼ばれます。ここでは即座にスマートセレクトを閉じ、ストアに値を反映します。

```js
// 通貨が選択された際の処理
const change = (e) => {
	app.smartSelect.close(e.target.parentElement);
	if (e.target.name === 'originCurrency') {
		$store.dispatch('setOriginCurrency', e.target.value);
	} else {
		$store.dispatch('setTargetCurrency', e.target.value);
	}
};
```

### キーパッド入力処理

キーパッドは数字、ドット、Cの3パターンがあります。それぞれタップされた際に `calc` 関数が呼ばれます。入力された内容に応じて、入力値のクリア・連結などを行います。ドットは1つしか入力できない、頭にゼロを補完するなどの処理も行っています。

入力値は連結して数値にしています。そして、2秒間同じ値（入力がない）だった場合には通貨変換処理 `convert` を実行しています。

```ts
// キーパッド入力処理
const calc = (e) => {
	const value = e.target.innerText;
	switch (value) {
		case 'C': // 入力値のクリア
			nums.splice(0, nums.length);
			converted = 0.00;
			break;
		case '.': // ドットの入力
			// 入力値がなかった場合には頭に0を追加
			if (nums.length === 0) {
				nums.push('0');
			}
			// 入力値にドットがなかった場合には追加
			if (nums.indexOf('.') === -1) {
				nums.push(value);
			}
			break;
		default:
			// 入力値が0の場合には入力値を追加
			nums.push(value);
			break;
	}
	// 入力値を連結して数値に変換
	const num = parseFloat(nums.join(''));
	// 画面を更新
	$update();
	// 2秒間入力がなかった場合には通貨変換を実行
	setTimeout(() => {
		const value = parseFloat(nums.join(''));
		if (num === value) {
			convert();
		}
	}, 2000);
};
```

### 通貨変換処理

通貨を変関する処理 `convert` は以下の通りです。基本的にはExchangeRate-APIを実行して、その結果はレートなので、それを使って通貨換算しているだけです。

最後にNCMBにデータを保存する `save` 関数を実行しています。

```js
// 通貨変換処理
const convert = async () => {
	// ExchangeRate-APIのURL
	const url = `${EXCHANGERATE_API_URL}${originCurrency.value}`;
	// APIを実行して結果を取得
	const res = await fetch(url);
	const json = await res.json();
	// レーティングを取得して、返還後の通貨の金額を取得
	const rate = json.conversion_rates[targetCurrency.value];
	const value = parseFloat(nums.join(''));
	converted = Math.floor(value * rate * 100) / 100; // Math.floorは小数点以下を取得するための処理
	// 表示更新
	$update();
	// NCMBにデータ保存
	save();
};
```

### NCMBにデータを保存

`save` 関数の内容は以下の通りです。入力値、変換元・先のデータをHistoryクラスに保存します。

```ts
// NCMBにデータを保存
const save = async () => {
	// クラス（RDBMSでいうテーブル相当）を作成
	const History = ncmb.DataStore('History');
	// クラスのインスタンス（RDBMSでいう行相当）を作成
	const history = new History();
	// データをセット
	history
		.set('originCurrency', originCurrency.value)
		.set('targetCurrency', targetCurrency.value)
		.set('originNumber', parseFloat(nums.join('')))
		.set('targetNumber', converted);
	// 保存実行
	await history.save();
};
```

これでデータの保存まで完了します。

## まとめ

今回は通貨換算画面の表示と、換算結果の保存処理を実装しました。次回は[履歴の表示処理]()を実装します。