Zen アプリケーション
Zen アプリケーションは、Zen のクライアントとサーバとの対話動作の結果生じるすべてのアクティビティを明確に定めています。このようなアクティビティには、Web ページの表示、データベースへのクエリの発行、ディスクへのデータの書き込みなどがあります。Zen アプリケーションを作成するときは、これらのアクティビティを指定する一連の Zen クラスを作成します。これらのクラスについては、ObjectScript 言語を使用します。必要に応じて、XML、HTML、SVG、JavaScript などの埋め込みスニペットも同時に使用できます。
この章では、"Zen の使用法" で提供されている基礎に基づいて Zen アプリケーション・プログラミングについて説明します。ここでは、以下のトピックを取り上げます。
CSP クラスとしての Zen クラス
ベース・アプリケーション・クラス %ZEN.applicationOpens in a new tab およびベース・ページ・クラス %ZEN.Component.pageOpens in a new tab は、それぞれ %CSP.page を継承します。したがって、各 Zen アプリケーション・クラスおよび Zen ページ・クラスは、インスタンス化可能な Caché Server Page ともなっています。CSP ページ・クラスのプロパティ、メソッド、パラメータ、および変数 (%session など) はすべて、Zen アプリケーション・クラスおよび Zen ページ・クラスで利用可能です。
経験を積んだ CSP プログラマであれば、Zen のクラスと CSP のクラスとの間に重要な相違があることに気が付きます。例えば、#()# 構文に記述できる式に対して、Zen では CSP よりもはるかに制約が多くなっています。詳細は、“Zen の実行時式” を参照してください。
Zen アプリケーションの構成
Zen アプリケーションでは、Caché システム設定での構成を必要とすることがあります。このためには、Zen アプリケーションに関連付けた Web アプリケーションを構成する必要があります。ここでは、この構成の意味と手順について説明します。
Zen アプリケーションのコードは Caché ネームスペースに存在します。それぞれの Caché ネームスペースには、そのネームスペースにマッピングされた Web アプリケーションが少なくとも 1 つ存在します。既定では、myNamespace という新しいネームスペースを作成すると、そのネームスペースには、既に 1 つの Web アプリケーションがマッピングされています。この Web アプリケーションは、既に既定の設定が行われています。これには、Zen にとって重要な以下の設定も含まれます。
-
ウェブアプリケーション名 — アプリケーションの名前。既定値は以下のとおりです。
/csp/myNamespace
どのような目的であっても、新しい Caché ネームスペースを作成するたびに、この名前の Web アプリケーションが自動的に作成されます。
-
CSP ファイル物理パス — サーバ上で、アプリケーションのファイルが格納されている場所。既定値は以下のとおりです。
install-dir/CSP/myNamespace
install-dir は Caché のインストール先ディレクトリです。
Zen のクラスをコンパイルすると、JavaScript ファイル (.js) およびカスケーディング・スタイル・シート・ファイル (.css) が生成されます。コンパイル時に、外部から .js ファイルや .css ファイルをインクルードすることもできます。Zen クラスがカスタム・コンポーネントであれば、INCLUDEFILES クラス・パラメータを使用して外部のファイルが列挙されます。また、Zen クラスがアプリケーションまたはページであれば、CSSINCLUDES クラス・パラメータまたは JSINCLUDES クラス・パラメータを使用して同様の処理が実行されます。このファイルを指定するには、完全な URL を使用するか、単純なファイル名を指定します。後者の場合、Zen では、Caché ネームスペースの既定の Web アプリケーションについて、これらのファイルへのパスが [CSPファイル物理パス] であることを前提とします。
通常、Zen アプリケーションでは、その Caché ネームスペースと関連付けられた Web アプリケーションに対して、すべて既定の設定が使用されます。つまり、普通は [CSP ファイル物理パス] を構成する必要はありません。特別に構成しなくても、すべてが良好に機能します。ただし、生成されたファイルを探し出す場合や、Zen アプリケーションで使用する外部の .js ファイルまたは .css ファイルを配置する場合は、このパスを知っている必要があります。[CSP ファイル物理パス] がこのパスに該当します。
-
デフォルト・タイムアウト — Zen アプリケーションがタイムアウトするまでの、連続してアクティビティがない時間 (秒)。
-
静的ファイルの提供 — Zen アプリケーションについては、この値を “[常に]” または “[常時かつキャッシュ]” に設定することを強くお勧めします。この値を [いいえ] に設定しないでください。
Caché からファイルを提供することを希望しない場合は、Zen を使用している各ディレクトリ内に、すべての Zen 静的ファイルのコピーが配置されている必要があります。Zen 静的ファイルは、Caché インストール・ディレクトリの下にある /csp/broker ディレクトリにあります。これらの各ファイルのコピーを、Web サーバ上の各 Zen アプリケーションと関連付けられたディレクトリに配置してください。Caché からファイルを提供している場合は、この操作を実行する必要はありません。
-
カスタム・エラー・ページ — アプリケーションからのページの欠落など、Zen アプリケーションで解決不能なエラーが発生したときに表示されるページ。このフィールドは既定では空白で、ブラウザで既定のエラー・ページが表示されます。このフィールドに値を指定する場合は、適切な形式の URL とする必要があります。つまり、[ウェブアプリケーション名]の後に、表示する Zen ページのパッケージ名とクラス名を記述します。例えば、以下のようになります。
/csp/myNamespace/MyDemo.Error.csp
-
ログイン・ページ — アプリケーションのログイン・ページとして使用するページの URL。この URL の先頭は [ウェブアプリケーション名]とし、それに続いて、表示する Zen ページのパッケージ名とクラス名を記述します。例えば、以下のようになります。
/csp/myNamespace/MyDemo.Login.csp
[パスワード変更ページ] も [ログイン・ページ] と同様ですが、ユーザによるパスワードの変更を可能にするページの指定があります。説明と例は、“Zen のセキュリティ” の章の “アプリケーションへのアクセスの制御” を参照してください。
Zen アプリケーションをコンパイルすると、それに関連付けられた Web アプリケーションが探し出され、その構成設定が適用されます。つまり、Zen では以下に示す順序の処理が実行され、最初に見つかった Web アプリケーションが返されます。
-
Zen アプリケーションは、myNamespace など、自身が存在する Caché ネームスペースを認識しています。
-
/csp/myNamespace というネームスペースに Web アプリケーションが存在していると、その Web アプリケーションの設定が Zen で使用されます。
-
前述の Web アプリケーションが存在しない場合は、myNamespace にマッピングされた Web アプリケーションが検索され、標準の Caché グローバル照合順序に基づいていずれかの Web アプリケーションが返されます。例えば、myNamespace にマッピングされた /A および /X という Web アプリケーションが存在すると、アプリケーション /A の設定が使用されます。
Zen アプリケーションと Web アプリケーションとの間の明示的な関連付けを構成する手段はありません。暗黙の関連付けを管理する最も簡単な方法は、各 Zen アプリケーションを、それ以外にはアプリケーションが存在しない専用の Caché ネームスペースに保持し、アプリケーションの設定を構成するときに、該当ネームスペースの既定の Web アプリケーションを構成することです。
実際に Web アプリケーションの設定を構成する必要があるかどうかは、Zen アプリケーションに追加した機能によって異なります。このセクションの冒頭に、調整が必要になることが多い設定の一覧があります。Web アプリケーションの設定を構成する基本的な手順は以下のとおりです。
-
管理ポータルを起動します。
-
[ウェブアプリケーション] ページに移動します ([システム管理]→[セキュリティ]→[アプリケーション]→[ウェブアプリケーション])。
-
一覧から該当の Web アプリケーションを探し出し、その [編集] ボタンをクリックします。[ウェブ・アプリケーション編集] ページが表示されます。
-
必要な変更を行い、[保存] をクリックします。
CSP 上で実行している高可用性ソリューションについては、ハードウェア・ロード・バランサを使用して負荷分散とフェイルオーバーを行うことをお勧めします。このロード・バランサでは、スティッキー・セッションをサポート可能にする必要があります。これにより、特定のゲートウェイ・インスタンスと特定のアプリケーション・サーバの間でセッションがいったん確立された後は、そのユーザからのそれ以降の要求はすべて、同じインスタンスとサーバの組み合わせで処理されることが保証されます。
この構成では、セッション ID とサーバ側のセッション・コンテキストが常に同期していることが保証されます。この構成を使用しない場合は、セッションがあるサーバ上で生成されたにもかかわらず、そのユーザからの次の要求はこのセッションが存在しない別のシステムで処理される可能性があり、その結果として実行時エラーが発生します (特に、要求を復号化するためにセッション・キーを必要とするハイパーイベントを使用している場合)。スティッキー・セッションをサポート可能にするには、お使いのロード・バランサのドキュメントを参照してください。
スティッキー・セッションを使用せずにシステムが動作するように構成することは可能ですが、そのためには、CSP セッション・グローバルを企業内のすべてのシステムにまたがってマップする必要があり、その結果として多大なロック競合が発生する可能性があるため、このような構成は推奨されません。
Zen アプリケーション・クラス
Zen アプリケーション・クラスは、高レベルの処理を実現し、アプリケーションが持つすべてのページに対する単一のルートとして機能する %ZEN.applicationOpens in a new tab から派生したクラスです。アプリケーション・クラスは、それが関連付けられているページのインベントリを保持しません。アプリケーションとそのページとの関連付けは、アプリケーション・クラスを識別するオプションのパラメータ APPLICATION を、それぞれの Zen アプリケーションが持っているために発生します。アプリケーション・クラスがオプションなので、このパラメータもオプションです。Zen アプリケーションでアプリケーション・クラスを指定するのは、そのアプリケーションに高い利便性があると考えられる場合のみとします。その場合でも、指定するアプリケーション・クラスは 1 つのみとし、アプリケーションのすべてのページで、APPLICATION パラメータを使用してこのアプリケーションを特定する必要があります。
以下のテーブルは、Zen アプリケーション・クラスの要素をまとめたものです。
要素 | ロール | 特定の項目 | 説明 |
---|---|---|---|
クラス・パラメータ | 一般アプリケーション設定 | APPLICATIONNAME | タイトルやラベルに使用可能なテキスト文字列を定義します。 |
CSSINCLUDES | アプリケーションの各ページにインクルードするカスケーディング・スタイル・シート (.css) のファイル名をコンマで区切って並べたリスト。URL または単純なファイル名を使用できます。単純なファイル名を使用する場合は、該当するファイルへの物理パスを [CSPファイル物理パス] で指定します。詳細は、“Zen アプリケーションの構成” のセクションを参照してください。スタイル・シートの詳細は、"Zen の使用法" の “Zen のスタイル” の章を参照してください。 | ||
HOMEPAGE |
アプリケーションの既定のホーム・ページの URI。 これにより、アプリケーション・クラス自体はページを表示しなくても、このクラスをアプリケーションの起点として使用できるようになります。このアプリケーション・クラスは HTTP 要求を受け取ると、それを HOMEPAGE クラス・パラメータで指定された URI にリダイレクトします。 |
||
JSINCLUDES | アプリケーションの各ページにインクルードする JavaScript (.js) インクルード・ファイルの名前をコンマで区切って並べたリスト。URL または単純なファイル名を使用できます。単純なファイル名を使用する場合は、該当するファイルへの物理パスを [CSPファイル物理パス] で指定します。詳細は、“Zen アプリケーションの構成” のセクションを参照してください。 | ||
USERPACKAGES | 事前生成済みのインクルード・ファイルに記述された HTML クラスおよびスタイル定義を持つユーザ定義クラス・パッケージの名前をコンマで区切って並べたリスト。これらのインクルード・ファイルは、アプリケーション内の各ページで使用できます。 | ||
USERSVGPACKAGES | 事前生成済みのインクルード・ファイルに記述された SVG クラスおよびスタイル定義を持つユーザ定義クラス・パッケージの名前をコンマで区切って並べたリスト。これらのインクルード・ファイルは、アプリケーション内の各ページで使用できます。 | ||
埋め込み XML ドキュメント | CSS スタイル定義 | XData Style | この XData ブロックにあるすべての CSS スタイル定義は、アプリケーションのあらゆるページに配置されます。"Zen の使用法" の “Zen のスタイル” の章を参照してください。 |
プログラミングの際、アプリケーション・クラスで持つことができるサーバ側メソッドは、ObjectScript、Caché Basic、または Caché MVBasic で記述したものに限られる点に注意が必要です。JavaScript キーワードでマークしたクライアント側メソッド、および ZenMethod キーワードでマークしたクライアント/サーバ・メソッドは、指定できません。アプリケーション・クラスは、サーバのみでメソッドを実行できます。
開発プロジェクトの例
"Zen の使用法" の “Zen のレイアウト” の章では、テンプレートのページとペインを使用して Zen アプリケーションのレイアウトとスタイルを整理する方法を説明しています。ここでは、複合コンポーネントと動的なページ生成を使用して Zen アプリケーションの動作を構成する例を紹介します。この例では、Zen を使用して、既存の Weblink アプリケーションに新しい発注入力モジュール・ページを追加します。この例での制約は以下のとおりです。
-
既存のアプリケーションから新しいページを呼び出せることが必要です。
-
アプリケーション全体を書き直す余裕はないので、一度に 1 つずつモジュールを Zen に発展させます。
-
発注入力ページは、柔軟なコンテンツを備える必要があります。つまり、状況に応じ、容易にページを部分的に更新できることが必要です。
-
発注入力ページは規模が大きく、複雑なので、ページを最初にロードするときは必要な部分のみを構築し、以降は状況に応じて必要な部分をその場で順次追加します。
-
複数の開発者が同時に開発を進めることができるような構成部分にページを細分化できれば理想的です。
次の例は、このような制約に応えるものです。この例は、レイアウトやスタイルの扱いを目的としたものではなく、動作に力点を置いています。
-
次のクラスでメイン・ページを実現します。これは、一連の名前付きグループを定義します。各グループには、ユーザ・イベントに応答して、サーバから得たコンポーネントが生成されます。メイン・ページでは、ページを扱うためにさまざまなコンポーネントから呼び出すことができる API (メソッドの集合) も定義します。例えば、ページ上のいずれかのグループに新しいコンポーネントをロードするメソッドがあります。パラメータ USERPACKAGES、g1 グループと g2 グループ、およびページに指定されたパラメータに基づいて特定のタイプのフォームをページに追加する loadForm メソッドが重要です。
-
各グループのコンテンツは、複合コンポーネントを使用して定義します。複合コンポーネントは、組み込みの Zeb コンポーネントを 1 つ以上、固有の方法でグループ化して構築するカスタム・コンポーネントです。詳細は、“カスタム・コンポーネント” の章の “複合コンポーネント” を参照してください。複数の開発者が個別に複合コンポーネントを開発することが可能で、これにより、プロジェクトの制約を満足できます。このサンプル・コードでは、このような複合コンポーネントを 4 つ使用しています。その 1 つは開始フォームの searchForm で、これをロードすることで他のフォームがロードされます。他の 3 つは formOne、formTwo、および formThree で、これらはサンプルのフォームです。
Class TestMe.MainPage Extends %ZEN.Component.page
{
/// Comma-separated list of User class packages whose HTML class
/// and style definitions are in pre-generated include files.
Parameter USERPACKAGES = "TestMe.Components";
/// Class name of application this page belongs to.
Parameter APPLICATION;
/// Displayed name of this page.
Parameter PAGENAME;
/// Domain used for localization.
Parameter DOMAIN;
/// This Style block contains page-specific CSS style definitions.
XData Style
{
<style type="text/css">
/* style for title bar */
#title {
background: #C5D6D6;
color: black;
font-family: Verdana;
font-size: 1.5em;
font-weight: bold;
padding: 5px;
border-bottom: 1px solid black;
text-align: center;
}
/* container style for group g1 */
#g1 {
border: 1px solid black;
background: #DDEEFF;
}
/* container style for group g2 */
#g2 {
border: 1px solid black;
background: #FFEEDD;
}
</style>
}
/// This XML block defines the contents of this page.
XData Contents [ XMLNamespace = "http://www.intersystems.com/zen" ]
{
<page xmlns="http://www.intersystems.com/zen"
xmlns:testme="TestMe"
title="">
<html id="title">TestMe Test Page</html>
<vgroup id="gSearch" width="100%">
<testme:searchForm/>
</vgroup>
<spacer height="40"/>
<!-- these groups are containers for dynamically loaded components -->
<hgroup>
<vgroup id="g1"/>
<spacer width="20"/>
<vgroup id="g2"/>
</hgroup>
</page>
}
/// Create a form (via a composite element) and place it into the group with
/// the id groupId. formClass is the name of the composite element
/// containing the form. formId is the id applied to the form.
ClientMethod loadForm(formClass, formId, groupId) [ Language = javascript ]
{
try {
// if there is already a form, get rid of it
var comp = zen(formId);
if (comp) {
zenPage.deleteComponent(comp);
}
var group = zen(groupId);
var form = zenPage.createComponentNS('TestMe',formClass);
form.setProperty('id',formId);
group.addChild(form);
group.refreshContents(true);
}
catch(ex) {
zenExceptionHandler(ex,arguments);
}
}
}
TestMe.MainPage クラスについては、以下に挙げる重要な点に注意してください。
-
パラメータ USERPACKAGES は次のように設定します。
Parameter USERPACKAGES = "TestMe.Components";
これは、このパッケージのコンポーネント向けに生成されたインクルード・ファイルをページで使用することを指定しています。必要なオブジェクトをその場で作成すること、また関連の JavaScript オブジェクトを利用可能にしておくことを考慮すると、この指定は重要です。
-
ページの XData Contents ブロックは、次のように新しい複合コンポーネントの XML ネームスペースへの参照を提供します。
xmlns:testme="TestMe"
-
XData Contents では、次のように searchForm コンポーネントをページに配置します。
<testme:searchForm/>
ユーザが serachForm のいずれかのボタンを押すと、Main ページで loadForm メソッドが呼び出されます。これにより、ID で指定された現在のフォームが削除され、新しいフォームがロードされます (実際には、ロードするものがフォームである必要はありません。ここでは例としてフォームをロードしています)。
ここで、例としてこれらのコンポーネントを確認してみます。“複合コンポーネント” での推奨に従い、これらの複合コンポーネント・クラスはすべて専用のパッケージ TestMe.Components に収められています。この理由は、指定されたパッケージにあるコンポーネントの JS ヘッダ・ファイルと CSS ヘッダ・ファイルが Zen では自動的に生成されるためです。これらのコンポーネントをパッケージに置くことで、前述のようにメイン・ページでパラメータ USERPACKAGES をこのパッケージ名に設定すれば、この自動生成機能を利用できます。
Parameter USERPACKAGES = "TestMe.Components";
また、複合コンポーネントでは、それ専用の XML ネームスペースを使用して、他のコンポーネントとの競合を回避します。
複合コンポーネント・クラスは以下のとおりです。
-
searchForm — 開始フォーム。これをロードすることで他のフォームがロードされます。
Class TestMe.Components.searchForm Extends %ZEN.Component.composite { /// This is the XML namespace for this component. Parameter NAMESPACE = "TestMe"; /// This Style block contains component-specific CSS style definitions. XData Style { <style type="text/css"> </style> } /// Contents of this composite component. XData Contents [ XMLNamespace = "http://www.intersystems.com/zen" ] { <composite labelPosition="left"> <button caption="Show FormOne in Group 1" onclick="zenThis.composite.btnForm1();"/> <button caption="Show FormTwo in Group 1" onclick="zenThis.composite.btnForm2();"/> <button caption="Show FormTwo in Group 2" onclick="zenThis.composite.btnForm3();"/> <button caption="Show FormThree in Group 2" onclick="zenThis.composite.btnForm4();"/> </composite> } /// User click on form 1 button. ClientMethod btnForm1() [ Language = javascript ] { // Notify the page that the search button was pressed // and that a new form should be loaded. zenPage.loadForm('formOne','form1','g1'); } /// User click on form 2 button. ClientMethod btnForm2() [ Language = javascript ] { // replace contents of 'g1' with formTwo zenPage.loadForm('formTwo','form1','g1'); } /// User click on form 3 button. ClientMethod btnForm3() [ Language = javascript ] { // replace contents of 'g2' with formTwo zenPage.loadForm('formTwo','form2','g2'); } /// User click on form 4 button. ClientMethod btnForm4() [ Language = javascript ] { // replace contents of 'g2' with formThree zenPage.loadForm('formThree','form2','g2'); } }
複合コンポーネントを構成する各コンポーネントは、それが属する複合グループを、それぞれの composite プロパティを使用して参照できます。これを確認するには、ユーザがこのフォームのボタンをクリックしたときに、複合クラスで定義されているクライアント側メソッドを呼び出す次の構文を調べます。
onclick="zenThis.composite.btnForm1();"
-
formOne — 次のサンプル・フォーム。
Class TestMe.Components.formOne Extends %ZEN.Component.composite { /// This is the XML namespace for this component. Parameter NAMESPACE = "TestMe"; /// This Style block contains component-specific CSS style definitions. XData Style { <style type="text/css"> </style> } /// Contents of this composite component. XData Contents [ XMLNamespace = "http://www.intersystems.com/zen" ] { <composite labelPosition="left"> <titleBox title="Form One"/> <text id="ctrlF1" label="Search:"/> </composite> } }
-
formTwo — 次に示す別のサンプル・フォーム。
Class TestMe.Components.formTwo Extends %ZEN.Component.composite { /// This is the XML namespace for this component. Parameter NAMESPACE = "TestMe"; /// This Style block contains component-specific CSS style definitions. XData Style { <style type="text/css"> </style> } /// Contents of this composite component. XData Contents [ XMLNamespace = "http://www.intersystems.com/zen" ] { <composite labelPosition="left"> <titleBox title="Form Two"/> <text id="ctrlF1" label="F1:"/> <text id="ctrlF2" label="F2:"/> <text id="ctrlF3" label="F3:"/> <text id="ctrlF4" label="F4:"/> </composite> } }
-
formThree — 次に示す別のサンプル・フォーム。
Class TestMe.Components.formThree Extends %ZEN.Component.composite { /// This is the XML namespace for this component. Parameter NAMESPACE = "TestMe"; /// This Style block contains component-specific CSS style definitions. XData Style { <style type="text/css"> </style> } /// Contents of this composite component. XData Contents [ XMLNamespace = "http://www.intersystems.com/zen" ] { <composite labelPosition="left"> <titleBox title="Form Three"/> <colorPicker onchange="zenThis.composite.colorChange(zenThis.getValue());"/> </composite> } /// colorChange ClientMethod colorChange(color) [ Language = javascript ] { alert('You have selected: ' + color); } }