Closure Templatesを使ってみた!
Google Closure Toolsの1つClosure Templatesを試してみました。
Closure Templates | Google Developers
Closure Templates単体でも使えますが、私はClosure Libraryと共に使っています。
Java版とJavaScript版がありますが、私が使ったのはJavaScript版です。
Closure TemplatesはTemplatesのためのファイルを作成し、それをコンパイルすることでjsができあがります。
まずは、Templateのためのファイルです。
book.soy
{namespace templates.book} /** * Book. * @param isbn The isbn of the book. * @param name The name of the book. * @param link The link of the book. */ {template .book} <div class="book"> <div class="{css amazon}"><a href="{$link}">amazon</a></div> <div class="{css isbn}">{$isbn}</div> <div class="{css name}">{$name}</div> </div> {/template} /** * Book List. * @param bookList */ {template .bookList} {foreach $book in $bookList} {call .book} {param isbn: $book.isbn /} {param name: $book.name /} {param link: $book.link /} {/call} {ifempty} No book list. {/foreach} {/template}
templateのためのファイルは、soyという拡張子で作成します。
$xxxがtemplate変数となり、値を受け取る事ができるようになっています。
また、上記の例だと2つのtemplateを定義しています。
1つ目が{template .book}です。
中身は殆ど無く、divで囲って値を表示しているだけです。
2つ目は{template .bookList}です。
こっちは、foreachでループするようになっています。
また、中で{template .book}をcallしています。
このように、複数のtemplateを組み合わせることも可能です。
soyファイルが出来たら、SoyToJsSrcCompiler.jarで、Jsへとコンパイルします。
コマンドから実行可能ですが、Eclipseを使っているので、Antを利用してみました。
build.xml
<?xml version="1.0"?> <project basedir="." default="soy_to_javascript"> <target name="book"> <property name="target" value="../index.html" /> <antcall target="_create_soy" /> </target> <target name="soy_to_javascript"> <java jar="../../../../lib/SoyToJsSrcCompiler.jar" fork="true"> <arg value="--outputPathFormat" /> <arg value="../scripts/templates.js" /> <arg value="--shouldProvideRequireSoyNamespaces" /> <arg value="book.soy" /> </java> </target> </project>
Closure TemplateがJavaScriptのテンプレートエンジンでいい気がしてきた - Lush Life
こちらを参考に作ったのですが、何故か自動では動いてくれず・・・。
自動で動かせるようになったら、また追記したいと思います。
因みにbook.soyをJsにコンパイルすると、以下のようになります。
templates.js
// This file was automatically generated from book.soy. // Please don't edit this file by hand. goog.provide('templates.book'); goog.require('soy'); goog.require('soy.StringBuilder'); templates.book.book = function(opt_data, opt_sb) { var output = opt_sb || new soy.StringBuilder(); output.append('<div class="book"><div class="amazon"><a href="', soy.$$escapeHtml(opt_data.link), '">amazon</a></div><div class="isbn">', soy.$$escapeHtml(opt_data.isbn), '</div><div class="name">', soy.$$escapeHtml(opt_data.name), '</div></div>'); return opt_sb ? '' : output.toString(); }; templates.book.bookList = function(opt_data, opt_sb) { var output = opt_sb || new soy.StringBuilder(); var bookList26 = opt_data.bookList; var bookListLen26 = bookList26.length; if (bookListLen26 > 0) { for (var bookIndex26 = 0; bookIndex26 < bookListLen26; bookIndex26++) { var bookData26 = bookList26[bookIndex26]; templates.book.book({isbn: bookData26.isbn, name: bookData26.name, link: bookData26.link}, output); } } else { output.append('No book list.'); } return opt_sb ? '' : output.toString(); };
ただのJsなので、デバッグ時にはこの中にconsole.log()とかを埋め込めば、普通に実行されます。
次にtemplateを呼び出している箇所です。
app.js
goog.provide('xhrmanagerSample.App'); goog.require('goog.soy'); goog.require('templates.book'); goog.scope(function(){ var gdom = goog.dom; var gsoy = goog.soy; /** * ajax get book list compete. * @param {Object} e */ xhrmanagerSample.App.prototype.onGetBookListComplete = function(e){ var bookList = gdom.getElement('book-list'); gdom.removeChildren(bookList); //domの中身を吹き飛ばしている var data = { bookList: [] }; for (var i = 0, len = e['bookList'].length; i < len; i++) { data.bookList[i] = { isbn: e['bookList'][i]['isbn'], name: e['bookList'][i]['name'], link: e['bookList'][i]['link'] }; } gdom.append(bookList, gsoy.renderAsElement(templates.book.bookList, data)); }; });
猛烈に省略しているのですが、呼び出しているの上記の部分です。
ajaxで取ってきたjsonの値を渡しているのですが、ここで少し困ったことがあります。
Closure Libraryを使っているため、最終的にClosure Compilerでコンパイルを行います。
その時に、templateの中の変数名なども短縮されます。
しかし、ajaxで取ってきたjsonの中の名前は短縮されませんので、一致しません。
そのため、上記のようにJsでリマッピングしてやります。
この辺りは、どうすれば良いのかさっぱり分からなかったので、伊藤殿に聞いてしまいました・・・。
https://plus.google.com/u/0/114061805681880927213/posts/TbB36g3PdNY
伊藤殿、いつもありがとうございますorz
因みにClosure Compilerをかけた後だと、{template .bookList}は以下のようになっていました。
function Ic(a, b) { var c = b || new V, d = a.Hb, f = d.length; Uncaught TypeError: Cannot read property 'length' of undefined if (0 < f) for (var e = 0; e < f; e++) { var h = d[e], l = h.ic, p = h.name, h = h.link; (c || new V).append('<div class="book"><div class="', "amazon", '"><a href="', Dc(h), '">amazon</a></div><div class="', "isbn", '">', Dc(l), '</div><div class="', "name", '">', Dc(p), "</div></div>") } else c.append("No book."); return b ? "" : c.toString() };
opt_data.bookListだったのものa.Hbに。
bookList26.lengthだったものが、d.lengthになっています。
ajaxで取得したjsonをそのまま渡していた時は、a.Hbでundefinedになっていまいました。
そのため、上記のようなリマッピングをしてやったのですね。
また、このエントリーでは色々と省略している部分があるので、伊藤殿のBlogも合わせて見ることをお勧めします。
Closure Stylesheets で CSS を最適化する (2) - WebOS Goodies
他にもこんなBlogを書いている方もいらっしゃいました。
Closure Templatesのオートエスケープが最強すぎる件 - teppeis blog
私はClosure Libraryと組み合わせて使っていますが、単体で使っても悪くないかもしれませんね。
jQuery.template()だとjspでは$がかち合って使えませんが、Closure TemplateならばJsにコンパイルされた後は、独自の文字はありませんので。
最後に、今回のサンプルの全ソースです。
book.soy
Google Code Archive - Long-term storage for Google Code Project Hosting.
build.xml
Google Code Archive - Long-term storage for Google Code Project Hosting.
app.js
Google Code Archive - Long-term storage for Google Code Project Hosting.
一応、動くものはこちらです。
ViewBookListボタンを押すと、本の一覧が出てくるだけですが・・・。
ログイン - Google アカウント