ERB
本来、ERB とはテキストドキュメントに Ruby スクリプトを埋め込むためのライブラリです。よく知られているとおり、Rails などのテンプレートエンジン(の1つ)に採用されており、<% … %> で挟んでHTML の中にコードを埋め込みます。
TreeFrog Framework でも同じように、<% … %> で C++ コード を埋め込みます。便宜上、ここでの実装も ERB と呼ぶことにします。
まず、設定ファイル development.ini にある項目が次のとおりになっていることを確認します。デフォルトから変更していなければ、そうなっているはずです。
TemplateSystem=ERB
それから、ジェネレータコマンドでビューを生成すると、ERB 形式のテンプレートが生成されます。生成されたテンプレートのファイル名は、全て小文字で次のルールとなります。
views/コントローラ名/アクション名.erb (小文字で)
新たにテンプレートを追加する場合は、この命名規則にならってください。
アクションとビューの関係
アクションとビューの関係をおさらいしますが、例として Fooコントローラの bar アクションが render() メソッドを引数なしで呼び出すと、次のテンプレートの内容が出力されます。
views/foo/bar.erb
引数を指定して render() メソッドを呼び出せば、該当するテンプレートの内容が出力されます。
テンプレートファイルは好きに追加して構いませんが、新規に追加したら view ディレクトリで次のコマンドを1度実行してください。ビルドのエントリを makefile に追加するために必要なことです。
$ cd views
$ make qmake
これを忘れると追加したテンプレートが共有ライブラリに反映されないことがあるので、注意してください。
結論: テンプレートファイルを追加したら “make qmake” せよ。
文字列を出力する
お約束の “Hello world” を出力してみましょう。2通りの方法があります。 まず、メソッドを使う方法です。
<% eh("Hello world"); %>
この eh() メソッドは、クロスサイトスクリプティング対策のためにHTML エスケープ処理をして文字列を出力します。もしエスケープ処理をしたくなければ、echo() メソッドを使用してください。
もう1つの方法は、<%= … %> という記法を使います。この方法は eh() メソッドを使ったものと全く同じ結果になります。
<%= "Hello world" %>
エスケープ処理したくなければ、<%== … %> を使います。この方法は echo() メソッドを使ったものと全く同じ結果になります。
<%== "<p>Hello world</p>" %>
Note:
<%= … %> と記述すると HTML エスケープせずに文字列出力するのが、本来の仕様(eRuby)です。TreeFrog では、より短く記述できるほうが HTML エスケープし忘れがなくなり、安全性が高まると考えて、上述の挙動にしています。最近はそれが主流になりつつあるようです。
デフォルト値の表示
変数が空の文字列である場合に、代わりとしてデフォルト値を表示させてみましょう。 次のように記述することで、変数 str が空である場合に”none” が表示されます。
<%= str %|% "none" %>
コントローラから渡されたオブジェクトを使う
コントローラから渡された(texport された)エスポートオブジェクトを表示するには、先に tfetch マクロまたは T_FETCH マクロで、型(クラス)と変数名を宣言してから使います。この操作をフェッチと呼ぶことにします。
<% tfetch(Blog, blog); %>
<%= blog.title %> // blog.title を出力
フェッチされた変数(オブジェクト)は、同一ファイル内のそれ以降で参照することができます。つまり、ローカル変数だということですね。
ところで、フェッチ処理は使うたびに毎回実行されなければならないのでしょうか。
お分かりだと思いますが 、毎回フェッチしてはいけません。
フェッチされたオブジェクトは関数内のローカル変数です。従って、同じオブジェクトに対し2度フェッチを行うと、同じ変数が2つ定義されてしまうので、コ ンパイル時にエラーになります。もちろん、ブロックを分ければコンパイルエラーにはなりませんが、そうする意味はあまりないでしょう。
結論: フェッチは一度だけせよ。
もしエクスポートオブジェクトが QString 型や int 型であれば、tehex 関数だけで出力することができます。この方法だとフェッチ処理が不要になります。
<% tehex(foo); %>
この tehex() は、フェッチと eh() メソッドの処理を合わせた関数と理解してください。フェッチを行うとローカル変数が定義されるのとは違い、この関数は変数(この例では foo)を定義せずにその値を出力するのです。
もし、HTML エスケープ処理をしたくなければ、techoex 関数を使ってください。これはフェッチと echo() メソッドの処理を組み合わせたものです(同様に変数は定義されません)。
さらに、エクスポートオブジェクトを出力するにはもう1つ別の方法があります。<=$ .. %> という記法を使います。 ‘=’(イコール)と’$’(ダラー)の間は空けてはいけません。
上記の “<% tehex(foo); %>” は次のようにも書けます。
<%=$ foo %>
だいぶ短くなりました。つまり、tehex() メソッドは “=$” で置き換えることができるということです。
同様に、<% techoex( .. ); %> は <==$ .. %> で書き換えることができます。
以上をまとめると、エクスポートオブジェクトが QString 型か int 型であり、1回しか出力しない場合は <=$ .. %> の記法を使い、そうでなければフェッチ処理してから出力するのがベターだと思います。
結論: 1度しか出力しないエクスポートオブジェクトは <=$ .. %> を使え。
コメントの書き方
次の例はコメントの書き方です。HTML としては何も出力されません。ただし、C++ コードにはその内容が残ります。
<%# comment area %>
変換された C++ コードは views/_src ディレクトリに置かれてから、コンパイルされます。C++ コードを参照したい場合はここをのぞいてみてください。
ファイルのインクルード
ERB テンプレートの中でモデルなどのクラスを使用する場合、C++と同じようにヘッダファイルをインクルードする必要があります。自動ではインクルードされませんので注意してください。
次のような書き方でインクルードしてください。
<%#include "blog.h" %>
# と include の間を空けないことです。この例では、blog.h ファイルがインクルードされます。
テンプレートはそのまま C++ コードに変換されることを覚えておき、忘れずにファイルをインクルードしましょう。
ループ処理
リストに対するループ処理を書いてみましょう。例えば、blogList という Blog オブジェクトのリストがあったとして、次のようになります。もしそれがエクスポートオブジェクトならば前もってフェッチ処理をしておきます。
<% QListIterator<Blog> i(blogList);
while ( i.hasNext() ) {
const Blog &b = i.next(); %>
...
<% } %>
Qt 付属の foreach 文を使えば、より短く書けます。
<% foreach (Blog b, blogList) { %>
...
<% } %>
そのまんま C++ です。
<a> タグを作成する
<a> タグを作成するには、linkTo() メソッドを使います。
<%== linkTo("Back", QUrl("/Blog/index")) %>
↓
<a href="/Blog/index">Back</a>
linkTo() メソッドには他にも引数が指定可能です。詳細は API リファレンス をご覧ください。
URL を指定するのに url() メソッドを使えば、このようにも書けます。第1引数、第2引数にそれぞれコントローラ名、アクション名を指定します。
<%== linkTo("Back", url("Blog", "index")) %>
↓
<a href="/Blog/index/">Back</a>
同じコントローラのテンプレートであれば、urla() メソッドを使ってアクション名だけ指定すればよいです。
<%== linkTo("Back", urla("index")) %>
↓
<a href="/Blog/index/">Back</a>
JavaScript の確認ダイアログつきのリンクは、次のように書けます。
<%== linkTo(tr("削除"), urla("remove", 1), Tf::Post, "confirm('Are you sure?')") %>
↓
<a href="/Blog/remove/1/" onclick="if (confirm('Are you sure?')) {
var f = document.createElement('form');
: (省略)
f.submit();
} return false;">削除</a>
タグに属性を追加するのは、THtmlAttribute クラスを使います。
<%== linkTo("Back", urla("index"), Tf::Get, "", THtmlAttribute("class", "menu")) %>
↓
<a href="/Blog/index/" class="menu">Back</a>
同じ出力結果を、THtmlAttribute を生成する a() メソッドを使って少しだけ短く書けます。
<%== linkTo("Back", urla("index"), Tf::Get, "", a("class", "menu")) %>
複数の属性がある場合は、| 演算子でつなげて並べます。
a("class", "menu") | a("title", "hello")
↓
class="menu" title="hello"
ちなみに、anchor() メソッドという linkTo() メソッドのエイリアスもあり、全く同じように使えます。
このほか、多くのメソッドが提供されています。それらは API Document をご覧ください。
フォーム
フォームを使ってブラウザからデータをポストしてみましょう。次の例では、formTag() メソッドを使い、同じコントローラの create アクションにデータをポストしています。
<%== formTag(urla("create"), Tf::Post) %>
...
...
</form>
わざわざ formTag() メソッドを使わなくとも formタグをそのまま記述すれば同じことができそうですが、メソッドを使うとフレームワークが CSRF 対策を施してくれるのです。セキュリティの観点から、これは使うべきです。この CSRF 対策を有効にするには、設定ファイル application.ini で次のように項目を設定してください。
EnableCsrfProtectionModule=true
これで、不正なポストデータはフレームワーク がガードしてくれます。ただし、開発中は様々なテストを繰り返し行うので、その間は無効にしておいて良いでしょうね。
CSRF 対策の詳細については、セキュリティの章も参照して下さい。
レイアウト
レイアウトとは、サイトで共通に使われるデザインの大枠となるテンプレートです。レイアウトには自由にHTML要素を配置できるわけですが、大きくはヘッダー部、メニュー部、フッター部が置かれることが多いでしょう。
コントローラがビューへ依頼する時に、レイアウトを設定する方法として次の4つがあります。
- アクションごとにレイアウトを設定する。
- コントローラごとにレイアウトを設定する。
- デフォルトのレイアウトを使う。
- レイアウトを使わない。
ビューを描画する際にレイアウトは1つだけ使われますが、この並びで上位にあるレイアウトが優先されて使われます。つまり、「コントローラごとに設定されたレイアウト」よりも、「アクションごとに設定されたレイアウト」が優先して使用されるということです。
とても簡単なレイアウトの例ですが、次のような内容を .erb という拡張子をつけて保存します。レイアウトの保存場所は view/layouts ディレクトリです。
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<title>Blog Title</title>
</head>
...
<%== yield() %>
...
</html>
このポイントは、<%== yield() %> の行です。この部分には、テンプレートの内容が出力されます。つまり、render() メソッドで呼び出されたテンプレートの内容がマージされます。
このようにレイアウトを使用することで、ヘッダやフッタのようなサイトの共通デザインをまとめることができます。サイトのデザインを変更するには、レイアウトだけを変更すれば良くなるのです。
結論: サイトデザインの大枠としてレイアウトを使え。
では、レイアウトを設定するそれぞれの方法について説明します。
1.アクションごとにレイアウトを設定
アクションで呼ばれる render() メソッドの第2引数でレイアウト名を設定します。 次は simplelayout.erb をレイアウトに使う例です。
render("show", "simplelayout");
これで、simplelayout のレイアウトに show テンプレートの内容がマージされて、レスポンスとして返却されます。
2.コントローラごとにレイアウトを設定
コントローラのコンストラクタの中で、レイアウト名を引数に指定して setLayout() メソッドを呼びます。
setLayout("basiclayout"); // basiclayout.erb をレイアウトに使う
3.デフォルトのレイアウト
デフォルトのレイアウトファイルは application.erb です。個別にレイアウトを指定しない場合、このデフォルトレイアウトが使われます。
4.レイアウトを使わない
上記3つの条件に当てはまらない場合は、レイアウトは使われません。また、レイアウトを使用しない設定をするには、アクションの中で setLayoutDisabled(true) を呼ぶことです。
部分テンプレート
ネットでページを閲覧していると、複数のページで同じ内容を表示する領域が存在するのに気づきます。それは広告領域であったり、ツールバーであったりします。
このようなケースにWebアプリで対応するには、上で説明したようにその領域をレイアウトに含めてしまう方法以外に、「部分テンプレート」として切り出して共有する方法もあります。
まず、その部分を切り出し、テンプレートとして views/partial ディレクトリに保存します。拡張子は .erb とします。そして、renderPartial() メソッドで描画します。
<%== renderPartial("content") %>
これで content.erb の内容がおおもとのテンプレートに埋め込まれて、出力されます。部分テンプレートでは、おおもとのテンプレートと同様にエクスポートオブジェクトの値を出力することができます。
部分テンプレートとレイアウトはどちらも2つのテンプレートをマージすることからも、とても似た機能です。どう使い分ければ良いのでしょうか。
個人的な考えですが、ページに常に存在するヘッダ部やフッタ部などについてはレイアウトで定義し、常に存在するとは限らない断片的なパーツを表示するのには部分テンプレートを使うのが良いと思っています。