jQuery Templateの使い方
前フリ
jQueryを使ったテンプレートの実装がいくつかあったが、MicrosoftのASP.NETチームがjQueryチームと協力して、jQuery Templateという素晴らしいプラグインをリリースしていたので、試してみた。
中島聡さんのAjax世代のアーキテクチャはかくあるべき、という議論によると、MVCアーキテクチャのViewはHTML・CSSによって表現されるべきで、今まで仕事でSmartyを使ったり、自分でもDjangoを使ってみた経験から、テンプレートはやっぱりHTMLで表現されるべきだと思う。
この議論はモデルをどう実装すべきか(Model = Business Entity + Business Logic)、が論点な気がするけど、JSONなりRESTなりでコントローラと通信可能なら、確かにPHPなりPythonでテンプレートを書く必要はないわけだし。jQTouchやjQuery mobileを使ってみたり、最近のスマートフォン向けにWebサービスを提供するなら、HTMLテンプレートは合理的かも、と思う。
jQuery Templateについて
${variable}
や{{each}}のようなテンプレートタグを使って、HTMLをレンダリングするjQueryプラグイン。まだベータ版ではあるが、2011年1月にリリース予定のjQuery 1.5にはjQuery本体に取り込まれる予定。
実装サンプル
マニュアルによると、テンプレートはscriptタグの中に書いておき、コンテナとなるタグにappendするという使い方を想定しているようだ。まずはマニュアル通りに使ってみる。
読み込むjsファイル。
<script type="text/javascript" src="../res/js/jquery-1.4.4.js"></script>
<script type="text/javascript" src="../res/js/jquery.tmpl.js"></script>
サンプル。
<section>
<h2>data list</h2>
<ul id="list"></ul>
</section>
<script id="template" type="text/x-jquery-tmpl">
<li>${name}</li>
<li>${url}</li>
</script>
<script type="text/javascript">
var list =
{
name: "satooshi",
url: "http://d.hatena.ne.jp/with_no_parachute/"
};
$("#template")
.tmpl( list )
.appendTo( "#list" );
</script>
しかし、テンプレートがscriptタグの中、というのは直観的に理解しづらい。テンプレートはViewであって、Controllerではないから。JSをControllerとして使うなら、Viewであるテンプレートも当然
<section>
<h2>data list</h2>
<ul id="list">
<li>${name}</li>
<li>${url}</li>
</ul>
</section>
のように書きたい。ただし、テンプレートタグが見えるのは良くないので、次のような方針で実装する。
- 初期状態ではテンプレート部分は
display:none;
- テンプレートにデータをバインドする
- コンテナとなるタグの子要素をコンパイル済みテンプレートと置き換える
-
display:none;
を解除して表示
というわけで、もう一度実装サンプル。
CSS
.template{display:none;}
HTML
<article>
<header>
<h1>jquery template test</h1>
</header>
<section>
<h2>data list</h2>
<ul id="list" class="template">
<li>${name}</li>
<li>${url}</li>
<li>SNS
<ul>
{{each sns}}
<li>${$value}</li>
{{/each}}
</ul>
</li>
<li>E-mail
<ul>
{{each(i,address) email}}
<li>${i+1} ${address.label}: ${address.value}</li>
{{/each}}
</ul>
</li>
</ul>
</section>
<section>
<h2>data table</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>City</th>
<th>twitter</th>
<th>E-mail</th>
</tr>
</thead>
<tbody id="databody" class="template">
<tr>
<td>${name}</td>
<td>${city}</td>
<td>${twitter}</td>
<td>${email}</td>
</tr>
</tbody>
</table>
</section>
</article>
バインドするデータとレンダリング処理。
var list =
{
name: "satooshi",
url: "http://d.hatena.ne.jp/with_no_parachute/",
sns: [
"twitter",
"mixi",
"flickr",
"delicious"
],
email: [
{label:"home", value:"email1@gmail.com"},
{label:"mobile", value:"email2@gmail.com"},
{label:"work", value:"email3@gmail.com"}
]
};
var persons = [
{
name:"satooshi",
city:"Yokohama",
twitter:"satooshi",
email:"email1@gmail.com"
},
{
name:"satooshi_jp",
city:"Kanagawa",
twitter:"satooshi_jp",
email:"email2@gmail.com"
},
{
name:"satooshi_en",
city:"New York",
twitter:"satooshi_en",
email:"email3@gmail.com"
},
{
name:"satooshi_fr",
city:"Paris",
twitter:"satooshi_fr",
email:"email4@gmail.com"
}
];
// render template
$(document).ready(function(){
var bindTo = [
{selector:"#list", data:list},
{selector:"#databody", data:persons}
];
renderAll(bindTo);
});
ここで使っている関数は次のようなもの
/**
* bind jQuery template to selector.
*
* @param {String} selector
* @param {jQuery} template
*/
var bindTemplate = function(selector, template){
$(selector)
.children()
.replaceWith(template)
.end()
.show();
};
/**
* render jQuery template.
*
* @param {String} selector
* @param {Object|Array} data
*/
var render = function(selector, data) {
var $t = $(selector).tmpl(data);
bindTemplate(selector, $t);
};
/**
* render all jQuery template.
*
* @param {Array|Object} bindTo
*/
var renderAll = function(bindTo){
for (i=0; i <=bindTo.length; ++i){
t = bindTo[i];
render(t.selector, t.data);
};
};
Chromeで見た結果はこんな感じになる。
まとめ
今さらながら、jQueryって便利。JSONでデータを受け取って表示とか、APIを公開しているWebサービスからデータを取ってくる場合だとか、GAEを使っている場合などなど、すごく簡単に実装できるようになる。SEOって何?みたいなサイトだと、このテンプレートエンジンは確かに強力だ。
{{each}}についてちょっと補足事項
いわゆるforeach文で、コレクションをイテレーションするときに使うもの。引数を渡さない場合、イテレーションされるデータにアクセスするときには${$value}
を使う。インデックスには${$index}
でアクセスできる。引数を渡すと、これらを別の名前で使うことが可能になる。
上の例でいうと、
{{each(i,address) email}}
の部分。${$index}
、${$value}
がそれぞれ、${i}
、${address}
で使えるようになる。
注意点
上のサンプルを書いていたときに実際にやった失敗例。
tableタグにidを付けて、tableタグ以下をテンプレートとすると、バインドする配列の要素数に応じて、theadやらtbodyやらが出力されてしまう。そのため、テンプレートにバインドするのは、基本的に配列、と考えておいた方が良い。とすると、idを付けるタグもtableではなくtbodyにしなければならないことが分かる。1要素しかない場合だとか、[]じゃなくて{}を渡す場合でも単純なミスが防げる。
問題点
別のサンプルを書いていて、気が付いた問題点。
<section>
<h2>index</h2>
<nav>
<ul id="navigation" class="template">
<li><a href="${path}/${name}" target="_blank">${name}</a></li>
</ul>
</nav>
</section>
<script type="text/javascript">
//<![CDATA[
var index = [
{
path:"./html",
name:"delicious.html"
},
{
path:"./html",
name:"datetime.html"
},
{
path:"./html",
name:"databind.html"
}
];
$(document).ready(function(){
render("#navigation", index);
});
//]]>
</script>
aタグのhrefの値にテンプレート変数を設定すると、firefox3、4では変数の値がうまく展開されず、テンプレート変数がそのまま出力されてしまうことが分かった。Chrome7、IE8、Safari5、Opera10といった、他のブラウザだと平気だった。
環境
- Windows 7
- ブラウザ
- Chrome 7.0.517.44
- IE8
- Firefox 3.6.12、4.0b7
- Safari 5.0.3
- Opera 10.63