Counter(カウンター) App

今回はCounter(カウンター) Appを作成します。これは数を数える単純なアプリです。

Counter(カウンター)アプリの実装

プラス(+)ボタンを押すと、現在の値を1ずつ増やし、マイナス(-)ボタンを押すと、現在の値から1ずつ減らします。
<div class="wrapper">
<button class="button" id="increment">+</button>
<span class="current-value" id="counter">0</span>
<button class="button" id="decrement">-</button>
</div>
HTMLはシンプルにbuttonとspanのみとなります。それぞれJSから参照できるように固有のidを持っています。
.wrapper {
display: inline-block;
border: 1px solid #eeeeee;
border-radius: 4px;
}
.button {
padding: 8px 12px;
background-color: #8197e8;
border-radius: 4px;
color: white;
font-size: 14px;
font-weight: bold;
}
.current-value {
margin: 0 12px;
font-size: 16px;
font-weight: bold;
font-family: sans-serif;
}
CSSで注目したいのは .wrapper に指定されている display: inline-block; です。 .wrapper を持つ要素は div タグなのでデフォルトではブロックレベル要素(つまり display: block が元々適用されている)です。これを inline なブロックとするために、 display: inline-block; を指定しています。
"display: inline-block;" の場合
"display: block;" の場合
display: block; のままだと、上記のように行いっぱいまでborderが伸びてしまっています。今回は 子要素のbuttonを囲むようなスタイルにしたかったので、 display: inline-block; としました。良く使う指定方法なので覚えておきましょう。
// 各要素への参照を取得する。
const buttonInc = document.querySelector("#increment");
const buttonDec = document.querySelector("#decrement");
const counter = document.querySelector("#counter");
// モデルを用意する。
// ※ ProxyはIE11では動かないので注意
// SEE: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
// SEE: https://caniuse.com/#search=proxy
let model = new Proxy(
{ value: 0 },
{
set(obj, prop, newval) {
obj[prop] = newval;
// valueに変更があったら、counter要素のテキストを変更する。
// (= モデルからビューへの反映)
if (prop === "value") {
counter.innerText = obj[prop];
}
}
}
);
// 各ボタン(+/-)のクリック時に、要素の値を更新する。
buttonInc.addEventListener("click", () => {
// 現在の値に1を足す = Increment
model.value += 1;
});
buttonDec.addEventListener("click", () => {
// 現在の値から1を引く = Decrement
model.value -= 1;
});
JSは今回は jQuery を使わずに、ブラウザの標準APIだけで実装しています。モデルの部分は前回のMVVMの実装例で使った Proxy というクラスを利用しています。(IE11では動かないので注意してください) ポイントは以下の通りです。
  • Proxyの第二引数に指定した set() 関数の中で、 counter.innerText = "任意の値" とすることで、 id="counter" な要素のテキストを置き換える。(= 値が増減する)
  • プラス・マイナスボタンのクリックイベントのハンドラの中では、モデルの値の変更を行う。そうすることで、前述の counter.innerText の更新が呼び出され、画面内のカウンターの値が増減する。
ここまでで基本的なカウンターの基本的な動きは出来ました。ですが現状は実行し直すと(タブ/画面をリロードすると)値が0に戻ってしまいます。このデータを保存(save)するためにはどうしたら良いでしょうか?
実際のWebアプリケーションでは対応するバックエンド(APIサーバ)を用意する事が多いですが、今回はかんたんに試すために、ブラウザの localStorage という機能を使って作りました。

保存/復元機能の付きのCounter(カウンター)アプリの実装

画面に save(保存)restore(復元) のボタンが増えています。値を変更した後に、 save を押すと保存され、ページをリロード(もしくはCodePenの右下の Rerun ボタンを押す)した後に、 restore ボタンを押すと、以前の値が復元されるのが分かるかと思います。関連するコードを見てみましょう。(HTML/CSSは今回は割愛します)
// 各要素への参照を取得する。
const buttonSave = document.querySelector("#button-save");
const buttonRestore = document.querySelector("#button-restore");
// ...中略...
const COUNTER_VALUE_KEY = 'COUNTER_VALUE_KEY';
// ...中略...
buttonSave.addEventListener("click", () => {
// 現在の値を保存する。
localStorage.setItem(COUNTER_VALUE_KEY, JSON.stringify(model.value));
});
buttonRestore.addEventListener("click", () => {
// 以前の値を復元する。
model.value = JSON.parse(localStorage.getItem(COUNTER_VALUE_KEY));
});
HTML上にボタンを増やした(save/restore)のに合わせて、JSでの要素の参照を取得するコードが増えています。まず最新の値を保存(save)するコードから見ていきます。
localStorage.setItem(COUNTER_VALUE_KEY, JSON.stringify(model.value));
ブラウザの localStorage APIの setItem() メソッドを呼び出しています。第一引数にキー名(今回は "COUNTER_VALUE_KEY" )、第二引数に保存する値 JSON.stringify(model.value) としています。今回保存するのは最新の値の 数値 なので、正しく数値として扱えるように JSON.stringify 関数を使って、JSON化しています。続いて、以前の値を復元(restore)するコードを見ていきます。
model.value = JSON.parse(localStorage.getItem(COUNTER_VALUE_KEY));
ここでは、まずブラウザの localStorage APIの getItem() メソッドを呼び出しています。第一引数に取得するキー名( 今回は "COUNTER_VALUE_KEY" )を指定することで、localStorageに保存された値を取得できます。取得した値を JSON.parse() 関数に渡すことで、JSON文字列から、JSの数値型としてparse(パース/解析)しています。その数値を model.value に渡すことで、通常の増減の場合と同様に画面に反映されます。
ここまでで保存機能を備えたCounter(カウンター)アプリは実装できました。今回は保存/復元を localStorage を使ってやりましたが、保存/復元ボタンを押した時の処理をバックエンド(APIサーバ)へのリクエスト(データの更新/取得)に置き換えることで、より本格的なWebアプリケーションを作ることができます。もし興味があれば作ってみてください。
続いては、より アプリ っぽいToDo(タスク管理)アプリを作ってみます。