読者です 読者をやめる 読者になる 読者になる

@thorikiriのてょりっき

@thorikiriがWebとかAndroidとかの技術ネタや本を読んだブログです

jQueryMobileのpagebeforecreateでハマった

jQuery mobile

pagebeforecreateのイベントで、getJSONしてから、listview("refresh")してたんですが、getJSONの結果をlocalStorageに突っ込んで、次回以降はlocalStorageから取れば良いよね。
と思ってた時期が僕にもありました。いや、今もそう思ってるんですが、ハマったので晒しておく。

元のコードはこんな感じでした。

$("#hoge").live("pagebeforecreate", function(event) {
    $.getJSON("sample.json")
    .success(handleHoge)
    .error(handleAjaxError);
});
var handleHoge = function(data) {
    $.each(data.items, function(i, item) {
        $("<li></li>").text(i + ":" + item).appendTo("#listview");
    });
    $("#listview").listview("refresh");
};

getJSONが非同期なので、handleHogeも遅延して行われるために、jQueryMobileがページを組み立てた後にlistviewにDOMが弄られます。だから、listview("refresh")をしてあげないとちゃんとスタイルを適用してくれませんね。
で、getJSONの結果をlocalStorageにキャッシュしようとしたコードが次のようになります。

$("#hoge").live("pagebeforecreate", function(event) {
    var items = localStorage.getItem("items");
    if (items) {
        handleHoge(JSON.parse(items));
        return;
    }
    $.getJSON("sample.json")
    .success(function(data) {
        handleHoge(data.items);
        localStorage.setItem("items", JSON.stringify(data.items));
    }).error(handleAjaxError);
});
var handleHoge = function(items) {
    $.each(items, function(i, item) {
        $("<li></li>").text(i + ":" + item).appendTo("#listview");
    });
    $("#fuga").listview("refresh");
};

これ、初回のgetJSONした場合にはちゃんと動くのですが、2回目にlocalStorageにした場合は非同期じゃないので、listview("refresh")が出来ないって怒られます。
「Uncaught cannot call methods on listview prior to initialization; attempted to call method 'refresh'」ってエラーメッセージですね。
なもんで、listview("refuresh")するのは、非同期通信した時だけにしないとダメだよって話し。どうするかと言うと次の通り。

$("#hoge").live("pagebeforecreate", function(event) {
    var items = localStorage.getItem("items");
    if (items) {
        handleHoge(JSON.parse(items));
        return;
    }
    $.getJSON("sample.json")
    .success(function(data) {
        handleHoge(data.items);
        $("#fuga").listview("refresh"); // ここのあたりでリフレッシュする
        localStorage.setItem("items", JSON.stringify(data.items));
    }).error(handleAjaxError);
});
var handleHoge = function(items) {
    $.each(items, function(i, item) {
        $("<li></li>").text(i + ":" + item).appendTo("#listview");
    });
    //$("#fuga").listview("refresh"); ← ここではリフレッシュしない。
};

こうすることで、非同期で遅延した時だけlistview("refresh")をして、非同期通信しないで遅延しなかった時にはlistview("refresh")をしないようにしたんだよね、ってお話でした。
これが正しい方法なのか、もっと賢いやりかたがあるのかわからないです。
あったら教えてくだしあ。

最終的なコード

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>サンプル</title>
  <script type="text/javascript" src="http://code.jquery.com/jquery-1.6.4.min.js"></script>
  <script type="text/javascript">
  (function() {
      $("#second").live("pagebeforecreate", function(event) {
          var items = localStorage.getItem("items");
          if (items) {
              handleHoge(JSON.parse(items));
              return;
          }
          $.getJSON("sample.json")
          .success(function(data) {
              handleHoge(data.items);
              $("#listview").listview("refresh");
              localStorage.setItem("items", JSON.stringify(data.items));
          }).error(handleAjaxError);
      });
      
      var handleHoge = function(items) {
          $.each(items, function(i, item) {
              $("<li></li>").text(i + ":" + item).appendTo("#listview");
          });
      };
      var handleAjaxError = function(e, s) {
          alert("error!!" + e.status + ":" + s);
      };
  })();
  </script>
  <script type="text/javascript" src="http://code.jquery.com/mobile/1.0.1/jquery.mobile-1.0.1.min.js"></script>
  <link rel="stylesheet" href="http://code.jquery.com/mobile/1.0.1/jquery.mobile-1.0.1.min.css" />
</head>
<body>
  <!-- main -->
  <section data-role="page" id="main">
    <header data-role="header">
      <h1>サンプル</h1>
    </header>
    <div data-role="content">
      <a href="#second">次へ</a>
    </div>
    <footer data-role="footer">
      <h4>サンプル</h4>
    </footer>
  </section>

  <section data-role="page" id="second">
    <header data-role="header">
      <a data-rel="back" data-role="button" data-icon="arrow-l">戻る</a>
      <h1>サンプル2</h1>
    </header>
    <div data-role="content">
      <ul data-role="listview" id="listview">
      </ul>
    </div>
    <footer data-role="footer">
      <h4>サンプル2</h4>
    </footer>
  </section>
</body>
</html>