WebStencils

提供: RAD Studio
移動先: 案内検索

RAD Studio バージョン12.2 では、WebStencils(サーバー側のスクリプト ベースのインテグレーション)と共に、WebBroker および RAD Server テクノロジーへの HTML ファイルの処理機能が導入されました。この柔軟な機能により、RAD Studio のサーバー側アプリケーションによって抽出や処理されたデータを活用し、あらゆるJavaScript ライブラリをベースにした、最新 のWeb サイトを開発できます。これにより、独自のニーズに合わせた Web サイトを自由に作成できます。

WebStencils の主な目的は、Web テクノロジー(WebBroker、DataSnap、RAD Server)を採用し、サーバー側スクリプトを提供することで、ナビゲーション機能を備えた Web サイトの構築を支援することです。例えば、WebStencils を利用することで、標準ツールで HTML ページを生成し、任意の CSS および JavaScript ライブラリを採用しつつも、データベース クエリの結果などアプリケーションから生成されたページからデータを追加する機能は、保持することができます。

さらに、WebStencils は、Web 開発のひとつのソリューションとして、HTMX の優れた基盤となります。 HTMX ページはサーバー側コード生成からメリットを享受し、コンテンツの更新のために REST サーバーにフックすることができます。 Delphi Web テクノロジは、ページ生成と REST API を、非常に高い品質レベルで提供することができます。

WebStencil の構文

RAD Studio の WebStencils スクリプト言語は、2 つの要素をベースとしたシンプルな構文となっています:

  • @
  • 波括弧 { }

@ シンボルの利用

WebStencils は、@ シンボルを、HTML/XML タグやその他の表記としてではなく、特別なマーカーとして使用します。@ シンボルの後には、次が続きます:

  • オブジェクトまたはフィールドの名前
  • 特殊処理のキーワード
  • 別の @ シンボル
    ヒント: @ シンボルの利用はさらに柔軟です。そのため、一部のユースケース シナリオの内部では(ヘッダー内など)、2 重にする必要はない場合があります。

一般的な値へのアクセスは、object.value のようにドット表記に基づいているため、次のようなタグが表示されます:

@object.value

オブジェクトの名前は、シンボリック ローカル名で、登録プロセスでスクリプト実行に割り当てられた、実際のサーバー アプリケーション オブジェクトと一致するものです。またこれは、OnGetValue イベント ハンドラの処理中に、コード内で解決することもできます。

値は、プロパティの名前(汎用オブジェクトの場合)、フィールドの名前(オブジェクトが TDataSet から継承している場合)、プロセッサに関連付けられた OnGetValue イベント ハンドラに渡される文字列、で構成されます。

メモ: WebStencil で処理された値の出力は、TNetEncoding.HTML.Encode メソッドでエンコードされます。構文 @\value を使用することで、エンコードを抑制することができます。

ネストされたオブジェクトやデータセットへのアクセス

次のシナリオで推奨されている構文を使用することで、@ シンボルを、ネストされたオブジェクトやデータセットへアクセスに使用することもできます。

  • ネストされたクラスやオブジェクト:
    @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>

@@ シンボルはスキップされ(出力には @ が1つだけと想定しています)、1 つのシンボルが処理を発生させます。 また、オブジェクト名と値名の両シンボルは、有効な Delphi 識別子である必要があり、スペースを含めたり、数字で始まったりすることはできない点に注意してください。同じ名前の一部に、アンダースコアや数字を使用することは可能です。

OnValue イベント ハンドラ

OnValue イベント ハンドラを備えたコンポーネントを使用shちえ、データを提供することができます: 例:</br>

Delphi:

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(シンボルのドット後の部分)もチェックすることが推奨されています。

C++:

void __fastcall TWebModule13::WebStencilsValueFromEventValue(TObject *Sender,
const String ObjectName, const String FieldName, String &ReplaceText, bool &Handled)
{
if (SameText(ObjectName, "data"))
{
Handled = true;
ReplaceText = "You requested " + FieldName;
}
}

上記の例では、String は、C++Builder では、Delphi 互換の UnicodeString です。また、SameText は大文字/小文字を区別しない文字列比較関数で、SysUtils より提供されています。最後に、&ReplaceText&Handled は、変更を可能とし、Delphi の var の動作と一致するよう、参照で渡されます。

スクリプト変数辞書でのオブジェクトの利用

別の方法としては、オブジェクトを使用して値を提供する方法が挙げられます。この場合、オブジェクトを設定し、アクションのプロデューサと関連づける必要があります。

次はサンプルの完全版で、Name 文字列プロパティを持つ TMyObject クラスを想定しています:Delphi:

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' で登録されており、後から WebStencils スクリプトで使用されるので、留意しておいてください。

C++:

void __fastcall TWebModule1::WebModule1WebActionItem2Action(TObject *Sender,
TWebRequest *Request, TWebResponse *Response, bool &Handled)
{
WebStencilsProcessor1->InputLines->Text =
"<h2>GetValue</h2>\n"
"<p>Value obtained from data: @world.Name</p>";

TMyObject *MyObj = new TMyObject();
MyObj->Name = "Hello hello";

WebStencilsProcessor1->AddVar("world", MyObj, true);

Response->Content = WebStencilsProcessor1->Content;
Handled = true;
}

次は、サポートされている式です:

  • @ (<expression>) - 変数マーカー。式は括弧で囲む必要があります。
  • @if (<expression>) - "if" コマンド。式は括弧で囲む必要があります。
  • @Import (<expression>)@LayoutPage (<expression>)@AddPage (<expression>) - ファイル コマンド。式は括弧で囲む必要があります。

次の例では、上記のオプションとの違いのみを示しています:

WebStencilsProcessor1.InputLines.Text := '''
<h2>GetExpression</h2>
<p>Value obtained from data: @(world.value*2)</p>
''';
MyObj.Value := 2;

出力結果は次のとおり:

"Value obtained from data: 4"

シンプルな式に加え、システムを独自の関数呼び出しで拡張できる点に注目してください。次は、2 つの文字列パラメータを受け取る Concat 関数を登録するコード サンプルです:</br>

Delphi:

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']]

C++:

#include <System.Bindings.EvalProtocol.hpp>
#include <System.Bindings.Methods.hpp>
#include <System.Rtti.hpp>
#include <System.SysUtils.hpp>

using namespace System::Bindings::EvalProtocol;
using namespace System::Bindings::Methods;
using namespace System::Rtti;

class TValueWrapper : public TInterfacedObject, public IValue
{
private:
TValue FValue;

public:
__fastcall TValueWrapper(const TValue &AValue) : FValue(AValue) {}

virtual TValue __fastcall GetValue() { return FValue; }

// Implement other IValue methods as needed...
};

void RegisterCustomBindingMethod()
{
TBindingMethodsFactory::RegisterMethod(
new TMethodDescription(
MakeInvokable([](const TArray<IValue*> &Args) -> IValue*
{
String s1 = Args[0]->GetValue().ToString();
String s2 = Args[1]->GetValue().ToString();
return new TValueWrapper(TValue::From<String>(s1 + s2));
}),
"Concat", "Concat", "", true, "", nullptr));
}

DataSet の利用

別の選択肢として、オブジェクトではなく、データセットの現在のレコードを使用する方法があります。この場合、コードは次のようになります: </br>

Delphi:

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;

C++:

void __fastcall TWebModule13::WebModule13waCompanyAction(TObject *Sender,
TWebRequest *Request, TWebResponse *Response, bool &Handled)
{
ClientDataSet1->Open();

if (!WebStencilsValueFromObject->InVars("dataset"))
{
WebStencilsValueFromObject->AddVar("dataset", ClientDataSet1, false); // false = do not destroy
}

Response->Content = WebStencilsValueFromObject->Content;

ClientDataSet1->Close();
}

対応する 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 がどのようにデータを処理すべきが指定することができます。

たとえば、データ モジュールに [WebStencilsVar] でマークされたTFDMemTable 型の FDMemTable1 コンポーネントがあり、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) { … } 列挙子内の要素の数と同じ回数、それに続く括弧に囲まれたブロックを実行します。
現在サポートされているのは次のとおりです:
  • データセット
  • TObjectList - プロセッサ ディクショナリに追加される必要があります。
  • GetEnumerator メソッドを持つオブジェクト - 列挙型はオブジェクト値を返します。
@ForEach object { … } 列挙子内の要素の数と同じ回数、それに続く括弧に囲まれたブロックを実行します。
現在サポートされているのは、Datasets と TObjectList で、スクリプト変数として追加される必要があります。
@loop 列挙型(リストやデータセット)の現在の要素を参照するために、ブロックの中身を使用します。オブジェクトの名前を使用するのではなく、プロパティやフィールドの名前が続きます。
@Import 外部ファイルを、ファイル名が .HTML 拡張子を持つ外部ファイルの特定の位置にマージすることができます。
形式は次のとおり:
@import filename [ {<alias1> = <var1> [..., <aliasN> = <varN>]} ]
@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 one {@o1 = @object, @d1 = @data} Hello

このコードでは、@object@data は、実行スクリプト内の実際の変数です。エイリアス @o1@d1 は "one" スクリプトのスクリプト変数であり、このスクリプト内では通常の変数としてアクセスできます。これにより、スクリプトがエイリアスとして想定する名前をつけて、QA スクリプト変数に異なる名前で渡すことができます。

WebStencils テンプレート: レイアウトと本文

テンプレートエンジンに必須の機能の一つは、共有 HTML テンプレートをページの実際のコンテンツと結合する機能です。これは(.NETバージョンと同様に)4つの特殊記号を使用して実現されます:

  • @LayoutPage ファイル名: このコマンドは、特定の HTML ページ(通常はページ上部)で使用され、そのページの構造としてどのテンプレート ファイルを使用するかを指定します。複数のページで同じテンプレートを共有できますが、プロジェクト内のすべてのページ(TWebStencilsEngine を使用している場合でも)または仮想フォルダ内のすべてのページで、同じテンプレートを使用する必要はありません。
メモ: これは、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 のヘッダー セクション)内で、ページの追加ヘッダーを追加する場所を指定します(存在する場合)。
メモ: 両オプションとも、省略可能です。

WebStencils コンポーネント

WebStencils パッケージは、2 つのコンポーネントをインストールします: それは、単一ファイル プロセッサとエンジンで、個々のプロセッサをインスタンス化し、スタンドアロン プロセッサ、または、それらに手動で接続されたプロセッサに対して、グローバル設定を提供することができます。

このコア コンポーネントは HTML ファイルを受け取り、WebStencils 表記が組み込まれており、@ タグを処理すると、それをプレーンな HTML に変換します。

TWebStencilsProcessor クラスは、個々のファイル(通常は HTML 拡張子)と、その関連テンプレート(あれば)を処理します。プロセッサは、スタンドアロンで使用して TWebActionItem.Producer に割り当てられることも、もしくは、ファイル ディスパッチャによって返されるテキスト ファイルのポストプロセッサとして、TWebStencilsEngine によって作成および呼び出されるようにすることもできます。

WebStencilsEngine コンポーネント

engine コンポーネントは 2 つのシナリオで使用できます:

  • 1 つ以上の WebStencilsProcessor コンポーネントに接続して、共有設定と動作を提供
  • 必要に応じて WebStencilsProcessor コンポーネントを作成し、このコンポーネントのみを Web モジュールに配置

WebStencils を RAD サーバー で利用する

WebStencils テンプレート ライブラリは、RAD サーバー アプリケーションでも使用できます。既存の TEMSFileResource コンポーネントは、TWebStencilsEngine コンポーネントと組み合わせることが推奨されます。前者はファイル システムのマッピングする一方、後者は HTTP マッピングとテンプレート処理を管理します。コンポーネントは、WebStencil エンジンの Dispatcher プロパティを使用して接続されます。

下の例は、RAD サーバー 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 を実際のファイルにマッピングするのに使用されます。設定が設計時にはどのようになっているかは以下のとおりです:

=link

さらに、date モジュールで高レベルのマッピングを構成する必要があります:

type
[ResourceName('testfile')]
 TTestfileResource1 = class(TDataModule)
[ResourceSuffix('./')]
 [ResourceSuffix('get', './')]
 [EndpointProduce('get', 'text/html')]
 html: TEMSFileResource;

複数の異なる(上記と比較して)マッピングをするには、TEMSFileResource コンポーネントを追加します。TWebStencilsEngine コンポーネントは、一致するプロパティで指定された Dispatcher を 1 つのみ持つことができます。複数のディスパッチャを関連づけるには、次のコードを使用します:

AddProcessor(html2, WebStencilsEngine1);

関連項目