jQuery Mobileで作成したTODOリストアプリケーションのサンプル

jQuery Mobile 1.0 Beta 2でTODOリストアプリのサンプルを作成してみました。
動作確認はFirefox、Chrome、iPhone 4にて行いました。細かい手直しはすると思いますが、コードが汚いのはご勘弁を。

  
この記事は2年以上前に書かれたものです。
情報が古い可能性があります。

Firefoxではローカルファイルとして参照するとF5でlocalStorageの値が消えてしまうのでサーバにアップロードして確認すると良いかと思います。

なお、jQuery Mobile 1.0 Beta 2以外のバージョンで使用すると正常に動作しない場合があります。以下、私が挙動の違いで気付いた点をまとめておきます。

バージョンによる挙動の違い

1.0b2(今回使用したバージョン)

$(document).ready(function() { 内で listview('refresh'); がコールされるとエラーになる。

1.0a4(古いバージョン)

<a>タグでhref指定してる要素のクリックイベントが動いたり動かなかったり不安定。(内部リンクでページ切り替え時)

1.0a41(古いバージョン)

上記2つの問題はありませんでした。

本アプリ、コードの説明

HTML5 API Web StorageのLocal StorageをJSON形式で読み書きしています。

ChromeやiOSではnullのオブジェクトをJSON.parse()するとエラーになったのでgetJSONメソッドを作成して読み出したlocalStorageの値がnullじゃない場合にJSON.parse()するようにし、nullの場合は配列として初期化して返すようにしています。

リストの要素を書き出す時に配列のインデックスをidに設定しているので、削除するときは選択した要素のidをキーに配列に対してspliceメソッドを呼んでいます。

以前のバージョンでは大丈夫だったのですが、Beta 2では $(document).ready(function() { のタイミング でlistview('refresh'); が呼ばれるとエラーになったのでloadメソッドとreloadメソッドを作成し、追加や削除後にだけ listview('refresh'); するようにしました。

Ajaxや rel="external" での別ページの読み込みは使用せず内部リンクを使用してページの切り替えを実装しています。初期のページ読み込み時以外はreloadメソッドが使用されるようにしています。

<!DOCTYPE html> 
<html lang="ja"> 
<head>
    <meta charset="utf-8">
    <title>TODOリスト</title>
    <meta name="viewport" content="width=device-width, initial-scale=1"> 
    <link rel="stylesheet" href="http://code.jquery.com/mobile/1.0b2/jquery.mobile-1.0b2.min.css" />
    <script type="text/javascript" src="http://code.jquery.com/jquery-1.6.2.min.js"></script>
    <script type="text/javascript" src="http://code.jquery.com/mobile/1.0b2/jquery.mobile-1.0b2.min.js"></script>
    <script>
    $(document).ready(function() {
    
        var Model = function() {
            /**
             * データの読み込み
             */
            this.load = function() {
                var text = this.getJSON();
                
                $('#todoList li').remove();   //リスト初期化
                
                //リストの書き出し
                for(var i=text.length-1; i>=0; i--){
                    $('#todoList').append('<li data-icon="delete" id="' + i + '"><a>' + text[i] + '</a></li>');
                }
            }
            
            /**
             *  ローカルストレージから保存したJSON形式のオブジェクトを取得
             */
            this.getJSON = function() {
                var text;
                if(localStorage['text'] != null) {
                    text = JSON.parse(localStorage['text']);
                }
                
                if(text == null) text = new Array();
                
                return text;
            }
            
            /**
             * データの追加
             */
            this.add = function() {
                var todoDescription = $('#todoDescription').val();
                var text = this.getJSON ();
                
                //localStorageへのデータの追加
                if(todoDescription.length > 0){
                    text.push($('#todoDescription').val());
                    localStorage['text'] = JSON.stringify(text);
                    $('#todoDescription').val("");
                }

                this.reload();
            }
            
            /**
             * データの再読込
             */
            this.reload = function() {
                this.load();
                $('#todoList').listview('refresh');
            }
            
            /**
             * データの削除
             */
            this.deleteAll = function() {
                localStorage.clear();
                this.reload();
            }
            
            this.load();    //初回読み込み
            
        }//-- End Model
        
        //ロジックインスタンス生成
        var model = new Model();

        /** 追加 */
        $("#addButton").click(function(){
            model.add();
        });
        
        /** すべて削除 */
        $("#delButton").click(function() {
            var text = model.getJSON();
            if(text instanceof Array) {
                if (text.length == 0) {
                    return;
                }
            }else {
                return;
            }
            if(confirm('全データを削除します')) {
                model.deleteAll();
            }
        });
        
        /** 1件削除(動的追加した要素にイベントを付与するためdelegateを使用)  */
        $("#todoList").delegate("li", "click", function() {
            var id = $(this).attr("id");
            var text = JSON.parse(localStorage['text']);
            text.splice(id,1);
            localStorage['text'] = JSON.stringify(text);
            $(this).remove();
        });
    });
    </script>
</head>
<body>
    <!-- main page -->
    <div data-role="page" id="main"> 
        <div data-role="header" data-theme="b"> 
            <h1>TODOリスト</h1>
            <a href="#add" data-rel="dialog" data-transition="pop" data-icon="plus">追加</a>
        </div>
        <div data-role="content">
            <ul data-role="listview" id="todoList">
            </ul>
        </div>       
        <div data-role="footer" data-theme="b">
            <a data-role="button" id="delButton" data-icon="delete">すべて削除</a>
        </div>
    </div>
    <!--/ main page -->
             
    <!-- add page -->
    <div data-role="page" id="add"> 
        <div data-role="header" data-theme="c"> 
            <h2>TODOの追加</h2>
        </div>

        <div data-role="content">
            <div data-role="fieldcontain">
                <label for="todoDescription">TODOを入力:</label>
                <input type="text" name="todoDescription" id="todoDescription" value=""  />
            </div>
            <a href="#main" data-role="button" id="addButton" data-theme="b">追加</a>
            <a href="#main" data-role="button">キャンセル</a>
        </div>
    </div>
    <!--/ add page -->
</body>
</html>
  

共有やブックマークなど