WebStencilsの概要
RAD Studio 12.2では、WebStencilsが導入されました。これは、サーバーサイドスクリプトを使ってHTMLファイルを処理し、WebBrokerやRAD Serverと統合できる機能です。この柔軟な機能により、RAD Studioのサーバーサイドアプリケーションでデータを抽出・加工し、それを活用して任意のJavaScriptライブラリに基づいたモダンなWebサイトを構築できるようになります。つまり、自分に合った自由なWebサイト作成が可能です。
WebStencilsの主な目的は、WebBroker、DataSnap、RAD ServerといったWeb技術を採用しながら、サーバーサイドスクリプトによるナビゲーション型Webサイトを作成する手助けをすることにあります。たとえば、標準的なツールでHTMLページを生成し、好きなCSSやJavaScriptライブラリを自由に使いつつ、アプリケーション側で生成されたデータ(たとえばデータベースクエリ結果)をページに組み込むことが可能です。
さらに、WebStencilsはHTMXを利用したWeb開発にも適しています。HTMXページはサーバーサイドのコード生成とRESTサーバーによるコンテンツ更新に対応しており、DelphiのWeb技術が提供するページ生成やREST APIは非常に高品質です。
WebStencilsの構文
RAD StudioにおけるWebStencilsのスクリプト言語は、次の2つの要素に基づいたシンプルな構文です:
@記号- 中括弧
{ }
@ 記号の使用
WebStencilsでは、HTML/XMLタグなどではなく、@記号を特別なマーカーとして使用します。この記号に続くものは以下のいずれかです:
- オブジェクトまたはフィールドの名前
- 特殊な処理キーワード
- 別の
@記号
@記号の使用は柔軟であり、ヘッダーなどの特定の使用シナリオでは二重にする必要がない場合があります。基本的な値のアクセスはドット記法(object.value)に基づいています。たとえば次のように記述します。
@object.value
ここでのobjectは、スクリプト実行時に登録されたサーバーアプリケーションのオブジェクトのローカル名を指します。もしくは、OnGetValueイベントハンドラ内で解決されることもあります。 value部分には、プロパティ名(汎用オブジェクトの場合)、フィールド名(TDataSet派生オブジェクトの場合)、またはOnGetValueイベントハンドラで受け渡された文字列が使われます。
@\valueという構文を使います。ネストされたオブジェクトやデータセットのアクセス
RAD Studio 12.3から@記号を使って、ネストされたオブジェクトやデータセットにもアクセスできます。例は以下の通りです:
- ネストされたクラスとオブジェクト:
@MyObject.SubObject.ProperyName
- ネストされたデータセット内のテーブル:
@MyDataSet.DataSetField.SubField
中括弧 { } の使用
2番目の記法は、{ と }を使って、条件付きまたは繰り返しブロックを示します。
HTMLファイル内でそれ以外に中括弧 が使われていても無視され、WebStencilsの条件文に続く場合のみ処理されます。
ドット記法による値のアクセス
以下のHTML WebStencilsスニペットを例にします:
<h2>GetValue</h2>
<p>Value obtained from data: @data.name</p>
<p>Value obtained with request @@data.value: @data.value</p>
ここで @@ と記述すると、実際の出力では @ ひとつが表示されます(つまりエスケープ処理です)。一方で @ data.name のように書けば、そのフィールドの実際の値が展開されます。
また、オブジェクト名と値名はともに有効なDelphi識別子でなければならず、空白や数字始まりは禁止されています(ただし、アンダースコアや数字の使用はOKです)。
OnValue イベントハンドラ
WebStencils では、OnValueイベントハンドラを使ってデータを提供することも可能です。たとえば次のように書きます:
procedure TWebModule13.WebStencilsValueFromEventValue(Sender: TObject;
const ObjectName, FieldName: string; var ReplaceText: string;
var Handled: Boolean)
begin
if SameText (ObjectName, 'data') then
begin
Handled := True;
ReplaceText := 'You requested ' + FieldName;
end;
end;
このコードではオブジェクト名(ドットの前の部分)だけをチェックしていますが、FieldName(ドットの後ろの部分)もチェックすることを推奨します。
スクリプト変数ディクショナリにオブジェクトを使用する
もう一つの方法として、オブジェクトを直接提供して値を渡すこともできます。 たとえば、TMyObjectクラスにNameプロパティを持っている場合、以下のようにします:
procedure TWebModule1.WebModule1WebActionItem2Action(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
WebStencilsProcessor1.InputLines.Text := '''
<h2>GetValue</h2>
<p>Value obtained from data: @world.Name</p>
''';
var MyObj := TMyObject.Create();
MyObj.Name := 'Hello hello';
WebStencilsProcessor1.AddVar('world', MyObj, True);
Response.Content := WebStencilsProcessor1.Content;
Handled := True;
end;
この例では、
「Value obtained from data: Hello Hello」
という結果になります。オブジェクトは'world'という名前で登録され、スクリプト内でこの名前を使って参照します。
式(Expressions)
WebStencilsでは、次のような式をサポートしています:
@ (<expression>)- 変数マーカー。式は必ず丸括弧で囲みます。@if (<expression>)- if命令。式は必ず丸括弧で囲みます。@Import (<式>),@LayoutPage (<式>),@AddPage (<式>)- ファイル操作コマンド。式は中括弧で囲みます。
以下はこの違いを示すサンプルです。
WebStencilsProcessor1.InputLines.Text := '''
<h2>GetExpression</h2>
<p>Value obtained from data: @(world.value*2)</p>
''';
MyObj.Value := 2;
この出力結果は次のようになります:
"Value obtained from data: 4"
さらに、カスタム関数を登録して式を拡張することも可能です。例えば、文字列を結合する「Concat」関数を登録するコードは以下の通りです:
uses
System.Bindings.EvalProtocol, System.Bindings.Methods;
TBindingMethodsFactory.RegisterMethod(
TMethodDescription.Create(
MakeInvokable(function(Args: TArray<IValue>): IValue
begin
Result := TValueWrapper.Create(
Args[0].GetValue.AsString + Args[1].GetValue.AsString);
end),
'Concat', 'Concat', '', True, '', nil));
WebStencils のスクリプト内では、次のような式で使えます:
@(Concat('aaa', 'bbb']]
データセットの利用
オブジェクトではなく、データセットの現在のレコードを利用することも可能です。この場合、コードは次のようになります:
procedure TWebModule13.WebModule13waCompanyAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse;
var Handled: Boolean];
begin
ClientDataSet1.Open;
if not WebStencilsValueFromObject.InVars ('dataset'] then
WebStencilsValueFromObject.AddVar(
'dataset', ClientDataSet1, False]; // False = do not destroy
Response.Content := WebStencilsValueFromObject.Content;
ClientDataSet1.Close;
end;
対応するHTMLは、レコードのフィールドを参照できます。以下はサンプル出力例です:
<div class="list-group w-50">
@foreach dataset {
<a href="/[email protected]" class="list-group-item
list-group-item-action" aria-current="true">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">@loop.company</h5>
<small>@loop.country</small>
</div>
<p class="mb-1">@loop.city</p>
<small>@loop.state</small>
</a>
}
モジュール変数の使用
特定のオブジェクトをWebStencilsProcessorに登録する代わりに、複数のオブジェクトを含んだ「モジュール」を登録することもできます。
モジュールとは、関連するオブジェクトや関数の集まりのことを指します。モジュール内のフィールドやプロパティに[WebStencilsVar]属性を付けることで、WebStencilsProcessorにどのデータを渡すか指定できます。
たとえば、FDMemTable1(TFDMemTable型)のコンポーネントにLastNameフィールドがある場合、次のように記述します:
WebStencilsProcessor1.InputLines.Text := '''
<h2>GetTableData</h2>
<p>Value obtained from table: @(FDMemTable1.LastName)</p>
''';
WebStencilsProcessor1.AddModule(self);
この例では、最初のレコードのLastNameフィールドの値が取得されます。すべてのレコードを表示したい場合は、@forループを使うことができます。
WebStencil キーワード
WebStencilsエンジンでは、以下の特別なタグを「キーワード」として扱います。
(注意:これらの名前はスクリプト変数名として使用できません)
| キーワード | 説明 |
|---|---|
@query
|
HTTPクエリパラメータを読み取るために使用します。 |
@page
|
ページ名やURL引数にアクセスします。詳しくはPathTemplateプロパティを参照してください。 |
@lang
|
翻訳機能を利用します。この場合、OnValueイベントではなく、OnLangイベントがトリガーされます。 |
@* .. *@
|
スクリプト内にコメントを挿入します(生成されるHTMLには含まれません)。 |
@if object.value { … } [@else { … }]
|
値がtrueの場合のみ、中括弧で囲まれた以下のブロックを実行します。
|
@if (<expression>)
|
|
@if not object.value { ... } [@else { … }]
|
値がtrueでない場合のみ、中括弧で囲まれた以下のブロックを実行します。 @if obj1.ValueBelowTen {
<p>@obj1.name <span> has a value of </span> @obj1.value</p>
}
|
@ForEach (var object in list) { … }
|
列挙子内の要素の数だけ、中括弧の中で以下のブロックを実行します。 現在サポートしているのは以下の通りです:
|
@ForEach object { … }
|
列挙子内の要素の数だけ、中括弧の中で以下のブロックを実行します。 現在、データセットとTObjectListをサポートしており、これらはスクリプト変数として追加する必要があります。 |
@loop
|
列挙(リストまたはデータセット)の現在の要素を参照するためにブロック内で使用する。オブジェクトの名前を使用せずに、プロパティ名またはフィールド名を続けて使用します。 |
@Import
|
外部ファイルを、ファイル名が拡張子.HTMLである外部ファイルの特定の場所にマージすることができます。 形式は次の通りです: @import filename [ {<alias1> = <var1> [..., <aliasN> = <varN>]} ]
|
@Scaffolding
|
scaffoldingのプレースホルダ。アプリケーションのデータ構造(オブジェクトのプロパティやデータベースのフィールド)に基づいて動的にHTMLを生成する仕組みです。サーバサイドで生成されたHTMLには、データにアクセスするための他のタグを含めることができます。 以下は、データベースの各フィールドに対してOnScaffoldingイベント・ハンドルで生成される単純なHTMLレイアウトの例です: procedure TWebModule1.ProcessorCompanyScaffolding(Sender: TObject;
const AQualifClassName: string; var AReplaceText: string);
begin
if SameText (AQualifClassName, 'Company') then
begin
AReplaceText := '';
for var I := 0 to ClientDataSet1.Fields.Count -1 do
AReplaceText := AReplaceText + '<p>'+
ClientDataSet1.Fields[I].FieldName +
': @dataset.' + ClientDataSet1.Fields[I].FieldName + '</p>';
end;
end;
|
@LoginRequired
|
ログインが必要で、ユーザーがログインしていない場合、実行を停止します。 |
@LoginRequired (<role>)
|
このエンジンは、ユーザーがログインしていることをチェックするだけでなく、ユーザーが特定のロールを持っているかもチェックします。 例: @LoginRequired (admin) |
Importエイリアスの使い方
エイリアスを使って変数名をマッピングする例です:
@import one {@o1 = @object, @d1 = @data} Hello
このコード例では、@objectと@dataが実行されるスクリプトの実際の変数である。エイリアス@o1と@d1は 「one 」スクリプトのスクリプト変数であり、このスクリプトでは通常の変数としてアクセスできる。このように、スクリプトが期待する名前をエイリアスとして指定することで、QAスクリプトに異なる名前の変数を渡すことができます。
WebStencilsテンプレート: LayoutとBody
テンプレート・エンジンの必須機能は、共有HTMLテンプレートをページの実際のコンテンツにマージする機能です。これは(.NET版のように)4つの特別なシンボルを使って実現されます:
- @LayoutPage filename:このコマンドは特定の HTML ページ (一般的にはトップ) で使用され、特定のページの構造として使用するテンプレートファイルを示します。複数のページは同じテンプレートを共有できますが、プロジェクト(TWebStencilsEngineを使用していても)や仮想フォルダ内のすべてのページが同じテンプレートを使用する必要はありません。
- 'Note: これは1つのページで1回だけ使うようにしてください。複数の@LayoutPageを使おうとすると、例外によってブロックされます。
- @RenderBody: このコマンド(パラメータなし)は、特定のページの実際のコンテンツを配置する場所を示すテンプレートファイルのプレースホルダです。
以下は@LayoutPage と @RenderBodyの使い方の例です:
Test.html
@LayoutPage BaseTemplate
<h2>Page Test3</h2>
<p>Local data param one is @page.name (the page name)</p>
BaseTemplate.html
<html lang="en">
<head>
<!-- Bootstrap CSS →
<link href="https://.../bootstrap.min.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-md bg-dark mb-4">...
<main class="container">
<div class="bg-light p-5 rounded">
@RenderBody
</div>
</main>
</body>
</html>
さらに、特定のドキュメント側からテンプレートに対して、追加のヘッダー情報(たとえば、ページごとに必要なJavaScriptファイルや追加のCSS)を挿入することもできます。 この場合、仕組みは逆方向になります。
- @ExtraHeader { ... }: 特定のHTMLページ内で使用し、そのページ専用の追加ヘッダー情報(コードブロック)を指定するために使います。
- @RenderHeader: テンプレートファイル内(通常はHTMLの<head>セクション)で、ページから渡された追加ヘッダー情報を挿入する位置を示します。
WebStencils Components
WebStencilsパッケージには、2種類のコンポーネントがインストールされます。 1つは単一ファイルプロセッサ、もう1つは個別のプロセッサをインスタンス化し、それらに対してグローバル設定を提供できるエンジンです。
このコアコンポーネントは、HTMLファイルを受け取り、WebStencils記法(@タグ)を処理し、プレーンなHTMLに変換することができます。
TWebStencilsProcessorクラスは、個別のファイル(通常はHTML拡張子)と、もしあれば関連するテンプレートを処理します。
このプロセッサは、単体で使用することもでき、TWebActionItem.Producerに割り当てて使うことも可能です。
また、ファイルディスパッチャーから返されたテキストファイルに対して、TWebStencilsEngineによってポストプロセッサ(後処理)として作成・呼び出されることもあります。
WebStencilsEngineコンポーネント
このエンジンコンポーネントは、次の2つのシナリオで使用できます:
- 1つ以上の TWebStencilsProcessor コンポーネントに接続し、共有の設定と動作を提供する。
- 必要に応じてTWebStencilsProcessorコンポーネントを生成し、Webモジュール上にはこのコンポーネントだけを配置する。
RAD ServerでWebStencilsを使う
WebStencilsのテンプレートライブラリは、RAD Serverアプリケーション内でも使用できます。 この場合、既存のTEMSFileResourceコンポーネントとTWebStencilsEngine コンポーネントを組み合わせて使用することが推奨されています。 TEMSFileResourceコンポーネントはファイルシステムのマッピングを担当し、TWebStencilsEngineコンポーネントはHTTPマッピングとテンプレート処理を管理します。 これら2つのコンポーネントは、WebStencilsEngineのDispatcherプロパティを使用して接続されます。 以下は、RAD ServerのWebモジュールに2つのコンポーネントを追加する設定例です:
object html: TEMSFileResource
PathTemplate = '..\..\html\{filename}'
end
object WebStencilsEngine1: TWebStencilsEngine
Dispatcher = html
PathTemplates = <
item
Template = '/'
Redirect = '/test1.html'
end
item
Template = '/list'
Redirect = '/companylist.html'
end
item
Template = '/company'
OnPathInit = WebStencilsEngine1PathTemplates2PathInit
end
...>
end
PathTemplatesは、受信したURLを実際のファイルにマッピングするために使用されます。デザイン時における設定は、次のようになります:
さらに、データモジュール側でも上位レベルのマッピング設定を行う必要があります:
type
[ResourceName('testfile')]
TTestfileResource1 = class(TDataModule)
[ResourceSuffix('./')]
[ResourceSuffix('get', './')]
[EndpointProduce('get', 'text/html')]
html: TEMSFileResource;
複数の異なるパス(上記とは異なるパターン)をマッピングする場合は、追加のTEMSFileResourceコンポーネントを作成してください。 TWebStencilsEngineコンポーネントは、対応するプロパティに指定できるDispatcherが1つだけに制限されています。 複数のDispatcherを関連付けるには、次のコードを使用します:
AddProcessor(html2, WebStencilsEngine1);