テンプレートとデータビュー
Sencha フレームワークは、JavaScript のオブジェクトやデータをコンポーネントに表示させるための、テンプレートエンジンを備えています。 前回学んだデータモデルの中のデータを効率的にビューに表示するときにも使える非常に便利なものです。 今回は、テンプレートとデータビューと題して、コンポーネントにデータを展開する方法を学びましょう。 テンプレートを実現するクラスには、Ext.Template と Ext.XTemplate という二つのクラスがあります。
Ext.Templateクラス
最初は、より簡単な Ext.Template クラスの機能から見ていきます。
このクラスは、HTMLコードを生成するための簡単な方法を提供します。 Ext.Template クラスでは、 No.4 DOM操作 の回で学んだ Ext.DomHelper と同じメソッドをサポートしています。
- append
- insertAfter
- insertBefore
- insertFirst
- overwrite
テンプレートのインスタンスを作り、そこに値を割り当てながら、そのエレメントをDOMツリーに追加する。といった使い方ができます。 実際のコードで見てみましょう。 なお、この記事のコードスニペットは、Ext JS の API ドキュメント中にある Live Preview 機能で実際に実行することができます。
1 2 3 4 5 6 7 8 9 | Ext.DomHelper.append(Ext.getBody(), { tag: 'ul', id: 'tplList' }); var tpl = Ext.create('Ext.Template', '<li>{text}</li>'); tpl.compile(); tpl.append('tplList', {text: 'リストにappendで追加します。'}); tpl.insertFirst('tplList', {text: 'そしてこの行をinsertFirstで挿入します。'}); |
最初の部分は、受け皿となる ul タグを作っています。 この中に、テンプレートを使って、li タグを追加しています。
6行目でテンプレートオブジェクトのインスタンスを作っています。 クラス名の次に文字列を渡していますが、これがテンプレートになります。 テンプレートの中には、{text} というブレスで囲まれた文字列があります。 この部分がプレースホルダになって、渡された値に展開されます。
7行目で、テンプレートをコンパイルし(こうすると少し高速になります)、 8行目で、テンプレートに値を適用しながら、その結果を tplList という ID を持つ DOM (1行目からのところで用意したulタグ) に追加しています。 append メソッドの第2引数にはオブジェクトを渡していますね。 このオブジェクトのキーが text になっていますので、それがテンプレートの {text} というプレースホルダの所に展開されます。
これがとても簡単なテンプレートの例です。
もうちょっと複雑なこともできます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | var t = Ext.create( 'Ext.Template', '<div id="{id}">', '<span class="{cls}">{name:trim} {value:ellipsis(16)}</span>', '</div>', { compile: true } ); var el = t.append(Ext.getBody(), { id: Ext.id(), cls: 'myclass', name: ' 名前は ', value: '寿限無寿限無五劫の擦り切れ海砂利水魚の水行末雲来末風来末' }, true); |
Ext.create の 第2引数以降に複数の文字列を渡していて、最後にオブジェクトを渡しています。 Ext.Template は渡された引数が文字列のうちは、それを全部テンプレート文字列であるとして処理します。 そして文字列でないオブジェクトが渡されると、それをインスタンス化の時のコンフィグオプションであると判断します。
上記の場合には、コンフィグオプションのcompileをtrueに設定しています。 するとインスタンス化と同時にコンパイルされます。
また、4行目の所に、{name:trim} とかいう書き方のプレースホルダがあります。 コロンの前は、プレースホルダの名前で、後の方は関数名です。 関数名を指定すると値に関数の処理を適用してから埋め込むことができます。 この場合ですと、前後の空白を取ってくれます。 関数名には、Ext.util.Format クラスで提供されているフォーマット用関数の全てが利用可能です。 上記の例では、
- {name:trim}とフォーマット関数を指定していますので、fooの前後の空白は取り除かれます。
- {value:ellipsis(16)}と指定しているので、16文字を超える部分は、省略して表記されます。
- append メソッドの3つめの引数にtrueを渡しています。これによりメソッドが Ext.dom.Element を返すようになります。false の場合の戻り値 HTMLElement になります。
Ext.XTemplateクラス
Ext.XTemplate クラスには、Ext.Template にない様々な機能が追加されています。 より高機能なテンプレートクラスになっています。
まず配列を扱うことができます。 Ext.Template の append メソッドなどに渡せる値は、単純なオブジェクト型でした。 しかし Ext.XTemplate クラスでは、配列を渡してループ処理することができます。
配列の繰り返し処理
<tpl for=”key”>で、配列の繰り返し処理ができます。 そのループ内で {#} を使うと配列のインデックス番号 (1から始まる) を挿入できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | var tpl = Ext.create( 'Ext.XTemplate', '<ul>', '<tpl for="oslist">', '<li>{#}.{name}</li>', '</tpl>', '</ul>' ); tpl.overwrite(Ext.getBody(), { oslist: [ { name: 'Mac OS X' }, { name: 'Windows 7' }, { name: 'Windows Vista' }, { name: 'Windows XP' } ] }); |
これで、oslist という配列の各要素をループして出力します。
- 1.Mac OS X
- 2.Windows 7
- 3.Windows Vista
- 4.Windows XP
このように表示されます。
言い忘れましたが、Ext.XTemplateでは、compile() は必要ありません。 自動的にコンパイルされます。
繰り返しのスコープの中では、その中のプロパティ名を {} で囲むと値を出力できます。 上記の例では {name} という所ですね。 繰り返しを入れ子にすることもでき、入れ子にするスコープが切り替わります。 内側のスコープから親オブジェクトの値へアクセスするには parent を使います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | var tpl = new Ext.XTemplate( '<table border="1">', '<tpl for="rows">', '<tr>', '<tpl for="cols">', '<td>{parent.row}行 {#}列 : {name}</td>', '</tpl>', '</tr>', '</tpl>', '</table>' ); tpl.overwrite(document.body, { rows: [{ row: 1, cols: [{ name: 'Item1' }, { name: 'Item2' },{ name: 'Item3' }] },{ row: 2, cols: [{ name: 'Item4' },{ name: 'Item5' },{ name: 'Item6' }] },{ row: 3, cols: [{ name: 'Item7' },{ name: 'Item8' },{ name: 'Item9' }] }] }); |
フラットな配列
各要素がオブジェクトではないフラットな配列の場合には、その値を {.} を使って出力できます。
1 2 3 4 5 6 7 8 | var tpl = new Ext.XTemplate( '<tpl for="computers">', '<div>{.}が欲しい。</div>', '</tpl>' ); tpl.overwrite(document.body, { computers : ['Windows PC', 'iMac', 'Macbook Air'] }); |
数値演算処理
Ext.XTemplateでは、プレースホルダの中に簡単な数値演算処理を記述できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | var tpl = Ext.create('Ext.XTemplate', '<p>価格表</p>', '<table border="1px">', '<tr>', '<th>商品名</th>', '<th>価格</th>', '<th>消費税</th>', '</tr>', '<tpl for=".">', '<tr>', '<td>{product}</td>', '<td>{price}</td>', '<td>{price*0.08}</td>', '</tr>', '</tpl>', '</table>' ); tpl.overwrite(document.body, [ {product: 'ノートパソコン', price: 108000}, {product: 'テレビ', price: 98000}, {product: 'エアコン', price: 210000} ]); |
条件分岐処理
<tpl>
タグにif
オペレーターをつけると条件分岐ができます。
if
オペレーターの値部分に条件式を記述します。
条件式に合致した場合に、</tpl>
閉じタグまでの間の処理を実行します。
その中に式を記述する際には、< や > の記号は、< > とエンコードして記述する必要があります。
1 2 3 | <tpl if="age >= 20"> <li>{name}</li> </tpl> |
else
やelseif
も使えます。
1 2 3 4 5 6 7 | <tpl if="age < 20"> <li>{name}: 未成年</li> <tpl elseif="age >= 65"> <li>{name}: 高齢者</li> <tpl else> <li>{name}: 成人</li> </tpl> |
switch構文での条件分岐もできます。 JavaScriptの構文とは違い、breakを挿入する必要はありませんが、 1つのtplタグに複数のcaseオペレーターを指定することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | var data = [{ name: 'Papa', age: 33, gender: 'male' }, { name: 'Mama', age: 27, gender: 'female' }, { name: 'Boku', age: 3, gender: 'male' }, { name: 'Jiji', age: 72, gender: 'female' }, { name: 'Baba', age: 66, gender: 'female' }]; var tpl = Ext.create('Ext.XTemplate', '<table>', '<tr>', '<th>名前</th>', '<th>年齢</th>', '<th>性別</th>', '<tpl for=".">', '<tr>', '<td>{name}</td>', '<td>{age}</td>', '<td>', '<tpl switch="gender">', '<tpl case="male" case="man">', '男性', '<tpl case="female" case="woman">', '女性', '<tpl default>', '不明あるいは...', '</tpl>', '</td>', '</tr>', '</tpl>', '</table>' ); tpl.overwrite(document.body, data); |
インラインコード実行
テンプレートの中で、JavaScript のコードを記述できます。 {[ … ]} で囲んだ部分は、式として評価され、その式の結果をそこに埋め込みます。 コード中では次の特別な変数を使うことができます。
- out: テンプレートが追加する出力配列。
- values: 現在のスコープの値。
- parent: 親のテンプレートのスコープ。
- xindex: ループ内でのインデックス(1から始まる)
- xcount: ループが繰り返される回数。
次の例ではインラインコードで xindex を使って行を交互に色づけするために、クラス属性を設定する処理をしています。
1 2 3 4 5 6 7 8 | var tpl = new Ext.XTemplate( '<p>Bikes: ', '<tpl for="bikes">', '<div class="{[xindex % 2 === 0 ? "even" : "odd"]}">', '{name}', '</div>', '</tpl></p>' ); |
インラインコードにはもう一つの書き方があります。 {% … %} で囲まれたブロックの中に書かれたコードは {[ … ]} と同じようにJavaScriptのコードとして評価されますが、こちらの場合はその結果は、結果として値が出力されません。 つまり、 {% … %} のブロックは結果としてテンプレートの表示に何の影響も与えませんが、コードは実行されます。 ループ中の break や continue あるいは変数を背後で加算するようなことに使えます。
カスタムメソッド
ここまで、 Ext.Template にはコンフィグオブジェクトを指定できましたが、Ext.XTemplate でもテンプレートにコンフィグオプションを設定する事ができます。 ただし、Ext.XTemplate ではコンパイルをする必要が無いので、compile オプションを指定しても意味はありません。 指定できるコンフィグは disableFormat のみとなります。
そして Ext.XTemplate では、コンフィグオブジェクトの中に関数を設定することによって、テンプレート用のカスタムメソッドを記述することができます。 次の例は、 さきほど数値演算のところで紹介した消費税計算のコードをカスタムメソッドで書き換えたものです。 こちらでは Math.round 関数を使って丸めの処理を加えています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | var tpl = Ext.create('Ext.XTemplate', '<p>価格表</p>', '<table border="1px">', '<tr>', '<th>商品名</th>', '<th>価格</th>', '<th>消費税</th>', '</tr>', '<tpl for=".">', '<tr>', '<td>{product}</td>', '<td>{price}</td>', '<td>{[this.getTax(values.price)]}</td>', '</tr>', '</tpl>', '</table>', { getTax: function (value) { return Math.round(value * 0.05); } } ); |
この例で示すように、インラインコードの中でカスタムメソッドを使う際には、this.getTaxというように、thisをつけて呼び出します。
Componentのtplコンフィグ
ここまでのサンプルではテンプレートの機能を見るために、テンプレートを単独で使用し、その結果を overwrite メソッドなどでDOMに書き出していました。 このような使い方でもテンプレートは充分に便利なものなのですが、コンポーネントと連携させることでより便利になります。
Ext.Component のサブクラスには tpl というコンフィグオプションがあります。 このコンフィグにテンプレートを設定します。 設定の方法は、テンプレートとなる文字列でもいいですし、Ext.Template や Ext.XTemplate のインスタンスでもいいです。 tpl をセットしたコンポーネントでは、コンポーネントのupdateメソッド (Touchでは setData メソッド) を使ってコンテンツを更新することができます。
次の例では、テンプレートをコンテナーにセットして、updateメソッドで値を適用しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | var tpl = Ext.create('Ext.XTemplate', '<p>価格表</p>', '<table border="1px">', '<tr>', '<th>商品名</th>', '<th>価格</th>', '<th>消費税</th>', '</tr>', '<tpl for=".">', '<tr>', '<td>{product}</td>', '<td>{price}</td>', '<td>{[this.getTax(values.price)]}</td>', '</tr>', '</tpl>', '</table>', { getTax: function (value) { return Math.round(value * 0.05); } } ); Ext.create('Ext.Panel', { width: 600, height: 400, title: 'テンプレート', id: 'contents', tpl: tpl, buttons: [{ text: '更新', handler: handler }], renderTo: Ext.getBody() }); function handler() { Ext.getCmp('contents').update([ {product: 'ノートパソコン', price: 108010}, {product: 'テレビ', price: 98000}, {product: 'エアコン', price: 210000} ]); } |
データビュー
ここまででテンプレートとコンポーネントを組み合わせてデータを整形して表示できることがわかりました。 データビュー (Ext.view.View) クラス (Touchでは、Ext.dataview.DataViewクラス) は、ストアとバインドして、ストアに格納されたデータをテンプレートを使って表示することができます。
ストアには通常複数のレコードがあります。
データビューにおいて、tpl コンフィグに <tpl for="">
タグを含むテンプレートを設定して、ストアの中のレコードをループさせて表示することができます。
また、データビューではストアのレコードを繰り返し処理するようなテンプレートを書かずにすませる方法もあります。その際は itemTpl コンフィグに、各レコードを表示するためのテンプレートを設定します。
そうすると、ストアからのデータ取得や繰り返し処理はデータビューがやってくれます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | // モデルの定義 Ext.define('Image', { extend: 'Ext.data.Model', fields: [ { name:'src', type:'string' }, { name:'caption', type:'string' } ] }); // ストアの定義 Ext.create('Ext.data.Store', { id:'imagesStore', model: 'Image', data: [ { src:'http://www.sencha.com/img/20110215-feat-drawing.png', caption:'Drawing & Charts' }, { src:'http://www.sencha.com/img/20110215-feat-data.png', caption:'Advanced Data' }, { src:'http://www.sencha.com/img/20110215-feat-html5.png', caption:'Overhauled Theme' }, { src:'http://www.sencha.com/img/20110215-feat-perf.png', caption:'Performance Tuned' } ] }); // ビューの定義 Ext.create('Ext.view.View', { store: Ext.data.StoreManager.lookup('imagesStore'), itemTpl: '<div class="thumb-wrap"><img src="{src}" />', itemSelector: 'div.thumb-wrap', emptyText: 'No images available', renderTo: Ext.getBody() }); |
テンプレートとコードの分離
テンプレートをプログラムのソースコードから分離して、HTML内に格納することもできます。 この機能によりテンプレートの作成にHTMLエディタの利用も可能となり、デザインとロジックの作業の分担が可能となります。 HTML中に記述するテンプレートは、その中に書かれたHTMLがブラウザのパーサーによって解釈されてしまわないように、textarea タグを使うことを推奨します。 そのままでは、textarea タグが表示されてしまうので、非表示にするために x-hidden クラスを設定します。
htmlファイル
1 2 3 4 5 6 7 | <textarea id="tplOs" class="x-hidden"> <ul> <tpl for="oslist"> <li>{#}. {name}</li> </tpl> </ul> </textarea> |
JavaScriptファイル
1 | var tpl = Ext.XTemplate.from('tplOs'); |
今回はテンプレートについて解説しました。 ここまででひとまず、Sencha Framworkの利用に関する初級編はおしまいです。