jQuery Mobile の NestedList で JSON データを動的にリスト表示

以前、jQTouch 等で作成したもの(iUI版 id:fits:20100715, jQTouch版 id:fits:20100731, SenchaTouch版 id:fits:20101005)と同様のサンプルを jQuery Mobile で作成してみました。(サーバー側の処理も同じものを使用)

ただし、今回のサンプル(静的HTML版も含む)のようにリストアイテムにアンダースコア "_" を含む名称が使われる場合(例 information_schema)、Firefox では正常に動作しますが、Safari では「Error Loading Page」が表示され次の画面に遷移できなかったので注意。(Android エミュレータでも同じ現象が発生)

原因は調査してませんが、jQuery Mobile が自動生成する ID に問題ありかも。(アンダースコアが使われると ID の最後の数値の前にスペースが入る模様)

jQuery Mobile は期待していただけに少し残念。

サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20101029/

画面構成

画面構成は jQTouch 版等と基本的に同じです。

(1) DBのリスト表示画面

(2) テーブルのリスト表示画面(mysql 選択時)

(3) テーブルの詳細表示画面(columns_priv 選択時)

静的リスト表示

まずは、動的なリストアイテムの追加を行わない静的な HTML です。
リスト表示化する ul 要素に data-role="listview" の属性定義を行う点と、ul をネストさせる事で自動的に NestedList 化される点に注目。

なお、ヘッダーで data-theme="b" としているのは、メイン画面のヘッダーの背景色を青にするためです。(デフォルトではメイン画面のヘッダー背景色が黒で次画面のヘッダー背景色が青になるため)

index_static.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <title>Nested List Sample(jQuery Mobile)</title>
  <link rel="stylesheet"  href="http://code.jquery.com/mobile/1.0a1/jquery.mobile-1.0a1.min.css" />
  <style type="text/css">
    table {
      font-size: 10px;
    }
  </style>
  <script src="http://code.jquery.com/jquery-1.4.3.min.js"></script>
  <script src="http://code.jquery.com/mobile/1.0a1/jquery.mobile-1.0a1.min.js"></script>
</head>
<body>
  <!-- ページ -->
  <div data-role="page">
    <!-- ヘッダー -->
    <div data-role="header" data-theme="b">
      <h1>Nested List Sample</h1>
    </div>
    <!-- 内容 -->
    <div data-role="content">
      <!-- DBのリスト表示 -->
      <ul data-role="listview">
        <li>information_schema
          <!-- テーブルのリスト表示 -->
          <ul>
            <li>CHARACTER_SETS
              <!-- テーブルの詳細表示 -->
              <ul data-inset="true">
                <li>
                  <table>
                    <tr>
                      <th>table_name</th>
                      <td>:</td>
                      <td>CHARACTER_SETS</td>
                    </tr>
                    <tr>
                      <th>table_type</th>
                      <td>:</td>
                      <td>SYSTEM VIEW</td>
                    </tr>
                  </table>
                </li>
              </ul>
            </li>
            <li>COLLATIONS
              <ul>
              </ul>
            </li>
          </ul>
        </li>
        <li>mysql
          <ul>
          </ul>
        </li>
      </ul>
    </div>
  </div>
</body>
</html>


この HTML はブラウザでの表示時に以下のような構成に変換されてしまうので注意。
変換の際にネストさせた ul は個々の div 要素内に展開されます。

jQuery Mobile による自動変換後の HTML body 要素内の構成例
  • メイン画面(DBのリスト表示)
    • ヘッダー
    • リスト
      • information_schema
      • mysql
  • information_schema 表示画面(テーブルのリスト表示)
    • ヘッダー
    • リスト
      • CHARACTER_SETS
      • COLLATIONS
  • CHARACTER_SETS 表示画面(テーブルの詳細表示)
    • ヘッダー
    • リスト
      • table_name と table_type の table 表示
  • ・・・

JSON データの動的リスト表示

次に本題の JSON データを取得して動的にリスト表示する HTML です。

JSON データを使って ul 要素の構成を作成し、コンテンツ表示の div 要素に append した後、追加した ul 要素を使って listview() を呼び出せば動的なリスト化を実現できます。
ただし、画面遷移時に動的リスト表示させる場合は以下の点に注意。

  • 次画面用に自動生成された div 要素内の ul 要素を置換(削除してから追加)してから listview() を実施する

listview() の実行対象が既に listview で自動生成(もしくは加工)された要素だった場合、listview() を実行してもリスト化が実施されないため、置換処理が必要になります。
ちなみに、置換せず ul 要素の class 属性や role 属性を削除しても listview は意図通りに動作しません。(listview させるには $(":mobile-listview") で取得できないようにしてやる必要があると思われる)

index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <title>Nested List Sample(jQuery Mobile)</title>
  <link rel="stylesheet"  href="http://code.jquery.com/mobile/1.0a1/jquery.mobile-1.0a1.min.css" />
  <style type="text/css">
    table {
      font-size: 10px;
    }
  </style>
  <script src="http://code.jquery.com/jquery-1.4.3.min.js"></script>
  <script src="http://code.jquery.com/mobile/1.0a1/jquery.mobile-1.0a1.min.js"></script>
  <script>
    $(function() {
      $.get("databases", null, function(res) {

        $("#db_list").append(createListHtml(res, "table_schema"));
        //追加した ul で listview 作成(DBのリスト表示)
        $("#db_list > ul").listview();

        //リストアイテムクリック時のイベントを設定
        //(テーブルのリスト表示画面への遷移)
        $("#db_list > ul > li").tap(function(event) {
          var alink = $(event.currentTarget).find("a");
          var targetId = alink.attr("href").replace("#", "");

          $.get("tables/" + alink.text(), null, function(res2) {
            //テーブルのリスト表示用 ul を取得
            //(画面遷移先の id を使って div 要素を取得して辿る)
            var el = $("[id='" + targetId + "']").find(".ui-content > ul");
            var parentEl = el.parent();

            //DBのリスト表示時に作成された
            //テーブルのリスト表示用の ul 要素を削除しておかないと
            //listview() が意図通りに機能しない
            el.remove();

            parentEl.append(createListHtml(res2, "table_name"));
            //新しく追加した ul で listview 作成
            parentEl.find("ul").listview();
          }, "json");
        });
        
      }, "json");
    });

    //リスト表示用ののHTMLを作成(DBのリスト表示・テーブルのリスト表示兼用)
    function createListHtml(json, titleKey) {
        var html = "<ul>";

        $.each(json, function() {
          html += "<li>" + this[titleKey];
          //table_name の値をリストアイテムに使う場合は詳細表示ページを用意
          if (titleKey == "table_name") {
            html += "<ul data-inset='true'" + createDetailHtml(this) + "</ul>";
          }
          else {
            html += "<ul />";
          }
          html += "</li>";
        });

        html += "</ul>";
        return html;
    }

    //詳細表示用のHTMLを作成(テーブルの詳細表示)
    function createDetailHtml(item) {
      var headers = ["table_name", "table_type", "engine", "create_time"];
      var html = "<li><table>";

      $.each(headers, function() {
        html += "<tr>";
        html += "<th>" + this + "</th><td>:</td>";
        html += "<td>" + item[this] + "</td>";
        html += "</tr>";
      });

      html += "</table></li>";
      return html;
    }
  </script>
</head>
<body>
  <!-- ページ -->
  <div data-role="page">
    <!-- ヘッダー -->
    <div data-role="header" data-theme="b">
      <h1>DB</h1>
    </div>
    <!-- 内容 -->
    <div id="db_list" data-role="content">
    </div>
  </div>
</body>
</html>

備考(サーバー側の処理)

jQuery Mobile には直接関係ありませんが、サーバー側の処理は以下のようなソースコードJRuby で実行しています。(jQTouch 等でも同様)

table_list.rb
require "rubygems"
require "sinatra"
require "sequel"
require "mysql-connector-java-5.1.13-bin.jar"
require "active_support/json"

set :public, File.dirname(__FILE__) + "/jquery-mobile"

DB = Sequel.connect("jdbc:mysql://localhost/information_schema?user=root")

#DBリスト取得
get '/databases' do
    DB["SELECT DISTINCT table_schema FROM tables ORDER BY table_schema"].to_json
end

#指定DBのテーブルリスト取得
get '/tables/:table_schema' do |t|
    sql = "SELECT table_name, table_type, engine, avg_row_length, create_time FROM tables WHERE table_schema=?"
    DB[sql, t].to_json
end