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: 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(タスク管理)アプリを作ってみます。

最終更新