今回はASTERIA Warpでテンプレートを利用するときに使う技術であるVelocityテンプレートをご紹介します。主にフローのVelocityコンポーネントでの利用について解説していますが、パイプラインでも利用できます。
1. Velocityとは
VelocityとはApache jakartaプロジェクトで開発された「テンプレートエンジン」です。テンプレートエンジンとはどういうものであるかを説明するために、まずは単純なメール本文の作成にVelocityを利用する例をご紹介します。
例えば自社の主催するセミナーに参加していただいたお客様に対してお礼のメールを送信するケースを考えてみます。メール本文は次のような文章となります。
○○株式会社 山田太郎様 このたびは6月23日開催の「ASTERIA Warp体験セミナー」にご参加いただき 誠にありがとうございました。 。。。 ご質問等ございましたら営業担当、××(xxxx@asteria.com)まで お気軽にお問い合わせください。
セミナー参加者が10人いた場合は10人に対してメールを送ることになります。セミナーが毎月開催されるものであるなら毎月同じようなメールを送ることになるでしょう。
上記のようなお礼メールは誰に対して送信する場合でも内容はほとんど変わりません。この例の場合、送信先によって
- 顧客の会社名
- 顧客の氏名
- セミナー開催日
- セミナー名
- 営業担当の名前
- 営業担当のメールアドレス
だけを変更すれば残りの部分はそのまま使いまわすことができます。
このようなケースでベースとなるテキストに対し、一部を置き換えることで複数のテキストを生成できるようにする仕組みがテンプレートエンジンです。
この例のメール本文をASTERIA WarpのVeloicityコンポーネントで作成する場合のテンプレート(ベースとなるテキスト)は次のようになります。
$flow.CorpName $flow.UserName様 このたびは$flow.SeminarDate開催の「$flow.SeminarName」にご参加いただき 誠にありがとうございました。 。。。 ご質問等ございましたら営業担当、${flow.StaffName}($flow.StaffMail)まで お気軽にお問い合わせください。
先に置き換える個所として挙げた部分がすべて「$flow.xxxx」というキーワードに置き換えられています。(一箇所キーワードが「{}」で括られているところがありますが今は気にしないでください。)
このようにテキスト中に「$xxxx」というキーワードを埋め込むことによってそれを実際の値と置き換えたテキストを生成することができるのがVelocityです。単純なテキスト置換だけではなく指示子と呼ばれる命令文を使用することで簡単なロジックを組み込むこともできます。
2. リファレンス
Velocityでは「$xxxx」のようなキーワードをテンプレート中に埋め込むと述べましたが、この「$」で始まるキーワードとそれに付随するプロパティとメソッドのことをリファレンスと言います。「$」で始まる単語自体はVelocityのドキュメントでは「変数」と呼ばれていますが、フロー変数やシステム変数などのASTERIA Warpの変数概念と区別するためにここでは「リファレンス変数」と呼びます。
フローのVelocityコンポーネントではあらかじめ次のリファレンス変数が設定されています。
リファレンス変数 | 設定内容 |
---|---|
$flow | フロー変数 |
$local | ローカル変数 |
$exvar | 外部変数セット |
$system | システム変数 |
$in | 入力ストリーム |
$sys | 汎用ユーティリティ |
$xpath | XPathユーティリティ |
$encoding | 出力エンコーディング |
先の例では「$flow.xxxx」というリファレンスを使用しましたが、これはフロー変数を参照するリファレンスです。
2.1. プロパティ
リファレンスではプロパティとメソッドが使用できます。
プロパティを参照するにはリファレンス変数の後ろに「.」(ドット) を置き、それに続けてプロパティ名を記述します。
リファレンス変数「$flow」ではプロパティとしてフロー変数名を指定することができます。例えばフロー変数「CorpName」に「アステリア株式会社」が設定されている場合、「$flow.CorpName」は「アステリア株式会社」に置き換えられます。
プロパティによって取得されたオブジェクトからさらにプロパティやメソッドが使用できることもあります。
プロパティの例
$flow.UserName - フロー変数「UserName」 $exvar.Exvar1.var1 - 外部変数セット「Exvar1」の変数「var1」 $system.RequestURL - システム変数「RequestURL」
2.2. メソッド
メソッドを使用するにはリファレンス変数の後ろに「.」(ドット) を置き、それに続けてメソッド名を記述し、その後ろに「()」をつけてその中に引数のリストを記述します。引数には次のものが指定できます。
- リファレンス
- 文字列リテラル
- 数値
- 真偽値(true/false)
メソッドとプロパティの違いは識別子の後ろに「()」をつけて引数が指定できるかどうかだけです。メソッドに引数がない場合でも「()」と空のカッコを指定する必要があります。
メソッドの例
$in.array(0).text() - 1番目の入力ストリーム $flow.UserName.strValue() - フロー変数「UserName」の文字列 $sys.formatDate($flow.date1, "yyyy/mm/dd") - 日付型のフロー変数「date1」 をフォーマットして表示
2.3. 表記法
ASTERIA Warpでの具体的なリファレンスの使い方を説明する前にリファレンスの表記法をまとめておきます。リファレンス変数、プロパティ、およびメソッドで使用可能な文字は以下のとおりです。
- アルファベット (a・・z, A・・Z)
- 数字 (0・・9)
- ハイフン ("-")
- アンダースコア ("_")
ただし識別子の先頭で使える文字はアルファベットのみです。つまり「$1ban」「$_temp」のようなリファレンス変数は認められませんし、「$日本語」のように日本語を使用することもできません。
有効なリファレンスの表記例
$flow.var1 - フロー変数「var1」 $in.text() - 入力ストリームの文字列 $sys.formatDate($flow.date1, "yyyy/mm/dd") - 日付型のフロー変数「date1」 をフォーマットして表示
2.3.1. 正式表記
リファレンスは生成するテキスト本文の中に埋め込まれます。そのためリファレンスの直後に別の語が置かれることも珍しくありません。例えば一章の例の
$flow.UserName様 このたびは$flow.SeminarDate開催の。。。
などはリファレンスが後続の文字と切れ目なしに使用されている例です。
この例の場合はリファレンス直後の文字が「様」「開」とリファレンス識別子として使用できない文字のためVelocityがリファレンスの区切りを間違うことはありません。ですが、リファレンス直後の文字が英数字であった場合はどうなるでしょうか?また、フロー変数として「var1」と「var11」が定義されている場合「$flow.var11」はどのように解釈されるでしょうか?
これらの場合、Velocityは識別子として使用できない文字が現れたところをリファレンスの区切りとして扱います。つまり「$flow.var11」は決して「$flow.var1 + "1"」とは扱われませんし、「$flow.UserNameABC」では「UserNameABC」がプロパティ名として解釈されます。「UserNameABC」というプロパティが存在しない場合は「$flow.UserNameABC」という文字列がそのまま出力され、仮に「UserName」あるいは「User」というプロパティが存在したとしてもその値が出力されることはありません。
こうした場合に対応するときに使うのが正式表記です。正式表記ではリファレンスの「$」に続く部分を「{}」で括ります。
正式表記の例
${flow.UserName}ABC ${flow.var1}1 ${in.text()}
正式表記を使用することでリファレンスの区切りを明示することができます。一章の例で「${flow.StaffName}($flow.StaffMail)まで」の部分で正式表記が用いられているのは、そうしないと後続のカッコによって「StaffName」がプロパティではなくメソッドとして解釈されてしまうためです。
2.3.2. 沈黙表記
前節でリファレンスで存在しないプロパティを指定した場合には「$flow.UserNameABC」のようにリファレンス指定の文字列がそのまま出力されると説明しましたが、このようにリファレンス文字列がそのまま出力される場合には次のものがあります。
- 存在しないリファレンス変数を指定した場合
- 存在しないプロパティを指定した場合
- 存在しないメソッドを指定した場合
- プロパティがnullを返す場合
- メソッドがnullを返す場合
- メソッドの返り値がvoidの場合
これを回避するために用いられるのが沈黙表記です。沈黙表記ではリファレンスの「$」の直後に「!」を記述します。沈黙表記を使用した場合、上記のような場合でもリファレンス文字列ではなく空文字が出力されます。沈黙表記は通常表記と正式表記の両方で使用可能です。
沈黙表記の例
$flow.email ##通常表記 $!flow.email ##沈黙表記 $!{flow.email} ##正式表記 かつ 沈黙表記 #* 実際には$flowのプロパティはnullの代わりに空文字を返すので、 あまり沈黙表記を使用する必要はありません。 *#
2.4. コメント
前節の例で「##」「#* ~ *#」という部分がありますが、これはVelocityのコメントです。テンプレート内に記述されていても出力結果には含まれません。
「##」以降の部分は1行コメントに、「#* ~ *#」で括られた部分はその範囲がコメントになります。
コメントの例
このテキストは表示されます。 ## このテキストは表示されません。 このテキストは表示されます。 このテキストは表示されます。 #* このテキストは、複数行コメントの一部 なので表示されません。このテキストも表示されません。 このテキストもまだ表示されません。*# このテキストはコメントの外側 なので表示されます。 ## このテキストは表示されません。
2.5. リファレンスとJavaの関係
Velocityを使う上ではJavaの知識は必要ありませんが、Javaの知識がある人にとってはそちらから理解した方がわかりやすいので、ここでリファレンスとJavaの関係について説明を補足しておきます。
リファレンス変数に設定されているオブジェクトは実はJavaのオブジェクトそのものです。 つまりリファレンスのメソッドとは設定されているJavaクラスのpublicメソッドそのものです。
ではプロパティとは何かというと、これは次のルールに従って実行されるメソッドの返り値です。
- getXXXX()メソッドとして定義されたメソッドの返り値
プロパティ名に対応するgetterメソッドがある場合、その返り値がプロパティ値となります。 - get(String propname) または get(Object propname)という名前のメソッドの返り値
StringまたはObjectの単一引数をとる「get」という名前のメソッドがある場合そのメソッドにプロパティ名を渡した返り値がプロパティ値となります。
例えばオブジェクトに「String getName()」というメソッドが定義されている場合、Velocityでは「$obj.Name」「$obj.getName()」のどちらの表記も使用できますが、この場合はプロパティを使用することが推奨されており、この文書でもプロパティとして扱っています。
メソッドまたはプロパティの返り値のオブジェクトは何でも構いません。Velocityは返り値のオブジェクトのtoString()を出力テキストとして使用します。
後述する #if や #foreach などの指示子と組み合わせるとVelocity内で自由度の高いプログラミングを行うことも可能です。
$flow.StaffMail.strValue().toLowerCase() ##文字列の小文字化 #set($array = $flow.var1.strValue().split(",")) ##フロー変数をカンマ区切りで配列化
逆から言えば自作の拡張クラスを作成してリファレンス変数に割り当てる場合は、このルールに従ってプロパティとメソッドを設計します。
3. 組込リファレンス その1
ここでは変数を中心にフローの組込リファレンスの詳細を説明していきます。入力ストリームを表す「$in」とユーティリティについては指示子の説明の後に扱います。
3.1. $flow - フロー変数
「$flow」はフロー変数を表すリファレンスです。デザイナーで定義したフロー変数がプロパティとして参照できます。
フロー変数の変数名として日本語などリファレンスで使用できない文字を使用している場合は以下のようにgetメソッドを使用して参照します。
$flow.get("変数1")
フロー変数参照の返り値はValueクラスのオブジェクトです。 ValueクラスはASTERIA Warp内でデータを扱う基本的なラッパークラスであり、変数やレコードのフィールドデータなどほとんどのデータはこのクラスでラップされます。
Valueクラスの代表的なメソッドを以下に示します。
メソッド | 引数 | 返り値 | 説明 |
---|---|---|---|
strValue() | なし | String | String型としての値 |
intValue() | なし | int | Integer型としての値 |
longValue() | なし | long | Integer型としての値 |
doubleValue() | なし | double | Double型としての値 |
decimalValue() | なし | BigDecimal | Decimal型としての値 |
dateValue() | なし | Date | DateTime型としての値 |
booleanValue() | なし | boolean | Boolean型としての値 |
byteValue() | なし | byte[] | Binary型としての値 |
isNull() | なし | boolean | 値がnullの場合にTrueを返します |
toString() | なし | String | strValue()と同じです |
フロー変数にはデータ型がありますが、そのデータ型に関わらず上記のメソッドでは自動的に型変換が行われます。(String型の変数であっても内容が数値を表す文字列であれば、intValue()メソッドでそれをint型の数値として取得できます。ただし、内容が数値でない場合は「0」が返ります。)
strValue()やintValue()などの各種メソッドは値がnullの場合でもnullを返すことはありません。値がnullの場合はstrValue()メソッドは空文字を返し、intValue()メソッドは0を返します。nullと空文字を区別する必要がある場合はisNull()メソッドを使用します。
ValueクラスのtoString()メソッドはstrValue()を返すので「$flow.var1」のような記述も指定のフロー変数が存在する限り結果がnullとなることはありません。したがって、指定のフロー変数が存在する限り出力結果に「$flow.var1」という文字列がそのまま出力されることはありません。
テンプレート内に値を埋め込むだけであればこれらのメソッドを使用する必要はありませんが、文字列を他のリファレンス変数に代入する場合や数値の演算を行う場合には必要になります。
また、「$flow」自体に定義されているメソッドは以下の2つのみです。
メソッド | 引数 | 返り値 | 説明 |
---|---|---|---|
get() | String | Value | 変数名を指定してフロー変数を取得 |
toString() | なし | String | フロー変数の一覧を返す |
toStringメソッドが定義されているのでテンプレート内に「$flow」とだけ記述した場合はフロー変数の一覧が出力されます。
3.2. $local - ローカル変数
「$local」はVelocityコンポーネントの「ローカル変数」タブで定義した変数を参照するリファレンスです。フロー変数がフロー内すべてに共通する変数であるのに対しローカル変数はそのテンプレート内だけで使用する変数です。
使用方法や注意点は「$flow」と同じです。
メソッド | 引数 | 返り値 | 説明 |
---|---|---|---|
get() | String | Value | 変数名を指定してローカル変数を取得 |
toString() | なし | String | ローカル変数の一覧を返す |
ローカル変数の例
$local.var1 $local.get("変数1")
3.3. $system - システム変数
「$system」はシステム変数を参照するリファレンスです。使用方法や注意点は「$flow」や「$local」と同じです。
メソッド | 引数 | 返り値 | 説明 |
---|---|---|---|
get() | String | Value | 変数名を指定してシステム変数を取得 |
toString() | なし | String | システム変数の一覧を返す |
システム変数として使用できるものに何があるかはフローサービスマニュアルの「システム変数」を参照してください。
システム変数の参照で使用できるのは英語表記のみです。日本語表記は使用できません。すべてのシステム変数はプロパティとして参照できます。
比較的よく使用するシステム変数にRequestURL(HTTPリクエストのURL)があります。HTMLフォームのactionに指定しておけば自分自身に対するリクエストとなります。あとにサンプルがありますが、検索条件と検索結果を同一ページに表示する画面を作成する場合などに便利です。
<form action="$system.RequestURL" method="post">
...
</form>
またWebから動作するフローでエラーが発生した場合に表示するページの中にExceptionDetail(エラー詳細)を埋めておくと原因究明が容易になることがあります。
<html>
<body>
<p>
エラーが発生しました
</p>
...
<!--
##HTMLのコメント内に埋めておくことで一般ユーザーの目には触れさせず
##ソースを表示した場合にのみエラー詳細を表示することができます。
$system.ExceptionDetail
-->
</body>
</html>
3.4. $exvar - 外部変数セット
「$exvar」は外部変数セットを参照するリファレンスです。フロー上に配置した外部変数セットを参照することができます。
外部変数セットは複数配置できるためフロー変数などに比べてリファレンスの表記が1階層が深くなります。最初に外部変数セット名を指定して次に変数名を指定します。外部変数セット名または変数名に日本語などのリファレンスで使用できない文字が使われている場合はgetメソッドを使用します。外部変数の種別(定数、リクエスト変数、セッション変数、アプリケーション変数)には関係なくすべて同じ指定方法で参照できます。
メソッド | 引数 | 返り値 | 説明 |
---|---|---|---|
get() | String | ExternalVariable | 外部変数セット名を指定して外部変数セットを取得 |
メソッド | 引数 | 返り値 | 説明 |
---|---|---|---|
get() | String | Value | 変数名を指定して外部変数セット内の変数を取得 |
外部変数セットの例
$exvar.Exvar1.var1 $exvar.get("外部変数セット1").var2 $exvar.get("外部変数セット1").get("変数3")
3.5. $encoding - 出力エンコーディング
「$encoding」はVelocityコンポーネントの出力ストリーム定義で指定したエンコーディング名の文字列(Stringオブジェクト)です。主に生成するHTMLのmetaタグでcharsetを指定する場合に使用します。
<html>
<head>
<meta http-equiv="content-type"
content="text/html; charset=$encoding" />
</head>
...
</html>
4. 指示子
指示子とはVelocityにおける処理命令のことで「#」で始まるキーワードで表現されます。「#if」「#foreach」といった指示子を使用することでテンプレートの中に分岐や繰り返しといったロジックを組み込むことができます。
4.1. #set
「#set」はリファレンス変数に対して値を代入する指示子です。代入の左辺はリファレンス変数となり右辺には以下のいずれかが使用できます。
- リファレンス(リファレンス変数、プロパティ、メソッド)
- 文字列リテラル
- 数値
- 配列
- 範囲(2つの整数間にある整数の配列)
- 真偽値(true/false)
#setの例
#set ($str1 = $flow.var1.strValue()) ##フロー変数の文字列(String) #set ($str2 = "aaa") ##文字列リテラル #set ($num1 = $flow.var2.intValue()) ##フロー変巣の数値(int) #set ($num2 = 123) ##数値 #set ($num3 = $num2 + 123) ##数値演算 #set ($arr2 = ["一", "二", "三"] ##配列 #set ($arr3 = [2000..2010]) ##範囲 #set ($flag = false) ##真偽値 #set ($arr1 = $flow.var3.split(",")) ##Stringのsplit関数の返り値(配列)
4.1.1. リファレンス
「#set」でリファレンスを代入する場合は、そのリファレンスが表すオブジェクトへの参照がリファレンス変数に代入されます。
#set ($var1 = $flow.var1) ##「$var1」はValueオブジェクト #set ($var2 = $flow.var1.strValue() ##「$var2」はStringオブジェクト
4.1.2. 文字列リテラル
「#set」で文字列リテラルを使用する場合、文字列は二重引用符「"」または単一引用符「'」で括ります。
どちらを使用した場合もリファレンス変数の内容はStringオブジェクトになりますが二重引用符「"」を使用した場合は、引用符内のリファレンスがパースされるという違いがあります。
#set($str1 = "AAA") ##二重引用符を使用。「AAA」をstr1に代入 #set($str2 = 'あああ') ##単一引用符を使用。 ##リファレンスがない場合はどちらでも同じ #set($str3 = "str1は「$str1」でした") ##文字列中の「$str1」は変換される #set($str4 = '変数名は「$str1」です') ##「$str1」はそのまま出力される ##文字列の連結する場合ははリファレンスを連続して記述 #set($str5 = "str1とstr2を連結「$str1$str2」")
4.1.3. 数値
「#set」で数値を使用する場合、簡単な演算を行うことが可能です。
演算で使用できるのは整数(Integer)のみです。Valueクラスには長整数(Long)を返すlongValue()メソッドや浮動小数を返すdoubleValue()メソッドがありますが、それらを用いての演算はできません。
#set ($num1 = $flow.num1.intValue() + 5) ##加算 #set ($num2 = 20 - $flow.num1.intValue()) ##減算 #set ($num3 = $flow.num1.intValue() * $flow.num2.intValue()) ##乗算 #set ($num4 = $flow.num1.intValue() / 5) ##除算。結果は整数 #set ($num5 = $flow.num1.intValue() % 5) ##剰余。除算のあまり ##括弧を使用して演算順序の制御もできる #set ($num7 = 10) #set ($num8 = $num7 + 5 * 10) ##結果は「60」 #set ($num9 = ($num7 + 5) * 10) ##結果は「150」
4.1.4. 配列
配列を使用する場合、「[]」内に配列要素をカンマ区切りで配列要素を指定します。配列要素にリファレンスを指定することもできます。
#set ($arr1 = ["one", "two", "three"]) #set ($arr2 = [$flow.str1.strValue(), $flow.str2.strValue()]
配列の主なメソッドを以下にしめします。
メソッド | 引数 | 返り値 | 説明 |
---|---|---|---|
get() | int | Object | 指定のインデックスに対応する配列要素を返します |
size() | なし | int | 配列の要素数を返します |
indexOf() | Object | int | 指定のObjectが配列要素に含まれている場合のインデックスを返します |
contains() | Object | boolean | 指定のObjectが配列要素に含まれているかどうかを返します |
配列の実体は「java.util.ArrayList」クラスです。
4.1.5. 範囲
範囲は2つの整数間にある数値を配列化する記述方法です。主に後述の「#foreach」と組み合わせて回数を指定してループする場合に用いられます。数値の指定にリファレンスを指定することもできます。
#set ($arr1 = [1..10]) ##「1」から「10」までを要素とする配列 #set ($arr2 = [$flow.num1.intValue()..$flow.num2.intValue()] #set ($arr3 = [10..1] ## 大きい数字から小さい数字の指定もOK
範囲の指定で先に大きい数字が指定された場合は、単純にカウントダウンしながら配列が作成されます。
4.1.6. 真偽値
真偽値は「true」または「false」のいずれかです。 主に後述の「#if」と組み合わせて用いられます。
#set ($flag1 = true) #set ($flag2 = $flow.var1.booleanValue())
4.1.7. nullについて
「#set」でリファレンス変数にnullを代入することはできません。右辺の評価結果がnullの場合は単純に無視されて、元の値がそのまま維持されます。
またnullに対するプロパティやメソッドの実行も無視されるだけでエラーとはなりません。
#set ($var1 = "aaa") ##文字列の代入 var1の値は「$var1」です。 ##この行の出力は「aaa」 ##フロー変数「var1」が定義されていないとする #set ($var1 = $flow.var1.strValue()) ##この行は無視される var1の値は「$var1」です。 ##この行の出力も「aaa」
このことはループの中で繰り返し同じ変数を使用する場合に特に注意が必要です。
4.2. #if
「#if」指示子を使用すると条件に一致する場合のみ出力されるテンプレートを記述することができます。「#if」はその区間の終了をしめす「#end」と必ず対にする必要があります。
#if ($flow.var1.strValue() == "test") この部分はフロー変数「var1」の値が「test」の場合だけ出力されます。 #end
「#else」を組み合わせることで条件に一致しない場合にのみ出力されるテンプレートを記述することもできます。また、「#elseif」を使用して複数の条件を組み合わせることも可能です。
#if ($flow.var1.intValue() == 1) フロー変数var1は「1」です。 #elseif ($flow.var1.intValue() == 2) フロー変数var1は「2」です。 #elseif ($flow.var1.intValue() == 3) フロー変数var1は「3」です。 #else フロー変数var1は不明です。 #end
ひとつの「#if」ブロック内に「#elseif」はいくつでも記述することができますが、「#else」は最後にひとつだけ記述することができます。条件は上から順に評価されていき、一度条件が一致するとそれ以降の条件式は評価されません。
4.2.1. 条件の評価方法
条件がリファレンスの場合以下のように判定されます。
- リファレンスの表すオブジェクトが真偽値の場合、それがtrueであれば一致
- それ以外の場合はオブジェクトがnullでなければ一致
#set ($flag1 = true) #set ($flag2 = false) #set ($obj1 = "hoge") #if ($flag1) flag1はtrueなので表示される #end #if ($flag2) flag2はfalseなので表示されない #end #if ($obj1) obj1はnullでないので表示される #end #if ($obj2) obj2は未定義なので表示されない #end #if ($flow.var1.booleanValue()) フロー変数var1がtrueならば表示される falseの場合や未定義の場合は表示されない #end
「==」を使用して値が等しいかどうかを判定することもできます。この場合両辺は同種のオブジェクトである必要があります。つまりフロー変数を文字列や数値と比較する場合にはそれぞれstrValue()、intValue()メソッドを使用する必要があります。
#if ($flow.var1 == $flow.var2) フロー変数var1とvar2が等しいかどうかを判定 #end #if ($flow.var3.strValue() == "aaa") フロー変数var3が文字列「aaa」と等しいかどうかを判定 ValueとStringの比較なのでValueをStringにする #end #if ($flow.var4.intValue() == 5) フロー変数var4が数値「5」と等しいかどうかを判定 Valueとintの比較なのでValueをintにする #end #set ($temp = "aaa") #if ($flow.var5.strValue() == $temp) フロー変数var5がリファレンス変数tempと等しいかどうかを判定 ValueとStringの比較なのでValueをStringにする #end
数値の比較の場合は「>」「<」「>=」「<=」が使用できます。
- m > n : mはnより大きい
- m < n : mはnより小さい
- m >= n : mはn以上
- m <= n : mはn以下
#if ($flow.var1.intValue() < 10) var1は10より小さい #elseif ($flow.var1.intValue() > 100) var1は10より大きい #end
「&&」「||」を使用して複数の条件を組み合わせたり、「!」を使用して条件を反転させることもできます。
- A && B : AとBの両方の条件に一致
- A || B : AとBいずれかの条件に一致
- !A : Aの結果の逆
#if ($flow.var1.intValue < 10 || $flow.var1.intValue() > 100) フロー変数var1が10より小さいか100より大きい場合 #end #if (!$temp) リファレンス変数tempがfalseかnull(未定義)の場合 #end
4.3. #foreach
「#foreach 」指示子はループを処理するために使用します。ループブロックの終わりをしめす「#end」と対にして使用します。構文は次のようになります。
#foreach (<リファレンス変数> in <リファレンスまたは配列> ... #end
「in」の前にはループ内でコレクションまたは配列の要素を受けるリファレンス変数を置きます。 「in」の後ろには以下ののいずれかが入ります。
- コレクション(java.util.Collectionの実装)を返すリファレンス
- 配列(範囲を含む)
もっとも典型的な #foreach の使用例はCSVなどの入力ストリームに対してレコードごとにループして処理を行うケースです。
<table> <tr> <th>フィールド1</th> <th>フィールド2</th> <th>フィールド3</th> </tr> #foreach ($r in $in.records) <tr> <td>$r.field1</td> <td>$r.field2</td> <td>$r.field3</td> </tr> #end </table>
リファレンス変数「$r」で一行分のレコードを受けて、#foreachブロック内の処理をレコードの行数回繰り返します。
ほとんどの場合「in」の後ろにはリファレンスが来ますが配列や範囲を直接指定することもできます。範囲を指定すること回数を決めてのループを行うことができます。
##配列を使用したループ #foreach ($name in ["佐藤", "鈴木", "田中"]) $nameさんが来ました。 #end ##範囲を指定したループ #foreach ($idx in [1..10]) ... #end
4.4. #includeと#parse
「#include」と「#parse」はいずれも外部ファイルをテンプレート内に取り込むための指示子です。Velocityコンポーネントのプロパティで「テンプレートの指定方法」プロパティが「ファイル」となっている場合のみ使用でき「直接入力」の場合は使用できません。
「#parse」では外部ファイル内のリファレンスや指示子が解釈されて変換されるのに対し、「#include」では単純にテキストがそのまま読み込まれます。
ファイルの指定はVelocityコンポーネントを使用しているASTERIA Warpユーザーのホームディレクトリからの相対パスで行います。テンプレートファイル位置からの相対パスではないので注意が必要です。絶対パスを使用したり、「..」を使用してホームディレクトリ以外の階層にあるファイルを参照することはできません。
ファイルの指定は通常文字列で指定しますが、リファレンスで指定して動的に取り込むファイルを変更することもできます。
#parse ("vm/header.vm") #include ($flow.includeFilename.strValue())
4.5. #macro
「#macro」はマクロを定義する指示子です。テンプレート内で頻繁に行われる処理をマクロとして定義しておけば名前を指定してそのマクロを呼び出すことで同じ処理を繰り返し記述することを避けられます。
「#macro」の定義では最初にマクロ名を、それに続けてスペース区切りで引数となるリファレンス変数名のリストを記述します。引数は0個以上の複数定義することができます。「#if」や「#foreach」と同じくマクロブロックの終わりに「#end」を使用します
次の例は引数がnullか空文字の場合に代替文字として「 」を出力するマクロです。HTMLのtableでは空文字の場合に枠線が表示されないのでそれを避けるために使用できます。
#macro(print $ref) #if (!$ref || $ref.toString().length() == 0) #else $ref #end #end
呼び出し側では「#」に続けてマクロ名を記述し、「()」内にスペース区切りで引数リストを指定します。引数の数が一致していない場合はマクロは無視されます。
#print ("hoge") ##出力は「hoge」 #print ("") ##出力は「 」 #print ($var1) ##var1が未定義か空文字の場合は「 」 #print ($var1 "hoge") ##引数の数が一致しないので無視される
5. エスケープ
リファレンス変数や指示子を意図的にそのまま出力させたい場合は「\」を使用してエスケープすることができます。
\$flow.var1 \#set ($hoge = "aaa")
6. 組込リファレンス その2
ここでは入力ストリームを扱うための組込リファレンスとユーティリティについて説明します。
6.1. $in - 入力ストリーム
「$in」は入力ストリームを扱うためのリファレンスです。「$in」の実体は複数の入力ストリームを扱うコレクションです。
Velocityコンポーネントには複数のコンポーネントからの入力を差し込むことができます。これにより例えば社員一覧と部署一覧をそれぞれRDBGetコンポーネントで取得してその両方をVelocityコンポーネントでまとめてひとつのHTMLページを生成するといった処理が可能になります。
「$in」では複数のストリーム群の中からひとつのストリームを取得するためのメソッドが定義されています。
メソッド | 引数 | 返り値 | 説明 |
---|---|---|---|
array() | int | Stream | インデックスを指定して入力ストリームを取得 インデックスはVelocityコンポーネントへの 入力順序の0ベースインデックスです |
array() | String | Stream | コンポーネント名またはリンク名を指定して入力ストリームを取得 |
size() | なし | int | 入力ストリームの数を取得 |
$in.array(0).text() #foreach ($in.array("RDBGet1").records) ... #end
ただし、実際にVelocityコンポーネントに複数の入力ストリームが差し込まれることはまれで、ほとんどの場合入力ストリームはひとつしかありません。このため次節で説明するStreamオブジェクトのプロパティ/メソッドはすべて$inオブジェクトで再定義されて最初のストリームにフォワードされています。
Javaの疑似コードで書くと以下のようになります。
/** プロパティの取得をストリームにフォワード */ public Object get(String name) { return array(0).get(name); } /** テキストの取得をストリームにフォワード */ public String text() { return array(0).text(); } ...
実質的には$in自体が最初に入力されたStreamオブジェクトであると考えて差し支えありません。
6.2. Stream
Streamは$inまたは$in.array(0)などで取得した入力ストリームを表すオブジェクトです。
Streamでは入力ストリームを扱うためのプロパティ/メソッドが定義されています。
プロパティ | 返り値 | 説明 |
---|---|---|
records | Collection | #foreachでレコード単位でループさせるためのRecordオブジェクトのコレクションを返します |
doc | Document | ストリームがXMLの場合にJDOMのDocumentオブジェクトを返します |
<フィールド名> | Value | 最初のレコードのフィールド値を返します |
メソッド | 引数 | 返り値 | 説明 |
---|---|---|---|
record() | int | Record | インデックスを指定してレコードをを取得します インデックスは0ベースインデックスです |
name() | int | String | インデックスを指定してフィールド名を取得します インデックスは0ベースインデックスです |
text() | なし | String | ストリーム全体を文字列で返します |
toString() | なし | String | text()と同じです |
size() | なし | int | レコード数を返します |
variable() | String | Value | 名前を指定してストリーム変数を返します |
field() | String | Value | 名前を指定して最初のレコードのフィールド値を取得します |
field() | int | Value | インデックスを指定して最初のレコードのフィールド値を取得します インデックスは0ベースインデックスです |
get() | String | Value | field(String)と同じです |
入力ストリームがRecordやCSVの場合には主にrecordsプロパティを使用して各レコードをループで処理します。
入力ストリームがXMLの場合はJDOMのDocumentを取得してそのメソッドや後述のXPathユーティリティを使用して各要素にアクセスできます。
フィールド名を指定して最初のレコードのフィールド値を取得することができます。入力ストリームがParameterListの場合「$in.field1」のように$inから直接フィールド値を取得できるので便利です。
6.3. Record
Recordでは1行分のレコードを扱うためのプロパティ/メソッドが定義されています。
プロパティ | 返り値 | 説明 |
---|---|---|
fields | Collection | #foreachでフィールド単位でループさせるためのValueオブジェクトのコレクションを返します |
No | int | レコードの行番号(0ベース)を返します |
<フィールド名> | Value | フィールド名に対応するフィールド値を返します |
メソッド | 引数 | 返り値 | 説明 |
---|---|---|---|
name() | int | String | インデックスを指定してフィールド名を取得します インデックスは0ベースインデックスです |
size() | なし | int | フィールド数を返します |
field() | String | Value | 名前を指定してフィールド値を取得します |
field() | int | Value | インデックスを指定してフィールド値を取得します インデックスは0ベースインデックスです |
get() | String | Value | field(String)と同じです |
レコード形式のストリームをHTMLのtableで表示させる汎用的なテンプレートの例を以下にしめします。
<table> <tr> ##ヘッダとしてフィールド名を出力 #set ($fieldCount = $in.record(0).size() - 1) #foreach($idx in [0..$fieldCount]) <th>$in.name($idx)</th> #end </tr> ##レコードとフィールドで二重ループして全データを出力 #foreach ($r in $in.records) <tr> #foreach ($v in $r.fields) <td>$v</td> #end </tr> #end </table>
6.4. JDOM
入力ストリームがXMLの場合はStreamオブジェクトのdocプロパティからJDOMのDocumentオブジェクトが取得できます。 JDOMはXMLを扱うためのAPIの一種で、通常のDOMとは異なり子要素をコレクションとして扱えるという特徴があります。このためVelocityとの連携では要素単位でループをまわして処理を行うことが可能です。
以下にJDOMクラスの主なプロパティとメソッドをしめします。名前空間のあるXMLを扱う場合などには、ここに記した以外のメソッドが必要となるので正確なメソッドの一覧を知りたい方はJDOMのJavaDocを参照してください。
プロパティ | 返り値 | 説明 |
---|---|---|
RootElement | Element | 文書要素を返します |
プロパティ | 返り値 | 説明 |
---|---|---|
Name | String | 要素名(ローカルネーム)を返します |
Text | String | 要素内容を返します |
Children | List | 子要素のリストを返します #foreachでループさせることができます |
メソッド | 引数 | 返り値 | 説明 |
---|---|---|---|
getAttributeValue() | String | String | 属性名を指定して属性値を取得します |
getChild() | String | Element | 要素名を指定しその名前を持つ最初の要素を返します |
getChildren() | String | List | 要素名を指定しその名前を持つ要素をListで返します #foreachでループさせることができます |
以下は入力データのXMLをHTMLのtableとして出力する例です。
入力データ -------------- <userData> <record id="1"> <name>山田太郎</name> <tel>.03-xxxx-xxxx</tel> <addr>東京都...</addr> </record> <record id="2"> <name>佐藤花子</name> <tel>.045-xxx-xxxx</tel> <addr>神奈川県...</addr> </record> </userData> -------------- テンプレート -------------- #set ($root = $in.doc.RootElement) <table> <tr> <th>ID</th> <th>氏名</th> <th>電話番号</th> <th>住所</th> </tr> #foreach ($r in $root.getChildren("record")) <tr> <td>$r.getAttributeValue("id")</td> <td>$r.getChild("name").Text</td> <td>$r.getChild("tel").Text</td> <td>$r.getChild("addr").Text</td> </tr> #end </table> -------------- 出力データ -------------- <table> <tr> <th>ID</th> <th>氏名</th> <th>電話番号</th> <th>住所</th> </tr> <tr> <td>1</td> <td>山田太郎</td> <td>.03-xxxx-xxxx</td> <td>東京都...</td> </tr> <tr> <td>2</td> <td>佐藤花子</td> <td>.045-xxx-xxxx</td> <td>神奈川県...</td> </tr> </table>
6.5. $sys - 汎用ユーティリティ
「$sys」では汎用的なユーティリティ関数がメソッドとして提供されます。
プロパティ | 返り値 | 説明 |
---|---|---|
CurrentDate | Date | 現在の日時を返します。 |
メソッド | 引数 | 返り値 | 説明 |
---|---|---|---|
escape() | ValueまたはString | String | 文字列中の「>」「<」「&」「"」をそれぞれ「>」「<」「&」「"」に変換します |
outputXML() | Document [boolean] |
String | JDOMドキュメントを文字列化します。第2引数はインデントの有無です。第2引数は省略可能で省略時はfalseになります |
outputXML() | Element [boolean] |
String | JDOMエレメントを文字列化します。第2引数はインデントの有無です。第2引数は省略可能で省略時はfalseになります |
strToInt() | String | int | 文字列をintに変換します |
strToDouble() | String | double | 文字列をdoubleに変換します |
formatDecimal() | Valueまたは任意の数値型 String |
String | 第1引数の数値を第2引数の書式で文字列化します |
formatDate() | ValueまたはDate String |
String | 第1引数の日付を第2引数の書式で文字列化します |
regexpReplace() | String String String [boolean] [boolean] [boolean] |
String | 第1引数の文字列の中から第2引数の正規表現にマッチした部分を第3引数の文字列で置換します。第4引数は英大文字小文字の区別するかどうか、第5引数はマッチする文字列をすべて置換するかどうか、第6引数は第3引数の文字列でメタ文字( REGEXPREPLACE 関数を参照)を使用できるかどうかをそれぞれ指定します。第4~6引数は省略でき、省略時の値はtrueとなります。 |
formatDecimalメソッドの使用時には注意が必要です。数値の書式化でもっともよく使用される書式文字列はカンマ区切りを行う「#,##0」ですが、「##」はVelocityのコメント開始文字でもあります。このため、
$sys.formatDecimal($num, "#,##0")
のように書式文字列を二重引用符「"」で括って指定した場合には文字列がVelocityによってパースされエラーとなります。この場合は
$sys.formatDecimal($num, '#,##0')
のように書式文字列を単一引用符「'」で括って指定してください。
6.6. $xpath - XPathユーティリティ
「$xpath」ではJDOMに対してXPathによるノードや文字列の選択が行えるユーティリティ関数がメソッドとして提供されます。
createXPathメソッドではJaxenのXPathオブジェクトが作成されます。 createXPath以外のメソッドはすべて作成したXPathオブジェクトのメソッドを実行しているだけです。 テンプレート内で同じXPathを対象要素を変えながら複数回実行する場合はcreateXPathで作成したXPathオブジェクトをリファレンス変数に保存して再利用する方が高速です。
メソッド | 引数 | 返り値 | 説明 |
---|---|---|---|
createXPath() | String | XPath | XPath式を引数としてXPathオブジェクトを作成します |
booleanValueOf() | String Object |
boolean | XPathの結果をbooleanとして取得します。 第1引数にはXPath式を、第2引数にはDocumentまたはElementを指定します。 createXPath(expr).booleanValueOf(object)と同じです。 |
numberValueOf() | String Object |
Number | XPathの結果を数値として取得します。 第1引数にはXPath式を、第2引数にはDocumentまたはElementを指定します。 createXPath(expr).numberValueOf(object)と同じです。 |
selectNodes() | String Object |
List | XPathの結果をListとして取得します。 第1引数にはXPath式を、第2引数にはDocumentまたはElementを指定します。 createXPath(expr).selectNodes(object)と同じです。 |
stringValueOf() | String Object |
String | XPathの結果をStringとして取得します。 第1引数にはXPath式を、第2引数にはDocumentまたはElementを指定します。 createXPath(expr).stringValueOf(object)と同じです。 |
selectSingleNode() | String Object |
Object | XPathの結果をObjectとして取得します。 第1引数にはXPath式を、第2引数にはDocumentまたはElementを指定します。 createXPath(expr).selectSingleNode(object)と同じです。 |
Velocityコンポーネントの「XPath式のプレフィックス」プロパティ値はXPath式の中で変数を参照する場合に使用します。 XPath式の中でもフロー変数、ローカル変数、システム変数が参照可能であり「XPath式のプレフィックス」の値が「fv」である場合、それぞれ以下のように参照できます。
フロー変数 | $fv:flow.var1 |
ローカル変数 | $fv:local.var1 |
システム変数 | $fv:system.FlowName |
外部変数セットはXPath式の中からは参照できません。
プレフィクスが必要となるのはXPathの変数とVelocityのリファレンス変数を区別するためです。もっとも、引数としてXPath式を渡す際に二重引用符「"」を使用すればVelocityによる文字列のパースが有効となるので結局のところ次の2つのXPath式は同じ処理になり、速度差もほとんどありません。
##述語の中でXPathの変数としてフロー変数を参照 $xpath.stringValueOf("/root/record[@name=$fv:flow.var1]/field", $in.doc) ##Velocityによって置換されたフロー変数の値を述語で使用 $xpath.stringValueOf("/root/record[@name='$flow.var1']/field", $in.doc)
後者の式では述語内の式が変数との比較ではなく文字列との比較になるため「$flow.var1」が単一引用符「'」で括られていることにもご注意ください。
7. 拡張クラス
Velocityコンポーネントの拡張タブを使用すると任意のJavaオブジェクトをリファレンス変数に設定することができます。
「定義名」列に「$」を除くリファレンス変数名を、「Javaクラス」列にJavaクラス名を設定すると定義名に指定した名前でテンプレート内から参照できます。
Javaクラスには引数なしのコンストラクタを持つクラスであれば何でも設定できます。 自作のクラスを拡張クラスに設定する場合は、そのjarファイルは「DATA_DIR/system/lib/userlib」に置きます。
以下は「map」という定義名に「java.util.TreeMap」を設定してテンプレート内で項目毎の小計を集計した例です。 計算過程がテンプレート内に出力されるのであまり実用的なサンプルではありませんが、拡張クラスがどのように使用できるかを理解するのには役立つと思います。
入力データ - フィールド名は「Product」「Amount」 -------------- 製品A,10000 製品B,10000 製品C,10000 製品B,10000 製品A,10000 製品B,10000 -------------- テンプレート -------------- <!-- ##計算過程はHTMLコメント内に出力 #foreach ($r in $in.records) #set ($product = $r.Product.strValue()) #set ($sum = 0) ##map.getはnullの場合があるので毎回リセット #set ($sum = $map.get($product)) #set ($sum = $sum + $r.Amount.intValue()) $product, $sum, $!map.put($product, $sum) ##mapに製品毎の累計を設定 #end --> <table border="1"> <tr><th>製品</th><th>小計</th></tr> #foreach ($product in $map.keySet()) ##map内のキーでループ <tr><td>$product</td><td>$map.get($product)</td></tr> #end </table> -------------- 出力データ -------------- <!-- 製品A, 10000, 製品B, 10000, 製品C, 10000, 製品B, 20000, 10000 製品A, 20000, 10000 製品B, 30000, 20000 --> <table border="1"> <tr><th>製品</th><th>小計</th></tr> <tr><td>製品A</td><td>20000</td></tr> <tr><td>製品B</td><td>30000</td></tr> <tr><td>製品C</td><td>10000</td></tr> </table>
8. Velocityの用途
Velocityはテキスト加工を行う処理ならどのような処理にでも適用できます。ASTERIA Warpのフロー作成では、次のようなケースで有効に使用できます。
- HTML
もっとも多く使用されるのがWebアプリケーション作成時のHTML生成です。フローにはWeb画面を作成する機能がないのでほとんどの場合HTML生成はVelocityで行われます。これについては次章で多数の例をとりあげます。 - メール本文
一番最初の例でしめしたとおりメール本文の差し込み的な用途にも多く使用されます。 - SQL
ASTERIA WarpではDBMSからのデータ取得は主にRDBGetコンポーネントで行います。RDBGetコンポーネントではあらかじめSQLBuilderで生成したSELECT文にパラメータを埋め込むことで動的なSQLを発行できますが、出力フィールド(SELECT文のSELECT句で指定するカラム)が変わらなければ「SQL」プロパティに値を差し込むことでSQL全体を差し替えることができます。自由な条件での検索画面を作成する場合などにはVelocityでSQL自体を生成してRDBGetコンポーネントに差し込む方が柔軟な対応が行えます。##ユーザーマスタの検索 SELECT USER_ID, ##ユーザーID DEPT_CODE, ##部署コード USER_NAME ##ユーザー名 FROM USER_MAS WHERE DEL_FLG = '0' ##削除フラグ=0 ##検索条件にユーザー名を指定された場合 #if ($flow.USER_NAME.strValue() != "") AND USER_NAME LIKE '%$flow.USER_NAME%' #end ##検索条件に部署を指定された場合 #if ($flow.DEPT_CODE.strValue() != "") AND DEPT_CODE = $flow.DEPT_CODE #end
ただしこの方法を使用する場合にはSQLインジェクションに注意が必要です。
- CSV
CSVデータを生成するのにもVelocityは使用できます。フローにおいてこの用途で使用されるのは主にMapperコンポーネントですが、Mapperでは行えないような変換がVelocityを用いることで可能になることがあります。
以下はレコードセットの行と列の入れ替えを行う例です。
入力データ フィールド名は「Product」「Amount04」「Amount05」「Amount05」 製品の月毎の数量の集計イメージ -------------- 製品A,100,200,300 製品B,400,500,600 製品C,700,800,900 -------------- テンプレート -------------- #set ($quot = '"') ##文字列内に「"」を埋めるためリファレンスに設定 #set ($r1 = "$quot製品名$quot") #set ($r2 = "$quot4月$quot") #set ($r3 = "$quot5月$quot") #set ($r4 = "$quot6月$quot") #foreach ($r in $in.records) #set ($r1 = "$r1,$quot$r.Product$quot") #set ($r2 = "$r2,$quot$r.Amount04$quot") #set ($r3 = "$r3,$quot$r.Amount05$quot") #set ($r4 = "$r4,$quot$r.Amount06$quot") #end $r1 $r2 $r3 $r4 -------------- 出力データ -------------- "製品名","製品A","製品B","製品C" "4月","100","400","700" "5月","200","500","800" "6月","300","600","900"
9. 実践的な例
アステリア社内ではいくつかの社内システムがASTERIA Warpを使用して作成されていますが、実はそのうちのかなりの数が次のようなパターンのフローとなっています。
処理内容としては売上を管理しているSFAのデータベースから検索条件にあうデータを検索してHTMLにテーブル形式で結果を表示しているだけです。 出力画面のイメージは次のようになります。
検索条件と検索結果を同一HTML上に表示しているので作成するフローはひとつだけですみます。検索条件や検索結果に含める情報に対するリクエストは日々変化していきますが、そうした仕様変更への対応で行う作業も
- HttpStartコンポーネントとフロー変数に検索条件を追加
- SQLの変更
- 出力HTMLの変更
のみであり容易に行えます。実際にはSQL生成とHTML生成のVelocityテンプレートはファイルとして外部に保存しているのでフローには一切手を加えずに対応できることも少なくありません。
ここではこの枠組みの中で実際に使用されている例をいくつかご紹介します。
9.1. 配列を使ったselect要素の作成
選択リストが固定のselect要素は、配列を利用すると簡単にユーザーの選択したアイテムを選択状態にできます。
テンプレート -------------- #set ($areaNameArray = ["東日本","西日本","すべて"]) #set ($areaCodeArray = ["1","2","3"]) <select name="area"> #foreach ($idx in [0..2]) #set ($area = $areaCodeArray.get($idx)) <option value="$area" #if ($area == $flow.area.strValue()) selected #end >$areaNameArray.get(0)</option> #end </select> 出力データ (例示のための改行は除く) -------------- <select name="area"> <option value="1" >東日本</option> <option value="2" selected >東日本</option> <option value="3" >東日本</option> </select>
9.2. 範囲を使ったselect要素の作成
select作成の応用編として年度を選択するselectを考えます。売上データなどの場合、現在の年度より大きい年度のデータは存在しないのでそこまでがリスト化されていると便利です。 $sys.CurrentDateと範囲を利用すると現在の年度に応じてリストの変化するselect要素を生成できます。
テンプレート -------------- #set ($year_start = 2007) ##リスト化する一番過去の年度 #set ($year_end = $sys.CurrentDate.Year + 1900) ##現在の年 <select name="year"> #foreach ($year in [$year_end..$year_start]) <option value="$year" #if ($flow.year.intValue() == $year) selected #end >$year</option> #end </select> 出力データ -------------- <select name="year"> 2010 - 2007 <option value="2010" >2010</option> <option value="2009" selected >2009</option> <option value="2008" >2008</option> <option value="2007" >2007</option> </select>
9.3. テーブル内でのstyleの変更
検索結果のレコードセットをtable形式で表示するのは、#foreachを使用すれば簡単にできます。 データの内容によって例えば背景色を変えるなどstyleを変更することもcssのclass定義との組み合わせで可能です。
テンプレート -------------- <table border="1"> <tr> <th>ID</th> <th>案件名<th> <th>ステータス<th> <th>金額</th> </tr> #foreach ($r in $in.records) #set ($class = "normal") #if ($r.status.strValue() == "成約") #set ($class = "commit") #end <tr class="$class"> <td>$r.id</td> <td>$r.project</td> <td>$r.status</td> <td>$sys.formatDecimal($r.amount, '#,##0')</td> </tr> #end </table> 出力データ -------------- <table border="1"> <tr> <th>ID</th> <th>案件名<th> <th>ステータス<th> <th>金額</th> </tr> <tr class="normal"> <td>1</td> <td>案件1</td> <td>提案中</td> <td>1,000,000</td> </tr> <tr class="commit"> <td>2</td> <td>案件2</td> <td>成約</td> <td>1,000,000</td> </tr> </table>
9.4. サブフローによるヘッダーやフッターの分離
図1のフローではHTML生成のVelocityコンポーネントの後にSubFlowコンポーネントが置かれていますが、 実はこのサブフローもその中身は
Start -> Velocity -> EndResponse
という単純なフローです。以下にそのサブフローで使用しているテンプレートをしめします。
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=$encoding"> <link rel="shortcut icon" href="../favicon.ico"> <link type="text/css" href="../style/common.css" rel="stylesheet"/> <script type="text/javascript" src="../scripts/common.js"/> <title>$flow.title</title> $flow.header </head> <body> <h1>$flow.title</h1> <div style="float:right"> <a href="../index.html" target="_top">メニュー</a> </div> $in.text() <hr> <div> <a href="../index.html" target="_top">メニュー</a> </div> </body> </html>
テンプレート内容は以下のようになっています。
- 画面タイトルをフロー変数「title」から取得し、画面最上部にh1タグで表示
- メニュー画面へのリンクを画面の上下に表示
- 共通的なスタイルシートとスクリプトにリンク
- 入力ストリームをbody内部にまるごと差し込み
- スタイルやスクリプトの追加がある場合はフロー変数「header」から差し込み
このようにすることで全ての画面に共通する枠組みと個別のコンテンツを切り離し、個別のフローでは処理結果の記述に集中することができます。
リンクするcssやjsファイル、イメージファイルなどは「<ユーザーのHOMEディレクトリ>/htdocs」以下に配置します。 (「htdocs」の部分はフローサービス管理コンソールの設定で変更することができます。)
例えば「/app1」というコンテキストパスのユーザーが「htdocs/index.html」というhtmlファイルを作成した場合、そのhtmlには
http://<ホスト名>:<ポート>/app1/index.html
というURLでアクセスできます。 URL実行されたフローからリンクする場合の相対パスは、URL実行設定のパス内に含まれる「/」の数によって変わりますが、 デフォルト設定から変更せず「/Project1/Flow1」のようなパスを設定した場合は上記例のように「..」で一階層上に戻ります。
9.5. AJAXとの連携
最後にAJAXを使用してCSVを返すフローから動的にselectを作成する例を紹介します。 HTML内にスクリプトを埋め込む場合、そのスクリプト自体もテキストなのでVelocityで動的に生成することができます。 これを利用すればロード時に実行する処理を切り替えたり、AJAXで動的にフローを実行してその結果をページ内に埋め込むなどページ作成時の選択肢が大きく広がります。
このサンプルでは「部署」「氏名」という2つの選択リストを表示し、部署の選択に応じて氏名のリストを切り替えています。
部署、氏名ともに選択リストはCSVを返すフローを実行してその結果から作成しています。 (実際にはこれらのCSVはデータベースから取得して作成するイメージですが、このサンプルでは単純にテストデータを返しています。) そして、部署が変更された時にはそのonchangeイベントでAJAXリクエストを発行し、氏名のリストを再作成します。
処理の中核はcreateSelectByFlowという関数です。
//CSVを返すフローからselect要素を作成 //name select要素のname属性 //sel 選択状態にするoptionのvalue //url 実行するフローのURL //params 実行するフローに渡すパラメータのハッシュ function createSelectByFlow(name, sel, url, params) { ...
ロード時にselect要素を作成するにはページ内にこの関数をdocument.writeで埋め込みます。
<script>
document.write(createSelectByFlow("deptCode", "$flow.deptCode",
"deptList"));
</script>
スクリプト内にフロー変数を参照するリファレンス変数が埋め込まれていますが、これはVelocityによって置換されるのでブラウザが実行する際にはフロー内で設定された値となっています。
あるselectの変更に応じて別のselectを変更するにはonchangeイベントにその処理を行います。
var empSpan = document.getElementById("empSpan"); //部署の変更で社員リストを動的に変更 var deptList = document.form1.deptCode; deptList.onchange = function() { var deptCode = deptList.value; empSpan.innerHTML = createSelectByFlow("empCode", "0", "empList", {deptCode: deptCode}); };
このサンプルは以下のリンクからダウンロードできます。
ダウンロードしたら「VelocitySample.xfp」をユーザーのホームディレクトリにコピーし、「triggers.xml」をインポートして実行設定を行ってください。 実行のためのURLは「<ユーザーコンテキスト>/VelocitySample/selectEmp」です。
設定を容易にするためすべてのスクリプトをVelocityコンポーネント内で直接入力しましたが、createSelectByFlow関数を含むスクリプトを外部ファイルとしておけば実運用で応用することも可能です。
JavaScriptとの連携を考えた場合、ここで紹介した例にとどまらずVelocityの応用範囲は無限に広がります。jQueryなどのスクリプトライブラリとの親和性も高いので是非自分なりの活用方法を発見してください。