Otama テンプレートシステム

Otama は、テンプレートとプレゼンテーションロジックを完全に分離したテンプレートシステムです。TreeFrog Framework で独自に作られたシステムです。

設定ファイル(development.ini)を次のように編集してからジェネレータで足場を生成すると、ビューは Otama システムのものが生成されます。

 TemplateSystem=Otama

テンプレート(.html)は完全な HTML で記述し、書き換えたい要素(タグ)に対して「マーク」をつけておきます。そのマークと C++ コードの関連付けをプレゼンテーションロジックファイル(.otm)に記述します。そうしてから、ビルドすると自動的に C++ コードに変換され、ビューの共有ライブラリが作られます。

View Convention

基本的に、アクション毎にテンプレートとプレゼンテーションロジックのセットを作ります。ファイル名は、大文字小文字を区別 して 「アクション名.html」、「アクション名.otm」 とします。ファイルは「views/コントローラ名/」 ディレクトリに置きます。

新規にテンプレートを作成したら、ビューの共有ライブラリに反映させるために、view ディレクトリで”make qmake”を実行してください。

 $ cd views
 $ make qmake

本章に入る前に ERB の内容を読んでいない方は、まずそちらを読むことをお奨めします。Otama は ERB をベースにしている部分が多いので、あらかじめ ERB を知っておくと、Otama も理解しやすくなります。

文字列を出力する

お約束の “Hello world” を出力してみましょう。
HTML で書かれたページ(テンプレート)では、要素に対して「マーク」をつけるのに data-tf というカスタム属性を使います。その属性値には必ず “@” で始まる名前をつけます。例えば、次のように書きます。

 <p data-tf="@hello"></p>

この p 要素には @hello というマークをつけました。
マークに使える文字は、半角英数字と _ (アンダースコア) だけです。これ以外は使わないでください。

次に、C++ コードをプレゼンテーションロジックファイルに記述します。上で付けたマークに対して C++ コードを関連づけます。次のように書きます。

 @hello ~ eh("Hello world");

それからビルドして、アプリを実行すると、ビューは次の結果を出力するでしょう。

 <p>Hello world</p>

少し説明します。
プレゼンテーションロジックに記述したマークと C++ コードをつないでいる ~(チルダ)には、「マークされた要素のコンテント(内容)を、右辺の内容で置換する」という作用があります。eh() メソッドは渡された値を出力するのでしたね。

つまり、p 要素のコンテント(このケースでは空文字)を “Hello world” で置換するということです。data-tf 属性はまるごと消えてしまいます。

また、もう1つ別の方法として、次のように書くこともできます。同じ結果が出力されます。

 @hello ~= "Hello world"

~ と eh メソッドの組みあわせは 「~=」 に書き直せるということです。同様に、~ と echo メソッドの組みあわせは「~==」と書き直せます。ERB の書き方と同じですね。

ここでは、説明を簡単にするために静的な文字列を出力させましたが、変数を出力することが可能です。当然、コントローラから渡されたオブジェクトについても出力させることができます。

結論: 変数を出力する箇所にはマークを付けろ。そしてコードをつなげ。

Otama演算子

マークと C++ コードに挟まれている記号のことを、Otama 演算子と呼びます。

Otama 演算子を使って要素とC++コードを関連づけ、さらにどのように作用させるかを決めるわけです。プレゼンテーションロジックでは、この Otama 演算子の両脇には必ずスペースを入れるようにしてください。

今度は、別の Otama 演算子をつかってみます。プレゼンテーションロジックを次のように :(コロン)に書き換えたとします。

 @hello : eh("Hello world");

ビューの結果は次のようになります。

 Hello world

p タグがなくなりました。: (コロン) には、「マークされた要素をまるごと置き換える」という作用があるので、このような結果になります。上記と同様に、次のようにも書けます。

 @hello := "Hello world"

もう説明はいりませんね。

コントローラから渡されたオブジェクトを使う

コントローラから渡された(texport された)エスポートオブジェクトを表示するには、ERB と同様にして tfetch マクロまたは T_FETCH マクロでフェッチしてから使います。msg を QString 型のエクスポートオブジェクトとすると、次のように記述できます。

 @hello : tfetch(QString, msg);  eh(msg);

ERB と同様、フェッチされたオブジェクトはローカル変数として定義されます。

通常、C++ コードは1命令(1行)では収まらないでしょう。1つのマークに対して複数行の C++ コードを書くには、普通に並べて書いて最後に空行を入れてください。マークの箇所から空行までが1つの固まり(1セット)と見なされます。従って、プレゼンテーションロジックではマークとマークの間は必ず空行(空白文字だけの行も含む)で区切られます。

結論: ロジックは空行で区切れ。

エクスポートオブジェクトを別の2カ所で表示させたいケースがあるとしましょう。この場合は #init に記述しておけば、最初に呼び出される(フェッチされる)ことが約束されます。あとは、そのプレゼンテーションロジックの中で自由に使えます。次のようになります。

 #init : tfetch(QString, msg);

 @foo1 := msg

 @foo2 ~= QString("message is ") + msg

ということで、2回以上参照されるエクスポートオブジェクトに対しては、#init のところでフェッチ処理をしてください。

エクスポートオブジェクトを出力するには、さらにもう1つの方法があります。
Otama 演算子の後ろに “$” を付けるのです。例えば obj1 という エクスポートオブジェクトを出力するには次のように書けます。

 @foo1 :=$ obj1

これは、obj1に対してフェッチ処理しながら eh()メソッドで値を出力しているのです。ただし、フェッチ処理と同等の処理をするだけで、実際にそのローカル変数は定義されません。

echoメソッドで出力するには、次のように記述します。

 @foo1 :==$ obj1

ERB と同じですよね。

結論: エクスポートオグジェクトは :=$ か ~=$ で出力せよ。

ループ処理

リストの数だけ繰り返し表示を行うループ処理の方法について説明します。
テンプレートに、次の記述があったとします。

<tr data-tf="@foreach">
  <td data-tf="@id"></td>
  <td data-tf="@title"></td>
  <td data-tf="@body"></td>
</tr>

blogList は Blog クラスのリストのエクスポートオブジェクトだとします。for 文でループ処理を書いてみます。while 文も同様になります。

 @foreach :
 tfetch(QList<Blog>, blogList);    /* フェッチ処理 */
 for (auto &b, blogList) {
     %%
 }

 @id ~= b.id()

 @title ~= b.title()

 @body ~= b.body()

%% は意味のある記号で、そのマーク(@foreach)が付いた要素全体を指します。つまり、ここでは tr 要素(</tr> まで含む)を指します。従って、この foreach 文は tr 要素を繰り返しながら、@id, @title, @body が付いた要素のコンテントにそれぞれ値を設定します。その結果、ビューは次のような内容を出力するでしょう。

 <tr>
   <td>100</td>
   <td>Hello</td>
   <td>Hello world!</td>
 </tr><tr>
   <td>101</td>
   <td>Good morning</td>
   <td>This morning ...</td>
 </tr><tr>
   :  (リストの数の分を繰り返し)

これまで同様 data-tf 属性は消えてしまします。

属性を追加する

要素に属性を追加する Otama 演算子を使ってみます。 テンプレートに次のようなマークをつけたとします。

<span data-tf="@spancolor">Message</span>

プレゼンテーションロジックに次を書いたとします。

@spancolor + echo("class=\"c1\" title=\"foo\"");

この結果、次が出力されます。

 <span class="c1" title="foo">Message</span>

このように、+演算子を使うことで、属性だけを追加することができます。
注意点として、echo() メソッドの代わりに eh() メソッドは使えません。なぜなら、ダブルコーテーションがエスケープ処理されて違う意味になってしまうからです。

また別の方法として、プレゼンテーションロジックは次のようにも書けます。

@spancolor +== "class=\"c1\" title=\"foo\""

echo メソッドは == に書き換えられます。

また、別の方法として a メソッドを使って次にようにも書けます。同じ結果が出力されます。

@spancolor +== a("class", "c1") | a("title", "foo")

aメソッドは、HTML属性を表現する THtmlAttribute のオブジェクトを生成し、| (バーチカルバー)はこれらを連結するものです。その連結後は THtmlAttribute オブジェクトになるわけですが、echo メソッドで出力すると 「key1=”val1” key2=”val2” …」 の文字列に変換されるので、結果として属性が追加されることになります。

好きなほうをつかってください。

<a> タグを書き換える

: (コロン)演算子を使うことで書き換えられるのは、上記で説明したとおりです。
ちょっとおさらいしてみると、次のようにテンプレートの a タグにマークをつけたとします。

<a class="c1" data-tf="@foo">Back</a>

例として、Blog というビューのプレゼンテーションロジックに、次のように書いておきます。

@foo :== linkTo("Back", urla("index"))

この結果、ビューは次を出力します。

<a href="/Blog/index/">Back</a>

linkTo メソッドは a タグを生成するので、このような結果になります。 残念なことに、元々あった class 属性が消えてしまいましたね。この : 演算子にはまるごと要素を置き換える作用があるからです。

もし属性を設定したければ、linkTo のメソッドの引数に追加することができます。

@foo :== linkTo("Back", urla("index"), Tf::Get, "", a("class", "c1"))

上記と同じ結果のとおり class 属性も出力されます。

属性の情報は出力できたものの、プレゼンテーションロジックでそんな情報をわざわざ書きたくないものです。
この解決方法として、 |== 演算子というものがあります。これは、タグについた属性の情報を残しつつ内容を合併する作用があります。
プレゼンテーションロジックを次のように書き換えたとしましょう。

@foo  |== linkTo("Back", urla("index"))

その結果、ビューは次を出力します。

<a class="c1" href="/Blog/index/">Back</a>

もともと存在した class 属性が消えずに残りました。

|== 演算子が要素を合併する条件として、要素の種類が同じでなければいけません。また、もし同じ属性が両方に存在する場合は、プレゼンテーションロジックの値が優先されます。

この演算子を使うことで、デザインに関する情報(属性)は可能なかぎりテンプレート側に移すことができるしょう。

結論: デザインに関する属性はテンプレートに残し、|== 演算子でマージせよ。

注意:
|== 演算子はこの形式でのみ使用可能で、|(バーティカルバー)単独や |= では使えませんので注意してください。

フォームタグ

データをPOSTするにはフォームタグ <form> を使うわけですが、CSRF 対策を有効にしている場合は、テンプレートで単に form タグを記述しただけではPOST データを受け付けません。hidden パラメータとして秘密情報を埋め込む必要があるのです。

そのためには formTag メソッドを使います。テンプレートに form タグにマークを付けて、formTag() メソッドが出力する内容と合併しましょう。

テンプレート:

  :
<form method="post" data-tf="@form">
  :

プレゼンテーションロジック:

@form |== formTag( ... )

これで正常にデータをPOSTすることができるでしょう。

CSRF 対策について、もう少し詳細を知りたい方はセキュリティの章をご覧ください。

要素を消す

テンプレートの中で要素に @dummy というマークをつけると、その要素(開始タグ、コンテント、終了タグ)はビューとしては出力されなくなります。テンプレートに次を書いたとします。

<div>
  <p>Hello</p>
  <p data-tf="@dummy">message ..</p>
</div>

すると、ビューは次のような結果を出力します。

<div>
  <p>Hello</p>
</div>

タグを消す

コンテントは残し、開始タグと終了タグのみを消したいケースもあります。
例えば、レイアウトを使用する場合、<html>タグはレイアウトファイル側が出力するので、テンプレート側ではもう出力する必要はありませんが、テンプレート側にも<html>タグは残したままにして、HTMLに準拠したい時などです。
テンプレートに次を書いたとします。

<html data-tf="@dummytag">
  <p>Hello</p>
</html>

すると、ビューは次を出力します。

<p>Hello</p>

これらの特殊マークは Web デザインとしては残しておきたいが、ビューとしては消したい場合に使えます。

ヘッダファイルをインクルードする

テンプレートとプレゼンテーションロジックは C++ コードに変換されると説明しましたが、ユーザ定義のヘッダファイルなどは自動ではインクルードされないので、自分で記述しなければいけません。但し、TreeFrog の基本的なヘッダファイルはインクルードされます。

例として blog.h ファイルと user.h ファイルをインクルードしたい場合、プレゼンテーションロジックの上部へ次のように書きます。

#include "blog.h"
#include "user.h"

全く C++ コードと同じ! #include という文字列で始まる行は、ビューのコードにそのまま移されます。

Otama 演算子

説明した Otama 演算子を表にまとめます。

演算子 説明 備考
: 要素置換
マークされた要素および子要素を、右辺の eh() 文または echo() 文で出力される文字列で置換する
%% は置き換えられる要素そのものを指す
~ コンテント置換
マークされた要素のコンテントを、右辺の eh() 文または echo() 文で出力される文字列で置換する
 
+ 属性追加
右辺のecho() 文で出力される文字列を、マークされた要素の属性に追加する
+= はHTMLエスケープされるのであまり使われないかも
|== 要素合併
マークされた要素をベースにしながら、右辺に指定された文字列を合併する
| (バーティカルバー)単独や |= では使用不可


これら4つの演算子について、拡張版は以下のとおり。
eh() 文や echo() 文は不要になるので、より短く記述することができるでしょう。

演算子 説明
:=
:==
:=$
:==$
変数(文字列、数値)をHTMLエスケープしたもので要素置換
変数(文字列、数値)で要素置換
エクスポートオブジェクトをHTMLエスケープしたもので要素置換
エクスポートオブジェクトで要素置換
~=
~==
~=$
~==$
変数(文字列、数値)をHTMLエスケープしたものでコンテント置換
変数(文字列、数値)をコンテント置換
エクスポートオブジェクトをHTMLエスケープしたものでコンテント置換
エクスポートオブジェクトでコンテント置換
+=
+==
+=$
+==$
変数(文字列、数値)をHTMLエスケープし、それを属性追加
変数(文字列、数値)を属性追加
エクスポートオブジェクトをHTMLエスケープし、それを属性追加
エクスポートオブジェクトを属性追加
|==$ エクスポートオブジェクトを要素合併


コメント

プレゼンテーションロジックでコメントを書く場合は /* ~ */ の形式で書いてください。

@foo ~= bar    /*  This is a comment */

C++でのもう1つの書き方である // ~ は、プレゼンテーションロジックでは使えないので注意してください。