GAEpでiPhone向けWebアプリを作る (11)ビューデータバインダを書く

前回の続きです。

ビュー<->データバインダを書く

サーバから受け取ったデータを、html ビューにバインドする処理を、javascript を使って書いていきます。

この処理には、cssjquery の組み合わせは非常に有用です。java では jsp があったり、django にもテンプレート言語があったりしますが、jquery を使うのであれば、テンプレートエンジンはもう必要ないのではないかという気もしてしまいます。

メリットとしては以下です。

  • html ファイルがそのままテンプレートファイル
  • 特殊な文法のテンプレートを覚える必要がない
  • html ファイルを開くだけで見た目がチェック可能
  • 保守が楽。変更したいときにすぐに変更できる
  • 通信はデータのみで済む。処理速度的メリット
  • ビューとデータの結びつきが疎になる

もちろんデメリットもあると思います。

  • css ベースの選択処理(id や class)がどうしてもグローバル変数的になる
  • 同様に、ビュー・データ・バインダの結びつきもうまくまとめないとわかりにくい
  • テンプレート継承が使えない
  • javascript のコードが多くなる

今回はあまりできていませんが、以下あたりが今後の課題になってきます。

これらはどれも関連するように感じます。特に、html / css のモジュール化は非常に重要になってくるだろうなと感じます。

今回のコードの全体はこちらです。

グローバル変数

今回は、ふたつのグローバル変数を用意しています。

  • ユーザ情報を格納する変数
  • ToDo 項目のリストを格納する変数

どちらもビューの作成をやりやすくするために導入してます。特にグローバル変数に持っていなくても問題ないと思いますが、通信量は減りそうだと思って入れています。

js/default.js内

var context = {
  'userinfo': {},
  'items': []
};
ユーザ情報のバインド

#list 画面にはユーザ情報を表示する箇所があります。

views.html の div#list タグ内

      <div class="userinfo">
        Welcome, <span class="user-nickname">guest</span>.&nbsp;

        (<a class="user-logout" href="/todo/main">logout</a>)
      </div>

jquery 的には最も単純な処理です。
js/default.js内

function setUserInfo(nickname, logoutUrl) {
  $('#list .userinfo .user-nickname').text(nickname);
  $('#list .userinfo a.user-logout').attr('href', logoutUrl);
}

例えば以下では、div#list タグの下の div.userinfo タグの下の span.user-nickname タグにニックネーム文字列を挿入しています。

  $('#list .userinfo .user-nickname').text(nickname);
ToDo 項目データを一覧でビューにバインド

次に、ToDo 項目のデータをリスト形式で表示します。
表示部の html は以下です。
views.html の div#list タグ内

      <ul class="todoitem-list" >
      </ul>

      <ul class="todoitem-template" >
        <li class="todoitem" >
          <span class="todoitem-delete" >Delete</span>

          <a class="todoitem-anchor" href="#save">
            <span class="todoitem-title">This is a todo item area.</span>
            <em class="todoitem-deadline">YYYY-MM-dd</em>
          </a>
          <div class="clear">
        </li>
      </ul>

この処理は refreshItems() 関数で行っています。

js/default.js内

function refreshItems() {
  // (1)
  var $todoparent = $('#list ul.todoitem-list');
  var $todoitemTpl = $('#list ul.todoitem-template li.todoitem:first');
  $todoparent.find('li.todoitem').remove(); // 既存データを削除

  // (2)
  for(var idx = 0; idx < context.items.length; idx += 1) {
    // (3)
    var $todoitem = $todoitemTpl.clone(); // テンプレートをコピー
    var data = context.items[idx];
    $todoitem.find('.todoitem-title').text(data.title);
    $todoitem.find('.todoitem-deadline').text(data.deadline);
    $todoitem.find('.todoitem-anchor').attr('id', data.key); // 項目キー
    $todoitem.removeClass('hidden');
    // (4)
    $todoitem.appendTo($todoparent); // 項目を追加
    $todoitem.find('.todoitem-anchor').click(function(data) {
      // (5) ToDo 項目を選択されたら
      return function(e) {
        var item = data;
        setItemToSaveForm(item); // #save 画面にデータをバインド
        transitTo('#save');      // 画面遷移実行
        return false;
      };
    }(data));
    $todoitem.find('.todoitem-delete').click(function(item) {
      // (6) 削除ボタンを押されたら
      return function(e) {
        var bDelete = confirm('Do you really delete it?');
        if(bDelete) {
          deleteItem(item.key, function(data, ts, xhr) {
            if(ts === 'success') {
              loadItems();
            }
          });
        }
        return false;
      };
    }(data));
  }
}

まずは、(1) で項目の挿入先($todoparent)と項目テンプレート($todoitemTpl)を取得し、挿入先を空にしてます。
それから、ループで(2)ひとつずつ ToDo 項目を追加していきます。
まずテンプレートのクローンを作成し(3)、グローバル変数内の項目をタグに割り当てます。この際、a タグに id を割り当てています。この id は、GAEp のモデルのキーです。これによってデータを識別します。
続いて(4)で、挿入先($todoparent)にいま作成したタグを追加します。
最後に、いま追加した項目に対してクリックハンドラを付けます。.todoitem-anchor タグには #save 画面への画面遷移、.todoitem-delete タグには ToDo 項目の削除処理を書いています。

ToDo 項目編集ビューにデータをバインド

最後、ToDo 項目編集ビュー (div#save 要素) にデータをバインドします。この処理は #list 画面からの画面遷移直前に呼ばれます(上記の(5))。
js/default.js内

function setItemToSaveForm(item) {
  if(item === undefined) {
    item = {'title':'', 'detail':'', 'deadline':'', 'key':''};
  }
  var $saveForm = $('#save form.todo-ctrl');
  $saveForm.find("input[name='todo-title']").attr('value', item.title);
  $saveForm.find("textarea[name='todo-detail']").text(item.detail);
  $saveForm.find("input[name='todo-deadline']").attr('value', item.deadline);
  $saveForm.find("input[name='todo-key']").attr('value', item.key);
}

内容に特に難しい内容はありません。単純に input 系タグにデータを入れているだけです。
todo-key という名前を持った hidden タイプの input 要素に GAEp のモデルキーを持たせてデータの識別をしています。
item === undefined の場合は、新規作成です。

次回は、サーバとのデータ通信関数です。

参考

jquery の入門としては、以前にも紹介した次の本が非常によいと思います。

iPhoneアプリケーション開発ガイド ―HTML+CSS+JavaScript による開発手法

iPhoneアプリケーション開発ガイド ―HTML+CSS+JavaScript による開発手法

javascript に関しては、有名ですが、以下が間違いなくよい本です。

JavaScript: The Good Parts ―「良いパーツ」によるベストプラクティス

JavaScript: The Good Parts ―「良いパーツ」によるベストプラクティス

僕は仕事柄 javascript を使うことはほとんどありませんが、それでも、この本がその手の世界では必須の本であろうことは、なんとなくわかります。javascript が楽しい言語だと言うことがよくわかります。何より読んでて楽しいです。