one's way blog

ワクワクを生み出せるWebエンジニアを目指して。

【35:localStorage】HTML5のlocalStorageでTODOリストを作る

f:id:seintoseiya:20150818224020p:plain
プロジェクトNo.35:localStorage - DEMO

HTML5Web Storageの一つであるlocalStorageを使ってTODOリストを作ってみましたが、
今回は正直、結構はまりました。
そのバグトラッキングも含めてご紹介したいと思います。
新しく覚えた部分が多かったので少し長いです。

Web Storageとは

HTML5からWeb Storageという機能が追加されました。
Web Storageとはクライアントのディスク上に少量のデータを保存しておくためのストレージです。
Cookieと何が違うかというと、

  • サイズ制限がない
  • サーバーに送信されない
  • 有効期限がない
  • JavaScriptオブジェクトをそのまま保存できる

といった点があります。

特徴としては単純な「キー・バリュー」ストレージになっており、name:tanaka(key:value)という様に保存できます。

Web Storageには2種類あり、「ローカルストレージ」と「セッションストレージ」です。
これらの違いはデータの生存期間や有効範囲だけです。

今回、使うのは「ローカルストレージ」で、Webサイト毎にストレージを確保します。
同じページを別のウインドウで開いたり、ブラウザを終了して再表示したりしてもストレージの中身は永続されます。
ユーザーが明示的にストレージをクリアしない限り、データは永続します。

基本的な使い方

ストレージへの保存、取得、削除

HTML5の機能ですが、記述はJavaScript上に書いていきます。
HTMLのDOCTYPE宣言でHTML5を指定したら、
JavaScript上でlocalStorageというグローバル変数を使える様になります。
この変数に対して用意されているメソッドを使ってデータの入出力を行います。
いくつか方法はありますが、簡単な方法だと以下の様になります。

JavaScript
localStorage.setItem('memo','test'); // データセット
localStorage.getItem('memo'); // データ取得
localStorage.removeItem('memo'); // データ削除
localStorage.clear(); // データ全削除

TODOリストを作る | 問題:ソートが上手くいかない

ローカルストレージを利用して、TODOリストを作ってみようとしました。
・・・が、あるバグに悩まされました。
当初のバグありのソースは以下の通り。

JavaScriptJQuery
$(function () {
	var key = 0;

	// 初期読み込み
	// アルファベット順で格納されている(1,10,2,3,4,5 ...)ため意図したソートにならない・・・
	for(key in localStorage){
		// <li>を生成
		cleateLi(localStorage.getItem(key));
	}

	// 初期読み込み後にkeyを+1
	key++;

	// 保存ボタン
	$('#save').click(function(){
		// 入力フィールドの値を取得
		var memo = $('#memo').val();

		// 入力フィールドを空の場合は処理しない
		if(!memo) return;

		// ローカルストレージに保存
		localStorage.setItem(key, memo);

		// <li>を生成
		cleateLi(memo);

		// 入力フィールドを空にする
		$('#memo').val('');

		// keyのカウントアップ
		key++;
	});

	// <li>生成メソッド
	function cleateLi(value){
		var li = $('<li>').html(value).addClass('todo').attr('data-todo',key);

		// 要素を末尾に追加
		$('ul').append(li);

		// 動的に生成された要素<li>にonclickイベントを設定
		$('ul').on('click', '.todo', function(){
			localStorage.removeItem($(this).data('todo'));
			$(this).remove();
			console.log($(this).data('todo'));
		});
	}

	// 全消去ボタン
	$('#clear').click(function(){
		localStorage.clear();
		$('li').remove();
	});
})

問題となったのはlocalStorageからデータを取得する際に、
中身が格納した順ではなくてkeyのアルファベット順で保存されているために意図したソートにならないという点です。

上記のプログラムでは保存ボタンを押下する度に、ローカルストレージのkeyを1から順に増やしてデータを保存しています。
key:(1,2,3,4,5,6,7,8,9,10)
保存時は良いのですが、ローカルストレージに一旦入ったデータを取得する時に、アルファベット順で格納されているので、
key:(1,10,2,3,4,5,6,7,8,9)
という風になってしまいました。

TODOリストを作る | 解決方法:配列に入れ替える

色々悩んで、辿り着いた解決策が以下になります。

$(function () {
	// localStorage.setItem('memo','test'); // データセット
	// localStorage.getItem('memo'); // データ取得
	// localStorage.removeItem('memo'); // データ削除
	// localStorage.clear(); // データ全削除

	var list = []; // localStorageの内容をソートして格納するための配列
	var i = 0; // listのアドレス番号及びlocalStorage保存時のkey

	// 初期読み込み
	// アルファベット順で格納されている(key:1,10,2,3,4,5 ...)ので、配列に格納してソートする
	for(var key in localStorage){
		list[key] = localStorage.getItem(key);
		// console.log(list);
	}
	for(i in list){
		// <li>を生成
		console.log(i);
		cleateLi(list[i]);
	}
	// 初期読み込み後にiを+1
	i++;

	// 保存ボタン
	$('#save').click(function(){
		// 入力フィールドの値を取得
		var memo = $('#memo').val();

		// 入力フィールドを空の場合は処理しない
		if(!memo) return;

		// ローカルストレージに保存
		localStorage.setItem(i, memo);

		// <li>を生成
		cleateLi(memo);

		// 入力フィールドを空にする
		$('#memo').val('');

		// iのカウントアップ
		i++;
	});

	// <li>生成メソッド
	function cleateLi(value){
		// 消去ボタン押下時に要素を特定するためにdata属性を利用する
		var li = $('<li>').html(value).addClass('todo').attr('data-todo',i);

		// 要素を末尾に追加
		$('ul').append(li);

		// 動的に生成された要素<li>にonclickイベントを設定
		$('ul').on('click', '.todo', function(){
			localStorage.removeItem($(this).data('todo'));
			$(this).remove();
		});
	}

	// 全消去ボタン
	$('#clear').click(function(){
		localStorage.clear();
		$('li').remove();
	});
})

ローカルストレージから取得したデータを配列に格納します。
格納する際に配列の番地も取得したデータを指定することで上手くいきました。
key:(1,10,2,3,4,5,6,7,8,9)
(list[1]=1, list[10]=10, list[2]=2...)

カスタムデータ属性

また今回の実装にあたり、いくつか新たな機能も使いました。
その1つがHTML5の機能のカスタムデータ属性です。
これはTODOリストを1件消去する際にクリックした要素を特定するために必要でした。
出力されるHTMLは以下の通りです。

HTML
<li class="todo" data-todo="1">1</li>

カスタムデータ属性を定義するには「data-」という接頭辞に持つ属性を要素に追加するだけです。
呼び出す時はJavaScriptの方から以下のように指定します。

JavaScriptJQuery
$('ul').data.('todo');

動的に生成した要素に対してイベントを追加

今回のプログラムではTODOのリスト数は追加・削除できるため可変になっています。
静的な要素にクリックイベントを追加するには、最悪1つずつクリックした時の処理を記述すればよいのですが、
動的な要素の場合はそうはいきません。

そこで使えるのがonメソッドです。
書き方は以下の通り。

JavaScriptJQuery
$('ul').on('click', '.todo', function(){
    localStorage.removeItem($(this).data('todo'));
    $(this).remove();
});

イベントを追加したい要素の親要素に対してonメソッドを使用し、
イベント、対象の要素を指すセレクタ、コールバック関数という順に記述すればOKです。

全ソースはこちら

github.com