Chapter 4
コンテキストに対する理解を深める

3章まででシンプルなWebページを構築して、INTER-Mediatorの動作を見てもらいました。この章では、定義ファイルやページファイルの設定をさらに細かく見ていきます。定義ファイルのもっとも重要な設定要素は、「コンテキスト」です。コンテキストをどのように作成するのかが、INTER-Mediatorでのサイト制作のポイントになります。

4-1ターゲット指定

ページファイルでは、フィールドのデータを表示するためにタグのdata-im属性に指定を行うことを説明しましたこの指定は単にテキストを要素に設定するだけでなく、さまざまなバリエーションを持っています。それらを説明し、実際に演習で確認してみましょう。

リンクノードにおけるターゲット指定

 data-im属性を指定して、データベースのフィールドとバインドした要素を「リンクノード」と呼ぶことはすでに説明した通りです。このdata-im属性の値を「ターゲット指定」と呼ぶことにします。つまり、この値は、データベースやあるいは属性の何に対する設定なのかを示すものということで、「ターゲット」という名前をつけています。

 「ターゲット指定」は「コンテキスト名@フィールド名@ターゲット」が基本的な形式です。コンテキスト名と、コンテキストより得られるリレーションに含まれるフィールド名は、必ず指定します。最後の「@ターゲット」は省略可能です。本コースのこれまでの箇所では、すべて「@ターゲット」部分を省略していました。複数のターゲット指定をdata-im属性の値に設定することも可能です。その場合は、半角のスペースで区切ります。リスト4-1-1は、2つのターゲット指定を持つリンクノードの例です。

リスト4-1-1 2つのターゲット指定を持つリンクノードの例
<span data-im="person@name person@bgcolor@style.backgroundColor"></span>

 「@ターゲット」の部分に指定可能な要素は、表4-1-1に示しました。ターゲット指定の「@ターゲット」部分を省略した場合の動作を説明します。この場合、通常は、その要素のテキスト要素として、フィールドの値を設置します。DIVやH1などのタグではフィールドの値をそのタグ要素のテキスト要素として設定します。

 一方、フォームで使うINPUT、SELECT、TEXTAREAでは「@ターゲット」部分を省略した場合、そのタグの種類に応じて、適切な属性やあるいはテキスト要素への設定を行います。例えば、type="text"のINPUTタグ要素やSELECT要素の場合は、value属性に設定します。チェックボックスやラジオボタンの場合は、value属性と比較をして、チェックのオン/オフを行います。TEXTAREAタグでは、テキスト要素として設定をします。つまり、データが、ページ上に見えるようにするには、一般には、「@ターゲット」部分を省略すればよいということです。

@ターゲット動作
省略テキストノードあるいはvalue属性などの“目に見える状態”にする
属性名その要素の指定した属性に、フィールドの値を設定する
style.スタイル名スタイル名はJavaScriptでのスタイル名。フィールドの値を指定したスタイル属性として設定する
innerHTMLフィールドの値を、その要素のinnerHTMLプロパティへ設定する
nodeTextフィールドの値を、その要素のnodeTextプロパティへ設定する(基本動作は省略時と同様)
#____ターゲット指定を#で始めると、現在のデータに追記する
$____ターゲット指定を$で始めると、現在のデータの中にある$を、フィールドのデータに置き換える。複数のターゲット指定がある場合、前から順番に処理される
表4-1-1 @ターゲットに指定可能な要素

 リスト4-1-2には、リンクノードと、フィールドのデータを合成した結果の例を記載します。→の右側が、フィールドのデータを合成した結果で、変更されたタグの記述が実際にページ上で解釈されることになります。その結果、データベースの内容を表示することはもちろん、データベースの内容によってスタイルを変更したり、あるいは関数呼び出しのパラメーターの指定もできるようになっています。

リスト4-1-2 ターゲット指定の例(フィールド値がvalだったとする)
<td data-im="tbl@f1"></td>
 → <td data-im="tbl@f1">val</td>
<td data-im="tbl@f1@align"></td>
 → <td data-im="tbl@f1" align="val"></td>
<td data-im="tbl@f1@innerHTML"></td>
 → <td data-im="tbl@f1">*val*</td>
<td data-im="tbl@f1@style.backgroudColor"></td>
 → <td data-im="tbl@f1" style="background-color: val;"></td>
<input type="text" data-im="tbl@f1"/>
 → <input type="text" data-im="tbl@f1" value="val"/>
<td class="cell " data-im="tbl@f1@#class"></td>
 → <td class="cell val" data-im="tbl@f1@#class"></td>
<td class="cell " data-im="tbl@f1@#class tbl@f2"></td>
 → <td class="cell  val1" data-im="tbl@f1@#class tbl@f2">val2</td>
<span onclick="doClick($)" data-im="tbl@f1@$onclick"></span>
 → <span onclick="doClick(val)" data-im="tbl@f1@$onclick"></span>

FileMakerの場合のターゲット指定のフィールド名

 FileMakerでは、ひとつのレイアウトに複数のTO(リレーションシップのタブで定義するボックス)に含まれるフィールドを配置します。レイアウトに割り当てられたTOにあるフィールドは、単にフィールド名のみで指定できます。しかしながら、レイアウトに割り当てたTOとは異なるTOのフィールドの場合、原則として「TO名::フィールド名」の形式で記述します。

 したがって、TO名が長いと、ターゲット指定が「Patient@病棟マスター_表示用::背景色@style.backgroundColor」のようなとてつもなく長い表示になりがちです。この場合、Patientコンテキストが指定するレイアウトに割り当てたTOとは別の「病棟マスター_表示用」という名前のTOにある「背景色」フィールドを、このタグ要素の背景色に設定するという設定となります。このセクションの最後に紹介したエイリアス名を定義する方法もありますが、長い名前がページファイルから定義ファイルに移るだけで、長い記述をしなくても済むわけではありません。

ターゲット指定についての注意点

 「@ターゲット」の指定がない場合や、textNodeを指定した場合は、DOMの「テキストノード追加」の仕組みを使います。そのため、追加する文字列が、HTMLのタグであっても、タグとしては解釈せず、例えば、「<」は「&lt;」として追加され、テキストエリアであれば、タグ文字列として見えます。この状態では、フィールドのデータは、単に表示されるのであって、何か処理をされることはありません。しかしながら、@innerHTMLを使用した場合は、最新の注意を払ってください。この場合、フィールドのデータに、JavaScriptのプログラムがあれば、実行されてしまい、セキュリティホールになりえます。しかしながら、innerHTMLの機能がないと、HTMLの断片をフィールドに保持して、そのHTMLに従ったレイアウトをするような用途に使えなくなります。@innerHTMLを使用する場合は、不特定多数の人がデータベースへの入力ができるようにすることはまず避けましょう。責任ある人だけがデータの書き込みができるようにしておくことで、一定の範囲で悪意のあるスクリプトの混入は防ぐことができます。SCRIPTタグを抜くことでも、この危険性をいくぶん避けることはできますが、属性内にもスクリプトの書き込みができる場合があり、JavaScriptのプログラムを完全に排除することは、難しいと考えます。結論としては、@innerHTMLを使う場合には、システム全体のセキュリティ上の問題がないかをよく考えた上で使用をするということになります。

演習ターゲット指定のバリエーション

 INTER-Mediator-ServerのVMを利用して、データの書き戻しを伴うページの作成を行います。新たなページを作成して、更新の動作を検証します。

テキストフィールドとテキストエリアのあるページ

1ここからの作業は、Webブラウザー上で行います。ブラウザーで、「http://192.168.56.101」に接続します。「トライアル用のページファイルと定義ファイル」というタイトルの部分を特定します。
2「def05.phpを編集する」をクリックし、定義ファイルエディターでdef05.phpファイルを編集します。(もし、他の用途で5番目を利用しているのなら、例えば、def11.phpを利用するなど、別の番号のセットを使用してください。その場合ソースコードの記述が変わる部分がありますが、可能な限り注記します。)
3Contextsの中のQueryと書かれ背景がグレーの部分を特定します。そして、その次の行の右の方にある「削除」をクリックして、Queryの設定がある行を削除します。
4「レコードを本当に削除していいですか?」とたずねられるので、OKボタンをクリックします。
5同様に、Sortingの次の行にある「削除」ボタンを押し、確認にOKボタンをクリックして、こちらの設定も削除しておきます。
6name、table、viewともに「postalcode」、keyを「id」、pagingを「true」、recordsを「5」とします。Contextsのその他のテキストフィールドは空白にします。
7Database Settingsに設定を行います。
[MySQL]の場合
db-classは「PDO」のままでかまいません。dsnに「mysql:host=localhost;dbname=test_db;charset=utf8mb4」と入力します。そして、userに「web」、passwordに「password」と入力します。
[FileMaker]の場合
db-classを「FileMaker_FX」に書き換えます。databaseは「TestDB」、userに「web」、passwordに「password」、serverに「192.168.56.1」、portに「80」、protocolに「http」、datatypeに「FMPro12」と入力します。
8Debugについては、「false」にすると、デバッグ情報が出なくなります。なお、デバッグ情報をみながら動作を確認したい方は、「2」のままにしてこの後の作業を行ってください。
9「http://192.168.56.101」で開いたページに戻り「page05.htmlを編集する」をクリックし、ページファイルのpage05.htmlを編集するページファイルエディターが開きます。HTMLでの記述内容を以下のように変更します。(別の番号のセットで作業している場合には、該当する番号のリンクをクリックしてください。)
<!DOCTYPE html>
<!--
/*
 * INTER-Mediator Ver.@@@@2@@@@ Released @@@@1@@@@
 * 
 *   Copyright (c) 2010-2015 INTER-Mediator Directive Committee, All rights reserved.
 * 
 *   This project started at the end of 2009 by Masayuki Nii  msyk@msyk.net.
 *   INTER-Mediator is supplied under MIT License.
 */  -->
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
    <script type="text/javascript" src="def05.php"></script>
</head>
<body>
  <div id="IM_NAVIGATOR"></div>
  <table>
    <thead>
      <tr><th>Postal Code</th><th>Place</th><th>Memo</th></tr>
    </thead>
    <tbody>
      <tr>
        <td data-im="postalcode@f3"></td>
        <td>
          <span data-im="postalcode@f7"></span>
          <span data-im="postalcode@f8"></span>
          <span data-im="postalcode@f9"></span>
        </td>
        <td>
          <input type="text" data-im="postalcode@memo"/>
        </td>
      </tr>
    </tbody>
</body>
</html>
10「http://192.168.56.101」で開いたページに戻り、「page05.htmlを表示する」をクリックします。page05.htmlファイルが別のタブあるいはウインドウで開きます。(別の番号のセットで作業している場合には、該当する番号のリンクをクリックしてください。)テキストフィールドに見えているデータは、データベースに入力されているデータです。memoフィールドのみ編集可能な状態になっています。ここまでは、以前に作ったページから大きな違いはありません。
ここでも、スタイルシートは、INTER-Mediatorのサンプルの中にあるものをそのまま利用しています。そのため、ページネーションのコントロールのボタンはそれらしく見えていますが、『ページネーションの生成』で説明した通り、スタイルの指定がない場合には文字が単に見えるだけになってしまいます。ページネーションの各要素のためのスタイル定義が必要なのですが、ここでは簡単にするため、すでに定義されたファイルを参照しています。

フィールドの値を要素のスタイルに指定する

1「page05.htmlを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://192.168.56.101」で開いたページに戻り、「page05.htmlを編集する」をクリックします。ページファイルのコードを以下のように変更します。f8フィールドとバインドしているSPANタグ要素のdata-im属性に、「memoフィールドの値を、スタイル属性のcolorとして設定する」という定義が加わりました。
    <tbody>
      <tr>
        <td data-im="postalcode@f3"></td>
        <td>
          <span data-im="postalcode@f7"></span>
          <span data-im="postalcode@f8 postalcode@memo@style.color"></span>
          <span data-im="postalcode@f9"></span>
        </td>
        <td>
          <input type="text" data-im="postalcode@memo"/>
        </td>
      </tr>
    </tbody>
2「page05.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。そして、ページ内容の更新を行ってください。もし、閉じていたら「http://192.168.56.101」で開いたページに戻り、「page05.htmlを表示する」をクリックします。(別の番号のセットで作業している場合には、該当する番号のリンクをクリックしてください。)
3Memoの列にあるテキストフィールドに、HTMLのスタイルシートのcolorに対応した値(red、#888888、など)を入力してみます。区名に対応するf8フィールドのSPANタグ要素にのみ、colorのスタイルが適用されていますが、値はmemoフィールドから取り出されています。結果として、Memo列のテキストフィールドに設定した文字列が、f8フィールドの文字色として設定されています。
追加したターゲット指定が「postalcode@memo@style.color」となっていることを改めて確認してください。これにより、memoフィールドの値が、そのタグ要素のスタイル属性colorに対して適用されるということです。

既存のデータに接続する設定

1INTER-Mediator-Serverが稼働していると、サンプルの画像をブラウザーで表示できることをまず確認します。ブラウザーで新しいタブあるいはウインドウを開いて、次のURLを入力し、画像が表示されることを確認します。タブの追加は、タブの並びの一番右にあるボタンで通常は行えます。(こちらをクリックすることで、以下のURLを開くことができます。)
http://192.168.56.101/INTER-Mediator/Samples/Sample_products/images/tomatos.png
2「page05.htmlを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://192.168.56.101」で開いたページに戻り、「page05.htmlを編集する」をクリックします。ページファイルのコードを以下のように変更します。新たにIMGタグが追加されましたが、src属性は、前に確認したURLのファイル名の直前までの相対パスになっています。IMGタグ要素のdata-im属性はmemoフィールドの内容をsrc属性につなげるという設定ですが、「#」があるので、すでに入力されている属性値に、memoフィールドの文字列を追加することになります。
    <tbody>
      <tr>
        <td data-im="postalcode@f3"></td>
        <td>
          <span data-im="postalcode@f7"></span>
          <span data-im="postalcode@f8"></span>
          <span data-im="postalcode@f9"></span>
          <img src="INTER-Mediator/Samples/Sample_products/images/"
               data-im="postalcode@memo@#src"/>
        </td>
        <td>
          <input type="text" data-im="postalcode@memo"/>
        </td>
      </tr>
    </tbody>
3「page05.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。そして、ページ内容の更新を行ってください。もし、閉じていたら「http://192.168.56.101」で開いたページに戻り、「page05.htmlを表示する」をクリックします。(別の番号のセットで作業している場合には、該当する番号のリンクをクリックしてください。)
4Memoの列にあるテキストフィールドに、画像ファイル名を入力してみます。INTER-Mediator-Server VMには、以下のファイル名の画像ファイルが、src属性に指定したディレクトリに存在するので、これらの文字列をmemoフィールドにキータイプして入力し、Tabキーでフィールドを移動すると、即座に画像が出てきます。なお、画像が出てこない場合には、画面の更新を行ってください。
memoフィールドの文字列として画像のファイル名のみを入力します。すると、もともとsrc属性にある「INTER-Mediator/Samples/Sample_products/images/」と、ファイル名(例えば「tomatos.png」)が結合された「INTER-Mediator/Samples/Sample_products/images/tomatos.png」がsrc属性の値になります。この相対パスは、存在する画像ファイルへのURLと解釈できるので、IMGタグ要素に画像が表示されます。

innterHTMLによるHTML要素の表示

1「page05.htmlを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://192.168.56.101」で開いたページに戻り、「page05.htmlを編集する」をクリックします。ページファイルのコードを以下のように変更します。新たにIMGタグが追加されましたが、src属性は、前に確認したURLのファイル名の直前までの相対パスになっています。IMGタグ要素のdata-im属性はmemoフィールドの内容をsrc属性につなげるという設定ですが、「#」があるので、すでに入力されている属性値に、memoフィールドの文字列を追加することになります。
    <tbody>
      <tr>
        <td data-im="postalcode@f3"></td>
        <td>
          <span data-im="postalcode@f7"></span>
          <span data-im="postalcode@f8"></span>
          <span data-im="postalcode@f9"></span>
          <div data-im="postalcode@memo@innerHTML"></div>
        </td>
        <td>
          <input type="text" data-im="postalcode@memo"/>
        </td>
      </tr>
    </tbody>
2「page05.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。そして、ページ内容の更新を行ってください。もし、閉じていたら「http://192.168.56.101」で開いたページに戻り、「page05.htmlを表示する」をクリックします。(別の番号のセットで作業している場合には、該当する番号のリンクをクリックしてください。)
3Memoの列に、HTMLの文字列を適当に入力してみます。例えば、Bタグで囲った文字列は太字になります。HRタグで水平線が引けます。3行目には箇条書きのタグとして「<ul><li>a</li><li>b</li></ul>」と入力しています。

演習のまとめ

innerHTMLを使う場合のセキュリティ

 innerHTMLを使用することでHTMLで文字列などを記録できて便利と思うかもしれませんが、セキュリティ的な面での考慮が必要です。もし、フィールドのデータにSCRIPTタグが入っていたとしたら、そのページ内のさまざまな要素にJavaScriptでのプログラムから参照でき、第三者のサーバーに送信するということも可能です。自分ではそういうプログラムを作らないと思っていても、状況によってはそうしたことが可能になります。

 例えば、誰でも書き込めるBBSにおいて、HTMLでのメッセージを許可したとします。悪意のある利用者が、何の変哲もないメッセージに見せかけて、メッセージの中にスクリプトを仕掛けたとします。そのスクリプトは、メッセージを読んだ別の人のブラウザーでも実行されます。スクリプトは、例えばクッキーをすべてどこかのサーバーに送るようなものかもしれません。もちろん、今時の通販サイトでは他のドメインからのクッキー利用ができないようにしているのが普通ですが、そうではないようなサイトもあるかもしれません。そうなると、「BBSに任意のスクリプトを書き込めるようにしている」ということから、メッセージを読んだ人のブラウザーに残っているどこかのサイトのパスワードやあるいはクレジットカード情報を取り出して集める可能性がゼロではなくなります。これを、XSS(クロスサイトスクリプティング攻撃)と呼びます。

 したがって、innerHTMLを使用すると、XSSの可能性が発生します。しかしながら、フィールドにデータを書き込み可能なユーザーを限定し、そのユーザーが悪意がないと仮定すれば、問題は発生しません。例えば、Webサイトのコンテンツを管理するCMS(Contents Management System)のような用途だと、サイトを運営している限定されたメンバーだけが書き込み権限があり、不特定多数のユーザーが書き込みはできません。サイト運営者は善意がありかつサイト自体に問題が発生させることを起こさないという仮定があれば、脅威にならないと言えます。

 もし、HTMLの書き込みを許可しつつ、ユーザーがすべて善意で行動することを保証できない場合には、フィールドの内容から、SCRIPTタグを抜き取るという手法も考えられます。しかし、JavaScriptの多岐に渡る有用性をすべて封じて「スクリプトを取り除」いてしまうと、せっかくの使い勝手が損なわれてしまいます。

 例えば、マウスポインタが入ったイベントをトリガーにスクリプトを動かす必要性もあり、JavaScriptを完全に取り除くのが良いのかどうかは疑問です。別の考え方としては、SPAN/DIV/Pなど、安全なタグと属性を残すようなフィルタをかけるという手段があります。

 なお、「安全を確保するためのフィルタ」は、『4-5 データコンバータークラスを使ったフィールド単位の変換』で説明するデータコンバーターで実現できます。データコンバーターを利用すれば、クライアント側の操作でセキュリティバリアの回避はできません。しかしながら、INTER-MediatorはVer.5.1現在、そのコンバーターについては用意はしていません。セキュリティの要件はサイトごとに異なり、標準的なコンバーターを用意する必要性は薄いと考えます。

長いターゲット指定を短いキーワードで記述

 ターゲット指定は長くなりがちです。特にFileMakerでポータルの中にある別のTOを参照するような要素はかなり長い記述になります。そこで、ターゲット指定のエイリアス名の定義を、定義ファイル内でできるようにしてあります。IM_Entry関数の第二引数の連想配列において、aliasesキーで指定した配列でエイリアスを指定できます。リスト4-1-3がその指定例です。aliasesキーの配列は、キーがエイリアス名、値がターゲット指定となるような連想配列の配列です。複数のエイリアスを指定してもかまいません。

リスト4-1-3 aliasesの指定例
IM_Entry(
    array( ...... /* コンテキスト指定 */ ....... ),
    array(
         "aliases" => array(
             array( "bkcolor" 
             => "Patient@病棟マスター_表示用::背景色@style.backgroundColor" ))
    ),
    array( ...... /* データベース接続指定 */ ....... ),
    false
);

 このような定義があると、data-im属性に「bkcolor」とだけ指定すれば、「Patient@病棟マスター_表示用::背景色@style.backgroundColor」と指定したのと同じことになります。

このセクションのまとめ

 ターゲット指定には、コンテキスト名とフィールド名だけでなく、3つ目の要素を指定できます。これにより、タグ要素の属性やスタイルシート属性、あるいはinnerHTMLプロパティに、フィールドの値を設定できます。また、#によって既存の値に追加したり、$によって既存の値の中にある$と置き換えるということもできます。

4-2ページを合成するときのルール

テーブルの1行分がレコードの数に応じて複製される…という最も分かりやすいモデルでこれまで見てきました。しかしながら、これだけではさまざまなアプリケーションの開発には制約があります。この考え方を一般的にしたものがエンクロージャーとリピーターというモデルです。まず、このモデルについて説明をします。

エンクロージャーとリピーターの識別

 本コースのこれまでのところで、説明してきたことですが、ここで一般的な動作として改めて動きを紹介します。

 まず、データベースとユーザーインターフェースとの連携は、一般には「バインド」と呼ばれます。データベースの内容と、ユーザーインターフェースとして表示したオブジェクトとの間で連携が取れる、つまり、フィールドのデータを表示し、場合によってはユーザーインターフェース側で見ているデータを修正すると、それがきっかけとなってデータベースの更新が行われる状況を「バインド」と呼びます。INTER-Mediatorは、リンクノード(data-im属性によるターゲット指定が設定されたタグ要素)によって、バインドの仕組みが利用できます。

 INTER-Mediatorは、ページファイルを探索して、リンクノードがあるかどうかを探します。リンクノードがひとつでも見つかると、自分より上位のノード、つまり、自分を含んでいるタグ要素を上層に向かって探索し、「リピーター」であるノードを探します。表4-2-1には自動的にリピーターと識別しうるタグ要素についてまとめておきました。また、表4-2-2に記載したように、TRやLI、OPTIONタグでなくても、data-im-control属性で値を「repaeter」と指定すれば、DIVやSPANなど別のタグでもリピーターになります。テーブルのTRタグだけでなく、箇条書きのLIやポップアップメニューのLIタグがリピーターになりますし、data-im-control="repater"であるタグ要素は何でもリピーターになることができます。そして、リピーターの1階層上位にあるノードを「エンクロージャー」と識別します。こちらも、表4-2-1にまとめました。あるいは、表4-2-2にあるように、data-im-control="enclosure"と指定したタグ要素もエンクロージャーとなります。原則としてリピーターの親要素がエンクロージャーになりますが、DIVがエンクロージャー、その内部のSPANがリピーターなら、それぞれのタグにdata-im-control要素で「enclosure」ないしは「repater」の値を指定します。

ページでの形態エンクロージャーリピーターリンクノードになるうる要素
TBODYTR任意の要素
番号リストOLLILIないしはLIの内部の要素
箇条書きULLILIないしはLIの内部の要素
ポップアップ、リストSELECTOPTION要素OPTION要素そのもの
表4-2-1 INTER-Mediatorが属性なしでも識別するエンクロージャーとリピーター
data-im-control属性の値動作
enclosureエンクロージャー
repeaterリピーター
noresultレコードがひとつもない場合のリピーター
ignore_enc_rep指定のあるタグ要素はエクロージャーあるいはリピーターとしては判別しない
headerヘッダー(リピーターを繰り返す前に挿入)[Ver.5.3以降で利用可能]
separatorリピーターとリピーターの間に挿入[Ver.5.3以降で利用可能]
footerフッター(リピーターを繰り返した後に挿入)[Ver.5.3以降で利用可能]
表4-2-2 タグ要素のdata-im-control属性に指定可能な値

 エンクロージャーが決まると、続いて、そのエンクロージャーに対するリピーターになりうるノードを収集します。例えば、リンクノードが含まれるTRタグがあると、その上位のTBODYがエンクロージャーになりますが、TBODYに含まれるすべてのTRをひとつのリピーターとします。つまり、1レコードが複数のTRで構成される行に分割されていてもかまいませんし、単に装飾のためだけのTR/TDで構成されたHTMLコード群が含まれても、それらもリピーターの一部として識別します。ただし、表4-2-2に示されたdata-im-control属性の値を持つリピーターの場合はこれに限りません。この表にある項目は、本セクションの最後の方の『検索結果がない場合の表示』と『間に割り込む特殊なリピーター』、さらに次のセクションの最後の『エンクロージャーやリピーターであることを無視する』で説明します。しばらくは「エンクロージャー」「リピーター」があって、リピーター内部にあるリンクノードがデータベースのフィールドとバインドするという状況で説明をします。

 本コースのこれまでのところでは、TBODY/TRによるエンクロージャー/リピーターの利用を中心としてきました。つまり、TRタグ内にリンクノードがあれば、そのTRタグ要素は、属性などに何も指定しなくても、リピーターであるという識別を行います。INTER-Mediatorは、リピーターであるという識別を行った上で、改めてリピーター内部を探し、リンクノードのリストを作ります。リンクノードはひとつと限りませんが、このひとつのリピーターの中で利用するコンテキストに関しては、「一番多く使われているコンテキスト名」を選択し、そのコンテキストが展開されているリピーターであると識別します。そこで、リンクノードを集めて、一番多く使われているコンテキスト名を採用し、そのひとつのコンテキストを取得して展開します。したがって、使用頻度が2番目以降のコンテキスト名が指定されているリンクノードは、バインド動作は行わず、INTER-Mediatorは原則として無視します。

 テーブル以外にも、エンクロージャー/リピーターに関して、いくつかの組み合わせをサポートします。特に、SELECT/OPTIONタグ要素によるポップアップメニューはよく利用されます。このとき、SELECTタグ要素はバインド可能なリンクノードであり、同時にエンクロージャーになりえます。つまり、OPTIONタグ要素がバインドしていれば、SELECTタグ要素はエンクロージャー、OPTIONタグ要素はリピーターになり得ます。この、「リピーターの中にエンクロージャーがある」という状況については、『4-3 複数のコンテキストとリレーションシップ』で詳しく説明をします。

 箇条書きや番号リストなども、エンクロージャーやリピーターとして識別します。さらに、任意のタグについてdata-im-control属性を記述し値として「enclosure」もしくは「repeater」と指定すれば、それぞれ、エンクロージャーとリピーターになります。DIVとSPANや、ARTICLEとSECTIONなど、いろいろな組み合わせでの構成が可能です。

実際のページ合成

 INTER-Mediatorでは、エンクロージャーとリピーターの識別を行い、レコードに応じて、リピーターを複製することで、複数のレコードの表現が可能です。その様子を実際のコードやデータを当てはめながら、解説しましょう。

 まず、郵便番号と住所が入力されたテーブル「postalcode」があったとします。それを、同一の「postalcode」というnameキーの値を持つコンテキストで利用可能になっているとします。そのとき、表4-2-3のように、コンテキストpostalcodeからの検索結果が得られているとします。フィールド名は1行目の通りです。

pcodeaddress
102-0094千代田区紀尾井町
121-0813足立区竹の塚
161-0035新宿区中井
表4-2-3 postalcodeテーブルの内容

 ページファイル内の該当するソース部分を図4-2-1に示します。ここでは、data-im属性があるリンクノードが2つ含まれるリピーターのTRタグ要素と、その親要素であるエンクロージャーのTBODYタグ要素が識別されます。リンクノードのターゲット指定では、postalcodeコンテキストのみが記述されているのでpostalcodeコンテキストに対してクエリーが実行されます。クエリー結果は、表4-2-3の通りだとします。

図4-2-1 エンクロージャーとリピーターの識別

 データベースのデータを表示するために行う最初の作業は、リピーターをいったん取り除いて、保持することです(図4-2-2)。エンクロージャーの子要素は、TBODYの場合TR要素のみなので、結果としてTBODYの中身は空になり、いったん何もない表になります。取り除いたリピーターは、以後、複製の元として使えるように残されています。

図4-2-2 リピーターの取り除きと保持

 表4-2-3に示すクエリー結果について、1行目のレコードから順番に処理をします。1レコード目が存在するので、図4-2-3に示すように、①まずリピーターの複製を用意し、②複製のリンクノードにレコードのフィールドのデータを挿入し、③レコードを挿入したリピーターをエンクロージャーの子ノードとして追加します。こうして、1レコード分に相当するテーブルの1行が生成されます。

図4-2-3 1レコードを合成する作業

 同様に、2レコード目が存在します。そこで、図4-2-4のように、リピーターの複製、フィールドの値の挿入、そしてエンクロージャーへの複製したリピーターの追加を行い、2レコード目も合成されました。これで、表には2行分が表示されることになります。

図4-2-4 2レコード目の合成

 そして、3レコード目を合成した結果が図4-2-5です。ここで、クエリー結果が3レコードなら、処理が終わり、ページファイルの状態から、クエリー結果のデータを合成したテーブルが作られたことになります。

図4-2-5 3レコード目の合成

 ページ合成時には他にもいくつか準備をする必要があります。例えば、バインドしたものがテキストフィールドなどのユーザーインターフェースに関連するタグ要素であれば、クライアント側でデータを修正したときのイベントを受け取ってデータベースに更新を行うためにさまざまな準備が必要です。バインドを行った時点でのデータや、そのレコードを特定するために、定義ファイルのコンテキストにおいてkeyキーで指定したフィールドに設定されている値などを、INTER-Mediatorの側で用意した変数に記録しています。実際には、バインドした先のノードのタグに関わらず、データベースから取得したデータや、それを挿入した先などのさまざまな情報を、背後で行っています。これらは、JavaScriptのレベルでの利用が可能なものもありますので、『6-3 データベースへの書き込みを直接行う』で説明します。

検索結果がない場合の表示

 ここまでの手順では基本的に何か検索される状態を見てきました。ここで、条件を与えてもレコードがまったく得られない場合、つまり検索結果のレコード数が0の場合の対処を紹介します。リピーター要素がテーブルであった場合、これまでの動作だと、レコード数が0であったときには、中身の何もないテーブルが作られてしまいます。しかしこうした場合は、「レコードがありません」などの表示をするのが一般的でしょう。そのときには、リピーターの中にあるdata-im-control属性が「noresult」のものを用意します。テーブルの場合は、「<tr data-im-control="noresult"><td>.....</td></tr>」といったリピーターを用意します。data-im-control属性が「noresult」の場合、通常のレコードに合成するときには、そのタグ要素は削除されます。そして、レコードが0のときには、data-im-control属性が「noresult」のものだけをエンクロージャーの子ノードとして追加します。リスト4-2-1はページファイルの作成例です。

リスト4-2-1 検索結果がないときのノードを追加した例
<table>
    <thead>
    <tr><th>郵便番号</th><th>住所</th></tr>
    </thead>
    <tbody>
    <tr data-im-conrtrol="noresult_">
        <td colspan="2">検索条件に合致するレコードはありません</td>
    </tr>
    <tr>
        <td data-im="postalcode@pcode"></td>
        <td data-im="postalcode@address"></td>
    </tr>
    </tbody>
</table>

間に割り込む特殊なリピーター

 TBODYタグをエンクロージャーとした場合、data-im-control属性のないTRタグはすべてリピーターになります。しかしながら、data-im-control属性がそれぞれ「header」「separator」「footer」のTRタグ要素を用意すると、文字通りヘッダーは最初、フッターは最後、セパレーターは途中に配置されます。例えば、レコードが3つあれば、次のように展開されます。

リスト4-2-2 ヘッダー、フッター、セパレーターを挿入した結果
<tbody>
    <tr data-im-control="header">
    <tr>
    <tr data-im-control="separator">
    <tr>
    <tr data-im-control="separator">
    <tr>
    <tr data-im-control="footer">
</tbody>

 この仕組みは、元はあるフィールドに対して3つのレコードで「A」「B」「C」という値が設定されている時「A, B, C」のように表示したいためにseparator値の動作として実装しましたが、ヘッダーとフッターも同時に実装しました。なお、TBODYの直下のタグ要素はTR要素のみですが、DIVやSPANで展開したときには、data-im-controlがrepeaterのものはリピーターとして、その属性の値がない場合があります。その場合、data-im-control属性のない要素は合成前に取り残されることになり、事実上、ヘッダーと同じ位置に配置されることになります。headerの値が設定されたタグ要素があれば、何も設定されていないタグ要素はheaderのタグより前に来ます。

このセクションのまとめ

 INTER-Mediatorは、エンクロージャー/リピーターというタグ要素を識別することで、自動的にレコードの数だけ行が確保されるなどの動作を行います。このセクションでは、どんなタグ要素がエンクロージャー/リピーターになることができ、実際にレコードに応じてタグ要素が増殖される動作を説明しました。また、レコードがないときの特別な表示の方法についても説明しました。

4-3複数のコンテキストとリレーションシップ

 データベースを利用する場合、リレーションシップがあることで、さまざまな用途に展開できることは長年の実績で証明されています。INTER-Mediatorではリレーションシップの展開をクライアントで行い、ページ上で関連レコードをその都度サーバーから取得して合成するという手法をとっています。そのベースになる仕組みは、リピーターの中にエンクロージャーがあると、そこで異なるコンテキストの展開を始めることができる点です。その際に、リレーションシップを考慮したり、あるいはしなかったりといった動作を選択できます。

階層的に定義可能なエンクロージャー/リピーター

 INTER-Mediatorは、エンクロージャーとリピーターによる合成を、階層的に展開できます。リピーターの中にエンクロージャーが含まれている場合、リピーター合成後に、そのエンクロージャー内の合成処理に進み、階層的にいくらでも深くデータベースの取得と展開ができます。一番典型的な例は、テーブルでのTRタグの中に、TABLEタグで定義したテーブルが含まれている場合で、それぞれのTRタグ以下にそれぞれリンクノードが存在するような形になります。

 ただし、階層化をやりすぎるとパフォーマンスの低下が懸念されます。単純化した説明をすれば、INTER-Mediatorは、エンクロージャーの数だけデータベース接続を行ってクエリーを行います。エンクロージャーの数は、ページファイルに存在する数よりも、一般には多くなります。リピーターにエンクロージャーが含まれていれば、リピーターが複製された数だけエンクロージャーは増えます。したがって、リピーターの中にエンクロージャーがあるような場合、上位のリピーターに20個のレコードがあれば、内部のエンクロージャー向けのデータベース接続が20回行われます。その、内部のエンクロージャー向けのデータベース処理が重い、つまりアクセスに時間がかかったり、結果の数が多いといった場合には、パフォーマンスの低下が懸念されるものと思われます。その20回のアクセスが、毎回決まり切ったマスターからの取り出しであるなら、一般にはデータベースのクエリーはキャッシュが効きますので、ある程度速度低下が抑えられます。INTER-Mediator自体のキャッシュも制限が多いものの機能しますので、これを有効に使うことを考慮すべきです。場面に応じた、さまざまな手法でのパフォーマンス向上を図る必要があります。特に、FileMakerの場合、サーバーの応答時間がMySQLなどに比べて桁違いに遅いので、多数・大量のデータベース処理をいかに最適化するかが設計のポイントにもなります。FileMakerの場合は、このセクションの最後の方で、その対処法を説明しましょう。

リレーションを伴うページの合成

 あるリピーター(上位のリピーター)にエンクロージャー(下位のエンクロージャー)があり、異なるコンテキストに対するクエリーが定義ファイルで定義されていたとします。上位のリピーターに対応するひとつのレコードの合成が終わってエンクロージャーに追加された後、含まれる下位のエンクロージャーに関する合成処理が始まります。

 このとき、上位、および下位のエンクロージャーに対応するクエリー処理は、それぞれのコンテキストに定義された検索条件等に従い、基本的に独立して行われます。常に一定の選択肢をマスターテーブルから取り出すようなポップアップメニューではそうした利用方法がよく行われます。この場合、ポップアップメニューで「どれを選択しているのか」を示す数値が上位のエンクロージャー内にあるいずれかのフィールドで、下位のエンクロージャーとリピーターはSELECTおよびOPTIONタグに相当します。このとき、上位/下位のエンクロージャーは独立したデータベース処理を行えば問題ないはずです。つまり、下位のエンクロージャーのためのデータベース処理は、「常に同じもの」を必要としているからです。

 一方、下位のエンクロージャーに関連したページ合成を行うときに、リレーションシップを考慮することができます。そのためには、下位のエンクロージャーで使用されるコンテキスト定義において、「relation」キーによる値が定義ファイル上で定義されている必要があります。「realation」キーがなければ、独立したデータベースアクセスを行いますが、「relation」キーがあれば上位のリピーターに対応したレコードと関連したデータを取り出して、展開することができます。すなわち、名前通りリレーションシップに基づくデータベースアクセスを行うということです。結果として、1対多のような展開がエンクロージャー/リピーターの階層化とコンテキスト定義への「relation」キーの設定により、クライアントサイドで行われます。

 「relation」キーに指定する値は、表4-3-1のようなキーに対する値を持つ連想配列の配列です。SQL的な説明をすれば、「relation」キーのあるコンテキストに対するクエリーにおいて、「AND [foreign-key] [operator] [join-fieldの値]」という検索条件が付加されるということになりますが、この様子は後の演習で実際に値を見ながら改めて確認することにしましょう。

キー意味
foreign-key内側のコンテキストでの照合フィールドで、通常は外部キー
operatorforeign-keyによるフィールド名とjoin-fieldによるフィールドの値を結ぶ演算子
join-field外側のコンテキストでの照合フィールドで、通常は外部キーに対応するキー。このフィールドの値を実際の展開には使うので、取得したレコードの中にこのフィールドの値が存在する必要がある
表4-3-1 コンテキストのrelationキーに指定する配列のキー

演習リレーションを伴うページの作成

 エンクロージャー/リピーターの階層化や、リレーションシップを考慮した関連レコードの検索などを、実際の演習で確認していくことにします。ここで使用するデータベースは、INTER-Mediatorのサンプルにも使われているもので、若干複雑です。最初に、データベースの内容を確認した上で、実際の演習に取り掛かることにします。

元になるデータベースの確認

 演習に利用するデータベースを確認します。表4-3-2にテーブルをまとめました。MySQLはテーブル名そのままを使いますが、FileMakerでは、定義ファイルに指定するのは表の中の「レイアウト名」になるので、使用するデータベースに合わせて必要な情報を得てください。一番基本的なテーブルは、1人の人間を1レコードをとして管理するpersonです。その1人の人に対して、複数のコンタクト記録があるとして、その記録をcontactテーブルに残します。したがって、personの1レコードに対して、contactへの多重度は「0..*」(0個、1個、...多数、となり得るという意味)となります。そして、contactテーブルにはwayとkindという2つの整数フィールドがあり、それぞれのマスターテーブルとなるwayとkindテーブルのid値を設定するのを基本とします。wayは「direct」などの直接のコンタクトか、間接的なものなのかを示します。kindは、「電話」「メール」など、手段を示します。

テーブル名TO名レイアウト名テーブルの役割
personperson_toperson_layout1人の人間を1レコードをとして管理
contactcontact_tocontact_toそれぞれのpersonとのコンタクト記録
contact_waycontact_waycontact_wayコンタクト方法の分類
contact_kindcontact_kindcontact_kind具体的なコンタクト方法
cor_way_kindcor_way_kindcor_way_kindwayとkindの対応付け
表4-3-2 利用するテーブルの概要

 利用するテーブルのうち、contact_way(表4-3-3)、contact_kind(表4-3-4)、cor_way_kind(表4-3-5)については、INTER-Mediator-Serverでは最初からレコードが設定されており、そのまま利用するので、それらのテーブルに入っているレコードおよびフィールドについて、それぞれの表で見てみましょう。cor_way_kindテーブルは、wayとkindの関係が多対多になるため、それらを1対多の関係に分解するための中間的な対応付けのためのテーブルです。contact_wayテーブルの「Direct」つまり直接的なコンタクトは、idフィールドの値が「4」です。cor_way_kindテーブルで、way_idフィールドが「4」のレコードは3つあり、それぞれのkind_idフィールドの値は「4」「5」「7」となっています。そして、contact_kindテーブルのidフィールドが「4」「5」「7」のレコードは、「Talk」「Meet」「Meeting」です。つまり、Directな(直接的な)コンタクトとしては、Talk(会って話した)、Meet(ばったり会った)、Meeting(会議で会った)の3つのレコードに関連しているということです。この演習ので、「wayの選択肢に応じてkindの選択肢が設定される」というデーマで、この多対多の関連付けのためのテーブルcor_way_kindを利用します。

idname
4Direct
5Indirect
6Others
表4-3-3 contact_wayテーブル
idname
4Talk
5Meet
6Calling
7Meeting
8Mail
9Email
10See on Web
11See on Chat
12Twitter
13Conference
表4-3-4 contact_kindテーブル
idway_idkind_id
144
245
356
447
558
659
7610
8511
9612
10512
11613
表4-3-5 cor_way_kindテーブル

 テーブル間の連携、つまり外部キーがどのフィールドと結合するのかを示したのが図4-3-1です。FileMakerにはそれぞれのテーブル間の関連を図式する機能(リレーションシップ)があります。図はその様子を示したものですが、MySQLでも同様な関連になっていると考えてください。ボックスにはTO名(テーブル名)と、フィールドのいずれも見えるようにしてあります。MySQLの場合はテーブルを示すひとつのボックスの名前は単にテーブルと考えてもらってかまいません。

図4-3-1 FileMakerでのデータベースのリレーションシップ

 実際のデータについても、FileMakerで確認します。レイアウトにperson_to、ポータルにcontact_toが表示されています。MySQLでも同様にpersonテーブルに対して、その各レコードに対してcontactテーブルが選択されて表示されると考えてください。下側の繰り返し表示されている部分(ポータル)がcontactテーブルで、person_idフィールドが外部キーとなっていて、上側のpersonテーブルのidフィールドの値と対応付けられています。

図4-3-2 personテーブルとcontactテーブルの例

personテーブルを1レコードずつ表示する

1ここからの作業は、Webブラウザー上で行います。まず、INTER-MediatorがインストールされたVMを、VirtualBoxを使用して起動します(『1-2 演習を行うための準備』を参照)。続いて、ブラウザーで、「http://192.168.56.101」に接続します。「トライアル用のページファイルと定義ファイル」というタイトルの部分を特定します。
2「def07.phpを編集する」をクリックし、定義ファイルエディターでdef07.phpファイルを編集します。(もし、他の用途で7番目を利用しているのなら、例えば、def11.phpを利用するなど、別の番号のセットを使用してください。その場合ソースコードの記述が変わる部分がありますが、可能な限り注記します。)
3Contextsの中のQueryと書かれた背景がグレーの部分を特定します。そして、その次の行の右の方にある「削除」をクリックして、Queryの設定がある行を削除します。
4「レコードを本当に削除していいですか?」とたずねられるので、OKボタンをクリックします。
5同様に、Sortingの次の行にある「削除」ボタンを押し、確認にOKボタンをクリックして、こちらの設定も削除しておきます。
6nameに「person」、keyを「id」、pagingを「true」、repeat-controlを「confirm-delete confirm-insert」、recordsとmaxrecordsを「1」とします。Contextsのその他のテキストフィールドは空白にします。
[MySQL]
tableおよびviewには「person」を指定します。
[FileMaker]
tableおよびviewには「person_layout」を指定します。
7Database Settingsに設定を行います。
[MySQL]の場合
db-classは「PDO」のままでかまいません。dsnに「mysql:host=localhost;dbname=test_db;charset=utf8mb4」と入力します。そして、userに「web」、passwordに「password」と入力します。
[FileMaker]の場合
db-classを「FileMaker_FX」に書き換えます。databaseは「TestDB」、userに「web」、passwordに「password」、serverに「192.168.56.1」、portに「80」、protocolに「http」、datatypeに「FMPro12」と入力します。
8Debugについては、「false」にすると、デバッグ情報が出なくなります。なお、デバッグ情報をみながら動作を確認したい方は、「2」のままにしてこの後の作業を行ってください。
9「http://192.168.56.101」で開いたページに戻り「page07.htmlを編集する」をクリックし、ページファイルのpage07.htmlを編集するページファイルエディターが開きます。HTMLでの記述内容を以下のように変更します。太字が追加する箇所を示します。(別の番号のセットで作業している場合には、該当する番号のリンクをクリックしてください。)
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
    <script type="text/javascript" src="def07.php"></script>
</head>
<body>
  <div id="IM_NAVIGATOR"></div>
  <table>
    <tbody>
      <tr>
        <th>id</th>
        <td><input type="text" data-im="person@id"/></td>
      </tr>
      <tr>
        <th>name</th>
        <td><input type="text" data-im="person@name"/></td>
      </tr>
    </tbody>
  </table>
</body>
</html>
10「http://192.168.56.101」で開いたページに戻り、「page07.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。personテーブルの内容が、1レコードずつ参照できます。ここまでは、すでに説明した通りです。

contactテーブルの関連レコードを表示する

1「def07.phpを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://192.168.56.101」で開いたページに戻り、「def07.phpを編集する」をクリックします。
2Contextsのすぐ下にある「追加」ボタンをクリックして、コンテキスト定義をひとつ増やします。
3nameに「contact」、keyを「id」、repeat-controlを「confirm-delete confirm-insert」とします。Contextsのその他のテキストフィールドは空白にします。
[MySQL]
tableおよびviewには「contact」を指定します。
[FileMaker]
tableおよびviewには「contact_to」を指定します。
4contactコンテキストの中のRelationshipのすぐ下にある「追加」ボタンをクリックして、設定のための行を追加します。foreign-keyに「person_id」、join-fieldに「id」、operatorに「=」を指定します。
5「page07.htmlを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://192.168.56.101」で開いたページに戻り、「page07.htmlを編集する」をクリックします。太字で示した部分を追加します。テーブルの中にテーブルがある点に注意してください。外側のテーブルはpersonコンテキスト、内側のテーブルはcontactコンテキストで得られたデータを展開したものです。
<body>
  <div id="IM_NAVIGATOR"></div>
  <table>
    <tbody>
      <tr>
        <th>id</th>
        <td><input type="text" data-im="person@id"/></td>
      </tr>
      <tr>
        <th>name</th>
        <td><input type="text" data-im="person@name"/></td>
      </tr>
      <tr>
        <td colspan="2">
          <table>
            <thead>
              <tr><th>id</th><th>person_id</th><th>summary</th>
                <th>datetime</th><th>way</th><th>kind</th></tr>
            </thead>
            <tbody>
              <tr>
                <td><input type="text" data-im="contact@id" size="3"/></td>
                <td><input type="text" data-im="contact@person_id" size="3"/></td>
                <td><input type="text" data-im="contact@summary"/></td>
                <td><input type="text" data-im="contact@datetime"/></td>
                <td><input type="text" data-im="contact@way" size="3"/></td>
                <td><input type="text" data-im="contact@kind" size="3"/></td>
              </tr>
            </tbody>
          </table>
        </td>
      </tr>
    </tbody>
  </table>
</body>
</html>
6「page07.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。必要に応じて、ブラウザーの更新ボタンを押してください。開いていない場合には、「http://192.168.56.101」で開いたページに戻り、「page07.htmlを表示する」をクリックします。
7ページネーションのコントローラーを利用して、次のレコードなどに移動してください。外側のテーブルのコンテキストのidフィールドの値と、内側のテーブルのコンテキストのperson_idフィールドの値が同一であることを確認してください。
ここでは、外側のpersonコンテキストに対応付けられたエンクロージャー/リピーターに対して、その内部にcontactコンテキストに結び付けられたエンクロージャー/リピーターが存在します。内部のcontactコンテキストに関連付けられた側では、定義ファイルにrelationキーによる値が設定されていて、その設定を元にして、データベースアクセス時に、「関連するレコードのみを取り出す」という検索条件が加わることになります。具体的には、外側のpersonコンテキストのidフィルールドの値が「2」なら、内側のcontactコンテキストのクエリーを行うときにperson_idが「2」のものに絞り込みます。この、id、person_id、そして条件として=演算子で求めているという動作は、contactコンテキストのrelationキーに指定した値に基づいています。
このような、伝票形式のリレーションシップを、ページ上で展開できます。このとき、personに対応したcontactテーブルのレコードがない場合でも、先にpersonのレコードを展開するので、そのようなpersonテーブルのレコードも、ページ上に表示されます。SQLのテーブル結合の形式でいえば、これはLEFT JOINに相当する動作であり、INNER JOINの動作ではありません。

マスターの内容をそのまま表示するポップアップメニュー

1「def07.phpを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://192.168.56.101」で開いたページに戻り、「def07.phpを編集する」をクリックします。
2Contextsのすぐ下にある「追加」ボタンをクリックして、コンテキスト定義をひとつ増やします。
3nameに「contact_way」を設定し、そのコンテキストのその他のテキストフィールドは空白にします。
このコンテキストは、contact_wayテーブルの選択肢を常にそのまま表示していずれも選択できるようにします。選択肢をすべて取り出すためにコンテキストを定義します。このコンテキストには、relationキーの値を指定しない点に注意してください。
4「page07.htmlを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://192.168.56.101」で開いたページに戻り、「page07.htmlを編集する」をクリックします。太字で示した部分を変更・追加します。contactコンテキストの中で、wayフィールドの値をSELECTタグによるポップアップメニューで選択できるようにします。
<td colspan="2">
  <table>
    <thead>
      <tr><th>id</th><th>person_id</th><th>summary</th>
        <th>datetime</th><th>way</th><th>kind</th></tr>
    </thead>
    <tbody>
      <tr>
        <td><input type="text" data-im="contact@id" size="3"/></td>
        <td><input type="text" data-im="contact@person_id" size="3"/></td>
        <td><input type="text" data-im="contact@summary"/></td>
        <td><input type="text" data-im="contact@datetime"/></td>
        <td>
          <select data-im="contact@way">
            <option data-im="contact_way@id@value contact_way@name"/>
          </select>
        </td>
        <td><input type="text" data-im="contact@kind" size="3"/></td>
      </tr>
    </tbody>
  </table>
</td>
5「page07.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。必要に応じて、ブラウザーの更新ボタンを押してください。開いていない場合には、「http://192.168.56.101」で開いたページに戻り、「page07.htmlを表示する」をクリックします。wayの列は、ポップアップメニューで表示されています。
contactのコンテキストが複数行あるレコードを表示して、各行のwayのポップアップメニューの選択肢をみてください。いずれも同じです。つまり、このような場合のポップアップメニューの選択肢は、どのレコードでも同じなので、上位のコンテキストの関係は記述する必要はなく、relationキーの値を定義ファイルのコンテキストに追加する必要もありません。
6ポップアップメニューで選択を変更し、前後のレコードに移動して戻るなどして、以前に選択した通りのものになっていることを確認してください。INTER-MediatorはSELECTタグがあれば、そのターゲット指定にあるフィールドの値に応じた選択肢を自動的に選択するようになっています。また、ポップアップメニューを選択すると、その選択項目に応じたデータが、対応するフィールドに書き込まれます。

他のフィールド値に依存した選択肢を出すポップアップメニュー

 この単元についてはSQLデータベースに関する十分な知識が必要な、上級的な話題になります。

 これまでに作ってきたページで、wayの選択肢に応じてkindのポップアップメニューの選択肢を差し替えるということを行います。そのための情報がcor_way_kindテーブルに入っています。したがって、cor_way_kindテーブルのレコードをポップアップメニューの選択肢に展開することが考えられます。しかしながら、cor_way_kindテーブルにはkindテーブルのnameフィールドの情報がないので、そのままでは選択肢に必要な名称(つまり、「Meeting」「Mail」など)が表示できません。ではさらにリレーションシップを設定すればよいではないか、と言いたいところですが、OPTIONタグの中にはタグは記述できず、テキストしか記述できません。

 そこで、リスト4-3-1のようなビュー「cor_way_kindname」を定義します。ビューは、ひとつあるいは複数のテーブルから導出した表形式の結果を出力するものです。データ自体は保持せず、原則として、テーブルのデータを使って新たなリレーション(表形式のデータ)を出力します。集計などの複雑な処理ではよく利用されますが、ここでは、cor_way_kindテーブルとそれに関連したcontact_kindテーブルを結合した結果をビューで得ています。言い換えれば、cor_way_kindテーブルに、「名称」を付加したものです。リスト4-3-1に定義を示します。このビュー「cor_way_kindname」で得られる結果は、表4-3-6に示します。

リスト4-3-1 cor_way_kindnameの定義内容
CREATE VIEW cor_way_kindname AS 
	SELECT cor_way_kind.*,contact_kind.name as name_kind
		FROM cor_way_kind, contact_kind
		WHERE cor_way_kind.kind_id = contact_kind.id;
idway_idkind_idname_kind
144Talk
245Meet
356Calling
447Meeting
558Mail
659Email
7610See on Web
8511See on Chat
9612Twitter
10512Twitter
11613Conference
表4-3-6 cor_way_kindnameビューの出力

 なお、FileMakerについては、TestDBデータベース内のcor_way_kindレイアウトにcontact_kindテーブルのnameフィールドを配置しているので、別途レイアウトを作る必要はありません(図4-3-2)。FileMakerはレイアウトはテーブルを表示するだけでなく、SQL的な視点からはビューとしてみることもできます。ただし、データベースの管理の「リレーションシップ」のタブで、TOを定義してそれらの関連性を定義しておく必要があります。

図4-3-3 FileMakerのcor_way_kindレイアウト

 cor_way_kindに関して、以上のような設定がすでにデータベース側に準備されています。以下、wayの選択結果に応じたkindの選択肢が出てくるように、ページを変更します。

1「def07.phpを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://192.168.56.101」で開いたページに戻り、「def07.phpを編集する」をクリックします。
2Contextsのすぐ下にある「追加」ボタンをクリックして、コンテキスト定義をひとつ増やします。
3nameに「cor_way_kind」を設定します。
[MySQL]
viewに「cor_way_kindname」を指定します。その他のテキストフィールドは空白にします。
[FileMaker]
コンテキストのその他のテキストフィールドは空白にします。
4cor_way_kindコンテキスト内のRelationshipのすぐ下にある「追加」ボタンをクリックして、設定のための行を追加します。foreign-keyに「way_id」、join-fieldに「way」、operatorに「=」を指定します。
5「page07.htmlを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://192.168.56.101」で開いたページに戻り、「page07.htmlを編集する」をクリックします。太字で示した部分を変更・追加します。contactコンテキストの中で、kindフィールドの値をSELECTタグによるポップアップメニューで選択できるようにします。
<tr>
  <td><input type="text" data-im="contact@id" size="3"/></td>
  <td><input type="text" data-im="contact@person_id" size="3"/></td>
  <td><input type="text" data-im="contact@summary"/></td>
  <td><input type="text" data-im="contact@datetime"/></td>
  <td>
    <select data-im="contact@way">
      <option data-im="contact_way@id@value contact_way@name"/>
    </select>
  </td>
  <td>
    <select data-im="contact@kind">
      <option data-im="cor_way_kind@kind_id@value
                       cor_way_kind@name_kind"/>
    </select>
  </td>
</tr>
FileMakerの場合には、修正は以下のように行ってください。
    <select data-im="contact@kind">
      <option data-im="cor_way_kind@kind_id@value
                       cor_way_kind@contact_kind::name"/>
    </select>
6「page07.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。必要に応じて、ブラウザーの更新ボタンを押してください。開いていない場合には、「http://192.168.56.101」で開いたページに戻り、「page07.htmlを表示する」をクリックします。kindの列は、ポップアップメニューで表示されています。
wayで「Direct」を選択すると、kindの選択肢は「Talk」「Meet」「Meeting」になります。wayで「Indirect」を選択すると、「kind」の選択肢は「Calling」「Mail」「Email」「See on Chat」「Twitter」となります。つまり、wayの選択肢に応じて、kindの選択肢が変化するといった仕組みが実装されました。

レコードの追加とリレーションシップ

1「page07.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。適当なレコードで作業をしてください。以下の図はidフィールドが「3」のレコードが見えています。異なるレコードが見えている状態でもかまいません。
2ここで、内側のテーブルの下側にある「追加」ボタンをクリックします。このボタンにより、contactコンテキストに新しいレコードが作成できます。
3ボタンを押した後に作成していいかどうかの確認がありますので、OKボタンをクリックしてください。
4この場合だと、もともと2行だったcontactコンテキストのテーブルに、新たに3行目のレコードができたことを確認してください。
ここで、新たに作成されたcontactコンテキストのレコード(図ではidフィールドの値が「8」のレコード)の、person_idの値を見てください。なにもしなくても、外側のpersonコンテキストのidフィールドの値が追加されています。このように、relationキーの値を定義して、自身のperson_idと相手のテーブルのidフィールドによって関係性を持つことが定義されていると、新しいレコードを作成するときに、関連するフィールドに適切な値を設定するということを行います。つまり、外部キーに相当するフィールドに、関連付けのための値を設定するということです。FileMakerやMicrosoft Accessでは、リレーションシップで関連付けられているポータルあるいはサブフィールドに新しいレコードを作成すると、関連付けの手がかりとなるフィールドに自動的に値が設定されます。その動作を踏襲したものです。

レコードの複製とリレーションシップ

 レコードの複製は、『3-3 レコードの追加・削除・複製』で説明しました。レコードの複製は、Ver.5.2現在、MySQLのみで利用できます。その他のデータベースエンジンの場合は、対応するまでしばらくお待ち下さい。

1「def07.phpを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://192.168.56.101」で開いたページに戻り、「def07.phpを編集する」をクリックします。
2personコンテキストのrepeat-controlに「copy-contact」というキーワードを追加します。スペースを前に入れて区切ります。
3contactコンテキストのrepeat-controlに「copy」というキーワードを追加します。スペースを前に入れて区切ります。
4「page07.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。適当なレコードで作業をしてください。以下の図はidフィールドが「1」のレコードが見えていて、全部で6レコードあります。異なるレコードが見えている状態でもかまいません。
5「レコード複製:person」ボタンをクリックすると、画面上に大きな変化はありませんが、idフィールドがそれまでのレコード数よりもひとつ多いレコードが新たに作成されました。つまり、idフィールドが「1」のpersonコンテキストにあるレコードと、さらにそのレコードに関連したcontactコンテキストの3つのレコードが複製され、関連付けられたcontactコンテキストのperson_idの値は、personコンテキストのidフィールドの値である「7」(この例の場合)が設定されています。
6さらに、contactコンテキストの2行目の「複製」ボタンをクリックしました。その行と同じ内容レコード(ただし、idフィールドのみ自動的に設定されるので、変化している)が作成されています。

演習のまとめ

FileMakerでのポータルを利用した効率化

 このセクションの残りで、FileMakerを利用する場合の相違点についてまとめておきます。これまでの演習で見ていただいた通り、関連するテーブルの編集・更新は原則としてデータベースの違いに関係なく、コンテキスト間の関連性をrelationキーを用いて表現できることがお分かりいただけたと思います。ただし、このとき、FileMakerでは、コンテキストのもとになるのがレイアウトなので、演習で言えば、contact_toという、ポータルに展開したものや、さらにはポップアップメニューの選択肢を取り出すためのレイアウトまで定義しなければなりませんでした。FileMakerを使用する方にとっては、通常はひとつのレイアウトにポータルを配置して行うような、1レイアウトで済む作業がなぜいくつものレイアウトが必要なのかと思われるところかと思います。これについては、INTER-Mediatorの動作上、どうしてもこのようになるのが原則だと考えてください。

 しかしながら、INTER-Mediatorでは、ポータルの内容を、それを含むレイアウトにアクセスするだけで、同時に取り出すことができます。このため、ここでのperson_layoutレイアウトに存在するポータルに対して行われるcontact_toコンテキストに伴うネットワークアクセスや、あるいはレイアウトの定義は省略することができます。その動作を「ポータルモード」と呼んでいます。

 ポータルモードで利用するための方法を、以下に記載します。前提条件として、このセクションのここまでの演習をFileMakerを使う状態で最後までやり遂げたとして、そこからの修正点を示します。また、FileMakerのデータベースであるTestDBファイルについては、Ver.5.1以降のものを利用してください。まず、図4-3-4のpersonコンテキストは、原則今までと同様でも稼働しますが、keyキーの値はFileMakerが内部で管理している値で管理する機能を指定する「-recid」という値にします。そして、contactコンテキストは、nameキーの値を「contact_to」、tableとviewの値を「person_layout」、keyの値を「-recid」とします。そして、すでにひとつ存在するRelationshipの行のportalの値を「true」にします。

図4-3-4 修正したコンテキストpersonとcontact_to

 ポータルモードを使用するためには、ポータルに相当するコンテキストに対して、以下のような設定を行う必要があります。

 残り2つのコンテキストについては、cor_way_kindコンテキストに関して、Relationshipのjoin-fieldキーの値を「contact_to::way」に変更します。これは、ポータル内のフィールドの記述方法が変更するためで、記述方法はこの後にページファイルの変更点を示した上で、改めて説明します。

図4-3-5 修正したコンテキスト

 ページファイルについては、リスト4-3-2のように変更します。修正する箇所を太字で示します。

リスト4-3-2 修正したページファイル
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
    <script type="text/javascript" src="def08.php"></script>
</head>
<body>
  <div id="IM_NAVIGATOR"></div>
  <table>
    <tbody>
      <tr>
        <th>id</th>
        <td><input type="text" data-im="person@id"/></td>
      </tr>
      <tr>
        <th>name</th>
        <td><input type="text" data-im="person@name"/></td>
      </tr>
      <tr>
        <td colspan="2">
          <table>
            <thead>
              <tr><th>id</th><th>person_id</th><th>summary</th>
                <th>datetime</th><th>way</th><th>kind</th></tr>
            </thead>
            <tbody>
              <tr>
                <td><input type="text" data-im="contact_to@contact_to::id" size="3"/></td>
                <td><input type="text" data-im="contact_to@contact_to::person_id" size="3"/></td>
                <td><input type="text" data-im="contact_to@contact_to::summary"/></td>
                <td><input type="text" data-im="contact_to@contact_to::datetime"/></td>
                <td>
                  <select data-im="contact_to@contact_to::way">
                    <option data-im="contact_way@id@value contact_way@name"/>
                  </select>
                </td>
                <td>
                  <select data-im="contact_to@contact_to::kind">
                    <option data-im="cor_way_kind@kind_id@value
                                     cor_way_kind@contact_kind::name"/>
                  </select>
                </td>
              </tr>
            </tbody>
          </table>
        </td>
      </tr>
    </tbody>
  </table>
</body>
</html>

 ポータルモードの場合、ポータルの中のフィールドについては「コンテキスト名@TO名::フィールド名」で記述します。結果的に、ポータルのTOに存在するフィールドの場合、コンテキスト名とTO名は同一になるので、リスト4-3-2のSELECTタグのターゲット指定にあるように、「contact_to」が2つ重なったような、「contact_to@contact_to::id」といったターゲット指定をする必要があります。もちろん、ポータルの内部で、さらにポータルに割り当てたTOとは別のTOにあるフィールドがある場合には、@以降はそのTO名が記載されます。

 ポータルモードを使用すると、レイアウト上のポータルに展開したレコードの取得のために、別途レイアウトを定義する必要はありませんし、ポータル部分の取得に別のネットワーク処理を行う必要もありません。しかしながら、ここで使用した、ポップアップメニュー部分の処理については、ポータルモードを利用しても、個別のコンテキストの取り出し処理が必要になります。つまり、contact_wayとcor_way_kindコンテキストに対するレイアウトの定義は必要で、ポップアップメニューを構築するたびにネットワークアクセスが必要になります。「これらの情報もいっしょに処理する」ということは、FileMakerの場合、値一覧の情報を扱うという特殊な処理を組み入れる必要があり、Ver.5.2現在はそのような処理は組み込んでいません。

エンクロージャーやリピーターであることを無視する

 TABLEタグの中にテーブルタグがあると、このセクションで説明したように、そのままでは、それぞれ別々のコンテキストとして展開されます。したがって、relationキーの値がコンテキストに設定されていれば、それに従って内側のテーブルには関連するレコードが表示されます。しかしながら、テーブル内のテーブルは、外側と同じコンテキストを表示したいような場合があります。その時は、data-im-control属性にignore_enc_repという値を設定します。例えば、リスト4-3-3の場合だと、内側と外側のテーブルは共通のコンテキストpersonのフィールドをリンクノードに対してバインドします。内側のテーブルのTBODYとTRタグ要素にdata-im-control="ignore_enc_rep"が指定されているので、これらのタグ要素はエンクロージャーやリピーターとして判別しません。内側にテーブルが2つありますが、どちらも外側のテーブルの一部として扱われるため、どちらも同一のpersonコンテキストのレコードを展開します。したがって、このリストの部分はpersonコンテキストで定義された内容にしたがって、1回だけデータベースから取り出しを行いそれを元にページ合成を行います。

リスト4-3-3 ignore_enc_repが指定されたテーブルを含むテーブルの例
<table>
    <tbody>
        <tr><th>Name</th><td data-im="person@name"></td></tr>
        <tr><th>Age</th><td data-im="person@age"></td></tr>
        <tr>
            <td>
                <table>
                    <tbody data-im-control="ignore_enc_rep">
                         <tr data-im-control="ignore_enc_rep">
                             <th>Interest</th><td data-im="person@interest"></td>
                         </tr>
                         <tr data-im-control="ignore_enc_rep">
                             <th>Focus</th><td data-im="person@focus"></td>
                         </tr>
                    </tbody>
                </table>
            </td>
            <td>
                <table>
                    <tbody data-im-control="ignore_enc_rep">
                         <tr data-im-control="ignore_enc_rep">
                             <th>Prefer</th><td data-im="person@preferfood"></td>
                         </tr>
                         <tr data-im-control="ignore_enc_rep">
                             <th>Hate</th><td data-im="person@hatefood"></td>
                         </tr>
                    </tbody>
                </table>
            </td>
        </tr>
    </tbody>
</table>

このセクションのまとめ

 エンクロージャーとリピーターのセットがあれば、レコードの数だけそれらを複製し、レコードに含まれているフィールドの値を合成することで、クエリー結果のレコードを一覧します。さらに、リピーターの中に別のエンクロージャーがあれば、新たにデータベースアクセスを行いレコードを同様に展開します。このデータベースアクセス時には、上位のリピーターに含まれる指定したフィールドの値を手掛かりにした検索条件を付与できるので、リレーションシップに基づく1対多のレコードの取得もできます。また、それらのコンテキスト間で独立した検索も可能で、常に一定の選択肢のポップアップメニューの表示内容をマスターテーブルから取り出すという場合も同様な仕組みを利用します。FileMakerではポータルの値を取り出すことで、ある程度の効率化が可能です。

4-4計算プロパティの設定

データベースから得られた結果に含まれないプロパティを追加するのが「計算プロパティ」の仕組みです。値を求める計算式と共に定義ファイル内で定義することで、計算結果をあたかもフィールドのひとつのようにページファイル上に表示することができます。

計算プロパティの定義

 INTER-Mediatorのようなフレームワークは、データベースの内容を取り出して表示するなどの処理します。このとき、データベースの内容をプログラムの中で再現し、それをもとにさまざまな作業を組み立てています。この内部的なデータ表現は一般に「モデル」と呼ばれます。1レコードがひとつのオブジェクトとしてモデル内で管理されます。そのオブジェクトは、フィールド名をプロパティにした値を持ちます。こうしたオブジェクトを主体にした考え方が「モデル」の基本的な考え方です。現在のJavaScriptベースのいくつかのフレームワークでは、データベースの内容を「モデル」として抽象的に表現し、そのモデルとユーザーインターフェース要素のやりとりをするという考え方を採用しています。

 通常は、フィールドはデータベースにあるものですが、そのひとつを計算式で生成してしまうのが「計算プロパティ」です。計算結果を新たなフィールドとして追加する仕組みは、フィールドという名称を使わずに「計算プロパティ(Calculated Property)」と呼ばれるのが一般的ですので、INTER-Mediatorもそれに習いました。FileMakerで言えば、計算型フィールドに相当するものです。SQLだと、ビューで計算結果の列を追加でき、動作や概念はそれとよく似ています。

 計算プロパティは、定義ファイルのコンテキスト定義の中に記述できます。PHPで記述した定義ファイルの一部の例は、リスト4-4-1に示します。calculationキーに配列で指定します。その配列は、fieldキーと、expressionキーを持つ連想配列です。文字通り、fieldキーで新たに創出される計算プロパティのフィールド名を指定し、計算式をexpressionキーの値で記述します。なお、計算式を組み込むコンテスト定義では、必ずkeyキーによる主キーの設定をしてください。計算式については、この後にまとめて説明をします。

リスト4-4-1 定義ファイルでの計算プロパティの定義例
IM_Entry( array (
  array (
    "name" => "item",
    "key" => "id",	// keyキー指定は必須
    "repeat-control" => "confirm-delete confirm-insert",
    "calculation" => array (
       array (
         "field" => "price",
         "expression" => "qty * unitprice",
       ),
       array (
         "field" => "color",
         "expression" => "if ( qty > 100, 'red', if ( qty > 10, 'green', 'yellow' ) )",
       ),
    ), ....

 同様な設定を定義ファイルエディターで行うときの例が、図4-4-1です。このCalculationsの見出しの部分は、通常は見えておらず、Show Allボタンを押さないと見えませんので注意してください。

図4-4-1 定義ファイルエディターでの計算プロパティの定義例

 計算プロパティは、定義ファイルのコンテキスト定義の中に記述します。その結果、そのコンテキストを通じてデータベースへのクエリーを行った表形式のデータに、計算結果が得られるフィールドが増えると考えてください。図4-4-2はその動作の様子を示したものです。priceという計算プロパティによるフィールドが増えていますが、同一のレコードにあるフィールドの値を元に計算した結果を値としてもちます。なお、計算結果が値なので、逆に値を変更することはできず、計算プロパティの値は読み出しのみとなります。

図4-4-2 計算プロパティによるフィールドの増加

計算式の作成

 計算式のルールは、INTER-Mediatorのマニュアルサイトにある「計算式」のページに記載しています。(本コースはデジタル版なので、詳細はリンクとします。)

 まず、式の項として利用できるのは数値や文字のリテラル(記述した通りの一定のデータ)、フィールド、いくつかの定数です。数値は原則として一般的な記述で問題ありませんが、「e」を使用する指数表示や16進数はサポートしていません。文字列は、シングルクォーテーションで囲みます。ダブルクォーテーションはサポートしていません。文字列の中にシングルクォーテーションの文字がある場合には、バックスラッシュに続いてシングルクォーテーションを記述してください。

 四則演算子などの記号はそのまま利用でき、数学のルールにしたがって、「10 + 20」などと記述できます。また、半角の ( ) による計算順の指定も可能です。

 フィールド名はそのまま、フィールド名で記述が可能です。そのとき、フィールド名だけの場合は、同じレコードの該当するフィールド名のデータに置き換えて計算を行います。式の中で、「コンテキスト名@フィールド名」の形式の項を記述すると、その計算プロパティが存在するリピーターの内部にある、コンテキスト名のエンクロージャーを探し出し、そのエンクロージャーに対応するリピーターから該当するフィールドの値を抜き出します。要するに、HTMLで言えば、内部にある別のコンテキストの展開を探してフィールドの値を集めるということになります。通常、そのような場合はフィールドの値はひとつとは限りませんが、sumなどの合計するような関数(複数の値を受け入れる関数)で処理をすることで、合計を求めることなどができます。なお、より上位のエンクロージャーとリピーターにさかのぼっての参照はサポートしていません。

 前述の「計算式」のページには、利用できる関数が一覧されています。条件分岐にはifを利用します。なお、日付や時刻の関数を用意していますが、1日を1として数値で管理する場合と、1秒を1として数値で管理する場合の、両方の関数を用意しました。SQLで言えばDATE型のフィールド値は1日が1として処理をし、DATETIME型だと1秒を1として計算するのが自然と考えて、このような関数の構成になっています。

 計算式の結果をページ上に表示することだけでなく、スタイルや属性の値として設定することもできます。そのときは、ターゲット指定を「コンテキスト名@計算プロパティ名@style.スタイル属性名」のような記述にします。あるコンテキストcontextにあるフィールドisShowが1のときにはあるタグを表示にして、そうでない場合には非表示にしたい場合、コンテキストに、fieldが例えば「showStr」、expressionに「if ( isShow = 1, 'inline', 'none' )」のような定義を行い、要素は「<span data-im="context@showStr@style.display"></span>」のように記述します。これで、isShowフィールドが1の場合、showStrの値は「inline」となり、この要素のdisplayスタイルの値はinlineとなります。isShowフィールドが1でない場合にはshowStrの値は「none」となり、この文字列が要素のdisplayスタイルに設定されるので、要素は見えなくなります。

演習計算プロパティを追加する

 計算プロパティの利用例を、同一レコードの値から計算する場合、別のコンテキストの値を合計する場合、さらにスタイル属性に計算結果を適用する場合の3通りの方法で説明します。

元になるページの準備

1ここからの作業は、Webブラウザー上で行います。まず、INTER-MediatorがインストールされたVMを、VirtualBoxを使用して起動します(『1-2 演習を行うための準備』を参照)。続いて、ブラウザーで、「http://192.168.56.101」に接続します。「トライアル用のページファイルと定義ファイル」というタイトルの部分を特定します。
2「def09.phpを編集する」をクリックし、定義ファイルエディターでdef09.phpファイルを編集します。(もし、他の用途で9番目を利用しているのなら、例えば、def21.phpを利用するなど、別の番号のセットを使用してください。その場合ソースコードの記述が変わる部分がありますが、可能な限り注記します。)
3Contextsの中のQueryと書かれた背景がグレーの部分を特定します。そして、その次の行の右の方にある「削除」をクリックして、Queryの設定がある行を削除します。
4「レコードを本当に削除していいですか?」とたずねられるので、OKボタンをクリックします。
5同様に、Sortingの次の行にある「削除」ボタンを押し、確認にOKボタンをクリックして、こちらの設定も削除しておきます。
6nameに「product」、keyを「id」、pagingを「true」、repeat-controlを「confirm-delete confirm-insert」、recordsとmaxrecordsを「1」とします。Contextsのその他のテキストフィールドは空白にします。
7Database Settingsに設定を行います。
[MySQL]の場合
db-classは「PDO」のままでかまいません。dsnに「mysql:host=localhost;dbname=test_db;charset=utf8mb4」と入力します。そして、userに「web」、passwordに「password」と入力します。
[FileMaker]の場合
db-classを「FileMaker_FX」に書き換えます。databaseは「TestDB」、userに「web」、passwordに「password」、serverに「192.168.56.1」、portに「80」、protocolに「http」、datatypeに「FMPro12」と入力します。
8Debugについては、「false」にすると、デバッグ情報が出なくなります。なお、デバッグ情報をみながら動作を確認したい方は、「2」のままにしてこの後の作業を行ってください。
9「http://192.168.56.101」で開いたページに戻り「page09.htmlを編集する」をクリックし、ページファイルのpage09.htmlを編集するページファイルエディターが開きます。HTMLでの記述内容を以下のように変更します。太字が追加する箇所を示します。(別の番号のセットで作業している場合には、該当する番号のリンクをクリックしてください。)
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
    <script type="text/javascript" src="def09.php"></script>
</head>
<body>
  <div id="IM_NAVIGATOR"></div>
  <table>
    <tr><th>id</th><td data-im="product@id"></td></tr>
    <tr><th>acknowledgement</th>
      <td data-im="product@acknowledgement"></td>
    </tr>
    <tr><th>name</th>
      <td><input type="text" data-im="product@name"/></td>
    </tr>
    <tr><th>unitprice</th>
      <td><input type="text" data-im="product@unitprice"/></td>
    </tr>
    <tr><td colspan="2">
      
    </td></tr>
  </table>
</body>
</html>
10「http://192.168.56.101」で開いたページに戻り、「page09.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。productテーブルの内容が、1レコードずつ参照できます。ここまでは、すでに説明した通りです。

contactテーブルの関連レコードを表示する

1「def09.phpを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://192.168.56.101」で開いたページに戻り、「def09.phpを編集する」をクリックします。
2Contextsのすぐ下にある「追加」ボタンをクリックして、コンテキスト定義をひとつ増やします。
3nameに「item」、keyを「id」、repeat-controlを「confirm-delete confirm-insert」とします。Contextsのその他のテキストフィールドは空白にします。
4itemコンテキストの中のRelationshipのすぐ下にある「追加」ボタンをクリックして、設定のための行を追加します。foreign-keyに「product_id」、join-fieldに「id」、operatorに「=」を指定します。
5「page09.htmlを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://192.168.56.101」で開いたページに戻り、「page09.htmlを編集する」をクリックします。太字で示した部分を追加します。テーブルの中にテーブルがある点に注意してください。外側のテーブルはproductコンテキスト、内側のテーブルはitemコンテキストで得られたデータを展開したものです。
    <tr><th>unitprice</th>
      <td><input type="text" data-im="product@unitprice"/></td>
    </tr>
    <tr><td colspan="2">
      <table>
        <thead>
          <tr><th>invoice_id</th><th>qty</th><th>unitprice</th><th></th></tr>
        </thead>
        <tbody>
          <tr>
            <td data-im="item@invoice_id"></td>
            <td><input type="text" data-im="item@qty"/></td>
            <td><input type="text" data-im="item@unitprice"/></td>
            <td></td>
          </tr>
        </tbody>
      </table>
    </td></tr>
  </table>
</body>
</html>
6「page09.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。必要に応じて、ブラウザーの更新ボタンを押してください。開いていない場合には、「http://192.168.56.101」で開いたページに戻り、「page09.htmlを表示する」をクリックします。productテーブルのidフィールドの値と、itemテーブルのproduct_idフィールドの値が照合され、下半分に見せる内側のテーブルには、同一のproduct_idフィールドの値を持つレコードが見えています。
7内側のテーブルの下の方にある「追加」ボタンをクリックして、レコードの数を5、6個程度まで増やしておきます。invoice_idの値が空欄のままですが、このフィールドはサンプルのひとつ「Sample_invoice」で、invoiceテーブルと関連づけるためのフィールドです。この演習ではinvoiceテーブルを使わないので、空欄のままでも気にしなくてかまいません。

同一コンテキストの値から計算する

1「def09.phpを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://192.168.56.101」で開いたページに戻り、「def09.phpを編集する」をクリックします。
2ページ冒頭のShow Allボタンをクリックして、すべての設定項目を見えるようにします。
3itemコンテキストの中に、Calculationsという見出しがあります。そこの下にある「追加」ボタンをクリックします。
4新たに登場した行で、fieldは「price」、expressionは「qty * unitprice」と入力します。Tabキーで移動して確定することを忘れないでください。
5「page09.htmlを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://192.168.56.101」で開いたページに戻り、「page09.htmlを編集する」をクリックします。太字で示した部分を追加します。定義ファイルに定義した「price」が、あたかもitemコンテキストのフィールドのように記述できることを確認してください。
<table>
  <thead>
  <tr><th>invoice_id</th><th>qty</th><th
      >unitprice</th><th>price</th></tr>
  </thead>
  <tbody>
    <tr>
      <td data-im="item@invoice_id"></td>
      <td><input type="text" data-im="item@qty"/></td>
      <td><input type="text" data-im="item@unitprice"/></td>
      <td data-im="item@price"></td>
      <td></td>
    </tr>
  </tbody>
</table>
6「page09.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。必要に応じて、ブラウザーの更新ボタンを押してください。開いていない場合には、「http://192.168.56.101」で開いたページに戻り、「page09.htmlを表示する」をクリックします。qtyやunitpriceフィールドに適当に値を入力します。priceフィールドは、qtyフィールドとunitpriceフィールドの乗算結果が見えています。テキストフィールドを入力するたびに再計算されています。また、再計算はTabキーにより、フィールドの値を確定させた後に行われており、キータイプごとに実行されていないことも分かります。

別のコンテキストの値を集計する

1「def09.phpを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://192.168.56.101」で開いたページに戻り、「def09.phpを編集する」をクリックします。
2すべての設定項目が見えていない場合には、ページ冒頭のShow Allボタンをクリックして、すべての設定項目を見えるようにします。
3productコンテキストの中に、Calculationsという見出しがあります。そこの下にある「追加」ボタンをクリックします。
4新たに登場した行で、fieldは「total」、expressionは「format(sum(item@price))」と入力します。Tabキーで移動して確定することを忘れないでください。この式は、productコンテキストから、itemコンテキストを参照し、そこにある複数のpriceフィールド(計算プロパティ)の値をsum関数で合計し、さらにformat関数で、カンマ付き数字に変換しています。
5「page09.htmlを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://192.168.56.101」で開いたページに戻り、「page09.htmlを編集する」をクリックします。太字で示した部分を追加します。定義ファイルに定義した「price」が、あたかもitemコンテキストのフィールドのように記述できることを確認してください。
<body>
  <div id="IM_NAVIGATOR"></div>
  <table>
    <tr><th>id</th><td data-im="product@id"></td></tr>
    <tr><th>acknowledgement</th>
      <td data-im="product@acknowledgement"></td>
    </tr>
    <tr><th>name</th>
      <td><input type="text" data-im="product@name"/></td>
    </tr>
    <tr><th>unitprice</th>
      <td><input type="text" data-im="product@unitprice"/></td>
    </tr>
    <tr><th>total</th>
      <td data-im="product@total"></td>
    </tr>
    <tr><td colspan="2">
      <table>
          :
6「page09.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。必要に応じて、ブラウザーの更新ボタンを押してください。開いていない場合には、「http://192.168.56.101」で開いたページに戻り、「page09.htmlを表示する」をクリックします。qtyやunitpriceフィールドに適当に値を入力し、テキストフィールドを入力するたびに再計算されていることを確認してください。

計算結果をスタイルに反映させる

1「def09.phpを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://192.168.56.101」で開いたページに戻り、「def09.phpを編集する」をクリックします。
2すべての設定項目が見えていない場合には、ページ冒頭のShow Allボタンをクリックして、すべての設定項目を見えるようにします。
3itemコンテキストの中に、Calculationsという見出しがあります。すでに1行分の設定がありますが、その下にある「追加」ボタンをクリックします。
4新たに登場した行で、fieldは「color」、expressionは「if ( qty > 100, 'red', if ( qty > 10, 'green', 'yellow' ) )」と入力します。Tabキーで移動して確定することを忘れないでください。この式は、同じレコードのqtyフィールドの値に応じて、100より大きければ「red」、10より大きければ「green」、それ以外だと「yellow」という文字列を求めます。
5「page09.htmlを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://192.168.56.101」で開いたページに戻り、「page09.htmlを編集する」をクリックします。太字で示した部分を追加します。data-im属性に2つのターゲット指定があり、それらは空白で区切ってください。定義ファイルに定義した「color」は、文字列を値として持ちますが、その値を、data-im属性があるタグ要素のcolorスタイル、つまり文字色としてスタイル情報へ結果を適用します。つまり、qtyフィールドの値に応じてpriceフィールドの文字列の色が、赤、緑、黄色に変化するということです。
    <tr><td colspan="2">
      <table>
        <thead>
          <tr><th>invoice_id</th><th>qty</th>
            <th>unitprice</th><th>price</th></tr>
        </thead>
        <tbody>
          <tr>
            <td data-im="item@invoice_id"></td>
            <td><input type="text" data-im="item@qty"/></td>
            <td><input type="text" data-im="item@unitprice"/></td>
            <td data-im="item@price item@color@style.color"></td>
            <td></td>
          </tr>
        </tbody>
      </table>
    </td></tr>
  </table>
</body>
</html>
6「page09.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。必要に応じて、ブラウザーの更新ボタンを押してください。開いていない場合には、「http://192.168.56.101」で開いたページに戻り、「page09.htmlを表示する」をクリックします。qtyにいろいろな値を入れてみて、priceフィールドの文字列の色が変化することを確認してください。

演習のまとめ

このセクションのまとめ

 コンテキストに計算プロパティを定義することで、他のフィールドなどを元にした計算結果の値を持つフィールドが追加されたものとして、そのフィールドをページファイルのターゲット指定に指定できます。計算式では、同一レコードの他のフィールドの参照や、HTMLのノード上で包含する他のコンテキストのフィールドを参照することもできます。

4-5データコンバータークラスを使ったフィールド単位の変換

ここまでは、データベースのフィールドの内容をそのまま画面に表示し、修正した結果をそのままデータベースに更新していました。しかしながら、常にそれでいいわけではなく、何らかの変換を伴って表示したい場合があります。変換処理そのものがロジック化しない範囲、つまり1フィールドに対する変換をINTER-Mediatorでは「データコンバータークラス」という設定でサポートします。

データコンバータークラス

 フィールド単位でのデータ変換をサポートするのがデータコンバータークラスで、表4-5-1に示すような、さまざまなクラスがINTER-Mediatorに付属しています。自分で作成することもできます(『Chapter 8 サーバーサイドでのプログラミング』を参照)が、一般的なコンバーターは付属のクラスで行えるでしょう。クラス名は、必ずDataConverter_で始めるのが規則です。データコンバータークラスの動作を具体的に説明しましょう。DataConverter_MySQLDateTimeクラスはMySQLのDATEやDATETIME型などの日付時刻のフィールド用です。SQLデータベースでは、フィールドから得られるデータは「2015-06-12 13:45:00」といった形式です。このまま表示することは日本での開発ではほとんどなく、「2015/06/12」等の書式設定をしたくなります。データコンバータークラスは、適用するフィールドを指定することで、初期設定が可能です。また、多くの場合、「2015/06/12」から「2015-06-12」への変更といった、もともとの形式に戻すということも行います。フィールドにコンバーターの適用を設定するだけで、双方向の変換ができるのです。

クラス名動作parameterに指定する値
DataConverter_AppendPrefix指定文字を前につける付加する文字列
DataConverter_AppendSuffix指定文字を後につける付加する文字列
DataConverter_HTMLStringクエリ時のみ、改行のBRタグと& < > の参照形式への変換(不要)
DataConverter_MarkdownStringマークダウン形式をHTML文字列に変換する(読み出しのみ)(不要)
DataConverter_FMDateTimeFileMakerの日付、時刻、タイムスタンプ日付時刻の書式、省略可
DataConverter_MySQLDateTimeMySQLの日付、時刻、タイムスタンプ日付時刻の書式、省略可
DataConverter_Numberカンマ区切りの数値小数点以下の桁数
DataConverter_Currency通貨表記の数値小数点以下の桁数
DataConverter_NumberBase数値表示の基底クラス(そのままでは使えない)-
DataConverter_templateインターフェース定義のみ(そのままでは使えない)-
表4-5-1 添付されているデータコンバータークラス

 DataConverter_FMDateTimeやDataConverter_MySQLDateTimeのparameterキーに対する値は、書式指定文字列を使用します。完全なリファレンスは、PHPのマニュアルにあるstrftime関数のリファレンスを参照してください。よく使うのは、年が「%Y」、月が「%m」、日が「%d」、時分秒がそれぞれ「%H」「%M」「%S」です。これらとスラッシュやコロンの組み合わせで多くの場合は対処できるでしょう。

 DataConverter_MarkdownStringクラスについても説明しましょう。入力用にはTEXTAREAタグ要素を使ってフィールドに入力しますが、この時、行頭に*や-などの記号を使ったいわゆるマークダウン形式で入力します。出力時にこのデータコンバータークラスを利用してHTML形式に変換します。したがって、ターゲット指定の3つ目の設定ではinnerHTMLで結果を受け取る必要があります。サポートしているマークダウン表記を表4-5-2に、さらに生成されるHTMLに含まれるCSSクラス名を表4-5-3に示します。

行頭の記号動作
*その行をHnタグで囲む。nは*の個数に対応する
-その行を箇条書きにする。-を重ねて階層的に記述することも可能
#クラスが「_im_markdown_p1」のPタグで囲む。#は2つおよび3つにも対応し、クラス名の末尾の数字と#の個数が対応する
@@IMG[file]href属性がfileのIMGタグを生成し、さらにクラスが「_im_markdown_para_img」のPタグで囲む
|TABLEタグで表を作る。冒頭、セルの区切り、末尾に|を入れる
表4-5-2 サポートしているマークダウン表記
CSSのクラス名設定される要素
_im_markdownマークダウンの領域全体を囲むDIVタグ要素のクラス
_im_markdown_ul箇条書き全体
_im_markdown_li箇条書きの項目
_im_markdown_h1〜ヘッダー要素
_im_markdown_p1〜3#で始まる場合のPタグ要素
_im_markdown_para_imgIMGタグを囲むPタグに設定されるクラス
_im_markdown_tableテーブルのクラス
_im_markdown_trテーブルの行
_im_markdown_tdテーブルのセル
_im_markdown_para特別な表記がない場合、Pタグに入れられこのクラス名が指定される
表4-5-3 マークダウンより生成されるHTMLに含まれるCSSクラス名

 データコンバータークラスは、コンテキストではなく、オプション(定義ファイルエディターではOptions)での設定になります。「オプションでの設定」とは、定義ファイルに記述するIM_Entry関数の2つ目の引数に指定するということです。取り出したデータというよりも、データベース側のテーブルやビューにあるフィールドに対して設定をすることで、例えばひとつのテーブルから複数のコンテキストを利用しているときにも、ひとつの設定が複数のコンテキストに対して適用されることを意図しています。データコンバータークラスは、表4-5-4に示すキーを使った連想配列の配列を定義し、その値をformatterキーの値として指定します。リスト4-5-1は定義ファイルでの設定例で、定義ファイルエディターでの設定結果の例は図4-5-1に示します。

キー指定する値
field「テーブル名@フィールド名」の形式、コンテキスト名ではなくテーブルやビューの名前
converter-classコンバータークラスの名前。PHPのクラス名からDataConverter_を除く
parameterコンバーターが要求するパラメター(不要なクラスもある)
表4-5-4 formatterキーの配列に指定可能なキー
リスト4-5-1 定義ファイルに指定したコンバータークラスの例
require_once('INTER-Mediator/INTER-Mediator.php');

IM_Entry(
  array (  // ここからIM_Entryの2つ目の引数(コンテキスト)
    array (
      'name' => 'history',
      'key' => 'id',
    ), .....
  ),
  array (  // ここからIM_Entryの2つ目の引数(オプション)
    'formatter' => array (
      array (
        'field' => 'history@startdate',
        'converter-class' => 'MySQLDateTime',
        'parameter' => '%Y/%m/%d',
      ),
    ),
  ),
  array (  // ここからIM_Entryの2つ目の引数(データベース設定)
    'db-class' => 'PDO', .....
  ),
  false
);
図4-5-1 定義ファイルエディターで指定したコンバータークラスの例

データコンバータークラスの使い道と他の機能

 データコンバータークラスは、INTER-Mediator開発の比較的早い時期から実装していたのですが、その後にさまざまな機能で代替が行われてきました。例えば、DataConverter_AppendPrefixクラスと同じことが、ターゲット指定で#を追加することでもできます。また、書式化して表示するのは、計算プロパティで書式化した文字列を用意する方が自由度は高くなります。また、書式設定をタグの属性に指定する仕組みが実装され、データコンバータークラスが必ずしも必要ではなくなってきています。

演習日付フィールドにデータコンバーターを適用する

 日付フィールドにコンバーターを適用する場合としない場合で、どのように表示に違いがあるかを確認します。

定義ファイルの準備

1ここからの作業は、Webブラウザー上で行います。まず、INTER-MediatorがインストールされたVMを、VirtualBoxを使用して起動します(『1-2 演習を行うための準備』を参照)。続いて、ブラウザーで、「http://192.168.56.101」に接続します。「トライアル用のページファイルと定義ファイル」というタイトルの部分を特定します。
2「def10.phpを編集する」をクリックし、定義ファイルエディターでdef10.phpファイルを編集します。(もし、他の用途で10番目を利用しているのなら、例えば、def21.phpを利用するなど、別の番号のセットを使用してください。その場合ソースコードの記述が変わる部分がありますが、可能な限り注記します。)
3Contextsの中のQueryと書かれた背景がグレーの部分を特定します。そして、その次の行の右の方にある「削除」をクリックして、Queryの設定がある行を削除します。
4「レコードを本当に削除していいですか?」とたずねられるので、OKボタンをクリックします。
5同様に、Sortingの次の行にある「削除」ボタンを押し、確認にOKボタンをクリックして、こちらの設定も削除しておきます。
6nameに「history」、keyを「id」、repeat-controlを「insert delete」とします。
[MySQL]の場合
viewとtableは「history」とします。
[FileMaker]の場合
viewとtableは「history_to」とします。
Contextsのその他のテキストフィールドは空白にします。
7Database Settingsに設定を行います。
[MySQL]の場合
db-classは「PDO」のままでかまいません。dsnに「mysql:host=localhost;dbname=test_db;charset=utf8mb4」と入力します。そして、userに「web」、passwordに「password」と入力します。
[FileMaker]の場合
db-classを「FileMaker_FX」に書き換えます。databaseは「TestDB」、userに「web」、passwordに「password」、serverに「192.168.56.1」、portに「80」、protocolに「http」、datatypeに「FMPro12」と入力します。
8Debugについては、「2」のままにしてこの後の作業を行ってください。

データコンバータークラスの指定

1引き続き定義ファイルエディターで、def10.phpの編集を続けます。Optionsの見出しに、Formattersという小見出しがあることを確認します。
2「追加」ボタンをクリックすると、確認パネルが出てきますので、OKボタンをクリックします。設定枠が表示されます。
3[MySQL]の場合
fieldにはMySQLの場合「history@startdate」、converter-classには「MySQLDateTime」、parameterには「%y/%m/%d」と入力します。Tabキーを押して設定を確定させます。
[FileMaker]の場合
fieldには「history_to@startdate」、converter-classには「FMDateTime」、parameterには「%m/%d/%y」と入力します。Tabキーを押して設定を確定させます。
parameterに指定する文字列は、PHPのstrftime関数で使用できる書式指定文字列です。時間も含める場合は「%Y/%m/%d %H:%M:%S」などと指定します。

ページファイルの作成と表示

1「http://192.168.56.101」で開いたページに戻り「page10.htmlを編集する」をクリックし、ページファイルのpage10.htmlを編集するページファイルエディターが開きます。HTMLでの記述内容を以下のように変更します。太字が追加する箇所を示します。(別の番号のセットで作業している場合には、該当する番号のリンクをクリックしてください。)なお、[FileMaker]の場合は、ターゲット指定のコンテキスト名を「history」ではなく、「history_to」にしてください。
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
    <script type="text/javascript" src="def10.php"></script>
</head>
<body>
<table border="1">
  <tbody>
    <tr>   //[FileMaker]の場合は以下のhistoryをhistory_toにしてください
      <td data-im="history@id"></td>
      <td data-im="history@startdate"></td>
      <td data-im="history@enddate"></td>
    </tr>
  </tbody>
</table>
</body>
</html>
2「http://192.168.56.101」で開いたページに戻り、「page10.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。historyテーブルの内容が、1レコードずつ参照できます。最初にグレーの背景でデバッグ情報が表示されますが、ページ冒頭のclearボタンでデバッグ情報は消えます。
ここで、2列目と3列目を比較してください。2列目のstartdateフィールドへは、コンバータークラスが設定されており、年月日がスラッシュで区切られた形式になっています。しかしながら、3列目のenddateフィールドについては、データベースから得られた結果そのままが見えています。MySQLでは年月日がハイフンで区切られた形式ですし、FileMakerの場合は月日年がスラッシュで区切られた結果になっています。いずれにしても、データコンバーターにより、指定した形式での日付が出力されています。
3データコンバータークラスが設定されている2列目のテキストフィールドで、日付を変更し、Tabキーを押して変更を確定させます。デバッグ出力が有効なので、グレーの背景のデバッグ出力が見えていますが、そこでは、データベースサーバーとのやりとりも記録されています。入力した日付は年月日ですが、MySQLだとハイフンで区切られた年月日、FileMakerだと月日年の順序のテキストに置き換わっているのが分かります。

演習のまとめ

このセクションのまとめ

 データコンバータークラスにより、フィールド単位でのデータの変換をサポートします。データベースからの取り出し時はもちろん、データベースに書き込むときにも適切な変換ができる仕組みを持っています。日付や数値を中心として、汎用的なクラスはINTER-Mediatorに付属します。なお、本機能は初期の頃からあるものの、さまざまな代替機能が実装されており、他の方法で実現したほうがより手軽な場合もあります。

4-6データベースの機能の利用

このセクションでは、定義ファイルに指定する項目のうち、データベース側の仕組みを利用する機能についてまとめておきます。このセクションは演習はありません。

処理実行前後のスクリプトの処理

 コンテキスト定義にscriptキーで記述する内容は、FileMakerとそれ以外のSQLデータベースで大きく動作が異なります。FileMaker以外のデータベースでは、値として記述するのはSQLステートメントです。一方、FileMakerで値として記述するのは、データベース内にあるスクリプト名です。この指定により、例えば、データベースに新規レコードを作成したときに、作成直後にあるSQLステートメントを実行したり、FileMakerの場合はスクリプトを実行させるということができます。どちらかといえば、FileMakerでは複雑なロジックを組み込みたいような場合によく利用されます。一方、SQLデータベースの場合には原則として一定の文字列のステートメントになるため、あまり複雑な処理は作りにくい、というのが実情です。

 scriptキーは配列を値としてもちます。その配列の各要素は、表4-6-1のキーを持つ連想配列です。

キー取りうる値意味
db-operationloadデータを読み出すときにスクリプトを実行
updateレコードを更新するときにスクリプトを実行
create新規レコードを作成するときにスクリプトを実行
deleteレコードを削除ときにスクリプトを実行
situationpreデータベース処理の前にスクリプトを実行
presortデータベース処理の前でソート後にスクリプトを実行(FileMakerのみ)
postデータベース処理の後にスクリプトを実行
definitionSQLスクリプト、あるいはFileMakerのスクリプト名
表4-6-1 scriptキーの値で利用する連想配列のキー

 リスト4-6-1は、定義ファイルへの記述で、scriptキーを指定した例です。もちろん、定義ファイルエディターでも指定できます。

 ここでは、2つの配列がscriptキーの値に含まれています。最初の配列は、このテーブルmessageに対して新たなレコードを作成したとき、作成後に「INSERT log SET msg='new record'」というSQLコマンドを発行します。イメージ的にはデータベース操作のログをどこかのテーブルに書き込んでいるような感じで動くと考えてください。さらに2つ目の配列は、messageテーブルに対するクエリー処理の前に、「set names 'utf8'」というコマンドを実施します。

 通常、この種のコマンドはMySQLを正しくセットアップすると不要というのが現在の状況と思われます。しかし、レンタルサーバーなどのように自分で設定ファイルを書き換えられないような場合で文字化けするときに、この設定を行って回避できた場合もあります。もちろん、その場合はクエリー時だけでなく、必要なデータベース処理すべてに対して、この記述がコンテキストごとに必要となります。

リスト4-6-1 MySQLデータベースでのscriptキーの指定例
array(
    'name' => 'message',
    'key' => 'message_id',
    'script' => array(
        array(
            'db-operation' => 'new', 
            'situation' => 'post', 
            'definition' => "INSERT log SET msg='new record' "
        ),
        array(
            'db-operation' => 'load', 
            'situation' => 'pre', 
            'definition' => "set names 'utf8' "
        ),
    ),
),

 FileMakerでの指定例をリスト4-6-2に指定します。FileMakerには、message_layoutというレイアウトが存在し、さらに、「メール送信」というスクリプトを用意しているという前提です。このコンテキスト、messageに対して、例えばPost Onlyモードで新たなレコードを作成した場合、レコードを作成後に、「メール送信」スクリプトが呼び出されます。スクリプトが呼び出されたとき、それはFileMaker Server上で無人の操作が行われたとみなされます。ここで、レイアウトはmessage_layoutレイアウトに切り替わっており、新たに作成されたレコードがカレントレコードになっています。つまり、Post Onlyモードで入力したデータがレイアウト上で参照できる状態になっているわけです。

 ここで、「メール送信」スクリプトが実行されると、ページ上で入力されたメールアドレス宛に、メールが送信されるというわけです。

 例えば、入力を受け付けた旨のメッセージを送信するといったソリューションを組み立てることができます。なお、メール送信自体はINTER-Mediatorで実装する方法もありますが、FileMakerデータベース側のスクリプトを使った方が手早く実装できます。また、FileMakerのスクリプトはかなり自由度が高いので、さまざまなデータ処理を記述することも考えられます。

リスト4-6-2 FileMakerでのscriptキーの指定例
array(
    'name' => 'message',
    'table' => 'message_layout',
    'key' => '-recid',
    'script' => array(
        array(
            'db-operation' => 'new', 
            'situation' => 'post', 
            'definition' => "メール送信"
        ),
    ),
),

 なお、FileMakerのスクリプト処理を実行すると、選択されているレコードや、選択されているレイアウトを、スクリプトステップで違うものにすることもできてしまいます。もちろん、それはそれで便利なのですが、例えば新規レコード作成時にINTER-Mediatorのメール送信処理も組み込んでいる場合、スクリプトの指定がなければ、作成したレコードがそのままメール送信処理に引き継がれます。しかしながら、スクリプトでレイアウトを移動したり、検索を行うなどして選択されているレコードを変えた状態で終えると、その時の状態のレコードがメール送信処理に引き継がれます。もちろん、意図的にそうしたい場合にはひとつの方法ですが、複雑になるので注意が必要です。原則として、FileMakerのスクリプトでは、レイアウトや現在のレコードを一連の処理が終わったら元に戻すということを意識しておくのが良いと考えられます。

グローバルフィールドへ値を設定する

 FileMakerは、グローバルフィールドとして、あるテーブルにフィールド定義をしたとしても、レコードごとに同一の値を持つフィールドを定義できます。つまり、グローバルフィールドは、値をひとつ持つ、まさにグローバルなオブジェクトとして機能します。一方、テーブルに定義されたものだけに、一部制限はありますが、リレーションシップの手がかりとなるフィールドにも設定されます。

 このグローバルフィールドは、原則としてデータベースへ接続したときには値は未確定です。FileMaker Proで開いたら以前の値が見えるので、保持されるものと思われている向きもありますが、Web経由でのアクセスでは、常にデフォルトデータ値が設定されているわけではありません。そのため、データベースアクセスをWeb経由で行うときに、グローバルフィールドに設定する値を引き渡す必要があります。その値を指定するのがコンテキストのglobalキーの値です。表4-6-2には、連想配列で指定するキーを示しました。このglobalキーは、FileMakerでのみ有効で、MySQL等のデータベースでは設定は一切無視されます。

キー取りうる値意味
db-operationloadデータを読み出すときにスクリプトを実行
updateレコードを更新するときにスクリプトを実行
new新規レコードを作成するときにスクリプトを実行
deleteレコードを削除ときにスクリプトを実行
fieldグローバルフィールド名
value設定する値
表4-6-2 globalキーの値で利用する連想配列のキー

 定義ファイルでのコンテキストの定義での指定例を、リスト4-6-3に示します。この例では、message_layoutレイアウトにクエリーを行うときに、「g基準日」フィールドに2015年1月1日というデータをセットして、データの取り出しを行います。「g基準日」フィールドは、message_layoutフィールドに存在する必要があります。FileMakerのWeb利用は、アクセスするときにデータベースを開いて処理し、そして閉じるということを行います。前述したように、FileMakerの場合は常に、グローバルフィールドの値は何も設定されていないので、処理を開始するごとに設定してやる必要があります。

リスト4-6-3 globalキーの指定例
array(
    'name' => 'message',
    'table' => 'message_layout',
    'key' => '-recid',
    'global' => array(
        array(
            'db-operation' => 'load', 
            'field' => 'g基準日', 
            'value' => "1/1/2015"
        ),
    ),
),

 なお、ここでのvalueキーの値は一定値です。しかしながら、現実的には一定値ではなく、「本日の日付」などの動的な値を設定したいことが多いと思われます。INTER-Mediatorでは、検索条件のようなJavaScript側でのサポートをグローバルフィールドについては行っておらず、JavaScriptと、サーバーサイドでのPHPによるプログラムの拡張で対処できるようになっています。これについては、『8-4 プログラムの実例』で説明します。

このセクションのまとめ

 このセクションでは、定義ファイルに記述できるscriptとglobalキーについて説明をしました。SQLデータベースに関しては、scriptキーにより、データ処理の前後に指定したSQLステートメントの実行ができます。FileMakerについてはscritキーによってデータ処理前後にスクリプトを実行したり、globalキーによりグローバルフィールドへの設定を行うことができます。

4-7表示形式をページファイルで指定する

ページ上に見えるデータは、データベース上のものと原則は同じものですが、数値や日付、時刻は、より見やすい形式に書式設定して表示したいと考えます。データコンバータークラスは、サーバーから送り出すときやサーバーが受け取ったときに変更をするものですが、これに加えて、完全にクライアント側で書式設定を稼働できるように、Ver.5.7-devより機能が実装されました。

ページファイルでの書式指定

 書式設定は機能の上ではそれほど難しくないものです。ExcelやFileMaker等での書式設定をご存知であれば、ほぼその機能だと思っていただいてOKです。ページファイルの中のリンクノードについて、data-im-format属性があれば、その属性の記述に従って書式化された値が、ターゲット指定に従った場所に表示されます。設定可能な文字や記号などについては、HTMLの属性に記述する書式を適用する機能に掲載がありますので、本書ではポイントや設定例を中心に説明をします。

 表4-7-1に、ページファイルのタグ内に記述できる属性をまとめました。data-im-formatに加えて、数値に関する細かな書式設定を別の属性として指定が可能です。

属性役割
data-im-format書式設定(この属性のみ設定が必須)
data-im-format-options数値の3桁区切りなど
data-im-format-negative-color負の数の場合の色
data-im-format-negative-style負の数の表示形式
data-im-format-numeral-type数字の種類
data-im-format-kanji-separator数字を日本語で表示する
表4-7-1 書式指定関連のHTML属性

数値の書式設定

 数値やあるいは通貨表示は、data-im-format属性の値を「number()」あるいは「currency()」とします。()内は少数以下の桁数を示すもので、省略すると0を指定したものとみなします。通貨は、ロケールに応じた通貨記号を通常は前に付与します。リスト4-7-1は、itemコンテキストのunitpriceフィールドとバインドしたテキストフィールドですが、テキストフィールド上に、書式化された数値が表示されます。data-im-format-options属性の値が「useseparator」なので、3桁ごとのカンマも表示されます。

リスト4-7-1 数値をカンマ付きで表示する
<input class="price" type="text" size="8"
        data-im="item@unitprice" data-im-format="number()"
        data-im-format-options="useseparator">

 リスト4-7-2は、数値を通貨として表示する例です。通貨記号は、IM_Entry関数でのオプション指定にある'app-locale'キーや'app-currency'キーの値、もしくはparams.phpファイルに記述する$appLocale変数や$appCurrency変数に依存して決められます。もちろん、日本を指定していれば、¥が通貨記号として使われます。こちらもdata-im-format-options属性の値が「useseparator」なので、3桁ごとのカンマも表示されます。

リスト4-7-2 数値をカンマ付きかつ通貨記号をつけて表示する
<input class="price" type="text" size="8"
        data-im="item@unitprice" data-im-format="currency()"
        data-im-format-options="useseparator">

 これらの書式設定を行うと、テキストフィールドに「1,200」や「¥ 1,200」と表示されます。ここで編集の動作を説明します。もちろん、「きちんと書式を設定した文字列に書き換える」ということでも問題はありませんが、数値の書式設定を行ったテキストフィールドに関しては、数字とピリオド(小数点)以外の文字を無視します。したがって、「1,200」を「1,2000」と変更してTabキーを押すと、12000が数値としてフィールドに保存され、ページ上では「12,000」と見えるようになります。ただし、特殊は負の数の記号や、日本語の数値についてはデータ修正時の処理は正しく機能しませんので、これらの書式についてはテキストフィールドをreadonly属性を指定しておくか、あるいはDIVタグ等の編集できない要素にバインドするようにします。

日付や時刻の書式設定

 日付や時刻の書式設定は、data-im-format属性のみを使用します。「%Y」で4桁の年などとフォーマット文字列が定義されているので、それを利用して任意の形式の書式が設定できますが、シンプルな方法は、data-im-format属性に「date(...)」「time(...)」「datetime(...)」と指定することです。()内部は「long」「middle」「short」のいずれかの単語が指定できます。リスト4-7-3はdata-im-format属性の「date(long)」を指定した例です。()内の文字列をクォーテーションで囲う必要はありません。

リスト4-7-3 日付を書式設定する
<input type="text" data-im="invoice@issued"
        data-im-format="date(long)">

 これにより、テキストフィールドでは「2017年11月12日 日曜日」と表示されます。date、time、datetimeの使い分けは、フィールドの型とは関係なく使用して構いません。dateやtimeを指定すれば、型がdatetimeであっても、日付のみ、時刻のみを表示するといった動作をします。date型でdatetimeを使うことはあまり意味はありませんが、時刻が単に00:00:00になるだけです。

 long、middle、shortでの表示例を、表4-7-2に示しておきます。表の中の「ロケール指定」は、IM_Entry関数のオプション引数にあるapp-localeの値、あるいはparams.phpファイルの$appLocale変数で指定したカントリーコード、あるいはそれらがない場合にはブラウザーから得られたカントリーコードです。なお、datetimeの場合は、dateとtimeの間に半角のスペースを入れた文字列として生成されます。Ver.5.7-devの段階では、これらの設定は、英語と日本語のみの設定です。他の言語が必要な方は、IMLocaleFormatTable.phpファイルを参照していただき、是非ともソースコードをコミットしてください。

ロケール指定en, en_USja_JP
date(long)Sunday, November 12, 20172017年11月12日 日曜日
time(long)17:45:0017時45分00秒
date(middle)Sun, Nov 12, 20172017/11/12(日)
time(middle)17:45:0017:45:00
date(short)11/12/20172017/11/12
time(short)17:4517:45
表4-7-2 既定の日付時刻書式の例

 年月日等の要素を指定した位置に配置するなどは、dateなどの後の()内に書式指定文字列として設定します。リスト4-7-4は、%gにより元号を表示する方法です。この書式設定で、「平成29年11月12日」などとテキストフィールドに表示されます。どのような書式指定文字列が使えるかは、HTMLの属性に記述する書式を適用する機能を参照してください。書式指定文字列以外の文字はそのまま出力します。なお、書式指定文字列は、いくつかの種類がありますが、いずれも一貫性がない面があるため、それらを参考に独自に定義しました。特に、アルファベットの大文字と小文字は、同一の要素を示すように定義してあり、桁が多くなる傾向にある方を大文字にしてあります。また、タイムゾーンについてはデータベースの日付時刻フィールドにタイムゾーンを含めて記録することはあまり一般的でないと考え、書式設定においてもタイムゾーンの記述は行っていません。

リスト4-7-4 年号を付けた日付として書式設定する
<input type="text" data-im="invoice@issued"
        data-im-format="date(%g年%m月%d日)">

 書式設定した日付時刻をテキストフィールドで表示した場合、限定的に修正してデータベースへの更新が行えます。おおむね、西暦の年月日、24時間制の時刻の数値であれば修正できると考えてください。また、英語の月名は最初の3文字で判別して、月として修正可能です。一方、和暦や2桁年号、12時間制でAM/PM等が付く時刻の修正はできません。テキストフィールドにdate(long)によって「2017年11月12日 日曜日」と表示されていた場合、「2017年11月13日 日曜日」と変更した場合、曜日は無視して、2017-11-13が入力されたものとしてデータベース側を更新します。また、テキストフィールド内も更新されて「2017年11月13日 月曜日」と表示されるはずです。つまり、曜日は無視して変更してOKです。

 なお、日付時刻については、ISO8601形式の「2017-11-13 18:09:00」や、あるいはデータベースが「2017/11/13 18:09:00」の形式を受け入れる場合には、テキストフィールドにこの形式で値を入力できます。日付や時刻の場合、設定されている書式であると仮定して文字列を解析しますが、年月日等の数値が得られない場合は、入力されたデータそのものでデータベース更新をしようとします。したがって、書式の設定に関係なく、データベースが受け入れる形式で入力することができるようになっています。ただし、書式設定が解釈を変えてしまうような設定になっていないことが前提です。

ロケールへの対応

 言語や国によって、日付の形式や通貨記号が変わります。「どの言語、どの国なのか」という点について、INTER-Mediatorでの扱いを説明します。まず、前提として、ブラウザーから得られるクライアントの言語と国の情報があります。通常は、クライアントからのリクエストに含まれているAccept-Languageヘッダーの内容を元に、サーバー側で言語の判定が可能です。INTER-MediatorではAccept-Languageヘッダーの最初の項目を取り出して、これを「ブラウザーの言語および国」としてまず識別します。一般には利用しているOSの国と言語の情報通りですが、ブラウザーは言語の設定をカスタマイズできるので、日本語としてOSを使っているけれども、ブラウザーは英語で使うという方もいらっしゃるかもしれません。その場合はブラウザーの言語および国は、英語でアメリカと認識します。

 一般にはブラウザーの言語および国の情報に従って処理をすることになると考えられているかもしれませんが、Webアプリケーションではむしろそのような状況になって欲しくないことの方が一般的なのではないかと考えました。例えば、データベースのpriceフィールドがINT型だったとして、あるレコードでは100という数字が入力されていたとします。その数字が、ブラウザーを利用する国に応じて、¥100、$100のように表示されてしまうとしたらどうでしょうか? むしろ、ブラウザーの言語が日本語でも英語でも、¥100と表示されてほしい場合の方が多いと思われます。

 そこで、国と言語の設定を、INTER-Mediatorのアプリケーション側で行うようにしました。定義ファイルでのIM_Entry関数に指定する2つ目の引数(オプション設定)に、'app-locale'というキーで、「ja_JP」「en_US」などのUNIXロケール指定を値として指定します。また、通貨のみを特定の国指定ができるよに、'app-currency'というキーも指定できるようになっています。'app-currency'キーがない場合には、通貨記号は'app-locale'キーの指定に従います。なお、これらの設定は、『8-1 定義ファイルの設定内容と外部での設定』で説明するparams.phpでの変数$appLocale、$appCurrencyでも設定でき、こちらはシステム全体に渡って言語を指定できます。INTER-Mediatorは、Ver.5.6-devより、params.phpの初期状態として、$appLocaleを「ja_JP」、$appCurrencyを「JP」と設定してあります。この設定を「INTER-Mediatorで指定するロケール」と呼ぶことにします。

 このように、2つの国や言語の設定がありますが、INTER-Mediatorで指定するロケールの方が、ブラウザーの言語および国よりも優先度は高くなります。したがって、'app-locale'あるいは$appLocaleを指定した状態で使用するのが通常の状態であると想定しています。

カンマが出ないなどのトラブル

 Ver.5.7-devより、OSのロケールの機能を利用して、書式設定の情報を得ています。macOSやWindowsのようなデスクトップ向けOSでは通常各国のロケールが設定されているので、任意の国コードを指定できるでしょうけれども、Linux Serverでは最低限のロケールしか搭載していないことも多く、日本語を指定するとうまく機能しない場合もあります。その場合、OSにロケールを追加する必要があります。『9-2 Webサーバーのインストールと準備』の『システムロケールについて』を参考にしてください。

このセクションのまとめ

 data-im-formatやあるいは関連するHTML属性により、データベースから得られた値をそれらの設定を元に、数値や通貨、日付時刻等へと書式化して表示することができます。また、一定のルールに従っていれば、書式化した文字列を変更することで、元データの更新もできるようになっています。