Sencha Touch の NestedList で JSON データを動的にリスト表示
以前、iUI や jQTouch で作成したもの(iUI版は id:fits:20100715, jQTouch版は id:fits:20100731)と同様のサンプルを Sencha Touch で作成してみました。(サーバー側の処理も同じものを使用)
Ext.NestedList を使って実装してみましたが、Ext.NestedList(というより TreeStore)は階層的なデータを一括取得するケースを想定しているようで、今回利用したサーバー側の処理のように階層毎に別の URL からデータを取得するようなケース(スキーマ取得 とテーブル取得でURLとデータスキーマが異なる)での実装はかなり苦労しました。
そのため、今回のようなケース(階層毎に取得先URLやデータのスキーマが異なる)には Ext.List と CardLayout の組み合わせで実装するのが現実的かと思います。
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20101005/
ちなみに Sencha Touch 0.96 では、Safari 5.0.2 で NestedList のリストアイテムをダブルクリックすると遷移先の次画面が 2重に繋がってしまう現象が発生してましたが、1.0 では修正されています。
なお、1.0 では Firefox で表示すらできなくなりました。(WebKitPoint が未定義になる模様)
Ext.NestedList でのリスト表示
まず、JavaScript にハードコーディングした組み込みデータを使って Ext.NestedList でのリスト表示を実装してみます。
注目点は以下の通り。
- TreeStore を使って階層データを NestedList に設定
- Ajax でサーバーからデータ取得しなくても TreeStore に proxy の設定が必要
- 階層データの末端(leaf: true の指定があるデータ)は詳細画面に遷移可能(ただし、詳細画面を作成する getDetailCard 関数を実装する必要あり)
- getDetailCard 関数の戻り値に recordNode を設定(これが無いとヘッダータイトルと戻るボタン名が正常表示されなかった)
以前試した時に NestedList では Store を使わなかった気がしますが、今では TreeStore を使うようになってます。
なお、リストアイテム毎のアクセサリ表示(右端の " > " の画像)の仕組みが用意されていなかったので、スタイル指定で対応しました。
他に getItemTextTpl を使う手も考えられますが、生成した HTML が span 要素の中に入れられてしまうため、アクセサリ表示の用途には向いていないと思います。
index_static.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Nested List Sample(static data)</title> <link rel="stylesheet" href="js/resources/css/sencha-touch.css" type="text/css" /> <style type="text/css"> /* セルのアクセサリ " > " を表示するためのスタイル定義 */ .x-list-item { background-image: url(img/chevron.png); background-position: right center; background-repeat: no-repeat; } </style> <script type="text/javascript" src="js/sencha-touch-debug.js"></script> <script type="text/javascript"> Ext.setup({ onReady: function() { //画面表示用の階層データ var data = { items: [ {title: "cdcol", items: [ {title: "cds", leaf: true} ]}, {title: "information_schema", items: [ {title: "CHARACTER_SETS", leaf: true, table_name: "CHARACTER_SETS", table_type: "SYSTEM VIEW", engine: "MEMORY", create_time: "" }, ・・・ ]}, ・・・ ] }; //モデル定義 Ext.regModel("ListItem", { fields: [ {name: "title", type: "string"}, {name: "table_name", type: "string"}, {name: "table_type", type: "string"}, {name: "engine", type: "string"}, {name: "create_time", type: "string"}, {name: "items", type: "array"} ] }); //実際に ajax の処理をさせなくても //proxy 設定(type も必須)が必要な点に注意 var dbStore = new Ext.data.TreeStore({ //モデルの指定 model: "ListItem", //データの指定 root: data, proxy: { type: "ajax", reader: { type: "tree", root: "items" } } }); var list = new Ext.NestedList({ fullscreen: true, title: "DB", displayField: "title", store: dbStore, //詳細画面の設定 getDetailCard: createDetail }); } }); //詳細画面の作成 function createDetail(record, parentRecord) { var rec = record.getRecord(); return new Ext.Panel({ //ヘッダータイトルと戻るボタン名を正常表示させるために //recordNode の設定が必要 recordNode: record, items: [ { xtype: 'textfield', label: 'table_name', placeHolder: rec.get("table_name") }, { xtype: 'textfield', label: 'table_type', placeHolder: rec.get("table_type") }, { xtype: 'textfield', label: 'engine', placeHolder: rec.get("engine") }, { xtype: 'textfield', label: 'create_time', placeHolder: rec.get("create_time") } ] }); } </script> </head> <body> </body> </html>
Ext.NestedList での JSON データ動的リスト表示
基本的に前述のサンプルと同じ内容ですが、以下の点が異なっています。
- TreeStore に url を設定し、サーバーからスキーマ(table_schema)一覧を取得
- スキーマをクリックした際にサーバーから該当するテーブルの一覧を取得し、TreeStore に動的データ追加(コメントで private 指定のある fillNode を使用)
- テーブル一覧取得時に同期型の Ajax 処理を行わせるため jQuery を使用
なお、table_schema を画面表示項目に指定したため、テーブルの一覧取得時にも table_schema キーに対してテーブル名を設定するようにしています。(ヘッダーのタイトルや戻りボタン名にも影響するため、階層毎の変更は困難)
また、TreeStore に fillNode する際、元々 TreeStore に指定したモデル(SchemaItem)とは異なるモデル(TableItem)で作成したレコードを追加していますが、この点に関しては一応問題なく動作します。
index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Nested List Sample</title> <link rel="stylesheet" href="js/resources/css/sencha-touch.css" type="text/css" /> <style type="text/css"> /* セルのアクセサリ " > " を表示するためのスタイル定義 */ .x-list-item { background-image: url(img/chevron.png); background-position: right center; background-repeat: no-repeat; } </style> <script type="text/javascript" src="js/jquery-1.4.4.js"></script> <script type="text/javascript" src="js/sencha-touch-debug.js"></script> <script type="text/javascript"> Ext.setup({ onReady: function() { Ext.regModel("SchemaItem", { fields: [ {name: "table_schema", type: "string"} ] }); Ext.regModel("TableItem", { fields: [ {name: "table_name", type: "string"}, {name: "table_type", type: "string"}, {name: "engine", type: "string"}, {name: "create_time", type: "string"} ] }); var dbStore = new Ext.data.TreeStore({ model: "SchemaItem", proxy: { type: "ajax", url: "databases", reader: { type: "tree" } } }); var list = new Ext.NestedList({ fullscreen: true, title: "DB", displayField: "table_schema", store: dbStore, //テーブル詳細画面の設定 getDetailCard: createDetail }); //アイテム選択時の処理 list.on("itemtap", function(subList, subIdx, el, e) { var rec = subList.getStore().getAt(subIdx); var schema = rec.get("table_schema"); //table_schema 表示画面の時だけテーブル一覧の取得処理を実行 if (schema) { var req = new jQuery.ajax({ method: "get", url: "tables/" + rec.get("table_schema"), async: false }); var jsonObj = eval("(" + req.responseText + ")"); var records = []; var Model = Ext.ModelMgr.getModel("TableItem"); Ext.each(jsonObj, function(item, index, allItems) { item["leaf"] = true; //ヘッダータイトルとリスト表示用に設定 item["table_schema"] = item.table_name; var record = new Model(item, item.table_name); record.raw = item; records.push(record); }); //TreeStore へのデータ追加 dbStore.fillNode(rec.node, records); } }); } }); //詳細画面の作成 function createDetail(record, parentRecord) { ・・・ index_static.html と同じ・・・ } </script> </head> <body> </body> </html>