jQBinder, ブラウザー側でのHTML templateを可能にするjQuery plug-in
2009.10.14
一昨日はMVCの話で妙に盛り上がってしまったが、考えてみるとModel/View/Controller間の分離が不十分という話はサーバー側だけの話ではなく、クライアント側にも言える事。事実、私自身も
div.innerHTML = "<span class='red'>" + message + "</span>";
みたいなHTMLが混ざったJavaScriptコードを書く事は良くある。特に、最近はJSONとして取得して来たデータセットをリストとして表示するケースが増えて来たが、そんな時に「サーバー側のようなHTMLテンプレートが使えたらいいな」と思う事は良くある。手っ取り早くとりあえず動くものを作るのにはHTML埋め込み型のJavaScriptで良いのかも知れないが、後々のメンテナンスを考えると少なくともModelとViewぐらいはキチンと切り話しておいた方が良い事は確か。
ということで、jQueryの勉強もかねて、簡単なHTMLテンプレート・エンジン(jQBinder)を作ってみた。使い方は至って簡単、まずはHTMLファイルの中に以下のようにテンプレートを埋め込んでおく。
- <div id="stage">
- <div class="tweet">
- <div class='item_left'><img class='prof_img' src='$(.user.profile_image_url)' /></div>
- <div class='item_right'>$(.text)</div>
- <div class='name'>$(.user.screen_name) $(.created_at)</div>
- <div class='clearfix'></div>
- </div>
- </div>
一番外側のDIVがリストの表示の際の親になるエレメントで、その間にイタリックで書いた部分が各行をどう表示するかを記述したテンプレートである。$(.〜)の部分がデータとの結合(data-bind)時に置き換えられる。例えば$(.text)は、json[k].text (k:行番号)の値で置き換えられる。
ドキュメントのロード時にテンプレートのままでは表示したくないので、CSSで親エレメントに "display:none" を指定して隠しておき、適切なタイミングで表示する(.show()を呼ぶ)ようにする。
このテンプレートを利用してTwitterの最新のタイムラインを表示するには、以下のコードをドキュメントのロード時に実行する。
$("#stage").dataBindTo( {
url:"http://twitter.com/statuses/public_timeline.json?callback=?"})
.html("(loading...)").show(); // jsonを取得している間 "Loading..."と表示
こうすると、指定したURLからJSONを非同期に取得し、取得後は各行のデータ(json[0..n])のプロパティを使ってテンプレートを展開する。
実際のサンプルを左に貼付けたので、見ていただきたい(Safari4, Chrome4, iPhone Safari3.1, Firefox3.5, Opera9, IE8で動作確認済み)。
ソースコードは、http://widgetpad.com/815/ からアクセスできるので、そのシンプルさを実感していただきたい(htmlが22行、jsが5行)。
ちなみにこのjQBinder、せっかく作ったので、オープンソースのjQuery plug-inとして進化させて行くのも悪くないと考えている(jquery.comにはプラグインとして登録済み)。ご意見・ご要望・バグレポート等、大歓迎である。特にIE6やIE7の実行環境をお持ちの方がいたらこれがちゃんと表示されるかどうかの確認をしていただけるととても助かる。
IE6ではエラーが発生して、真っ黒い画面ですね。
line:5、line:14で「識別子、文字列または数がありません」との事。
あと、javascript、HTML交じりのコードは別にMVCの分離とは関係の無い話ではないでしょうか?
せっかくですのできちんとした例で説明しないとMVCの誤解を解くどころか助長してしまいそうです。
クライアントサイドのテンプレートエンジンは楽しそうですので頑張ってください。
Posted by: 山田某 | 2009.10.14 at 18:33
IE6 (Parallels上のWindows2000上)で表示させたところ以下のエラーが発生し表示出来ませんでした。
ライン: 14
文字: 6
エラー:識別子、文字列または数がありません。
コード:0
URL: http://widgetpad.com/815/index.html
Posted by: Yuumi3 | 2009.10.14 at 18:35
IE7(VMWare上のWindowsXP上)もIE6と同じエラーで表示されませんでした
Posted by: Yuumi3 | 2009.10.14 at 18:41
こんにちは。いつも楽しく読ませていただいております。
私は jBind ( http://arashkarimzadeh.com/index.php/jquery/10-jbind-jquery-bind-template.html )というのを使っておりました。jBindは配列データに対しループして置換・連結してくれたりといろいろ便利です。
しかし、こちらもinnerHTMLをreplaceで置換する実装なので、長い配列を与えた場合に置換後の文字列を連結してからHTMLのレンダリングが始まるので、置換後の文字列が長くなる場合に遅いと感じておりました。
そこで、「innerHTMLを置換するのではなく、DOMツリーを走査してノード毎に置換を行えば逐次的にレンダリングされ見た目の速度が向上するのではないか」という素人考えの下、作ってみましたが、体感的には早くなりませんでした。(^^;
まだちゃんと見ていないのですが、置換が走っている間はレンダリングが開始されないようでした。まだ改善の余地があるような気もするので、引き続き遊び中です。
Posted by: hs | 2009.10.14 at 18:47
情報ありがとうございます。"trailing comma"の問題でIR6/7で動かなかったようです。まだ確認はできていないんですが、それだけはひとまず修正しました。
Posted by: Satoshi | 2009.10.14 at 20:23
IE6で試してみましたが
ライン: 5
文字: 3
エラー:識別子、文字列または数がありません。
コード:0
でした。
"trailing comma"の問題はIE8ではおきないのですか、知らなかった。
Posted by: Yuumi3 | 2009.10.14 at 22:38
おっと、widget.jsの方にもtrailing commaがありました。しばらくObjective Cばかり書いていたので、trailing commaを付ける癖があるようです。困ったものです。
Posted by: Satoshi | 2009.10.15 at 03:33
おはようございます。 IE6, IE7ともに動きました。
やはり、IE6,7は遅いですねSafariでは見えない、 (loading...) の見えます。
Posted by: Yuumi3 | 2009.10.15 at 17:16
IE6での動作確認、ありがとうございます。遅いのはJavaScriptの実行速度そのものの問題でしょうね。困ったものです。
Posted by: Satoshi | 2009.10.15 at 22:46
こんにちは。いつも楽しく読ませていただいております。
jqbinderですが、ちょっと変更してみました。
【内容】
・escape時に強制的にString型に変換(Date型のtextへの対処)
・領域再確保を減らし、文字列連結を高速化
--- jqbinder-1.0.2.js.orig Fri Nov 20 09:16:10 2009
+++ jqbinder-1.0.2.js Fri Nov 20 14:31:44 2009
@@ -11,7 +11,7 @@
var defaults = {
complete: function() {},
model: function(data) { return data; },
- escape: function(text) { return text.replace(/&/g, '').replace(//g, '') }
+ escape: function(text) { return (text+'').replace(/&/g, '').replace(//g, ''); }
};
var options = $.extend(defaults, options);
return this.each(function() {
@@ -23,11 +23,12 @@
$.getJSON(url,
function (data) {
var model = options.model(data);
- var htmlText = "";
+ var func = $element.attr("view");
+ var htmlText = new Array();
for (var i in model) {
- htmlText += $element.attr("view")(model[i]);
+ htmlText[i] = func(model[i]);
}
- $element.html(htmlText);
+ $element.html(htmlText.join(''));
options.complete.call($element);
});
});
Posted by: amano | 2009.11.20 at 03:09
こんにちは。
JS上でのモデルビュー住み分けは理想的ですね。
私もその方向性を探っているものですが、
HTMLにテンプレートを埋め込む場合、問題が一点あります。
テンプレートに img タグを記述する場合、
src 属性値がロード時にブラウザに読み込まれてしまう形になります。
お作りになられた jqbinder のサンプルも firebug 等で確認していただければおわかりになると思いますが、
ページのロード時に
http://widgetpad.net/815/play/$(.profile_image_url)
というURLに接続して403エラーが発生しています。
私はこの回避方法を考えましたが、
スマートなアイデアはありませんね。
どうやら src 属性にプレースホルダを埋め込む限り、
display: none; を指定してもブラウザに読み込まれるようですので…。
なんとかしたいものですが。
Posted by: まるお | 2010.03.04 at 03:21
こんにちは。
jqbinderを改造して、社内で使っている者です。
imgタグのsrc属性に適用する場合は、imgタグ自体を特殊コメントタグ「<!--@」、「@-->」(実際は半角)で囲み、テンプレート実行時にその特殊コメントを除去するようにしています。
Posted by: amano | 2010.03.22 at 09:59