Chapter 5
さまざまなユーザーインターフェース構築

このセクションでは、定義ファイルやページファイルの設定だけで可能な機能のうち、これまでに説明していない機能について説明をします。最初は一覧と詳細を行き来するユーザーインターフェース、続いて電子メールの送信、異なるクライアント間で編集結果を連動させる方法、JavaScriptで作られたユーザーインターフェース部品を利用する方法を説明します。

5-1マスター/ディテール形式のナビゲーション

一覧と詳細を切り替えて表示するようなユーザーインターフェースはよくみられます。業務系システムでは多くの場合、こうした動作が基本です。INTER-Mediatorではこうした動作を2つのコンテキストで実現して自動的に切り替えるユーザーインターフェースを用意します。そこまでの作業は、特別なプログラムコードを書かなくても、定義ファイルとページファイルの設定だけで可能です。また、一覧と詳細の切り替え時にプログラムを記述すれば、より高度なユーザーインターフェース構築も可能です。

マスター/ディテールあるいは一覧/詳細

 データを一覧表示し、さらに特定のレコードについてより多くの情報を表示するといった形式のユーザーインターフェースは一般的なテクニックです。その場合、異なる画面であるだけに、2つのページを作成しておき、それぞれの機能動作を組み込むということが一般的かもしれません。しかしながら、INTER-Mediatorでは、ひとつのページに一覧表示のためのコンテキストと、詳細表示のためのコンテキストを両方を用意して、切り替えることなどが可能です。

 iOSには、UISplitViewControllerというクラスがあり、iPadの「メール」などにみられるように、一覧と詳細を同時に、あるいは個別に表示できる仕組みがあります(図5-1-1)。INTER-Mediatorの機能は、このスプリットビューをヒントにしています。

図5-1-1 UISplitViewControllerを利用したアプリケーションの例

 FileMakerを利用するときに、レイアウトを2種類作成し、同じTOを2つのレイアウトに割り当てることで、一覧(図5-1-2)と詳細表示(図5-1-3)の切り替えがスムーズに行われます。切り替えるために、なんらかのスクリプトは必要ですが、一覧で選択した結果は、TOで記録されるので、レイアウトを切り替えるだけで、一覧で選択されているレコードを詳細レイアウトでも表示することができます。

図5-1-2 FileMakerでの一覧表示
図5-1-3 FileMakerでの詳細表示

 これらのユーザーインターフェースを本コースでは、「マスター/ディテール形式」あるいは「一覧詳細形式」と総称することにして、「一覧表示側」「詳細表示側」という用語で、2種類の画面をそれぞれ特定することにします。

コンテキストに記述するnavi-controlキー

 INTER-Mediatorでの一覧詳細形式のユーザーインターフェースを構築するには、原則として同一テーブルを元にした2つの異なるコンテキストを定義します。同じテーブルであっても、2つのコンテキストを定義してください。もちろんnameキーで指定する名前は別々のものにします。このとき、同一のテーブルというのは、より厳密に言えば、keyキーで指定する主キーフィールドとその値が、適切なレコードを検索する状態であるということです。極端に言えば、全く異なるテーブルでも構いませんが、主キー値を共有するものであれば、動作はします。しかしながら、それはかなり難しい運用となります。ありうる運用としては、viewキーで参照されるものが、同一のテーブルを元にしたビューであってもかまいません。それぞれが同一名の主キーフィールドを持ちkeyキーで指定されていることがポイントになります。

 そして、それぞれのコンテキストでは、navi-controlキーによる値を定義します。設定可能な値は表5-1-1に記載します。このnavi-controlキーの値が設定されたコンテキストは、ひとつの定義ファイルで必ず2つにしてください。3つ以上ある場合の動作は保証できません。navi-controlには、そのコンテキストが一覧表示側なのか、詳細表示側なのかを指定します。詳細表示側は、recordsキーの値を「1」にしておきます。また、pagingキーは記述するとしたら、一覧表示側のコンテキストに指定をしてください。

一覧表示側詳細表示側
masterdetail
master-hidedetail-top
detail-bottom
detail-update
表5-1-1 navi-controlキーに設定可能な値

 2つのコンテキストの動作は、一覧表示側のnavi-controlキーの設定に依存します。「master-hide」を指定すると、前に説明したFileMakerの一覧と詳細タイプの動作をします。つまり、最初は一覧表示側だけが見えていて、詳細表示側は見えていません。一覧表示側には「詳細」ボタンが各レコードの冒頭に付加されます。それをクリックすると、一覧側は消えて、詳細表示側のみが見えます。詳細表示は1レコードだけが表示され、一覧側でクリックしたレコードが表示されます。なお「見えなくなる範囲」は原則としてエンクロージャーですが、TBODYについては、それを含むテーブル全体が見えなくなります。したがって、一番シンプルな構成は、2つのコンテキストをそれぞれ別々のTABLEタグのテーブルに表示するという手法になります。詳細表示側には、「一覧に戻る」ボタンが自動的に追加され、クリックすると、詳細表示が消えて一覧表示のみとなります。

 一方、一覧表示側のnavi-controlキーの値に「master」を指定すると、前に説明した、iOSのスプリットビュー形式のユーザーインターフェースになり、一覧側、詳細側、どちらも常に表示しています。一覧表示側には「詳細」ボタンが各レコードの冒頭に付加され、クリックすると詳細側に対応するレコードが表示されます。初期状態では、詳細側は、マスター側の最初のレコードが表示されるようになっています。なお、iPadのような左右に分離された形式での表示にするには、スタイルシートの仕組みを利用して、レイアウトが意図したようになるようにします。

 詳細表示側の設定値の種類が多いですが、「top」「bottom」は、「一覧に戻る」ボタンをエンクロージャーの前か後かを指定することができます。また「detail」と「detail-top」は同じ意味です。「detail-update」は、詳細から一覧に戻るときに、一覧のコンテキストを再表示します。データベースアクセスからやり直して、表示内容を更新します。このように、一覧側に「master-hide」を指定したときにだけ、詳細側のnavi-controlの値にバリエーションがあり、一覧側が「master」の場合は、詳細側にどの値を指定しても「detail」を指定するのと変わりません。

 一覧表示側に表示される「詳細」ボタン、詳細表示側に表示される「一覧に戻る」ボタンのボタン名は、いずれも、button-namesキーの配列で変更できます。それぞれのコンテキスト内で、button-namesキーの配列を定義し、要素のキーを一覧表示側は「navi-detail」、詳細表示側は「navi-back」で指定します。これらのキーの値が、ボタン名になります。

 2つのコンテキストの外観をカスタマイズするには、スタイルシートを使ってさまざまに設定します。各オブジェクトに関して、表5-1-2のように、class属性を設定しています。これらのclass属性をボタン等のスタイルの変更に利用してください。なお、IM_NaviBack_TRとIM_NaviBack_TDは、詳細表示側のコンテキストのエンクロージャーがTBODYの場合だけ設定されます。このとき、THEADあるいはTFOOTに新たにTRタグ要素を作り、TDタグ要素を含み、さらにその中にBUTTON要素を配置します。これらすべてにclassを割り当てているので、うまく設定すると表の外にボタンがあるように見えるかもしれません。エンクロージャーがTBODY以外の場合、class属性がIM_NaviBack_TRとIM_NaviBack_TDの要素は作られません。

class属性の値適用先
IM_Button_Master一覧表示側に追加される「詳細」ボタンのBUTTONタグ要素
IM_Button_BackNavi詳細表示側に追加される「一覧に戻る」ボタンのBUTTONタグ要素
IM_NaviBack_TR詳細表示のエンクロージャーがTBODYの場合、「一覧に戻る」ボタンを含むTRタグ要素
IM_NaviBack_TD詳細表示のエンクロージャーがTBODYの場合、「一覧に戻る」ボタンを含むTDタグ要素
表5-1-2 一覧詳細形式の表示により自動的に付加されるclass属性値

一覧と詳細の切り替え時に呼び出されるメソッド

 一覧表示と詳細表示で、コンテキスト内のリンクノードについては、データベースの内容がそれぞれ表示されますが、それ以外のなんらかの処理を追加したい場合には、いくつかのメソッドを利用することができます。少ない作業で確認ができるので、このセクションの演習で実際にプログラムを追加して動作を紹介しておきます。

演習一覧と詳細を利用したユーザーインターフェース

 iPadのようなユーザーインターフェースや、一覧と詳細が切り替わるユーザーインターフェースを実際に作成してみましょう。また、JavaScriptを利用した高度なカスタマイズも紹介します。

2つのコンテキストを定義ファイルに定義

1ここからの作業は、Webブラウザー上で行います。まず、INTER-MediatorがインストールされたVMを、VirtualBoxを使用して起動します(『1-2 演習を行うための準備』を参照)。続いて、ブラウザーで、「http://192.168.56.101」に接続します。「トライアル用のページファイルと定義ファイル」というタイトルの部分を特定します。
2「def13.phpを編集する」をクリックし、定義ファイルエディターでdef13.phpファイルを編集します。(もし、他の用途で13番目を利用しているのなら、例えば、def21.phpを利用するなど、別の番号のセットを使用してください。その場合ソースコードの記述が変わる部分がありますが、可能な限り注記します。)
3Contextsの中のQueryと書かれた背景がグレーの部分を特定します。そして、その次の行の右の方にある「削除」をクリックして、Queryの設定がある行を削除します。
4「レコードを本当に削除していいですか?」とたずねられるので、OKボタンをクリックします。
5同様に、Sortingの次の行にある「削除」ボタンを押し、確認にOKボタンをクリックして、こちらの設定も削除しておきます。
6nameに「person_list」、keyを「id」、pagingを「true」、repeat-controlを「confirm-insert」、recordsを「10」、maxrecordsを「100」とします。
[MySQL]の場合
viewとtableは「person」とします。
[FileMaker]の場合
viewとtableは「person_layout」とします。
Contextsのその他のテキストフィールドは空白にします。
7Contextsという見出しのすぐ下の「追加」ボタンをクリックします。コンテキストの定義領域がひとつ分増えます。
8nameに「person_detail」、keyを「id」、recordsを「1」、maxrecordsを「1」とします。
[MySQL]の場合
viewとtableは「person」とします。
[FileMaker]の場合
viewとtableは「person_layout」とします。
Contextsのその他のテキストフィールドは空白にします。
9Database 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」と入力します。
10Debugについては、「false」にすると、デバッグ情報が出なくなります。なお、デバッグ情報をみながら動作を確認したい方は、「2」のままにしてこの後の作業を行ってください。

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

1「http://192.168.56.101」で開いたページに戻り「page13.htmlを編集する」をクリックし、ページファイルのpage13.htmlを編集するページファイルエディターが開きます。HTMLでの記述内容を以下のように変更します。太字が追加する箇所を示します。(別の番号のセットで作業している場合には、該当する番号のリンクをクリックしてください。)
<!DOCTYPE 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="def13.php"></script>
</head>
<body>
  <div id="IM_NAVIGATOR"></div>
  <table>
    <tr>
      <td>
        <div data-im="person_list@name"></div>
        <div data-im="person_list@mail"></div>
      </td>
    </tr>
  </table>
  <table>
    <tr><th>id</th>
      <td data-im="person_detail@id"></td>
    </tr>
    <tr><th>name</th>
      <td><input type="text" data-im="person_detail@name"/></td>
    </tr>
    <tr><th>address</th>
      <td><input type="text" data-im="person_detail@address"/></td>
    </tr>
    <tr><th>mail</th>
      <td><input type="text" data-im="person_detail@mail"/></td>
    </tr>
    <tr><th>category</th>
      <td><input type="text" data-im="person_detail@category"/></td>
    </tr>
    <tr><th>checking</th>
      <td><input type="text" data-im="person_detail@checking"/></td>
    </tr>
    <tr><th>location</th>
      <td><input type="text" data-im="person_detail@location"/></td>
    </tr>
    <tr><th>memo</th>
      <td><textarea data-im="person_detail@memo"></textarea></td>
    </tr>
  </table>
</body>
</html>
2「http://192.168.56.101」で開いたページに戻り、「page13.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。2つのテーブルが見えています。ひとつは、personテーブルの内容が一覧になっています。もうひとつはpersonテーブルのひとつのレコードが見えています。ページネーションが見えていますが、こちらは定義ファイルで指定した通り、テーブルの一覧表示のコンテキストに関連付けられたものです。ここまでは、本コースでこれまでにやってきたことと同一です。
3「page13.htmlを編集する」をクリックして表示したタブあるいはウインドウに戻ります。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「page13.htmlを編集する」をクリックして開きます。そして、最初のTABLEタグにスタイル属性を設定します。floatスタイル属性により、2つのテーブルは左右に配置されます。marign-right属性により、テーブルの右側に空きを作ります。
<body>
  <div id="IM_NAVIGATOR"></div>
  <table style="float: left; margin-right: 20px;">
    <tr>
      <td>
4「page13.htmlを表示する」をクリックして表示したタブあるいはウインドウに戻り、ブラウザーの更新機能を使ってページ内容を更新します。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「page13.htmlを表示する」をクリックして開きます。style属性で指定した通り、テーブルが左右に配置され、テーブル間には空間が作られています。

同一ページでのマスター/ディテール形式のユーザーインターフェース

1「def13.phpを編集する」をクリックして表示したタブあるいはウインドウに戻ります。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「def13.phpを編集する」をクリックして開きます。
2最初の「person_list」コンテキストのnavi-controlを「master」にします。
32つ目の「person_detail」コンテキストのnav-controlを「detail」にします。設定後、Tabキーを押すなどして、入力結果を確定させてください。
4「page13.htmlを編集する」をクリックして表示したタブあるいはウインドウに戻ります。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「page13.htmlを編集する」をクリックして開きます。一覧を表示するテーブルの行の最初に、空のセルを付け加えておきます。
<body>
  <div id="IM_NAVIGATOR"></div>
  <table style="float: left; margin-right: 20px;">
    <tr>
      <td></td>
      <td>
        <div data-im="person_list@name"></div>
        <div data-im="person_list@mail"></div>
      </td>
    </tr>
  </table>
5「page13.htmlを表示する」をクリックして表示したタブあるいはウインドウに戻り、ブラウザーの更新機能を使ってページ内容を更新します。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「page13.htmlを表示する」をクリックして開きます。一覧のテーブルの各行に追加した空白のセルの中に、「詳細」ボタンが自動的に設定されています。
63番目の「詳細」ボタンをクリックすると、右側には、一覧表示側と対応したレコードの内容が表示されています。つまり、「詳細」ボタンをクリックしたレコードの内容が右側のテーブル(詳細表示)に表示しています。iPadでよく見られるような一覧と詳細が左右に並ぶ形式のユーザーインターフェースがこれで実現しています。(「詳細」ボタンをクリックしたときに、ページネーションの「レコード追加」ボタンが消えるのはVer.5.1現在の不具合で、将来解消される予定です。)
7詳細表示側はテキストフィールドになっています。nameフィールドの中身を変更してみて、ReturnキーあるいはTabキーを押して設定を確定します。
8自動的に左側の一覧表示側のnameフィールド値も、書き換えたものに即座に変更されました。同一のレコードの同一のフィールドは、ページ内では連動しています。

一覧表示側に対して作用するページネーション

1一覧表示側で、10より多くのレコードがあるようにします。ない場合には、「レコード追加:person_list」ボタンをクリックしてレコードを追加します。このボタンが見えない場合には、「更新」ボタンをクリックして、ページを更新してください。「レコードを本当に作成していいですか?」とたずねられるので、OKボタンをクリックします。「レコード追加」ボタンがない場合には、ブラウザーの更新機能を利用するか、ページネーションの「更新」ボタンをクリックして、ページを更新します。
2例えばレコードを全部で13個作成した場合、最初の10レコードが左側の一覧表示側に見えています。また、ページネーションコントロールでは、次のページに移動するボタンがクリックできるようになっています。
3ページネーションコントローラーの「>」ボタンをクリックして、ページネーションを次のページに移動します。左側の一覧表示側は11レコード目より10レコード以内のレコードが一覧されていますが、詳細表示側は特に変更はなく、最初のレコード(idフィールドが「1」)がそのまま見えています。これは、本機能の仕様です。「詳細」ボタンがクリックされるまで、詳細表示側の内容はそのままとなります。
4最後のレコードの「詳細」ボタンをクリックして、詳細表示側で適当に入力しました。もちろん、その結果はデータベースに保存されるとともに、一覧表示側にも即座に反映しています。

レコード削除に対する詳細側の動作

1「def13.phpを編集する」をクリックして表示したタブあるいはウインドウに戻ります。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「def13.phpを編集する」をクリックして開きます。
2最初の「person_list」コンテキストのrepeat-controlを「confirm-insert confirm-delete」にします。
3「page13.htmlを編集する」をクリックして表示したタブあるいはウインドウに戻ります。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「page13.htmlを編集する」をクリックして開きます。一覧を表示するテーブルの行の最後に、空のセルを付け加えておきます。
  <table>
    <tr>
      <td>
        <div data-im="person_list@name"></div>
        <div data-im="person_list@mail"></div>
      </td>
      <td></td>
    </tr>
  </table>
4「page13.htmlを表示する」をクリックして表示したタブあるいはウインドウに戻り、ブラウザーの更新機能を使ってページ内容を更新します。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「page13.htmlを表示する」をクリックして開きます。一覧のテーブルの各行に、「削除」ボタンが自動的に設定されています。
5適当なレコードの「詳細」ボタンをクリックして、詳細表示側に何か表示されている状態にします。
6詳細表示側に表示されている対応するレコードを一覧表示側で特定して、そのレコードの「削除」ボタンをクリックします。削除するかどうかをたずねるので、OKボタンをクリックして、本当に削除します。
7一覧表示側からレコードは消えましたが、詳細表示側ではレコードは消えていません。これは、Ver.5.1現在はこういう動作になっています。将来的には動作に変更があるかもしれません。
8削除されたレコードが詳細表示側で見えていますが、そこでテキストフィールドの内容を修正しようとしても、エラーになります。データベースのいずれかのデータが勝手に書きかわるということはありません。

一覧と詳細が切り替わるユーザーインターフェース

1「def13.phpを編集する」をクリックして表示したタブあるいはウインドウに戻ります。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「def13.phpを編集する」をクリックして開きます。
2最初の「person_list」コンテキストのnavi-controlを「master-hide」にします。Tabキーを押して、確実に内容を定義ファイルに反映させるようにしてください。
3この状態で、Webアプリケーションの表示がどのようになっているか確認してみましょう。「page13.htmlを表示する」をクリックして表示したタブあるいはウインドウに戻り、ブラウザーの更新機能を使ってページ内容を更新します。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「page13.htmlを表示する」をクリックして開きます。すると、一覧表示部分は見えていますが、INTER-Mediatorのサインがページの途中に表示されてしまっています。
float属性を使用したオブジェクトとfloat属性のないオブジェクトを並べた場合、それぞれのオブジェクトが重なりあってしまいます。float属性のあるオブジェクトの一群と重ならないようにオブジェクトを配置するには、BRタグを追加しておきます。このBRタグ要素のclear属性を「all」にすることで、floatによるレイアウトを一旦クリアし、オブジェクトが配置された下の部分から新たに要素を展開します。
4「page13.htmlを編集する」をクリックして表示したタブあるいはウインドウに戻ります。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「page13.htmlを編集する」をクリックして開きます。以上の点を踏まえて、ページの最後にBRタグ要素を追加します。
    <tr><th>memo</th>
      <td><textarea data-im="person_detail@memo"></textarea></td>
    </tr>
  </table>
  <br clear="all"/>
</body>
</html>
5「page13.htmlを表示する」をクリックして表示したタブあるいはウインドウに戻り、ブラウザーの更新機能を使ってページ内容を更新します。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「page13.htmlを表示する」をクリックして開きます。一覧表示側だけが表示されています。
6適当に「詳細」ボタンをクリックすると、そのクリックしたページの詳細表示側だけが見えています。つまり、一覧と詳細が切り替わるユーザーインターフェースが自動的に構築されています。詳細側では、ページネーションも見えていないことを確認してください。
7詳細側の「一覧表示」ボタンをクリックすると、一覧表示側つまり、最初の状態に戻ります。ページネーションも見えるようになっています。
8「def13.phpを編集する」をクリックして表示したタブあるいはウインドウに戻ります。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「def13.phpを編集する」をクリックして開きます。
92つ目の「person_detail」コンテキストのnav-controlを「detail-bottom」にします。設定後、Tabキーを押すなどして、入力結果を確定させてください。
10「page13.htmlを表示する」をクリックして表示したタブあるいはウインドウに戻り、ブラウザーの更新機能を使ってページ内容を更新します。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「page13.htmlを表示する」をクリックして開きます。一覧表示側だけが表示されています。適当なレコードの「詳細」ボタンをクリックして詳細表示側を見てみます。「一覧表示」ボタンが、下部に表示されました。

エンクロージャー外の要素のコントロール

1「page13.htmlを編集する」をクリックして表示したタブあるいはウインドウに戻ります。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「page13.htmlを編集する」をクリックして開きます。ヘッダー部にスクリプトを追加するとともに、ボディ部の最初に一覧表示と詳細表示の両方の見出しを表示しておきます。そして、詳細側はdisplay属性をnoneにして、非表示にしておきます。スクリプトは、一覧から詳細あるいはその逆に変化する段階で呼び出されるメソッドで、H1タグの見出しをそれぞれ表示/非表示を切り替えているだけです。
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
    <script type="text/javascript" src="def13.php"></script>
  <script type="text/javascript">
    INTERMediatorOnPage.naviAfterMoveToDetail
      = function(master, detail){
      document.getElementById("master_title").style.display = "none";
      document.getElementById("detail_title").style.display = "block";
    }

    INTERMediatorOnPage.naviAfterMoveToMaster
      = function(master, detail){
      document.getElementById("master_title").style.display = "block";
      document.getElementById("detail_title").style.display = "none";
    }
  </script>
</head>
<body>
  <h1 id="master_title">住所録一覧表示</h1>
  <h1 id="detail_title" style="display:none">住所録詳細表示</h1>
  <div id="IM_NAVIGATOR"></div>
  <table style="float: left; margin-right: 20px;">
    <tr>
      <td></td>
      <td>
        <div data-im="person_list@name"></div>
使用しているメソッドについての説明は、この演習のすぐ後にある『一覧と詳細の切り替え時に呼び出されるメソッド』を参照してください。
2「page13.htmlを表示する」をクリックして表示したタブあるいはウインドウに戻り、ブラウザーの更新機能を使ってページ内容を更新します。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「page13.htmlを表示する」をクリックして開きます。一覧表示側だけが表示されていて、ページの見出しは「住所録一覧表示」だけが表示されています。「住所録詳細表示」は初期状態ではdisplayスタイル属性が「none」なので、非表示です。
3適当な「詳細」ボタンをクリックして、詳細表示にすると、見出しは「住所録詳細表示」に切り替わります。これは、一覧から詳細に移行する途中でINTERMediatorOnPage.naviAfterMoveToDetailメソッドが呼び出され、ヘッダーに記述したプログラムが実行され、一方の見出しは非表示に、もう一方は表示されるようになったということです。
4「一覧」ボタンをクリックして、一覧表示にすると、見出しは「住所録一覧表示」に戻ります。これは、詳細から一覧に移行する途中でINTERMediatorOnPage.naviAfterMoveToMasterメソッドが呼び出され、ヘッダーに記述したプログラムが実行され、一方の見出しは非表示に、もう一方は表示されるようになったということです。

演習のまとめ

一覧と詳細の切り替え時に呼び出されるメソッド

 以下、マスター/ディテール形式のページにおいて使用可能なJavaScriptのAPIについて説明をします。JavaScriptについては、本ページよりも後になりますが、『2-3 JavaScriptプログラムの記述』の記述を踏まえたものとします。

 一覧表示と詳細表示で、コンテキスト内のリンクノードについては、データベースの内容がそれぞれ表示されますが、それ以外のなんらかの処理を追加したい場合には、いくつかのメソッドを利用することができます。一覧表示から詳細表示、あるいは詳細表示から一覧表示に切り替わるとき、切り替え前に呼び出されるメソッドと、切り替わり後に呼び出されるメソッドの、合計4種類のメソッドを、グローバル変数INTERMediatorOnPageのメソッドとして定義可能です。引数を2つ取り、mContextが一覧表示側、dContextが詳細表示側のコンテキストオブジェクトです。コンテキスト定義ではなく、モデルとして動作するコンテキストオブジェクトへの参照が得られます。返り値は不要です。

INTERMediatorOnPage.naviBeforeMoveToDetail = function (mContext, dContext) {...}

 一覧表示から詳細表示に切り替わる前に呼び出されます。

INTERMediatorOnPage.naviAfterMoveToDetail = function (mContext, dContext) {...}

 一覧表示から詳細表示に切り替わった直後に呼び出されます。

INTERMediatorOnPage.naviBeforeMoveToMaster = function (mContext, dContext) {...}

 詳細表示から一覧表示に切り替わる前に呼び出されます。

INTERMediatorOnPage.naviAfterMoveToMaster = function (mContext, dContext) {...}

 詳細表示から一覧表示に切り替わった直後に呼び出されます。

 これらの4つのメソッドは、一覧/詳細の切り替わるときにしかこれらのメソッドは呼び出されません。ページ表示直後に何らかのプログラムの追加が必要なら、INTERMediator.doBeforeConstructメソッドの呼び出し前や、INTERMediatorOnPage.doAfterConstructメソッド(『6-1 ブラウザーを判断するページ』を参照)を利用します。

マスター/ディテール形式のページでのそれぞれのコンテキストの取得

 マスター表示とディテール表示の切り替え前後に呼び出されるメソッドは、それぞれのコンテキストオブジェクトを引数として渡されるので、コンテキストは即座に参照できます。これら以外のメソッドで、マスターあるいはディテールのコンテキストを得るには、次のAPIを利用することができます。

IMLibContextPool.getMasterContext()

 マスター側のコンテキストへの参照を返します。

IMLibContextPool.getDetailContext()

 ディテール側のコンテキストへの参照を返します。

詳細へのナビゲーション

IMLibPageNavigation.moveToDetail(keyField, keyValue, isHide, isHidePageNavi)

 引数に指定した仕様の詳細画面を表示します。一覧側にある「詳細」ボタンを押したときに利用されるメソッドです。

引数指定内容
keyField詳細側に表示するレコードのキーフィールド名
keyValue詳細側に表示するレコードのキーフィールド値
isHide一覧側を非表示にするのならtrue、そのままならfalse
isHidePageNaviページネーションを非表示にするのならtrue
表5-1-3 moveToDetailメソッドに指定する引数

 このメソッドの返り値は、関数です。その関数を引数なしで呼び出すことで、詳細への画面切り替えが発生します。例えば、マスター領域の最初のレコードに対応した詳細を表示するには、リスト5-1-1のようなプログラムで可能です。

リスト5-1-1 マスターの最初のレコードを表示する詳細への移動プログラム
var context = IMLibContextPool.getMasterContext();
var keys = Object.keys(context.store);
var comp = keys[0].split('=');
var func = IMLibPageNavigation.moveToDetail(comp[0], comp[1], true, true);
func();

 リスト5-1-1のプログラムを利用すると、最初に一覧表示のマスター領域ではなく、最新データの詳細を表示することができます。リスト5-1-2にあるように、INTERMediatorOnPage.doAfterConstructメソッドに処理を記述します。INTERMediator.partialConstructingプロパティは、ページ全部が生成し終わっているかどうかを判定できるものです。最初のページ生成ではfalseになっていて、その後のマスターと詳細を行き来するときにはtrueになります。つまり、ページの最初の生成時にのみ、moveToDetailメソッドが実行されるということです。moveToDetailメソッドの返り値は関数なので、「func();」のように即座に実行しています。ここで、詳細側にどのレコードを表示するのかを、moveToDetailメソッドの引数に指定します。詳細側のコンテキストからレコードを取り出すとき、「第1引数="第2引数"」といった条件式が適用されると考えてください。一方、マスター領域はすでにコンテキストが作られています。マスター側のコンテキストを取得するには、IMLibContextPool.getMasterContextメソッドが手軽で便利です。そのオブジェクトのstoreプロパティがデータベースから取得したデータですが、「主キー=値」がプロパティとなったオブジェクトの形式になっています。Object.keysメソッドで、プロパティ名の配列を得て、その最初の要素から、主キーフィールド名とその値を分離して得ています。

リスト5-1-2 ページを表示したときに詳細を表示する
INTERMediatorOnPage.doAfterConstruct = function () {
  if (!INTERMediator.partialConstructing) {
    var context = IMLibContextPool.getMasterContext();
    var keys = Object.keys(context.store);
    var comp = keys[0].split('=');
    var func = IMLibPageNavigation.moveToDetail(comp[0], comp[1], true, true);
    func();
  }
}

IMLibPageNavigation.moveDetailOnceAgain()

 最後に行った詳細画面への移行を再度行います。マスター/ディテール形式のページから別のページに移動し、またマスター/ディテール形式のページに戻ったときに、以前表示していた詳細レコードを再度表示したいような場合に利用できます。

このセクションのまとめ

 一覧と詳細を行き来するユーザーインターフェースや、一覧と詳細を同時に表示するユーザーインターフェースを、定義ファイルへの指定だけで作成可能な機能がINTER-Mediatorには搭載されています。2つのコンテキストを用意して、navi-controlキーにmasterやdetailなどの値を指定することが基本です。レコードの削除の動作には若干、気を付ける必要がありますが、2つのコンテキストは連動しているので、一方で編集した結果はもう一方に原則として即座に反映されます。ナビゲーションのためのボタンが自動的に付けられますが、ボタン名のカスタマイズや、スタイルシートによる書式設定もできます。

5-2メールの送信

Webアプリケーションでは、メールの送信を行うことがよくあるため、INTER-Mediatorでもメール送信の機能を実装しました。単に送信するだけなら、定義ファイルへの設定のみで可能ですが、一般にはJavaScriptとの連携を行うことが多いと思われます。

メールを送るタイミング

 まず、メールを送るタイミングについて説明をします。メールの送信をクライアントから実行するという手もありますが、ブラウザーからクライアントOSや別のアプリケーションを操作するのはかなり難しく、セキュリティ面から、原則として大きく制約されているのが一般的です。そのため、メールを組み込む機能はサーバー上にある必要があります。

 そのこともあって、メールの送信機能は、「データベースに対する操作を行った後」に行うという実装としました。ただし、レコード削除後に送信するのは用途的に考えにくいのと、レコードの内容をメールに含める仕組みを実現しようとすると、この処理だけ例外的になってしまうので、削除は対象外としました。つまり、データの基本操作であるCRUD(Create Read Update Delete)のうちのCRUの3つの操作の後に、メールを送ります。コンテキストに対してsend-mailキーで連想配列を定義し、その要素のキーとして、表5-2-1のようなキーを指定します。言い換えれば、ひとつのコンテキストについて、CRUそれぞれひとつのメール送信だけを指定できるようになっています。

キー動作
readレコードの取り出しを行った後にメールを送信する
upateレコードの更新処理を行った後にメールを送信する
create新たなレコードを作るアクションを起こした後にメールを送信する
表5-2-1 send-mailキーの連想配列に設定可能なキー

 設定の上で若干柔軟性が低いと思われるかもしれません。例えば、フィールドAを更新したときだけメールを出したいといった場合です。そのようなときには、フィールドAの更新を行うときのコンテキストを新たに定義し、そこにメール送信の設定を行います。そして、フィールドAの更新を、例えばボタンを押して行うなどして、ボタンを押したときに新たなコンテキストの更新処理をJavaScriptで記述するという手法を使います。このように、メールの送信のための別のコンテキストを用意するといった手法で、柔軟にメール送信の仕組みが組み立てられると同時に、条件設定的な複雑な設定やプログラムを導入することなくメール送信が利用できます。

メールの内容に関する設定

 あるコンテキストで、新規にレコードを作ったときにメールを送るのであれば、send-mailキーのnewキーの値にさらに連想配列を定義して、その連想配列を、表5-2-2に示すキーの要素を追加します。表にあるすべてのキーを設定する必要はありませんが、送信者と送信先、そして本文の3つはなんらかのキーで指定は必要です。

キー値に設定する内容
from-constant送信者やアドレスを文字列で指定
to-constant送信先を文字列で指定
cc-constantCc先を文字列で指定
bcc-constantBcc先を文字列で指定
subject-constant件名を文字列で指定
body-constant本文を文字列で指定
from送信者名や送信者アドレスが含まれるフィールド名
to送信先が含まれるフィールド名
ccCc先が含まれるフィールド名
bccBcc先が含まれるフィールド名
subject件名が含まれるフィールド名
bodyメール本文が含まれるフィールド名
body-template本文のテンプレートとなるファイルのファイル名
body-fieldsテンプレートに差し込むフィールドの順序をカンマで区切る
f-optionUNIXでSMTPサーバーを経由しない場合にtrueを指定すると、fromの指定が有効
body-wrap右端の折り返しのバイト数(指定がないと72バイト)。0だと折り返ししない
表5-2-2 send-mailキーの連想配列の値に設定する連想配列に設定可能なキー

 メールの作成方法は、この後に演習を通じていくつかの事例を示しながら解説をします。表の中のf-optionは、UNIXマシンのsendmailコマンドを使ってメールを送るときに、送信者を指定したのにもかかわらず、送信者が、www(あるいはApacheの稼働ユーザー)になってしまうようなときに指定をしてください。OSに組み込まれているメール送信コマンド等の動作に依存しますが、より確実に送信者の指定ができるはずです。

 body-wrapは、長い行の折り返しを、Shift-JISのバイト数で指定します。なお、折り返しを入れますが、比較的追い込みを積極的に行うアルゴリズムを組み込んであります。

演習Post Onlyモードのページでメールを送信する

 Post Onlyモードでは、アンケートなどで使われることが多いと思われます。そのとき、投稿した上で、確認のメールを出すということはよく行われます。そうした場面を想定して、3種類のメールの送信方法を紹介します。

コンテキストを定義ファイルに定義

1ここからの作業は、Webブラウザー上で行います。まず、INTER-MediatorがインストールされたVMを、VirtualBoxを使用して起動します(『1-2 演習を行うための準備』を参照)。続いて、ブラウザーで、「http://192.168.56.101」に接続します。「トライアル用のページファイルと定義ファイル」というタイトルの部分を特定します。
2「def14.phpを編集する」をクリックし、定義ファイルエディターでdef14.phpファイルを編集します。(もし、他の用途で14番目を利用しているのなら、例えば、def31.phpを利用するなど、別の番号のセットを使用してください。その場合ソースコードの記述が変わる部分がありますが、可能な限り注記します。)
3Contextsの中のQueryと書かれた背景がグレーの部分を特定します。そして、その次の行の右の方にある「削除」をクリックして、Queryの設定がある行を削除します。
4「レコードを本当に削除していいですか?」とたずねられるので、OKボタンをクリックします。
5同様に、Sortingの次の行にある「削除」ボタンを押し、確認にOKボタンをクリックして、こちらの設定も削除しておきます。
6name、table、viewに「survey」、keyを「id」とします。このコンテキストには他にテキストフィールドがありますが、すべて空白にします。
Contextsのその他のテキストフィールドは空白にします。
7Contextsのすぐ下にある「追加」ボタンをクリックして、コンテキスト定義をひとつ増やします。
8nameに「survey_list」、table、viewに「survey」、keyを「id」、pagingを「true」、repeat-controlを「confirm-delete confirm-insert」、recordsを「10」、maxrecordsを「100」とします。その他のテキストフィールドは空白にします。
9Database 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」と入力します。
10Debugについては、「false」にすると、デバッグ情報が出なくなります。なお、デバッグ情報をみながら動作を確認したい方は、「2」のままにしてこの後の作業を行ってください。

Post Onlyモードのページファイルの作成

1「http://192.168.56.101」で開いたページに戻り「page14.htmlを編集する」をクリックし、ページファイルのpage14.htmlを編集するページファイルエディターが開きます。HTMLでの記述内容を以下のように変更します。太字が追加する箇所を示します。(別の番号のセットで作業している場合には、該当する番号のリンクをクリックしてください。)
<!DOCTYPE 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="def14.php"></script>
</head>
<body>
<table>
  <tbody data-im-control="post">
    <tr>
      <th>お名前</th>
      <td><input type="text" data-im="survey@Q1"/></td>
    </tr>
    <tr>
      <th>メールアドレス</th>
      <td><input type="text" data-im="survey@Q2"/></td>
    </tr>
    <tr>
      <th>ご意見</th>
      <td><textarea data-im="survey@Q3"></textarea></td>
    </tr>
    <tr>
      <th></th>
      <td><button data-im-control="post">送信</button></td>
    </tr>
  </tbody>
</table>
<div id="IM_NAVIGATOR"></div>
<table>
  <thead>
    <tr><th>お名前</th><th>メールアドレス</th><th>ご意見</th></tr>
  </thead>
  <tbody>
    <tr>
      <td data-im="survey_list@Q1"></td>
      <td data-im="survey_list@Q2"></td>
      <td data-im="survey_list@Q3"></td>
      <td></td>
    </tr>
  </tbody>
</table>
</body>
</html>
「http://192.168.56.101」で開いたページに戻り「page14.htmlを表示するする」をクリックして、どのような画面になっているか確認してみてください。最初のテーブルは、Post Onlyモードで動作し、ボタンをクリックすると、surveyコンテキストに対して新たなレコードを作成します。2つ目のコンテキストは、単にsurveyコンテキストの全レコードを表示しているもので、入力したレコードの確認用です。

送信者、送信先、本文がすべて一定のメール

 以下の演習では、2つのメールアドレスを使用します。それぞれ、「メールアドレス1」「メールアドレス2」と記述します。手順では筆者の新居が所持する2つのメールアドレスが書かれていますが、そのまま記述しても、皆さんのお手元にメールは届きません。ご自分のアドレスに置き換えてください。もし、メールアドレスをひとつしか持っていない場合には、そのひとつのアドレスを記載してください。

1「def14.phpを編集する」をクリックして表示したタブあるいはウインドウに戻ります。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「def14.phpを編集する」をクリックして開きます。Definition File Editorのページが開きます。右上に表示されている「Show All」のボタンを押します。Context内に表示されている項目が全て表示されます。
2surveyコンテキストの設定にあるSetting for Post-only Modeのpost-reconstructを「true」、post-dismiss-messageに「ありがとう」と入力します。post-dismiss-messageの文言はなんでもかまいません。データ送信を受けたことを示す簡潔な文言を入れてください。
3surveyコンテキストのSending Emailの設定にある、newの見出しの下に入力します。from-constantとto-constantに「メールアドレス1」、subject-constantに「ご意見承りました」、body-constantに「ありがとうございます。今後ともよろしくお願いします。」を入力します。f-optionを「true」に、body-wrapは「72」とします。Tabキーを押すなどして、確実に入力をしてください。
4「http://192.168.56.101」で開いたページに戻り、「page14.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。2つのテーブルが見えています。上のPost Onlyモードのテーブルに、適当に入力します。ここで、メールアドレスの右のテキストフィールドには「メールアドレス2」を入力してください。
5「送信」ボタンをクリックします。5秒後にページが更新され、下側のテーブルに、今入力したデータが追加されています。
6メールが到着しています。このメールは、「メールアドレス1」に送られており、Post Onlyモードのテーブル内で入力した「メールアドレス2」には送られていません。ここで、コンテキストに設定したto-constantが「メールアドレス1」であることを思い出してください。現在の設定では、メールの送り先は常に「メールアドレス1」になります。また、本文や件名、送信者は常に固定された文字列です。

データベースから得られた宛先を送信者にする

1「def14.phpを編集する」をクリックして表示したタブあるいはウインドウに戻ります。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「def14.phpを編集する」をクリックして開きます。
2すべての項目が表示されている状態でない場合には、定義ファイルエディターのページの最初にある「Show All」ボタンをクリックして、すべての項目を表示します。
3surveyコンテキストのSending Emailの設定にある、newの見出しの下に入力します。to-constantを空欄にし、toを「Q2」と入力します。Tabキーを押すなどして、確実に入力をしてください。
4「http://192.168.56.101」で開いたページに戻り、「page14.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。2つのテーブルが見えています。上のPost Onlyモードのテーブルに、適当に入力します。ここで、メールアドレスの右のテキストフィールドには「メールアドレス2」を入力してください。
5「送信」ボタンをクリックします。5秒後にページが更新され、下側のテーブルに、今入力したデータが追加されています。
6メールが到着しています。このメールは、Post Onlyモードのテーブル内で入力した「メールアドレス2」に送られています。ここで、コンテキストに設定したtoが「Q2」、つまりQ2フィールドに入力した文字列であることを思い出してください。現在の設定では、メールの送り先は、Post Onlyモードのテーブルで入力した「メールアドレス2」になります。本文や件名、送信者はここでも常に固定された文字列です。

メール本文にテンプレートを使用する

1INTER-Mediator-Server VMのWeb公開ディレクトリをホストOS側から参照します。OSに応じて、次のように作業します。
[macOS]
Finderで、「移動」メニューから「サーバーへ接続」を選択し、サーバーアドレスとして、「smb://192.168.56.101」を指定して、「接続」ボタンをクリックします。その後に、ユーザー名「developer」、パスワード「im4135dev」でログインをして「webroot」という共有ディレクトリを指定します。
[Windows]
エクスプローラーのアドレスの枠に「¥¥192.168.56.101¥webroot」と入力して、Enterキーを押します。ユーザー名「developer」、パスワード「im4135dev」でログオンします。
2現在作業中の定義ファイルdef14.phpが存在するディレクトリを、ホストOS側で参照できるようになったことを確認します。(以下の図はmacOSの場合です)
3適当なテキストエディターを起動して、メールの本文を入力します。基本的に自由でかまいませんが、このテキストファイルの「@@N@@」(Nは1以上の整数値)の部分が、フィールドの値に置き換わります。その部分以外はこの通りに入力しなくてもかまいません。
4作成したテキストファイルを、INTER-Mediator-Server VMのWeb公開ディレクトリに保存します。ファイルのエンコーディングはUTF-8、改行についてはLFのみとなるようにします。ファイル名は「mail.txt」とします。
5「def14.phpを編集する」をクリックして表示したタブあるいはウインドウに戻ります。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「def14.phpを編集する」をクリックして開きます。
6すべての項目が表示されている状態でない場合には、定義ファイルエディターのページの最初にある「Show All」ボタンをクリックして、すべての項目を表示します。
7surveyコンテキストのSending Emailの設定にある、newの見出しの下に入力します。body-constantを空欄にし、body-templateを保存したファイルのファイル名である「mail.txt」、body-fieldsを「Q1,Q2,Q3」と入力します。Tabキーを押すなどして、確実に入力をしてください。
8「http://192.168.56.101」で開いたページに戻り、「page14.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。2つのテーブルが見えています。上のPost Onlyモードのテーブルに、適当に入力します。ここで、メールアドレスの右のテキストフィールドには「メールアドレス2」を入力してください。
9「送信」ボタンをクリックします。5秒後にページが更新され、下側のテーブルに、今入力したデータが追加されています。
10メールが到着しています。このメールは、Post Onlyモードのテーブル内で入力した「メールアドレス2」に送られています。ここで、コンテキストに設定したtoが「Q2」、つまりQ2フィールドに入力した文字列であることを思い出してください。現在の設定では、メールの送り先は、Post Onlyモードのテーブルで入力した「メールアドレス2」になります。
さらに、このメールの本文は、mail.txtの内容になっていますが、@@N@@の部分がフィールドの値に置き換わっています。定義ファイルのbody-fieldsに指定した値は「Q1,Q2,Q3」でした。つまり、新規作成したレコードのQ1フィールドが@@1@@、Q2フィールドが@@2@@、Q3フィールドが@@3@@の部分と置き換わって、メールの本文が作られています。このように、メールの本文は別に作ってある文面に、対象となるレコードの内容を差し込んで作成することができます。

演習のまとめ

SMTPサーバーを利用してメールを送信する

 INTER-Mediatorでのメール送信は、PHPのmail関数を使用し、独自にエンコード等を行ってUTF-8のメールを送信する基本機能を備えています。UNIX系OSの場合は、sendmailあるいはPostfixをインストールしておけば、そのホストからメールの送信ができます。Windowsの場合はSMTPサーバーをサービスとして追加するなどしておけば、mail関数はSMTP通信を行ってメールを送信します。

 しかしながら、INTER-Mediatorが稼働するサーバーからのメール送信が許可されない場合もあるかもしれません。例えば、ファイアウォールの中にあって、ポートが開かれていない、あるいはプロバイダーが制限しているなど、一般には通信遮断が第三者によってなされていて解除できない場合が相当します。そのときには、SMTPサーバーに宛ててメールの中継を依頼することもできます。そのための設定は、定義ファイルのオプション部分に設定します。オプション部分にsmtpキーの要素を定義し、その値の連想配列の要素に表5-2-3のキーと対応する値を指定することで、SMTP通信を行ってメールのリレーを別のサーバーに依頼することができます。

キー対応する値
serverメール送信時に使用するサーバーのホスト
portメール送信時に使用するサーバーのポート
usernameメール送信時に認証で使用するユーザー名
passwordメール送信時に認証で使用するパスワード
表5-2-3 smtpキーの連想配列に設定可能なキー

 定義ファイルエディターで実際に設定する場合は、ページの冒頭にある「Show All」ボタンをクリックして、全項目を表示します。すると、図5-2-1のように、Optionsの最後に設定項目が表示されるようになります。定義ファイルへの設定を直接行う場合は、リスト5-2-1にあるように、オプション領域に記述を行います。

図5-2-1 stmpキーの値を設定した定義ファイル(定義ファイルエディター)
リスト5-2-1 stmpキーの値を設定した定義ファイル(PHPでの記述)
IM_Entry(
array (	// コンテキストの定義
  array (
    'name' => 'survey',
	:
  ),
),
array (  // オプション設定
  'smtp' =>  array (
    'server' => 'mail.msyk.net',
    'port' => 589,
    'username' => 'msyk_test',
    'password' => 'testpassword',
  ),
),
array (  // データベース接続設定
  'db-class' => 'PDO',
	:
),
false);

 SMTPによるメール送信では、QdSmtpというPHPのライブラリを使用しています。usernameとpasswordに何も指定をしなければ、認証なしにSMTP通信を実行します。一方、usernameとpasswordを使用すると、SMTP認証を行いますが、サポートしている認証方式はPLAINだけです。SMTPサーバーまでの経路によっては、SSL/TLSを使った通信の利用を検討してください。その場合は、serverキーの値には「ssl://ホスト名」「tls://ホスト名」というプロトコルを含めたURLで指定します。

メール送信を伴う機能組み込みのパターン

 このセクションの演習は、新規レコード作成とメール送信を連動させるという非常に分かりやすい事例を使いましたが、実際のアプリケーションではさまざまな状況でのメール送信のニーズが発生するでしょう。例えば、マネージャーが承認したら、関係者にメールが飛ぶといったような用途を考えてみましょう。このような場合、どのように機能を組み込めば良いのでしょう。もちろん、個別の事情によって異なると言えばその通りなのですが、いくつかの組み込みパターンがあり、それをヒントにすれば、機能の組み込み方法は見えているかもしれません。ここでは2つのパターンを紹介します。

 まず、最初のパターンは「メール送信用のコンテキストを定義する」ということです。つまり、一覧を表示したり、レコードの修正を行うためのコンテキストとは独立したメール送信専用のコンテキストを作ります。何種類かメールがあれば、それぞれコンテキストを作ります。こうすれば、例えば、「承認」というのは、「メール送信用コンテキストを通じて特定のフィールド(例えば「承認日」)に現在の日付を入力する」というデータベースの操作に置き換えることができます。この操作は、JavaScriptの記述が便利なので、『6-3 データベースへの書き込みを直接行う』で具体的なサンプルを示します。こうして、特定の処理だけ、メールの送信を伴うコンテキスト上で処理を進めるということで、他の処理とメール処理が混同することはなくなります。

 もうひとつのパターンは「メール送信コンテキストのviewキーの値を効果的に使う」ということです。メールの文面は、ファイルで用意したテンプレートを利用すると、長いものでも管理はしやすいでしょう。しかしながら、ここで問題になるのは、必要なフィールドの値をきちんとメールに含めることができるかどうかです。そのためには、メール作成時にどんなレコードが得られるかを知る必要があります。

 新しいレコードを作成すると、その作成したレコードの主キー値を使って、viewキーで指定したテーブルやビューに対して検索をかけて、新規に作成したレコードを取り出し、そのフィールドの値をメール内に@@N@@の記述とbody-fieldsキーの値としてフィールド名をカンマ区切りで指定することで含めることができます。このとき、新規レコードを作成したテーブルから検索をしてもいいのですが、SQL系のデータベースだとビューを定義することで、レコードを作成したテーブルにないフィールドも、関連付けを辿ってビューの結果に含めておくことで、メールに含めることができます。例えば、この演習ではメールアドレスを入力しましたが、顧客マスターのようなテーブルがあるなら、メールアドレスから名前や会社名を取り出して、それもメールに含めることも可能になります。そのためには、surveyテーブルと顧客マスターをJOINし、回答に加えてその顧客の名前や会社名、部署等をフィールドとして含むビューを作成します。こうした、メール専用のコンテキストで、メール専用のビューを作って使うということを行えば、別のテーブルの内容もメールに含めることができます。FileMakerの場合はレイアウトに、同一のTOG(テーブルオカレンスのグループ)に含まれるフィールドを配置することで、SQLのビューに近いことが可能です。

 レコードの更新を行う場合、あるレコードのあるフィールドが更新されると、そのレコードの主キー値を使ってviewキーの値のテーブルあるいはビューに対して検索をかけて、得られたレコードをメールの文面に含めることができます。レコードの検索の場合は、検索結果の最初のレコードから、指定のフィールドが抜き出されます。そのため、レコードの検索を行った後、その結果情報をメール送信に含めたい場合には、検索結果が基本的にひとつに絞られるようなものでないと、正しく動作しないかもしれません。

メール送信のトラブルシューティング

 メール送信のトラブルは、現実には単にキータイプミスという場合がほとんどではないかと思われますが、加えて、ネットワーク制限を認識しているかどうかという点も重要ではあります。しかしながら、トラブルに遭った方は「正しく設定しているはずなのに」という前提から抜け出せないことで、なかなか対処できないということにもなりがちです。ともかく、冷静かつ客観的に設定や状況を確認してください。「間違い」を除くと、以下のような原因が考えられます。

 SMTPやあるいは認証のトラブルはさらに複雑な設定が絡みます。うまく行かない場合には、以下のような原因が考えられます。

このセクションのまとめ

 コンテキストを通じて、データベースから検索した後、データベースの内容を更新した後、新しいレコードを作成した後に、メールを送信することができます。メールの宛先や本文などを定義ファイルで指定できます。メールの本文や宛先は、データベースから得られたレコードのフィールドの値を利用できます。メールの本文をテンプレートとしてテキストファイルで用意して、そこにフィールドを埋め込むといったメールの作成方法も可能です。メールの送信には、同一サーバーにあるSMPTサーバーを利用したり、別のホストに対して認証SMTPで送信することもできます。

5-3マルチクライアントでの同期

2つのクライアントで同一のレコードを表示しているとき、一方のクライアントで変更した結果を別のクライアントでも自動的に反映されるような動作が必要なときもあります。FileMakerでは当たり前に実現しているこうした機能は、Webアプリケーションで実装するには、通信処理などを含めてイチから構築しなければなりません。INTER-Mediatorには、変更結果を伝達する仕組みを搭載しているため、定義ファイルへの記述だけで実現できます。

クライアント間連動の仕組み

 通常のWebサーバーとのやりとり、すなわちHTTPは、通信が終了したら切れてしまうという動作を行います。つなぎっぱなしにはできないので、サーバー上のデータが更新されたかどうかは、接続してみないと分かりません。一定時間ごとに接続するとしても、すぐに変更が分かるわけではありません。では、0.5秒ずつ接続するということでは、サーバーの負荷が大きくなり、パフォーマンスを損なう可能性もありますし、バッテリー動作ならば消費電力が大きくなり、不利です。

 一方、インターネットの核となるTPC/IPは、通常はつなぎっぱなしができます。逆に言えば、HTTPはつないでは切るという動作をしているわけです。そこで、Webアプリケーションでもサーバーとつないだままにして、変化があったことだけを通知として受け取り、その後に必要なデータ処理を通常のHTTPで行うという仕組みが登場しました。それを、WebSocketと呼びます。

INTER-Mediatorでの対応

 INTER-Mediatorでは、こうしたWebSocketを使った通知を行うASPサービスの「Pusher」を利用しています。Pusherは数クライアントなら無料で使える範囲で十分に利用できますが、人数が増えると有料のプランでないとまかない切れないかもしれません。接続数などは、実際にアプリケーションを2、3人で稼働させてみて、見積もる必要があります。

 INTER-MediatorでPusherを利用するためには、いくつかのソフトウェアを組み込む必要があります。また、サービスに入会して、指定されたコードなどを作成しているWebアプリケーションの設定に含めることも必要です。以下の作業あるいは設定等が必要です。ライブラリのインストールについては、演習で詳細を確認してください。

定義ファイルでの設定

 Pusherでのサービスを利用するためには、そのサービスの利用のためのコードを、INTER-Mediatorで作成するアプリケーションに記述する必要があります。定義ファイルのIM_Entry関数の第2引数に、puhserというキーで連想配列を記述します。それぞれ、Pusherが指定するapp_id、key、secretを同名のキーとともに記述すれば、INTER-MediatorはPusherの利用を開始します。定義ファイルのPHP記述では、リスト5-3-1のようになります。

リスト5-3-1 定義ファイルにPusherのサービス利用のためのコードを記述する
IM_Entry(
    array(
       /* コンテキストの定義 */
    ),
    array(
        :
        'pusher' => array(
            'app_id' => '1234',
            'key' => '9876543210',
            'secret' => '9876543210',
        ),
    ),
    array('db-class' => 'PDO', ....),
    false
);

 定義ファイルで設定する場合は、図5-3-1のように、Optionsのところに設定項目があります。この設定項目は、Show Allボタンをクリックすることで表示され、通常は表示されていません。

図5-3-1 定義ファイルエディターでのPusherコード入力

管理用テーブルの作成

 INTER-Mediatorはどのクライアントにどのテーブルのデータが配布されているのかを、データベースに記録します。そのために表5-3-1のようなテーブルが必要になりますので、アプリケーションでクライアント間同期の機能を利用する場合には、これらと同一名および同一フィルードを持つテーブルを作成してください。PDO対応のデータベースエンジンの場合は、INTER-Mediator/dist-docsファイルにあるサンプルデータベースのスキーマ(sample_schema_*.txtファイル)に、CREATE TABLE等で記述されたSQLがあるので、それを利用できます。なお、registeredcontextテーブルのidフィールドと、registeredpksテーブルのcontext_idフィールドでリレーションシップが設定されています。

テーブル名フィールド名
registeredcontextidINT AUTO_INCREMENT,
clientidTEXT
entityTEXT
conditionsTEXT
registereddtDATETIME
registeredpkscontext_id INT
pkINT
表5-3-1 クライアント同期の動作に必要なテーブル

演習Pusherをアプリケーションに統合する

 Pusherを利用する方法と、既存のアプリケーションに組み込む方法を説明します。組み込むアプリケーションは、『4-4 計算プロパティの設定』で作成したものです。フィールドの値の変更や、レコードの追加や削除ができれば統合できますが、このアプリケーションは、リレーションシップを適用したレコードが展開されています。リレーションシップで結合している内部のエンクロージャー/リピーターでも機能するところを確認するのが、このアプリケーションを選択した理由です。なお、INTER-Mediator-Server VMのデータベースには、同期を実現するために必要なテーブルの定義はすでにできているので、この演習ではその作業は不要ですが、作成するアプリケーションではテーブルの定義が必要になることが一般的でしょう。

Pusherに入会する

 Pusherに入会する方法をすべての手順は説明しません。サイト上で必要な情報を入力して、アカウントを取得してください。

1ブラウザーで、Pusherのサイトを表示します。トップページの「Create a Free Account」の赤いボタンをクリックして、新たにアカウントを作成します。
2以後の作業は、表示された内容に従って進めてください。

PusherのAppを用意する

 Pusherでは、リクエストと通知を出すためのひとつの領域を「App」と呼んでいます。原則として、ひとつのWebアプリケーションで、ひとつのAppを利用することを想定しています。

1Pusherにログインした状態で、上部のナビゲーション部分の「Your Apps」をクリックして、Apps一覧を表示します。最初は「Main」というAppだけが存在しています。
2New Appボタンをクリックします。するとパネルが表示されます。Nameは識別のためだけのものですので、適当に名前をつけます。また、チェックボックスはすべて入れないで、Create appボタンをくりっくします。
3新たにAppが作られて、その詳細ページが表示されます。ページ右側のApp Credentialsの情報を、この後自分のアプリケーションに設定します。なお、以下の画面に見えているコードは無効化してあるので、そのまま設定しないでください。ご自分でアカウントを取得し、Appを作ってそこで表示されるコードを利用してください。

サーバーにPusherのライブラリを読み込む

 サーバー上にはPusherのPHPのクラスとして定義されたライブラリが必要です。INTER-Mediator-VMには入っていませんが、INTER-Mediator-Server VMではないサーバーでのインストールも視野に入れてインストール方法を説明します。

1INTER-Mediator-Server-VMにsshで接続します。VirtualBoxアプリケーションのコンソールに、ユーザー名「developer」、パスワード「im4135dev」でログインします。あるいは、以下の方法もあります。
[macOS]
「ターミナル」アプリケーションのウインドウで、「ssh developer@192.168.56.101 」とコマンドを入力し、Password:と表示されたら「im4135dev」とキータイプしてreturnキーを押します。
[Windows]
TeraTermやPuttyなどのアプリケーションを利用するか、Cygwinを利用するなどして、VMにssh接続することができます。
2PHPの追加ライブラリを配置する場所を確認します。この方法は、INTER-Mediator-Server VMではないサーバーでのインストールも視野に入れて、パスの確認を行います。$以降のコマンドを入力します。
$ php -i | grep include_path
include_path => .:/usr/share/php:/usr/share/pear => .:/usr/share/php:/usr/share/pear
3前のコードの2行目は出力された文字列です。これにより、PHPのファイルを検索するパスは、カレントディレクトリ(.)、/user/share/php、/usr/share/pearであることが分かります。Pusherのライブラリは/user/share/phpにインストールすることにします。
4次のようにコマンド入力して、PusherのPHPライブラリのレポジトリごとダウンロードします。その前のcdコマンドで、自分のホームに移動します。この手順だと、確実に自分自身に書き込み権限のあるディレクトリをカレントディレクトリにできます。なお、PusherのPHPライブラリには、稼働に不要なファイルがあるので、直接PHPのinclude_pathにはインストールしません。
$ cd
$ git clone https://github.com/pusher/pusher-http-php.git
5クローンが終了したら、以下のように、ディレクトリを進んで、Pusher.phpファイルを/user/share/phpディレクトリにコピーします。必要に応じて、lsコマンドでファイル一覧を参照しましょう。
$ cd pusher-http-php/
$ cd lib
$ sudo cp Pusher.php /usr/share/php/
6必須の作業ではありませんが、作業が終了したら、exitコマンドでログアウトしてかまいません。

定義ファイルにPusherの設定を行う

 『4-4 計算プロパティの設定』で作成したアプリケーションが、page09.htmlおよびdef09.phpで作成されたものとします。もし、異なる番号で作成されているのなら、該当する番号に読み替えてください。

1ブラウザーで、「http://192.168.56.101」に接続します。「トライアル用のページファイルと定義ファイル」というタイトルの部分を特定します。
2「def09.phpを編集する」をクリックし、定義ファイルエディターでdef09.phpファイルを編集します。(もし、異なる番号であれば、その番号のファイルを定義ファイルエディターで開きます。)
3ページ冒頭のShow Allボタンをクリックします。Optionsの最後に、「Pusher」という見出しがあり、3つのテキストフィールドがあります。この演習の『PusherのAppを用意する』で作ったAppの3つのコードを、それぞれここにコピー&ペーストします。

ページファイルにPusherの設定を行う

1「page09.htmlを編集する」をクリックし、ページファイルエディターでdef09.phpファイルを編集します。(もし、異なる番号であれば、その番号のファイルをページファイルエディターで開きます。)
2ヘッダーセクションに、以下のように、Pusherのクライアントライブラリを読み込む記述を追加します。Pusherのライブラリの読み込みは、定義ファイルの読み込みより前に記述してください。
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
  <script src="http://js.pusher.com/2.2/pusher.min.js" type="text/javascript"></script>
    <script type="text/javascript" src="def09.php"></script>
</head>
<body>

クライアントの動作を確認する

1クライアントの動作を確認するために、2つの異なるブラウザーを同時に起動します。SafariとFirefox、あるいはChromeとInternet Explorerなど、INTER-Mediatorの稼働可能なブラウザーをINTER-Mediator-Server VMが稼働するホストOS側で同時に起動します。これにより、異なるユーザーによる接続と同じ状況になります。なお、ひとつのブラウザーでウインドウが違うという状況では、厳密には「別のクライアント」と同等ではないので、2つのブラウザーを稼働させてください。
2それぞれのブラウザーで、「http://192.168.56.101」に接続します。「トライアル用のページファイルと定義ファイル」というタイトルの部分を特定します。
3それぞれのブラウザーで、「page09.htmlを表示する」をクリックし、ページを開きます。以後、背後にSafar、手間にFirefoxが表示された状態で動作を確認します。最初は当然、同一のデータがそれぞれのブラウザーで参照できています。
4変更可能なフィールドのひとつを修正してみます。ここでは、unitpriceのフィールドの値を変更してみます。手前のブラウザーで、unitpriceの値を変更しました。ここでは、まだ数値を変更しただけなので、背後のブラウザーでは表示内容に変更はありません。
5Tabキーを押して、修正結果を確定します。すると、背後のブラウザーの同一のフィールドが、修正後の値に自動的に変更されました。つまり、あるクライアントでの変更結果は、他のクライアントにも伝達されたということになります。
6手前のブラウザーで、明細行の下にある「追加」ボタンをクリックします。「レコードを本当に作成していいですか?」とたずねられるので、OKボタンをクリックします。
7手前のブラウザーでは新たに明細行が追加されましたが、背後のブラウザーでも自動的に明細行が追加されています。つまり、あるクライアントでレコードを作成すれば、その作成されたレコードは他のクライアント側でも認識されたということです。
8明細行のいずれかを「削除」ボタンで削除します。削除の可否が確認されるので、OKボタンで応答して実際に削除します。
9クリックしたボタンに対応したレコードはもちろん削除されますが、別のブラウザーで見えている同一のレコードも削除されました。

演習のまとめ

このセクションのまとめ

 複数のクライアント間で、編集やレコード作成、削除の結果をリアルタイムに反映させる仕組みをINTER-Mediatorは持っています。Pusherというサービスをベースにしています。Pusherのライブラリをページファイルおよびサーバー側に追加し、さらに管理用テーブルを作成すれば、この機能を利用できます。作成したWebアプリケーションは原則そのままで同期ができるようになります。

5-4JavaScriptコンポーネントの利用

現在、Webアプリケーションの機能を拡張するために、JavaScriptで作られた部品(コンポーネント)が盛んに使われています。INTER-Mediatorでも、JavaScriptのコンポーネントを利用して、データベースの内容を表示したり、あるいは修正結果をフィールドに描き戻すことができます。これらの機能の基本的な利用方法を説明します。また、ファイルのアップロードとアップロードした結果を表示する方法についてもこのセクションで説明します。

JavaScriptのコンポーネントを利用する

 多種多様なJavaScriptのコンポーネントがオープンソースで配布されています。ライセンス形態はMIT Licenseのものも多く、気軽にサイトで利用している人も多いでしょう。代表的なものといえばjQueryやあるいはそれをベースにしたユーザーインターフェース素材のjQueryUIなどがあります。まず、INTER-Mediatorでは、これら別途開発されたコンポーネントを活用し、テキストフィールドなどと同様に、データベースの値を表示したり、あるいは編集した結果をデータベースに更新する仕組みを組み込むことができます。この後の演習で実際に行いますが、jQueryUIの日付選択コンポーネントを利用することなどができます。これら、既存のコンポーネントを利用する場合は、そのコンポーネントに対する「アダプター」を作成しなければなりません。INTER-Mediator Ver.5.2現在では、jQueryUIのDatePickerやFile Upload、HTMLエディターのTinyMCE、ソースコードエディターのCodeMirrorのアダプターが付属しています。これら以外の素材を利用する場合には、独自にアダプターを開発しなければなりません。アダプターの開発方法は、『6-7 JavaScriptコンポーネント用のアダプターの開発方法』で説明をします。

 一方、INTER-Mediatorには独自のユーザーインターフェース用部品が搭載されています。ファイルのアップロードを行うためのもので、こちらは本体およびアダプターのいずれもがINTER-Mediatorに含まれています。

 これらのJavaScriptコンポーネントを利用するには、タグ要素にdata-im-widget属性を記述します。この属性の値は、それぞれのアダプターで定義された文字列を指定します。なお、設定可能なタグ要素はなんでもいいわけではなく、原則として、そのコンポーネントごとに決められています。例えば、jQueryUIのDatePickerは、テキストフィールドの要素に対して指定します。

 実際の試用方法は、演習で具体的に説明しましょう。

演習日付選択を行うコンポーネントを利用する

 jQueryUIのDatePickerを使って、日付をカレンダー形式の画面から入力できるようにしましょう。この演習では、テスト用にさまざまな形式のフィールドを用意しているtesttableというテーブルがサンプルデータベースに作ってあるので、それを利用します。

FileMaker Serverを利用している場合のデータベース変更

 FileMakerのデータベースファイル「Test_DB」を、FileMaker Proで開いて、testtableテーブルのdt1フィールドのタイプを、「タイムスタンプ」から、「日付」に変更してください。ファイルを開くためのユーザー名とパスワードは、「admin」「1234」です。データベースを開き、「ファイル」メニューの「管理」から「データベース」を選択します。上部で「フィールド」を選択し、テーブルのポップアップメニューで「testtable」を選択します。dt1フィールドを選択して、ダイアログボックスの下の方にあるタイプで「日付」を選択し、「変更」ボタンをクリックします。その後、OKボタンをクリックします。

2つのコンテキストを定義ファイルに定義

1ここからの作業は、Webブラウザー上で行います。まず、INTER-MediatorがインストールされたVMを、VirtualBoxを使用して起動します(『1-2 演習を行うための準備』を参照)。続いて、ブラウザーで、「http://192.168.56.101」に接続します。「トライアル用のページファイルと定義ファイル」というタイトルの部分を特定します。
2「def15.phpを編集する」をクリックし、定義ファイルエディターでdef15.phpファイルを編集します。(もし、他の用途で15番目を利用しているのなら、例えば、def21.phpを利用するなど、別の番号のセットを使用してください。その場合ソースコードの記述が変わる部分がありますが、可能な限り注記します。)
3Contextsの中のQueryと書かれた背景がグレーの部分を特定します。そして、その次の行の右の方にある「削除」をクリックして、Queryの設定がある行を削除します。
4「レコードを本当に削除していいですか?」とたずねられるので、OKボタンをクリックします。
5同様に、Sortingの次の行にある「削除」ボタンを押し、確認にOKボタンをクリックして、こちらの設定も削除しておきます。
6name、view、tableに「testtable」、keyを「id」、pagingを「true」、repeat-controlを「confirm-insert confirm-delete」、recordsを「10」、maxrecordsを「100」とします。
Contextsのその他のテキストフィールドは空白にします。
7[FileMaker]の場合のみ、OptionsのFormattersに設定を行います。Formattersの直下にある「追加」ボタンをクリックします。項目を作成していいかダイアログボックスでたずねられるので、OKボタンをクリックします。そして、fieldに「testtable@dt1」、converter-classに「FMDateTime」、parameterに「%Y/%m/%d」と入力します。
8Database 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」と入力します。
9Debugについては、「false」にすると、デバッグ情報が出なくなります。なお、デバッグ情報をみながら動作を確認したい方は、「2」のままにしてこの後の作業を行ってください。

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

1「http://192.168.56.101」で開いたページに戻り「page15.htmlを編集する」をクリックし、ページファイルのpage15.htmlを編集するページファイルエディターが開きます。HTMLでの記述内容を以下のように変更します。太字が追加する箇所を示します。(別の番号のセットで作業している場合には、該当する番号のリンクをクリックしてください。)
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html;charset=UTF-8"/>
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <link rel="stylesheet" 
    href="http://ajax.googleapis.com/ajax/libs/jqueryui/1/themes/cupertino/jquery-ui.css" >
  <title></title>
  <script 
    src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js">
  </script>
  <script 
    src="http://ajax.googleapis.com/ajax/libs/jqueryui/1/jquery-ui.min.js">
  </script>
  <script 
    src="http://ajax.googleapis.com/ajax/libs/jqueryui/1/i18n/jquery.ui.datepicker-ja.min.js">
  </script>
  <script type="text/javascript" src="def15.php"></script>
  <script 
    src="INTER-Mediator/Samples/Sample_webpage/jquery_datepicker_im.js">
  </script>
</head>
<body>
<div id="IM_NAVIGATOR"></div>
<table>
    <thead>
    <tr>
        <th>Date and Time</th>
        <th>Message</th>
        <th></th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td>
            <input type="text" data-im="testtable@dt1"
                   data-im-widget="jquery_datepicker">
        </td>
        <td data-im="testtable@text1"></td>
        <td></td>
    </tr>
    </tbody>
</table>
</body>
</html>
ヘッダー部のLINKおよびSCRIPTタグのパスが長く見づらいですが、正しく入力してください。これらは、jQueryやjQueryUI、およびそれらのテーマを読み込む部分です。
INTER-Mediatorで用意しているJQueryUIのDatePicker向けアダプターは、「jquery_datepicker_im.js」というファイルです。INTER-MediatorフォルダーのSamplesフォルダーにあるSample_webpageというフォルダーに、サンプルとして含めてあります。この演習では直接そのファイルへのリンクを指定しますが、実際の運用では、jquery_datepicker_im.jsファイルをコピーして利用してもいいでしょう。
INPUTタグ要素にdata-im-widget属性があり、「jquery_datepicker」という値が設定されている箇所がポイントです。
2「http://192.168.56.101」で開いたページに戻り、「page15.htmlを表示する」をクリックします。HTMLでの記載通り、テーブルが表示されます。レコードが存在しない場合には、「レコード追加:testtable」をクリックして、新たにレコードを追加しておきます。
3Date and Time列のテキストフィールドをクリックします。すると、jQueryUIのDatePickerが表示されます。
4カレンダー上で適当な日付をクリックすると、テキストフィールドに日付の文字列が入力されます。
5ページネーションコントローラー上にある「更新」ボタンをクリックして、再度ページを構築します。DatePickerで指定した日付が、データベースに書き込まれ、その日付が見ていることが分かります。

演習のまとめ

演習ファイルアップロードのコンポーネントを利用する

 INTER-Mediatorに組み込まれているファイルのアップロードのコンポーネントの利用方法を説明します。なお、アップロードにおいては、いろいろな準備が必要ですし、アップロードしたファイルを参照する方法も知っておく必要があります。これらをまとめて、この演習で説明をします。なお、この演習は、前の演習『日付選択を行うコンポーネントを利用する』の続きで行います。

 また、PHPの環境上の制限で現在の標準設定では1.5MB程度が上限となり、INTER-Mediator-Server VMはその設定を変更していません。それ以上のファイルをアップロードしようとしても、制限を超えているというメッセージが表示されてアップロード作業は完了しません。アプリケーションを実際に作るとき、精細な写真を貼付したい、あるいは動画を保存しておきたいときなど、業務上、どうしても大きなファイルを添付しなければならないという場合は、PHPの環境設定ファイルを書き直すなどの対応が可能です。方法は、『9-3 INTER-Mediatorを利用する開発プロセス』で説明します。

定義ファイルにファイル保存位置を指定する

1「def15.phpを編集する」をクリックして表示したタブあるいはウインドウに戻ります。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「def15.phpを編集する」をクリックして開きます。
2Optionsのセクションにある、media-root-dirに「/tmp」と入力します。入力結果を確定させるために、Tabキーを押すなどしてください。
サーバー上では、media-root-dirキーで指定したパス以下にアップロードしたファイルが保存されます。この演習では、データを永続的に残さないものとして、/tmpを利用します。言葉の通り、このディレクトリ内は再起動するとすべて消えてしまいます。実際のアプリケーション運用時は、適切なディレクトリを指定しますが、一般にはWebサーバーで公開していない範囲にディレクトリを作ります。また、Webサーバーのユーザーが読み書き権限があるように、ディレクトリのアクセス権を設定する必要があります。

ページファイルの修正

1「page15.htmlを編集する」をクリックして表示したタブあるいはウインドウに戻ります。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「page15.htmlを編集する」をクリックして開きます。
2次のように、ページファイルのコードを修正します。テーブルに、新たにtext1フィールドの値を表示する列を作りますが、data-im-widget属性はファイルのアップデートコンポーネントを示す値になっています。
<!DOCTYPE html>
<html lang="ja">
<head>
		:
</head>
<body>
<div id="IM_NAVIGATOR"></div>
<table>
    <thead>
    <tr>
        <th>Date and Time</th>
        <th>Message</th>
        <th></th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td>
            <input type="text" data-im="testtable@dt1"
                   data-im-widget="jquery_datepicker">
        </td>
        <td data-im="testtable@text1" data-im-widget="fileupload"></td>
        <td data-im="testtable@text1"></td>
        <td></td>
    </tr>
    </tbody>
</table>
</body>
</html>
text1はTEXT型のフィールドです。ファイルアップロードのコンポーネントは、このように、テキスト型のフィールドとバインドした要素に記述します。この例では、TDタグに記述しています。TDタグはセルの表示はできますが、編集はできません。しかしながら、コンポーネントがその内部に必要な要素を用意するので「ファイルのドラッグ&ドロップの受付」ができるのです。しかし、単にアップロードするだけではアプリケーションとしては成り立たず、そのファイルを後から活用する必要があります。そのために、アップロードしたファイルのパスを保存するテキスト型のフィールドとのバインドを必要とします。
3「page15.htmlを開く」をクリックして表示したタブあるいはウインドウに戻り、ブラウザーの更新ボタン等でページを再読み込みします。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「page15.htmlを開く」をクリックして開きます。グレーのボックスが表示され、メッセージを読むと、どうやらここにファイルのドラッグ&ドロップができそうです。
4適当なファイルをドラッグ&ドロップしてみます。ファイルサイズに制限がありますので、1MB程度のファイルを使って下さい。なお、この上限サイズは変更可能です(『9-3 INTER-Mediatorを利用する開発プロセス』)。ドラッグ中にマウスポインタがボックス内に入ると、グレーから水色に変化し、ドロップの受付ができることが確認できるようになっています。
5ファイルのアップロード中にはパスが見えるはずですが、すぐに終わるかもしれません。その後、ボックスはグレーに戻ります。text1フィールドには、アップロードしたファイルの絶対パスが入力されていますが、このパスの入力もコンポーネントが自動的に行います。

アップロードした結果の確認

 ファイルがアップロードされた結果を、INTER-Mediator-Server VM上で確認をします。

1INTER-Mediator-Server-VMにsshで接続します。VirtualBoxアプリケーションのコンソールに、ユーザー名「developer」、パスワード「im4135dev」でログインします。あるいは、以下の方法もあります。
[macOS]
「ターミナル」アプリケーションのウインドウで、「ssh developer@192.168.56.101 」とコマンドを入力します。初回に接続するときに限り、「本当に接続してもよいか?」という趣旨のメッセージ
Are you sure you want to continue connecting (yes/no)?
が表示されます。間違いないことを確認して、yesとタイプします。続いて「Password:」と表示されたら「im4135dev」とキータイプしてreturnキーを押します。
[Windows]
TeraTermやPuttyなどのアプリケーションを利用するか、Cygwinを利用するなどして、VMにssh接続することができます。
2まず、media-root-dirキーの値に設定した「/tmp」ディレクトリに移動して、その内容を見てみます。以下のように、「testtable」という名前が見えていますが、これは、コンテキスト名、つまり、nameキーの値がまず見えています。
$ cd /tmp
$ ls -l
total 4
drwxr--r-- 3 www-data www-data 4096 Jul 28 01:34 testtable
3testtableディレクトリ以下は、基本的にwww-dataユーザー(Apacheの稼働ユーザー)に読み書き権限があるので、「sudo -s」でスーパーユーザーになり、ディレクトリの内部を順次下ります。すると、コンテキスト名(name)、keyキーで指定した主キーフィールドを含むレコードを特定する条件式(id=1)、フィールド名(text1)とディレクトリが階層的に構成され、最後に画像ファイルが見えています。ドラッグ&ドロップしたときに、text1フィールドに入力されたパスのテキストが、この画像ファイルのパスになっていることを確認してください。
$ sudo -s
# cd testtable/
# ls -l
total 4
drwxr--r-- 3 www-data www-data 4096 Jul 28 01:34 id=1
# cd id=1/
# ls -l
total 4
drwxr--r-- 2 www-data www-data 4096 Jul 28 01:34 text1
# cd text1/
# ls -l
total 292
-rw-r--r-- 1 www-data www-data 298993 Jul 28 01:34 001_1936.png
もともと、ここにドラッグ&ドロップした画像のファイル名は「001.png」でしたが、アップロード時にファイル名に4桁のランダムな数字を付与し、既存のファイルの上書きがなされないようにしています。
4「page15.htmlを開く」をクリックして表示したタブあるいはウインドウに戻り、ブラウザーの更新ボタン等でページを再読み込みします。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「page15.htmlを開く」をクリックして開きます。ファイルのドラッグ&ドロップを行った同じグレーのボックスに、もう一度同じファイルをドラッグ&ドロップします。
5INTER-Mediator-Server VM内の同一のフォルダーの一覧を参照してみます。ファイルが増えています。さらにもう一度ファイルをドラッグ&ドロップすると、さらにファイルが増えています。text1フィールドは最後にアップロードしたファイルのパスが記録されています。
# ls -l
total 584
-rw-r--r-- 1 www-data www-data 298993 Jul 28 01:34 001_1936.png
-rw-r--r-- 1 www-data www-data 298993 Jul 28 01:37 001_5498.png
# ls -l
total 876
-rw-r--r-- 1 www-data www-data 298993 Jul 28 01:34 001_1936.png
-rw-r--r-- 1 www-data www-data 298993 Jul 28 01:37 001_5498.png
-rw-r--r-- 1 www-data www-data 298993 Jul 28 01:38 001_9124.png

アップロードしたファイルの履歴を残す

 ここまでの方法では、あるレコードのあるフィールドに対して、アップロードしたファイルのうち、最後のファイルへのパスだけがデータベースに残っていました。さらに発展させて、ファイルをアップロードした履歴も残すようにします。

1「def15.phpを開く」をクリックして表示したタブあるいはウインドウに戻り、ブラウザーの更新ボタン等でページを再読み込みします。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「def15.phpを開く」をクリックして開きます。
2Contextsのすぐ下にある「追加」ボタンをクリックして、コンテキスト定義をひとつ増やします。
3name、table、viewに「fileupload」、keyを「id」します。その他のテキストフィールドは空白にします。
4Relationshipのすぐ下の「追加」ボタンをクリックして、設定項目を増やし、foreign-keyを「f_id」、join-fieldを「id」、operatorを「=」、portalを空白とします。
5定義ファイルエディターの一番最初にあるShow Allボタンをクリックして、すべての設定項目を表示します。
6既存のコンテキスト(testtable)で、File Uploadingと記述された部分の下にある「追加」ボタンをクリックします。
7新たに追加された項目で、fieldを「text1」、contextを「fileupload」、containerを空欄とします。
8「page15.htmlを編集する」をクリックして表示したタブあるいはウインドウに戻ります。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「page15.htmlを編集する」をクリックして開きます。
9次のように、ページファイルのコードを修正します。テーブルに、新たにfileuploadコンテキストを展開するテーブルを定義します。
<!DOCTYPE html>
<html lang="ja">
<head>
		:
</head>
<body>
<div id="IM_NAVIGATOR"></div>
<table>
    <thead>
    <tr>
        <th>Date and Time</th>
        <th>Message</th>
        <th></th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td>
            <input type="text" data-im="testtable@dt1"
                   data-im-widget="jquery_datepicker">
        </td>
        <td data-im="testtable@text1" data-im-widget="fileupload"></td>
        <td><div data-im="testtable@text1"></div>
            <table>
                <tbody>
                <tr>
                    <td data-im="fileupload@path"></td>
                </tr>
                </tbody>
            </table>
        </td>
        <td></td>
    </tr>
    </tbody>
</table>
</body>
</html>
10「page15.htmlを開く」をクリックして表示したタブあるいはウインドウに戻り、ブラウザーの更新ボタン等でページを再読み込みします。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「page15.htmlを開く」をクリックして開きます。
11ページネーションのコントローラーにある「レコード追加」のボタンをクリックして、新たにレコードを追加します。そのMessageの列にあるファイルのドラッグ&ドロップを受け付ける場所に、適当なファイルをドラッグ&ドロップします。
12パスが2つ追加されましたが、上側がDIV要素に展開したtext1フィールドで、下側の枠で囲われているのはfileuploadコンテキストのpathフィールドです。
13同じレコードのMessage列のボックスに、何度か同じファイルをドラッグ&ドロップして追加します。fileuploadコンテキスト側では、アップロードしたファイルのパスを随時記憶しています。
14INTER-Mediator-Server-VMにsshで接続します。方法は、この演習の途中で説明されています。接続した状態であれば、そのまま以下のコマンドを打ち込んでください。
15カレントディレクトリを/tmp/testtableに移動します。そこで、ファイルの一覧をls -lで確認すると、2つのレコードに対応したディレクトリがそれぞれ作られています。
# cd /tmp/testtable
# ls -l
total 8
drwxr--r-- 3 www-data www-data 4096 Jul 28 01:34 id=1
drwxr--r-- 3 www-data www-data 4096 Jul 28 01:43 id=2
16新たに作ったレコードに対応する「id=2」ディレクトリに移動すると、フィールド名のディレクトリ「text1」が見えています。その中身を表示すると、ドラッグ&ドロップで追加されたファイルが見えています。
# cd id=2
# ls -l
total 4
drwxr--r-- 2 www-data www-data 4096 Jul 28 01:44 text1
# cd text1
# ls -l
total 72
-rw-r--r-- 1 www-data www-data 21809 Jul 28 01:44 002_1966.png
-rw-r--r-- 1 www-data www-data 21809 Jul 28 01:44 002_7751.png
-rw-r--r-- 1 www-data www-data 21809 Jul 28 01:43 002_9136.png

アップロードしたファイルをページに表示する

 ファイルがアップロードできるようになりましたが、それだけではファイルが取り扱えません。今度は、コンポーネントでアップロードしたファイルを、Webページの中で表示する方法を説明します。なお、この方法は、アップロードのコンポーネントを使わないでアップロードしたファイルについても適用できる手法です。

1「page15.htmlを編集する」をクリックして表示したタブあるいはウインドウに戻ります。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「page15.htmlを編集する」をクリックして開きます。
2次のように、ページファイルのコードを修正します。fileuploadコンテキストを展開するテーブルに、さらに画像を表示するIMGタグを追加します。
	:
<table>
    <thead>
    <tr>
        <th>Date and Time</th>
        <th>Message</th>
        <th></th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td>
            <input type="text" data-im="testtable@dt1"
                   data-im-widget="jquery_datepicker">
        </td>
        <td data-im="testtable@text1" data-im-widget="fileupload"></td>
        <td><div data-im="testtable@text1"></div>
            <table>
                <tbody>
                <tr>
                    <td data-im="fileupload@path"></td>
                    <td><img style="width: 150px" 
                        src="def15.php?media="
                        data-im="fileupload@path@#src">
                    </td>
                </tr>
                </tbody>
            </table>
        </td>
        <td></td>
    </tr>
    </tbody>
</table>
IMGタグのsrc属性は「定義ファイル?media=」で記述し、=以降は、ファイルの存在するパスを、media-root-dirキーの値のディレクトリからの相対パスで指定します。このパスはpathフィールドに入っています。ターゲット指定に#をつけることで、pathフィールドの値を現在のsrc属性の後につなげます。
3「page15.htmlを開く」をクリックして表示したタブあるいはウインドウに戻り、ブラウザーの更新ボタン等でページを再読み込みします。もし、閉じていたら、「http://192.168.56.101」で開いたページに戻り「page15.htmlを開く」をクリックして開きます。読み込んだ画像がページ上に見えています。

演習のまとめ

ファイルアップロードのその他の機能

 ファイルのアップロード履歴を、演習ではfileuploadコンテキストのテーブルに残していました。このテーブルの構成としては、pathという名前のテキスト型フィールドが必要ですが、その他は自由に設定可能です。relationキーによるリレーションシップが定義されていますが、演習のような設定をした場合には、fileuploadコンテキストのテーブルには、外部キーとなるf_idフィールドが必要です。また、この演習では設定していませんが、レコード作成日時を自動的に設定するタイムスタンプのフィールドを確保し、現在の日時を既定値にすれば、ファイルをアップロードした日時が分かります。

 演習ではファイルアップロードをドラッグ&ドロップでできることを確認しましたが、ドラッグ&ドロップに対応していないブラウザーの場合には、従来形式のフォームによるファイルのアップロードができるようになっています。ただし、リスト5-4-1のようなJavaScriptのプログラムを、例えばページ合成を行う前に実行されるメソッドに記述することで、ドラッグ&ドロップ対応のブラウザーでも、従来のフォーム形式の画面が表示されます。

リスト5-4-1 フォーム形式のファイルアップロードを行う
IMParts_Catalog["fileupload"].forceOldStyleForm = true;

 ファイルアップロードのコンポーネントは、プログレス表示にも対応していますが、既定の状態ではプログレス表示は行いません。コンポーネントのプログレス表示機能を有効にするには、リスト5-4-2のようなプログラムを、例えばページ合成前に実行されるメソッドに記述します。

リスト5-4-2 プログレス表示のサポート
IMParts_Catalog["fileupload"].progressSupported = true;

 なお、プログレス表示を行うには、PHPが稼働するサーバーで、PHPのAPC(Alternative PHP Cache)が稼働している必要があります。INTER-Mediator-Server VMではAPCは稼働していません。さらに、INTER-Mediatorのディレクトリでいえば、INTER-Mediator/Samples/Sample_webpage/upload_frame.phpファイルを、ページファイルと同じ場所にコピーしておいてください。これらの条件がすべて満たされなければプログレス表示は実現しません。一般的な環境では、これらすべてを揃えるのは難しいので、必ずしも実装できるとは限らないと思われます。PHPのAPCの機能は使われない傾向になっており、将来に渡って継続的に利用できる見込みは少ないと思われます。別の機能での置き換えが必要であると考えています。

その他の付属のコンポーネント

 ファイルのアップロードはINTER-Mediatorの標準機能ですが、jQueryなどは別のソフトウェアのインストールが必要です。これらの使用方法については、INTER-Mediatorにあるサンプルを参照してください。ディレクトリはINTER-Mediator/Samples/Sample_webpage/で、表5-4-1〜表5-4-3のようなファイルを参照してください。例えば、サンプルにはTinyMCEのファイル自体は含まれていません。TinyMCEをダウンロードし、ページファイルのヘッダーで、スタイルシートファイルやJavaScriptのファイルを、適切なパスを指定して読み込む必要があります。パスを指定しますので、必ずしもページファイルと同じ階層に存在する必要はありません。別のディレクトリにあっても参照ができれば問題はありません。

種類値とファイル名
コンポーネントTinyMCE
data-im-widgetの値tinymce
アダプターtinymce_im.js
ページファイルtinymce_MySQL.html
定義ファイルinclude_MySQL.php
表5-4-1 サンプルにあるJavaScriptコンポーネント「TinyMCE」
種類値とファイル名
コンポーネントCodeMirror
data-im-widgetの値codemirror
アダプターcodemirror_im.js
ページファイルcodemirror_MySQL.php
定義ファイルinclude_MySQL.php
表5-4-2 サンプルにあるJavaScriptコンポーネント「CodeMirror」
種類値とファイル名
コンポーネントjQuery DatePicker
data-im-widgetの値jquery_datepicker
アダプターjquery_datepicker_im.js
ページファイルjquery_datepicker_MySQL.php
定義ファイルinclude_MySQL.php
表5-4-3 サンプルにあるJavaScriptコンポーネント「jQuery DatePicker」
種類値とファイル名
コンポーネントjQuery DatePicker
data-im-widgetの値jquery_fileupload
アダプターjquery_fileupload_im.js
ページファイルfileupload_jQuery_MySQL.html
定義ファイルinclude_MySQL.php
注釈利用方法は、標準のfileuploadと同様にパスを記録するフィールドとバインドして利用します。
表5-4-4 サンプルにあるJavaScriptコンポーネント「jQuery File Upload」

メディアファイルの内容の取得

 定義ファイルは、通常はフレームワーク自体をページファイルに送り込むことや、データベースアクセスに利用しますが、他にもさまざまな機能があります。そのうちのひとつが、ファイルの内容を取り出す仕組みです。HTMLでは、IMGタグによる画像や、PDFファイルへのリンクといった用途に使うことを想定しており、パスを与えて、そのファイルの中身をMIMEタイプなどとともにクライアントに返すといった動作を行います。基本的にはリスト5-4-3のような記述を行います。mediaというキーでパラメーターを指定するということです。

リスト5-4-3 定義ファイルを利用してファイルの内容を取り出す
一般的な記述:定義ファイルへのパス?media=ファイルへのパス
例:def15.php?media=shot0001_3923.png

 ファイルへのパスが「http://」「https://」で始まるURLの場合には、そのURLから得られた結果をそのまま返します。パスが「class://」で始まる場合には、その後に自分で作成したPHPのクラスのプログラムを実行できます。これについては『Chapter 8 サーバーサイドでのプログラミング』で解説をします。

 上記のプロトコル以外は、ファイルへのパスとみなします。そして、media-root-dirキーで指定したパスに続いて、media=以降のパスで構成される絶対パスのファイルを取り出して、その内容を返します。なお、media=が「/fmi/xml/cnt」の場合、つまりFileMaker Serverを使っていて、オブジェクトフィールドを指定した場合のみ、FileMaker ServerへのURLに変換して、オブジェクトフィールドの内容を画像等で取り出すといった動作を行います。

 このセクションの演習で行ったように、ファイルアップロードのコンポーネントでアップロードしたファイルのパスが相対パスで記録されていれば、そのフィールドの値をmedia=の値に指定すればOKです。fileuploadコンテキストのようなアップロード履歴を残すテーブルは相対パスですが、演習でいえば、testtableコンテキストのtext1フィールドは絶対パスで記録されてしまっています。このままではIMGタグのsrc属性に指定をしたい場合には少し不便なので、この仕様は将来変更する可能性もあります。

FileMakerのオブジェクトフィールドへのアップロードと画像表示

 FileMakerを利用している場合、オブジェクトフィールドへ画像を保存することは一般的です。したがって、オブジェクトフィールドに画像を入れることを前提として、Webアプリケーションも作成したいと考えるでしょう。INTER-Mediatorでは、ファイルアップロードのコンポーネントが直接オブジェクトフィールドにデータを入力できますし、オブジェクトフィールドの画像をIMGタグ要素で画面に表示することもできます。

 このセクションの演習で作った一連のファイルに対して、オブジェクトフィールドを利用する場合にはどのようにすればよいかを説明します。まず、データベース「TestDB」について確認します。testtableレイアウトには、vc1という名前のオブジェクトフィールドがあります(図5-4-1)。このオブジェクトフィールドに画像等を入力するものとします。

図5-4-1 データベースで利用するtesttableレイアウト

 このオブジェクトフィールドは単に定義するだけではなく、計算値自動入力の設定を行う必要があります。「ファイル」メニューの「管理」から「データベース」を選択して、データベースの管理ダイアログボックスを表示します(図5-4-2)。

図5-4-2 定義ファイルの変更箇所

 フィールドvc1を選択して、右下の「オプション」ボタンをクリックして、フィールドオプションのダイアログボックスを表示します。このダイアログボックスの「入力値の自動化」を選択した状態で、「計算値」のチェックボックスをオンにします(図5-4-3)。

図5-4-3 定義ファイルの変更箇所

 そして、計算値として図5-4-4のような式を入力します。この式は、どんなフィールドでもそのままで指定すればかまいませんので、TestDBからコピーして利用すればよいでしょう。この計算式のポイントは、フィールドに何も入力されていないとき、つまり新規にレコードを作成したときなどに、Base64でエンコードされた結果をもとにして、元データを得てフィールドに保存しています。つまり、クライアントからBase64でデータを送り込み、この式を通じて元に戻しているのです。

図5-4-4 オブジェクトフィールドに指定する計算式

 続いて定義ファイルの変更点について説明します。コンテキストは、testtableを利用して、fileuploadは利用しません。testtableコンテキストのFile Uploadingの下の設定は、fieldにオブジェクトフィールドのフィールド名(ここでは「vc1」)を指定します。contextは空欄にし、containerをtrueにします。その他は変更の必要はありません。

図5-4-5 定義ファイルの変更箇所

 ページファイルをリスト5-4-4のように修正します。フィールド名がvc1に変わっただけで大きな違いはありません。ファイルアップロードのコンポーネントをdata-im-widget属性で指定するタグ要素は、vc1フィールドにバインドします。そして、vc1フィールドに入力されている画像を表示するには、vc1フィールドの値をmedia=の後につなげます。オブジェクトフィールドは、カスタムWeb経由でデータを得ると画像等のバイナリデータではなく、フィールドに入力することが可能なURLの一部分が得られます。それを定義ファイルの引数に与え、定義ファイルから先のINTER-Mediatorの内部で正しいURLを構築して画像データなどを得ています。

リスト5-4-4 修正した
<!DOCTYPE html>
<html lang="ja">
<head>
		:
</head>
<body>
<div id="IM_NAVIGATOR"></div>
<table>
    <thead>
    <tr>
        <th>Date and Time</th>
        <th>Message</th>
        <th></th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td>
            <input type="text" data-im="testtable@dt1"
                   data-im-widget="jquery_datepicker">
        </td>
        <td data-im="testtable@vc1" data-im-widget="fileupload"></td>
        <td>
            <img style="width: 150px" src="def15.php?media=" 
                    data-im="testtable@vc1@#src"/>
        </td>
        <td></td>
    </tr>
    </tbody>
</table>
</body>
</html>

 ページファイルを表示して、画像ファイルをドラッグ&ドロップします。直後に右側に画像は出てきませんが、更新すると、ドラッグ&ドロップした画像が見えるようになります。

図5-4-6 ページファイルを表示した

 なお、更新しないと画像が見えない点については、将来にバグ修正する予定です。

このセクションのまとめ

 JavaScriptで作られたさまざまなソフトウェアコンポーネントを利用する仕組みを持っています。しかしながら、利用するには、アダプターの開発が必要になります。INTER-Mediatorには、TinyMCE、CodeMirror、jQuery DatePickerのアダプターが付属しています。また、独自に組み込んだファイルアップロードのコンポーネントを利用すれば、ドラッグ&ドロップあるいはフォーム形式でのファイルのアップロードの機能をアプリケーションに組み込むことができます。また、定義ファイルは、画像ファイルなどの内容を取り出すことにも利用できます。

5-5クロステーブルの作成

クロステーブルは、表があって、一番上の列と、一番左の列がラベルとなっており、そのラベルの交差するセルには、ラベルに関連するデータが表示されるというものです。例えば、一番上の列には自社の「支店」が並び、一番左の列には年月が並ぶとすると、その交差するセルには、特定の支店の特定の年月の売り上げが見えるというものです。INTER-Mediatorではこうしたテーブルをデータベースの値を利用して作成することができます。

クロステーブルに必要な記述

 クロステーブルは、INTER-Mediator Ver.5.4-devで搭載された機能です。通常、INTER-Mediatorはひとつのコンテキストをページ上に展開してページ合成を行いますが、クロステーブルは3つのコンテキストを使用します。そのため、定義ファイルには3つの異なるコンテキストを定義しなければなりません。それぞれ、「列見出し用コンテキスト」「行見出し用コンテキスト」「交差セル用コンテキスト」と名付けることにします。クロステーブルの見出しは固定的なものではなく、データベースのマスターテーブル等から取り出す仕組みにしてあります。そのため、汎用性は高いですが、現状では、単に数字の100から150までのような見出しを利用するのが少し面倒です。この方法は、Samples/Sample_ExtensibleにあるYearMonthGen.phpファイルおよびそれを使用している定義ファイルを参照してください。

 行見出し用および列見出し用のコンテキストは通常と変わりありません。1レコードがひとつの見出しセルに展開されます。これに対して、交差セル用のコンテキストでは、relationキーの値を指定し、その要素は必ず2つ取り、ひとつ目は行見出し用のコンテキストとの関係、2つ目は列見出し用コンテキストとの関係、すなわち対応するフィールドが何かを記述します。設定の中にあるoperatorキーは無視され、イコールによる関係しか扱えません。コンテキスト指定のポイントを示したのが、リスト5-5-1です。なお、コンテキストの中にはqueryやsortキーでの検索条件や並べ替え設定があっても構いません。必須なことはコンテキスト定義が3つあることと、交差セル用コンテキストにおいて、2つの要素を持つrelationキーの値があることです。なお、ここでは説明を分かりやすくするために、nameキーと同一のテーブルがあるといった状況を想定してください。

リスト5-5-1 クロステーブルで使用する3つのコンテキスト
array(  // 列見出し用コンテキスト
  "name" => "item",
  "key" => "id",
),
array(  // 行見出し用コンテキスト
  "name" => "customer",
  "key" => "id",
),
array(  // 交差セル用コンテキスト
  "name" => "salessummary",
  "key" => "id",
  "relation" => array(
     array(  // 列見出し用コンテキストとの関連
       "foreign-key" => "item_id", 
       "join-field" => "id", 
       "operator" => "=",),
     array(  // 行見出し用コンテキストとの関連
       "foreign-key" => "customer_id", 
       "join-field" => "id", 
       "operator" => "=",),
  ),
),

 一方、ページファイルでは、TABLEタグ要素を利用したテーブルを利用します。エンクロージャーとなるTBODYタグ要素のdata-im-control属性には「cross-table」という値を設定します。その中には2行2列のセルだけを入れます(図5-5-1、リスト5-5-2)。セルはそれぞれ、THでもTDでもどちらのセルでも構いません。リスト5-5-2では、各セルの中にdata-im属性によってターゲット指定が記述されています。ここで、Ⓑのセルは行見出し用コンテキスト、Ⓒのセルは列見出し用のコンテキストが展開されます。したがって、これらのセルでは、コンテキスト定義に存在するコンテキスト名およびその中に存在するフィールド名を記述する必要があります。そして、Ⓓのセルには、交差セル用コンテキストに対応したターゲット指定を記述しますが、セルを埋めるルールは、この後の『クロステーブル生成の仕組み』で説明をします。なお、Ⓐのセルは、ページ合成後もテーブルの左上のセルとして配置されます。

図5-5-1 ページファイルに用意するテーブルの中身
リスト5-5-2 ページファイルへの記述例
<table>
<tbody data-im-control="cross-table">
<tr>
  <th></th>
  <th>
     <div data-im="item@id"></div>
     <div data-im="item@name"></div>
  </th>
</tr>
<tr>
  <th>
     <div data-im="customer@id"></div>
     <div data-im="customer@name"></div>
  </th>
  <td>
     <div data-im="salessummary@qty"></div>
     <div data-im="salessummary@total"></div>
   </td>
</tr>
</tbody>
</table>

クロステーブル生成の仕組み

 クロステーブルを合成する仕組みを解説しましょう。コンテキストが3つもあるとややこしく思われるかもしれませんが、それぞれの見出しを合成した上で、交差セルを埋めるという処理が全体的な流れになります。その流れが理解できれば、動作の見通しもつきやすいでしょう。

 まず、ページファイルでのクロステーブル部分の初期状態は図5-5-1のようなものです。この時、TBODY内部のセルを一度全部取り除きます。そして、ⒶⒷⒸⒹの4つのTHないしはTDタグ要素を別途記録しておきます。最初に1行目のTRタグ要素を生成し、その子要素としてⒶのセルを追加します(図5-5-2)。ここでのTRタグは、ページファイルにあったもではなく、プログラムで単に生成したものです。Ⓐのセルは単に複製して子要素にするだけですので、この中にターゲット指定を記述しても、データベースの内容を展開することはありません。

図5-5-2 テーブルにⒶのセルを合成する

 続いて、テーブルの1行目に列見出しを作成します。図5-5-3のように、1行目のTRタグ要素をエンクロージャー、Ⓑのセルをリピーターとして、ページ合成を行います。ここではⒷのセルの中身を解析して、ターゲット指定の設定を集めて、一番多く使われているコンテキスト名を決定し、そのコンテキスト名の定義に基づいてデータベース処理を行います。つまり、通常のエンクロージャー/リピーターの展開が行われます。そして、コンテキストオブジェクト自体も生成されます。もちろん、クエリー結果のレコードの個数だけⒷのセルが複製されて、その中ではフィールドの内容がタグ要素に合成され、データベースのデータがセルに見えることになります。もちろん、複数のリンクノードを記述して複数のフィールドを指定しても構いません。なお、内部にさらにエンクロージャー/リピーターが見つかれば展開は行いますが、一般には処理速度を考慮すべきところであり、1回のデータベースアクセスで必要なデータを取り出すようにデータベース側を設計しておくことが求められます。

図5-5-3 テーブルに列見出しを合成する

 次にテーブル2行目から、行見出しの合成を行います。図5-5-4のように、TBODYタグをエンクロージャーとし、ⒸのセルをTRタグで包んだ要素をリピーターとして通常通りの手順で合成を行います。したがって、Ⓒのセルに含まれるリンクノードのターゲット指定によってコンテキスト名が決まりデータベースアクセスして、取り出されたレコードのフィールドの値が2行目以降の1セル目に合成されます。Ⓒのセルを包むTRタグ要素も、元からページファイルにあったものではなく、単にプログラムで生成したものです。Ⓒのセル内に複数のリンクノードを設けても構いませんし、さらにその中にエンクロージャー/リピーターのセットがあれば展開を進めるのも同様です。

図5-5-4 テーブルに行見出しを合成する

 そして、2行目の2列目以降、列見出しのレコード数分Ⓓのセルを付加します。最初は単にセルを付加して、行列をセルで埋める作業を行います。そして、Ⓓのセルを解析してコンテキスト名を求めてコンテキストを決定し、データベースアクセスを行います。クエリー結果のレコードを順番に合成するのではなく、順番に調べて置き場所がテーブル内にある場合、その交差セルに対して合成、つまりリンクノードの指定に応じてフィールドの値をタグ要素内に埋め込む処理を行います。したがって、交差セル用コンテキストで得られた結果でも、対応する行あるいは列がなければ無視されます。この処理はクロステーブルだけで行われており、エンクロージャー/リピーターによる展開とは異なる処理が組み込まれています。したがって、エンクロージャーに相当するノードはないため、図の中ではエンクロージャーに対しては「規定なし」と記載をしました。なお、Ver.5.4-dev現在の実装では、交差セルへの合成は、単に合成するだけなので、同一のセルに2回以上合成すると、それらの数値の文字列がつながって見えるだけです。加算等は実装されていませんので、コンテキストから得られるデータは集計結果になっているものを利用するようにしてください。

図5-5-5 テーブルを交差セルで埋める

 最後の交差セルを埋める部分はデータを見ながら説明をしましょう。サンプルファイルはSamples/Practice/crosstable.htmlおよびcrosstable.phpにあります。VMを利用している場合には、ブラウザーで「http://192.168.56.101」に接続し、そこにある「サンプルプログラム」のリンクをクリックして、サンプルプログラムの一覧を表示します。そこにあるPracticesにある「cross table」の項目をクリックして、動作を確かめることができます。図5-5-6は、そのサンプルを動かした結果です。このサンプルのページファイルは、このセクションで示したページファイルの記述のサンプルと同一のものです。定義ファイルには、item、customer、salesummaryの3つのコンテキストが指定されていて、それぞれ、列見出し、行見出し、交差セルのコンテキストです。

図5-5-6 クロステーブルのサンプルプログラム

 以下、3つのコンテキストの具体的な値をもとに、クロステーブルの動作を検討しましょう。列見出しのコンテキスト「item」と、行見出しのコンテキスト「customer」に関して、クエリー結果のデータを示すと、表5-5-1と表5-5-2のような結果になります。それぞれの値が、セルの中に表示されているのが分かります。

idname
25Onion
26Parsnip
27Peppers
28Potato
29Pumpkin
30Peas
31Rhubarb
32Shallots
33Spinach
34Squash
35Sweet Potato
表5-5-1 コンテキスト「item」へのクエリー結果
idname
250Danio Food, Co.
251Darter Food, Co.
252Dartfish Food, Co.
253Dealfish Food, Co.
254Death Valley pupfish Food, Co.
255Deep sea anglerfish Food, Co.
256Deep sea bonefish Food, Co.
257Deep sea eel Food, Co.
258Deep sea smelt Food, Co.
259Deepwater cardinalfish Food, Co.
表5-5-2 コンテキスト「customer」へのクエリー結果

 salessummaryクエリーの結果は多数のレコードが取り出されますが、その一部を抜粋したのが表5-5-3です。id=1の最初のレコードですが、item_id=38、customer_id=549です。これらは、列見出しおよび行見出しのコンテキストの中には含まれていませんので、id=1のレコードは無視します。同様に、id=2のレコードも無視します。id=3のレコードは、item_id=30ですので、列見出しのコンテキストから「6列目」であることが分かります。また、customer_id=251であり行見出しのコンテキストから2行目であることが分かります。したがって、交差セルの行列で言えば2行目の6列目、テーブルで数えれば見出しの行が増えているので3行目の6列目に当たるセルに対して、id=3のレコードが展開されることになります。そして、実際に該当するセルに、qtyの値の「8」とtotalの値の「2984」が見えています。

 id=4はどうでしょうか? item_id=32なので8列目ですが、customer_id=496に対するレコードが行見出しにはないので、このレコードも無視します。こうして、この後沢山のレコードが無視された後、id=95のレコードが見つかります。item_id=27なので3列目、customer_id=255なので6行目であることが確定するので、対応するセルに対してqtyとtotalの値「41」「12177」が表示されます。salessummaryではこの2つのレコードだけが、クロステーブルの中にあるデータなので、2つのレコードだけが見える結果になったということです。

 なお、ここでの突き合わせは、交差セルのコンテキストのrelationキーでのフィールドで決まります。列見出しに対応する設定はforeign-keyは「customer_id」、join-fieldは「id」でした。つまり、列見出しのコンテキストのidフィールドの値と、交差セルのコンテキストのcustomer_idの値を付き合わせるということです。通常、join-fieldは見出しのコンテキストのkeyキーで指定するものかもしれませんが、必ずしもそうでないような場合もあり、その場合に見出しコンテキストでテキストフィールドを設定してフィールドを編集したいこともあるので、見出しコンテキスト側の突き合わせ対象フィールドは、relationキーでの指定から得るようにしました。

iddtitem_idcustomer_idqtyunitpricetotal
12010-01-01 00:00:003854975283696
22010-01-01 00:04:45246321678112496
32010-01-01 00:14:103025183732984
42010-01-01 00:27:02324964633115226
:::::::
942010-01-01 22:02:291686044721888
952010-01-01 22:24:34272554129712177
:::::::
表5-5-3 コンテキスト「salessummary」へのクエリー結果

 クロステーブルで3つのコンテキストを用意しますが、注意深く設定しなければならないのは、交差セル用コンテキストのrelationキーの値です。列見出しと行見出しとの対応が取れるようなフィールドを指定しなければなりません。また、交差セル用コンテキストは、集計した結果が得られるようにしておく必要があります。ページ合成の中で、合計を取るなどの処理は現在はできません。

このセクションのまとめ

 コンテキストを3つ用意することで、それらを列見出し、行見出し、交差セルに展開して、クロステーブルを作成できます。交差セルには、relationキーで指定したフィールドにおいて見出しと対応した値を持つものが配置されます。

5-6スタイルの設定を自動化するテーマ

見栄えの良いページを作成するには、CSSによるスタイルをかなり細かく設定しなければなりませんが、そのスタイル設定をテーマとしてひとつのフォルダーにまとめておき、それをページあるいはサイト全体に適用する機能をVer.5.6-devで組み込みました。

テーマの機能とdefaultテーマについて

 原則としてCSSの定義はアプリケーションごとに作るものであるとも言えるのですが、CSSを適用しないでWebページを作成すると、INTER-Mediatorが生成するようなページネーションやログインパネルがHTMLの定義そのままの形で出てきてしまいます。機能としては実現していても、次のページに移動するボタンがボタンらしく見えていないとユーザーが戸惑う原因になりますし、作っている時も画面がそっけなさ過ぎてモチベーションが落ちてしまいそうです。そこで、テーマの機能を内蔵して、Webページを作った段階で、ある程度のスタイルや画像リソースが自動的に適用されるように、Ver.5.6-devで改良しました。Ver.5.6の正式版以降では確実に利用できる機能となる予定です。

 INTER-Mediatorで自動的に適用されるデザインテーマは、太木裕子氏(京都造形芸術大学キャラクターデザイン学科専任講師)に開発していただきコントリビュートしていただきました。テーマ自体のシステム名称は「default」としていますが、テーマ名として『「楝」OUCHI』という名称がつけられています。「楝色(おうちいろ)」は薄い紫の和色名です。基本となるスタイルであることから、楝=家=HOMEといった言葉の連想に由来しているそうです。テーブルのTHやTD、INPUT等、よく利用するタグについても、見栄えが良くなるようなCSS定義がなされています。

テーマの適用と選択

 INTER-Mediatorには、表5-6-1に示すテーマがVer.5.6の段階でバンドルされています。defaultは何も指定しないと適用されるものですが、実際にはINTER-Mediatorがページファイルのヘッダー部分に自動的にテーマの定義を取り出してページに適用するlinkタグ要素を付加することで、CSSなどを適用しています。

テーマ名内容
defaultテーマに対する設定がない場合に「楝」テーマが適用される
leastページネーションおよびログインパネルのCSSと、処理中を示す表示のためのリソース
thosedays「楝」テーマの機能を組み込む以前のサンプル用CSSファイル「sample.css」を適用した状態と同じにする
表5-6-1 INTER-Mediatorに付属のテーマ

 テーマの設定は、以下のいずれかの方法で行えます。IM_Entry関数はもちろん定義ファイルに記述するもので、このテーマの設定は、定義ファイルを参照しているページにだけ適用されます。params.phpファイルに指定すると、そのファイルを参照しているINTER-Mediator全体に適用されるので、例えばサイト全体をまとめて設定したいときに利用できます。

 default以外の付属のテーマについて説明します。leastは、ページネーションとログインパネル以外の設定がない、最小限のテーマです。テーブルや段落等のCSSは全くされていない、文字通り最小限のテーマです。thosedaysは、テーマを実装する以前の状態という意味ですが、そのとき、サンプルファイル用のCSSファイルであるsample.cssをコピーしてアプリケーションに利用していたことに由来します。その時と同じ状態をテーマで実現しています。以前に作成したWebページにdefaultを適用すると、自分自身でCSSの定義を行なっている場合には異なる定義が混在してしまってかえって見栄えには問題が出てくるかもしれません。そこで、最小限ながら以前のsample.cssと同じ状態にするテーマを、既存のWebページ向けに用意してあります。

テーマのカスタマイズ

 テーマを自分自身で変更したい場合には、大きくわけて2通りのアプローチがあるでしょう。ひとつは、既存のテーマを使いつつ、特定のページでは、その定義を上書きして変更するというものです。もうひとつは、スタイルそのものを自分で作るかあるいは既存のものを作り変えるという方法です。後者の方法はこの次で説明します。

 ページネーションのカスタマイズをしたい場合の例を示しましょう。ページネーションのそれぞれの要素には、表5-6-2のようなスタイルが設定されています。

スタイルシートのセレクタページネーションの該当部分
#IM_NAVIGATORコントローラーの外側
.IM_NAV_panelコントローラー全体
span.IM_NAV_info文字を表示する部分
span.IM_NAV_buttonボタンになる部分(機能するボタン)
span.IM_NAV_disabled機能しないボタンの部分
表5-6-2 ページネーションで割り当てられたスタイルシートのセレクタ

 ここでボタンの背景と、黄色いマーキングを別のものに変えたいとします。色のセンスはさておいて、ボタンを白背景の赤文字にしたいとした場合、リスト5-6-1のようにヘッダー部にstyleタグ要素を追加して、ボタンに対するCSSの設定を上書きしてしまいます。

リスト5-6-1 ページネーションの要素に対するCSS定義の上書き
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <title></title>
  <script type="text/javascript" src="def01.php"></script>
  <style>
    span.IM_NAV_button {
        color: red;
        background-color: white;
    }
  </style>
</head>
<body>

テーマの構造とカスタマイズ

 INTER-Mediator既定のテーマは、INTER-Mediatorフォルダーの直下の「themes」フォルダーに収められています。このフォルダー以下は、「テーマ名」、「css」か「images」、実際のファイル、といった構成になっています。cssとimagesは決められたフォルダー名です。通常、cssフォルダーに入れてある拡張子が.cssのファイルは、自動的にマージされて、ページに適用されます。

 独自にテーマを作る場合には、defaultフォルダーをコピーして名前を付け直し、その中のファイルを変更するのが一番効率良い方法でしょう。その作成したテーマのフォルダーは、もちろん、INTER-Mediatorフォルダーの直下に配置して構いません。また、可能であれば、コミットしていただければ、テーマのバリエーションが増えるので、開発者としては歓迎です。

 しかしながら、INTER-Mediatorフォルダーの中にテーマを入れるとなると、もし、改めてレポジトリからpullしたときに、自分で作ったものが追加されていることでなんらかの対処が必要になるかもしれません。自分だけでテーマを使いたい場合にはINTER-Mediatorフォルダー外にテーマを置きたいと思われるでしょう。その場合、リスト5-6-2のように、params.phpファイルで$altThemePath変数に、テーマが保持されているフォルダーへのパスを文字列で指定します。そのサーバーで認識できる絶対パスを記述してください。また、テーマ名もその場合自分で作ったものになるでしょうから、$themeName変数も記述します。

リスト5-6-2 独自のテーマを独自のディレクトリに配置するときのparams.phpの一部
$altThemePath = "/var/www/thmeme";    //Your original thmeme directory.
$themeName = "blackbird";      //Default theme name.

 テーマ内のファイルの内容を取得するには、一般には次の形式で得られます。[ ] の部分は適切な文字列に置き換えますが、css|imagesは、cssないしはimagesのどちらかの文字列が入るということです。以下の形式を、例えば、linkタグ要素のhref属性や、imgタグ要素のsrc属性等に指定して、テーマの中身を取り出すことができます。なお、type=cssの場合にname=を省略することで、cssフォルダーにある全ての.cssファイルの中身をマージして返します。

リスト5-6-3 テーマの中身を取り出すURL
[定義ファイル名]?theme=[テーマ名]&type=[css|images]&name=[ファイル名]

 なお、Ver.5.6現在、テーマの機能はまだ完全ではないと認識しています。例えば、cssフォルダー内のCSSファイルで、背景をurlで取得したいけれども、その画像はimagesフォルダー内にあるような場合、CSSコンパイラ的になんらかの表記を行った上で、画像を取り込めるようにするなどの更新が必要と考えています。

このセクションのまとめ

 テーマの機能によって、特にスタイルシートの設定をしなくても、おおむねデザイン的に整ったサイトを作ることができるようになりました。テーマを自分で定義したり、テーマの定義内容を変更することもできます。

5-7ステップ動作を行うシングルページアプリケーション

モバイルアプリケーションによく見られる「ステップ動作」を実装できる機能をVer.5.7-devで組込みました。ステップ動作は、リスト形式で選択肢が表示され、タップすると次の選択肢が表示されるといった形式のユーザーインターフェースです。また、「戻る」ボタンがあることも特徴です。このひとつひとつの画面をコンテキストで定義し、それぞれの画面をひとつのページファイル内に記述する、シングルページアプリケーション形式でステップ動作のアプリケーションを組み込むことができます。このセクションの内容は、JavaScriptのプログラミングが必須であり、書籍内の順序では後の部分の説明を理解している必要があります。

ステップ動作のサンプルアプリケーション

 ステップ動作の機能を理解するために、サンプルアプリケーションを動かしてみましょう。VMを利用しているのであれば、まず、INTER-Mediator Ver.5.7-devの「2017年12月25日」以降にリリースされたものにアップデートしてから以下の解説を読み進めてください。『1-2 演習を行うための準備』の『VM内のINTER-Mediatorのアップデート』にある「VM内のINTER-Mediatorを開発中のバージョンにするコマンド」に示したコマンドを参考にINTER-Mediatorを更新してください。Ver.5.7の正式版以降は確実にステップ動作の機能が組み込まれています。

 さて、INTER-MediatorをVer.5.7にアップデートできましたら、VMのトップページにある「サンプルプログラム」のリンクをクリックし、Practicesの表にある、「Mobile」の「step」を、クリックしてください。ステップ動作は、マスター/ディテール形式のユーザーインターフェースと同じ仕組みを使っているため、モバイル対応しています。モバイル端末だとセル全体のどこをタップしても構いませんが、PC動作だと「詳細」ボタンが表示されます。PC/Macで手軽にモバイル端末のシミュレーションをするには、Chromeのデバッガにあるデバイスツールバーを表示する方法が手軽でしょう。デバッガの画面を表示すると、上部に「Elements Console…」とメニューが並びます。その「Elements」のすぐ左側にあるモバイルデバイスのアイコンをクリックしてオンにし、青い色になることを確認してください。そして、画面を更新すると、モバイル端末での表示状態になります。

 図5-7-1は、VMを稼働させて表示されるサンプルアプリケーションの最初のページです。Chromeでモバイルのシミュレーションを行っています。まずは、東京近辺の都県の名前が並んでいます。あとでコードを確認しますが、TABLEタグを利用しています。そして、ページ上部にはタイトルバー、ページ下部にはINTER-Mediatorを示すバーがあり、残りの部分はテーブルだけで埋め尽くされているのを確認してください。また、サンプルの郵便番号データベースには東京都のデータしかなかったことにお気づきの方は、「神奈川県」などはどういうことかと思われるかもしれませんが、これも後で説明します。なお、次のページに移動して何か表示されるのは、「東京都」だけです。

図5-7-1 ステップ動作を示すサンプルアプリケーション

 「東京都」をタップすると、東京都の市区町村名が一覧されます(図5-7-2)。ここで、まず、市区町村のリストもテーブルで構築していますが、スクロールすることを確認してください。その時、タイトルバーとページ下部の表示は画面に固定され、その間でスクロールされるという典型的なモバイルアプリケーションの動作になっていることを確認してください。この動作はINTER-Mediatorとは関係なく、CSSの設定で可能です。これについても、あとで説明します。また、ヘッダーの左側に、◀︎ボタンが表示され、これをタップすると、東京都などの最初のリストが表示されることが分かります。つまり、これは「戻る」ボタンであり、最初の画面では表示されていないことも確認してください。

図5-7-2 次の画面に移動すると「戻る」ボタンが表示される

 「市区町村」をタップしたあとは、町域名の一覧が表示されます。この時、前に選択した市区町村に含まれる町域名だけが表示されていることを確認してください。つまり、前のステップの選択肢が、次のステップの一覧表示に対して影響を与えている、つまり検索条件を与えるということができています。そして、最後は、郵便番号、都道府県、市区町村、町域名が一覧され、そこから先にはステップ動作での移動は行いません(図5-7-3)。戻ってまた別の場所を選択することもできます。郵便番号が見える画面では、いずれのセルをタップしても画面遷移は行いませんが、デバッガのコンソールにオブジェクトの内容が出力され、これまでの4つのステップで、何をタップしたのかが記録されていることが分かります。ここで、記録されている状況を例えばデータベースに書き込むなどすることになることも多いかもしれませんが、その作業はJavaScriptでプログラムを記述する必要があります。

図5-7-3 最後の画面のセルをタップすると、コンソールで遷移の履歴が参照できる

ステップ動作のためのコンテキスト定義

 定義ファイルの内容を参照しましょう。定義ファイルは、INTER-Mediatorフォルダーの中では、Samples/Sample_Mobile/step_MySQL.phpにあります。ソースコードをWebで参照するにはこちらをクリックしてください。リスト5-7-1は、ちょっと長いですが、IM_Entryの第1引数であるコンテキスト定義部分を示しています。全体的に見て、4つのコンテキストが定義されています。ここで、ステップ動作に特有の設定は、navi-controlキーのstepと、step-hideです。INTER-Mediatorは最初にページ全体の合成を行うとき、step-hideのコンテキストについては、エンクロージャーの要素のdisplayスタイル属性にnoneを設定して、全て非表示にします。また、そのとき、データベースアクセスを行わず、データをリピーターに合成する処理は行いません。一方、stepの方は、通常通り、ページ合成中にデータベースアクセスを行い、リピーターにデータを合成します。つまり、ひとつのstepと複数のstep-hideがnavi-controlに設定されたコンテキスト定義を作ることが、一般的な手法です。

 あるステップ動作のコンテキストでタップを行った時に、「次に表示するコンテキスト」をどのように決定するかを説明しましょう。まず、既定の動作をここで説明します。次々と表示されるコンテキストは、定義ファイルの定義の順番になります。つまり、prefectureコンテキストを表示している時にタップすると、次にnavi-controlキーの値がstep-hideのコンテキストを探し、その結果cityコンテキストが選択され、このコンテキストを利用しているページファイルの一部分が更新され、データベース処理が行われてクエリー結果をリピーターに合成します。そして、「次へ」という動作の時にスタック動作を行うグローバル変数に情報を残すので、「戻る」ことも自動的に行えるようになっています。

リスト5-7-1 ステップ動作のサンプルのコンテキスト定義
array(
    array(
        'name' => 'prefecture',
        'table' => 'not_available',
        'view' => 'postalcode',
        'aggregation-select'=>'MIN(id) AS pref_id, f7 AS pref',
        'aggregation-from'=>'postalcode',
        'aggregation-group-by'=>'f7',
        'records' => 10000,
        'maxrecords' => 10000,
        'key' => 'pref_id',
        'navi-control' => 'step',
        'before-move-nextstep'=>'doAfterPrefSelection',
        'appending-data'=>array(
            array('pref_id'=>101, 'pref'=>'埼玉県'),
            array('pref_id'=>102, 'pref'=>'神奈川県'),
            array('pref_id'=>103, 'pref'=>'千葉県'),
        )
    ),
    array(
        'name' => 'city',
        'table' => 'not_available',
        'view' => 'postalcode',
        'aggregation-select'=>'MIN(id) AS city_id, f8 AS city',
        'aggregation-from'=>'postalcode',
        'aggregation-group-by'=>'f8',
        'records' => 10000,
        'maxrecords' => 10000,
        'key' => 'city_id',
        'navi-control' => 'step-hide',
        'before-move-nextstep'=>'doAfterCitySelection'
    ),
    array(
        'name' => 'town',
        'table' => 'not_available',
        'view' => 'postalcode',
        'aggregation-select'=>'MIN(id) AS town_id, f9 AS town',
        'aggregation-from'=>'postalcode',
        'aggregation-group-by'=>'f9',
        'records' => 10000,
        'maxrecords' => 10000,
        'key' => 'town_id',
        'navi-control' => 'step-hide',
        'before-move-nextstep'=>'doAfterTownSelection'
    ),
    array(
        'name' => 'wrapup',
        'table' => 'not_available',
        'view' => 'postalcode',
        'records' => 10000,
        'maxrecords' => 10000,
        'key' => 'id',
        'navi-control' => 'step-hide',
        'before-move-nextstep'=>'doAfterLastSelection'
    ),
),

 他に、コンテキスト定義ではnavi-titleキーによるタイトルの指定が可能です。ただし、spanなどの要素で、「data-im="_@navi_title"」という属性が指定されたものをページ内のどこかに記述する必要があります。

ページファイルの記述内容

 ページファイルは定義ファイルと同じフォルダーにあるstep_MySQL.htmlという名前のフォルダーです。ソースコードをWebで参照するにはこちらをクリックしてください。リスト5-7-2に、定義ファイルで定義した4つのコンテキストを展開する部分を含む、ページファイルのボディ部を示しました。それぞれの展開部分は、特別なものはなく、単にフィールドをセルの値としているだけです。なお、データベース処理については、後でまとめて説明をします。ここで、ステップ動作に必要な準備は、「戻る」ボタンの確保です。リストの最初の方に、class属性が「IM_Button_StepBack」のspanタグがあります。タグの種類はおおむね何でもよく、class属性に必ず前述の名前を指定します。それが、ページ内の見えている場所に配置されていれば構いません。通常はひとつの要素だけで十分と思われますが、複数あってもかまいません。

リスト5-7-2 ページファイル内でのページ合成を行う部分
<div id="header">
    <span class="IM_Button_StepBack">◀︎</span>
    郵便番号検索
</div>
<div id="content">
    <table class="stepbox">
        <tbody>
        <tr>
            <td><span data-im="prefecture@pref"></span></td>
            <td class="accessary"></td>
        </tr>
        </tbody>
    </table>
    <table class="stepbox">
        <tbody>
        <tr>
            <td><span data-im="city@city"></span></td>
            <td class="accessary"></td>
        </tr>
        </tbody>
    </table>
    <table class="stepbox">
        <tbody>
        <tr>
            <td><span data-im="town@town"></span></td>
            <td class="accessary"></td>
        </tr>
        </tbody>
    </table>
    <table class="stepbox">
        <tbody>
        <tr>
            <td><span data-im="wrapup@f3"></span></td>
        </tr>
        <tr>
            <td><span data-im="wrapup@f7"></span></td>
        </tr>
        <tr>
            <td><span data-im="wrapup@f8"></span></td>
        </tr>
        <tr>
            <td><span data-im="wrapup@f9"></span></td>
        </tr>
        </tbody>
    </table>
</div>

画面遷移時に呼び出されるメソッド

 これまでの設定で、navi-controlの値がstepおよびstep-hideをもつコンテキストを展開したページの一部分が切り替わり表示できるようになっています。しかしながら、切り替わり前に、何か処理をしたいことが多いでしょう。サンプルでは、例えば、市区町村に「中野区」を選んだ時と、「渋谷区」を選んだ時では、町域名の検索条件が変わります。そうした処理などを組み込むために、コンテキスト定義に、before-move-nextstepキーを定義します。値はメソッド名を示す文字列です。例えば、prefectureコンテキストでは、「'before-move-nextstep'=>'doAfterPrefSelection'」という記述がありますが、これに対して、ページファイルのヘッダー部で、リスト5-7-3のようなメソッドを定義します。メソッドは、INTERMediatorOnPage変数のオブジェクトとして定義します。引数はありません。

リスト5-7-3 doAfterPrefSelectionメソッドの定義
INTERMediatorOnPage.doAfterPrefSelection = function () {
    var lastSelection = IMLibPageNavigation.getStepLastSelectedRecord()['pref'];
    INTERMediator.clearCondition('city');
    INTERMediator.addCondition('city', {field: 'f7', operator: '=', value: lastSelection});
};

 この例では返り値がありませんが、返り値によって、表5-7-1のように、画面遷移の動作をコントロールできます。したがって、最後の画面で何かの動作を組み込んだり、あるいはコンテキスト定義の順番とは関係のない任意のコンテキストを次に表示するなど、ナビゲーション自体を返り値でコントロールできます。

返り値動作
false画面遷移せず、現在のステップに留まる
nullコンテキスト定義の順番で決まる次のステップに画面遷移する(returnなしの場合もこれに相当する)
文字列文字列で指定したnameキーを持つコンテキストに対応した画面に遷移する
表5-7-1 before-move-nextstepキーで指定するメソッドの返り値

 コンテキスト定義内には、表5-7-2に示すキーで、メソッド名を定義できます。いずれも、INTERMediatorOnPage変数のオブジェクトとして、キーに対する値を名前に持つメソッドを定義します。before-move-nextstepキーのメソッドは前に示すように返り値により動作を定義できますが、残り2つのキーに対するメソッドは、引数も返り値も不要です。例えば、あるステップだけ、特定のフッターが必要な場合には、あらかじめdisplayスタイルをnoneにしておいて画面には見えないようにしておき、just-move-thisstepキーの値の名前のメソッドでフッターを表示します。また、just-leave-thisstepキーの値の名前のメソッドでフッターを非表示にします。

コンテキスト定義でのキーメソッドの呼び出しタイミング
before-move-nextstepセルをタップしたとき
just-move-thisstepコンテキストのページ合成を終えた直後
just-leave-thisstep次のコンテキストに移行する直前
表5-7-2 コンテキスト定義で指定できるステップ動作関連のメソッド

 before-move-nextstepキーで指定するメソッド内では、現在のステップや、それ以前のステップで選択した項目など得るために、以下の変数や、メソッドを利用することができます。

IMLibPageNavigation.stepNavigation

 ステップ動作で選択したそれぞれのセルが順番に入力された配列。最後の要素が、今表示されている画面での選択結果となる。要素は、key、contextの2つのプロパティを持つオブジェクトである。contextは、そのステップで利用されたコンテキストオブジェクト(IMLibContextクラス)を参照するので、選択したデータはもちろん、関連するフィールドや他のレコードを含めて参照できる。keyは、選択したレコードのキーで、「主キーフィールド名=主キー値」の形式を持つ。コンテキストのstoreプロパティで、keyの値をキーにすると、選択したレコードが取り出せる。

IMLibPageNavigation.getStepLastSelectedRecord()

 現在のコンテキストで選択したレコードを得る。

 なお、ページの冒頭にスタティックな内容を表示して、ボタンをクリックすればステップ動作が始まるようにしたい場合には、全てのコンテキストのnavi-controlをstep-hideにして、以下のメソッドを実行して、ステップ動作のきっかけを作ることができます。

IMLibPageNavigation.startStep()

 コンテキスト定義でのnavi-controlキーの値が全部step-hideの場合、このメソッドを実行することで、コンテキスト定義で最初にnavi-controlキーを持つコンテキストに対応する画面がページ上に表示する。

 ステップ動作を自分でコントロールしたい場合には、次のようなメソッドを利用できます。いずれも、ボタンをタップした場合に、特別な処理をするような場合に利用できるでしょう。

IMLibPageNavigation.backToPreviousStep()

 「戻る」ボタンと同等な処理を行う。なお、複数ステップを戻るには、このメソッドを必要回数指定する。

IMLibPageNavigation.moveNextStep(key)

 セルをタップしたのと同じくステップを進める処理を行う。この時、引数keyが、IMLibPageNavigation.stepNavigationの要素のkeyプロパティに設定される。

 例えば、ボタンをタップしたら、別のステップに進みたいとします。ボタンのonclick属性に指定した関数で、「IMLibPageNavigation.moveNextStep("buttontapped");」のように、moveNextStepメソッドを呼び出します。引数は適当な文字列ですが、レコードを選択したときには通常は「主キー=値」の形式になるものの、ここでは確実にレコードを選択したときと異なる値になるようなkeyプロパティを選んであります。そして、before-move-nextstepキーで指定したメソッド内で以下のように、直近の選択結果のkeyプロパティがmoveNextStepメソッドと同じかどうかを判定して、ボタンから次のステップに移動するのか、セルをタップするのかを判別することができます。

リスト5-7-4 moveNextStepメソッドでのステップ移動をbefore-move-nextstepキーで指定したメソッド内で判定する
var lastKey = IMLibPageNavigation.stepNavigation[IMLibPageNavigation.stepNavigation.length - 1].key;
if (lastKey === 'buttontapped') {
    /* ボタンをタップした場合の処理 */
} else {
    /* セルをタップした場合の処理 */
}

サンプルでのデータベースアクセス

 ここでまず、データベースへのアクセスがどのようになっているのかを説明します。利用するテーブルは他のサンプルでもおなじみの、postalcodeです。日本郵便が配布しているデータで、f3フィールドが郵便番号、f7フィールドが都道府県名、f8フィールドが市区町村名、f9フィールドが町域名を示しています。また、idフィールドに連番が入力されていて、このフィールドが主キーになります。表5-7-3は、4つのコンテキスト定義で指定されている値で合成されるSQLステートメント

コンテキスト名基本のSQLステートメント
prefectureSELECT MIN(id) AS pref_id, f7 AS pref FROM postalcode GROUP BY f7
citySELECT MIN(id) AS city_id, f8 AS city FROM postalcode GROUP BY f8
townSELECT MIN(id) AS town_id, f9 AS town FROM postalcode GROUP BY f9
wrapupSELECT * FROM postalcode
表5-7-3 それぞれのコンテキストで実行される基本のSQLステートメント

 まず、最初のprefectureコンテキストでのデータベースアクセス結果を検討しましょう。表5-7-3にあるようにSQLステートメントを実施します。都道府県はf7フィールドで得られますが、単に取ってくるだけだと、大量に「東京都」が出てきてしまいます。ここで、DISTINCTを使ったSELECT文も考えられるのですが、主キーフィールドを設定したいと考えます。この時、f3=東京都のレコードはたくさんありますが、ひとつのidフィールドの値を、コンテキストで得られるリレーションの主キーにするために、f7フィールドが同じレコードをGROUP BYでひとつにまとめるとともに、その中のidフィールドのうち、MIN関数で最小のものを取り出しています。仮にidが1から1000まで全部が東京都のレコードだったとします。その時、id=1なのか、id=2なのか、id=333なのかは、現実にはどれでもいいのです。ただし、f7フィールドしか参照しないというルールが守られていれば、idその中のひとつでいいので、ここでは最小値を取ってきています。prefectureコンテキストでは、appending-dataキーもあるので、結果的には、表5-7-4のようなリレーションが得られます。

pref_idpref注釈
1東京都SQL文で得られた結果
101埼玉県appending-dataキーで追加された結果
102神奈川県appending-dataキーで追加された結果
103千葉県appending-dataキーで追加された結果
表5-7-4 prefectureコンテキストで得られるリレーション

 ここで「東京都」をタップしたとします。すると、リスト5-7-4に示したINTERMediatorOnPage.doAfterPrefSelectionメソッドがスタートします。「IMLibPageNavigation.getStepLastSelectedRecord()['pref'];」の実行により、「東京都」のレコードのprefフィールドの値、つまり「東京都」という文字列が得られ、lastSelection変数にセットされています。そして、次のコンテキストcityに対して、「f7 = '東京都'」という検索条件が追加され、つまりは、「SELECT MIN(id) AS city_id, f8 AS city FROM postalcode WHERE f7 = '東京都' GROUP BY f8」というSQLステートメントが実施されます。結果的には、表5-7-5のようなリレーションが得られます。ここでも、同一のf8フィールドでグループ化して、その中の最小のidフィールドの値を利用して、主キーのcity_idを求めています。

city_idcity
1千代田区
447中央区
605港区
表5-7-5 cityコンテキストで検索条件「f7 = '東京都'」を付与して得られるリレーション

 cityやtownのコンテキストでセルをタップしたときに呼び出すメソッドは、prefectureコンテキスト同様に、次のコンテキストに検索条件を加えるものです。具体的には、ソースコードを参照してください。

画面に固定されたヘッダーとフッター

 まず、モバイルブラウザーでの縮小処理が行われないように、ページファイルのヘッダー部に「<meta name="viewport" content="initial-scale=1"/>」というタグを記述します。必要に応じて、ほかの記述も加えます。

 モバイルアプリケーションの特徴である、固定されたヘッダーやフッターは、CSSの機能を利用します。サンプルでは、ページファイル内にスタイルシートを記述しました。ヘッダーとフッターに関連する部分を、リスト5-7-5に示しました。まず、画面全体は、id=containerで囲まれています。その中に、id=headerのヘッダー、スクロールするテーブルのid=contentのブロック、そしてフッターに相当するのはid=IM_CREDITの要素です。container内部はdisplayをflexにして、固定値に配置されるようにしています。id=contentはdivタグで、その中にtableタグのテーブルがあります。そこでスクロールされるように、overflowの値をscrollにしています。

リスト5-7-5 ページファイル内に記述されたスタイルシートの一部
#container {
    display: flex;
    flex-direction: row;
}

#header {
    width: 100%;
    text-align: center;
    background-color: #2a2780;
    color: white;
    font-size: 160%;
    padding: 8px 0;
}

#content {
    overflow: scroll;
}

#IM_CREDIT {
    width: 100%;
    white-space: nowrap;
}

 しかしながら、CSSの定義だけでは画面にきっちりと配置はされません。画面の高さに応じて、id=contentの高さを調整することで、ヘッダーとフッターが画面上下のぴったりとした位置に配置されます。そのために、リスト5-7-6のようなプログラムをページファイル内に記述しました。ページ合成後や、デバイスを回転させた後に関数adjastObjectsを呼び出しています。そして、ヘッダー、フッター、画面の高さから、id=contentの高さを求めて設定をしています。なお、このプログラムは、ページデザインを変更した場合など、ヘッダーやフッターの状況によって作り変えが必要になります。

リスト5-7-6 id属性がcontentの高さを調整するプログラム
INTERMediatorOnPage.doAfterConstruct = function () {
    document.getElementById('container').style.display = "block";
    adjastObjects();
};

window.addEventListener("orientationchange", function () {
    adjastObjects();
});

function adjastObjects() {
    var headerNode = document.getElementById('header');
    var footerNode = document.getElementById('IM_CREDIT');
    var wHeight = screen.availHeight;
    var stepBoxHeight = wHeight - headerNode.clientHeight - footerNode.clientHeight;
    document.getElementById('content').style.height = stepBoxHeight + "px";
}

そのほかのスタイル設定

 リスト5-7-7も、ページファイル内に定義したスタイルシートです。まず、stepboxクラスは、tableタグに適用されており、テーブルを画面はばいっぱいに表示するとともに、余計なマージンが設定されないようにして、空白なくヘッダーや画面左右の境界までレイアウトされています。セルについては、高さを32pxにするとともに、フォントサイズを大きめにしました。また、セルの右にある▶︎は、このようにセルを分離してクラスaccessaryを設定し、CSS属性でキャラクタを表示しています。最後には、ヘッダーの左端にある「戻る」ボタンのクラスであるIM_Button_StepBackに対しての設定があります。ヘッダー内で固定位置に配置されるように、positionをabsoluteにして、座標位置や幅を数値で与えています。

リスト5-7-7 そのほかのスタイル設定
.stepbox {
    width: 100%;
    margin: 0;
}

td.accessary {
    width: 20px;
    text-align: right;
}

td.accessary::after {
    content: "︎▶";
    color: #9b9b9b;
}

td {
    height: 32px;
    font-size: 130%;
}

.IM_Button_StepBack {
    position: absolute;
    top: 8px;
    left: 4px;
    width: 40px;
    cursor: pointer;
    color: #9393ee;
}

演習入力のあるステップ動作のページ

 ステップ動作を行うページでは、表示されたものの選択をすることが一般的な使い方ですが、さらに、テキストエリアなどの入力可能なコンポーネントがある場合のサンプルを演習で作ってみます。ここまでに説明したサンプルでは、選択結果はそのままではデータベースにバインドされていないので、もし、データベースに反映させるとしたら、JavaScriptで書き込みを行うようなプログラムが必要でした。同様に入力可能なコンポーネントがあっても直接バインドはできませんので、ローカルコンテキストにバインドして、必要な時にプログラムで入力した値を得られるようにします。なお、この演習は、FileMakerではできません。

定義ファイルエディターを開き最初のコンテキストを入力

1ここからの作業は、Webブラウザー上で行います。まず、INTER-MediatorがインストールされたVMを、VirtualBoxを使用して起動します(『1-2 演習を行うための準備』を参照)。続いて、ブラウザーで、「http://192.168.56.101」に接続します。「トライアル用のページファイルと定義ファイル」というタイトルの部分を特定します。
2「def16.phpを編集する」をクリックし、定義ファイルエディターでdef16.phpファイルを編集します。(もし、他の用途で16番目を利用しているのなら、例えば、def21.phpを利用するなど、別の番号のセットを使用してください。その場合ソースコードの記述が変わる部分がありますが、可能な限り注記します。)
3ページ上の「Show All」ボタンをクリックして、全ての項目を表示します。
4Contextsの中のQueryと書かれた背景がグレーの部分を特定します。そして、その次の行の右の方にある「削除」をクリックして、Queryの設定がある行を削除します。
5「レコードを本当に削除していいですか?」とたずねられるので、OKボタンをクリックします。
6同様に、Sortingの次の行にある「削除」ボタンを押し、確認にOKボタンをクリックして、こちらの設定も削除しておきます。
7nameを「citylist」、tableを「dummy」、viewを「postalcode」、keyを「city_id」、pagingを空欄、repeat-controlを空欄、navi-controlを「step」、recordsを「10000」、maxrecordsを「10000」、before-move-nextstepを「doAfterCitySelection」とします。
さらに、Aggregation Query Accessにあるselectを「MIN(id) AS city_id, f8 AS city」、fromを「postalcode」、group-byを「f8」とします。

2つ目のコンテキストを入力

1引き続き定義ファイルエディターでの作業を続けます。Contextsのすぐ下にある「追加」ボタンをクリックして、新たにコンテキスト定義を追加します。
2nameを「opinion」、tableを「dummy」、viewを「postalcode」、navi-controlを「step-hide」、recordsを「1」、maxrecordsを「1」、before-move-nextstepを「doAfterOpinion」とします。その他は空欄にします。
3Database Settingsにあるdb-classは「PDO」のままでかまいません。dsnには「mysql:host=localhost;dbname=test_db;charset=utf8mb4」と入力します。そして、userに「web」、passwordに「password」と入力します。
4Debugについては、「false」にすると、デバッグ情報が出なくなります。なお、デバッグ情報をみながら動作を確認したい方は、「2」のままにしてこの後の作業を行っても構いませんが、ブラウザーをモバイルシミュレーション動作させるとページ上でのデバッグ情報の参照ややりにくくなるので、コンソール等で参照してください。

ページファイルの修正

1「http://192.168.56.101」で開いたページに戻り「page16.htmlを編集する」をクリックし、ページファイルのpage16.htmlを編集するページファイルエディターが開きます。(別の番号のセットで作業している場合には、該当する番号のリンクをクリックしてください。)
2最初にヘッダー部に、metaタグの要素をひとつ追加します。
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="initial-scale=1">
    <title></title>
    <script type="text/javascript" src="def16.php"></script>
3ヘッダー部の、def16.phpを含むscriptタグの次に、以下のプログラムをscriptタグで囲んで記載します。なお、最初の方は、サンプルプログラムのページファイルにあるものと同一ですので、そちらからコピーしてペーストしましょう。後半は入力します。太字の部分は入力、それ以外の部分はサンプルファイルからのコピー&ペーストを行います。
citylist、opinionのそれぞれのコンテキストで定義されたbefore-move-nextstepキーの値がメソッド名になります。INTERMediatorOnPage変数のオブジェクトに、そのメソッドを定義します。citylistでのタップにより、その時にcity_idフィールド値を次のopinionコンテキストの検索条件に設定しています。opinionコンテキストは最後のページなので、遷移しないようにfalseを返しています。
<script>
    INTERMediatorOnPage.doAfterConstruct = function () {
        document.getElementById('container').style.display = "block";
        adjastObjects();
    };

    window.addEventListener("orientationchange", function () {
        adjastObjects();
    });

    function adjastObjects() {
        var headerNode = document.getElementById('header');
        var footerNode = document.getElementById('IM_CREDIT');
        var wHeight = screen.availHeight;
        var stepBoxHeight = wHeight - headerNode.clientHeight - footerNode.clientHeight;
        document.getElementById('content').style.height = stepBoxHeight + "px";
    }

    INTERMediatorOnPage.doAfterCitySelection = function () {
        var lastSelection = IMLibPageNavigation.getStepLastSelectedRecord()['city_id'];
        INTERMediator.clearCondition('opinion');
        INTERMediator.addCondition('opinion', {field: 'id', operator: '=', value: lastSelection});
    };

    INTERMediatorOnPage.doAfterOpinion = function () {
        return false;
    };
    
    function finishSurvey() {
        IMLibQueue.setTask(function(completeTask) {
            var opinion = IMLibLocalContext.getValue('opinion');
            var selkey = IMLibPageNavigation.stepNavigation[0].key;
            var city = IMLibPageNavigation.stepNavigation[0].context.store[selkey]['city'];
            alert('市区町村: ' + city +'\nご意見: ' + opinion);
            completeTask();
        });
    }
</script>
4ヘッダー部の、末尾に以下のスタイルシートをstyleタグで囲んで記載します。なお、最初の方は、サンプルプログラムのページファイルにあるものと同一ですので、そちらからコピーしてペーストしましょう。後半は入力します。太字の部分は入力、それ以外の部分はサンプルファイルからのコピー&ペーストを行います。
ボタンとテキストフィールドの枠線が消えてしまうので、そのためのスタイルを追加しました。
<style>
            #container {
            display: flex;
            flex-direction: row;
        }

        #header {
            width: 100%;
            text-align: center;
            background-color: #2a2780;
            color: white;
            font-size: 160%;
            padding: 8px 0;
        }

        #content {
            overflow: scroll;
        }

        .stepbox {
            width: 100%;
            margin: 0;
        }

        td.accessary {
            width: 20px;
            text-align: right;
        }

        td.accessary::after {
            content: "︎▶";
            color: #9b9b9b;
        }

        td {
            height: 32px;
            font-size: 130%;
        }

        #IM_CREDIT {
            width: 100%;
            white-space: nowrap;
        }

         .IM_Button_StepBack {
            position: absolute;
            top: 8px;
            left: 4px;
            width: 40px;
            cursor: pointer;
            color: #9393ee;
         }
    
    textarea {
        margin: 2px;
        padding: 2px;
        border: 1px solid gray;
    }
    
    button {
        margin: 2px;
        padding: 2px;
        border: 1px solid gray;
  }
</style>
5ボディ部は以下のように記述します。bodyタグ以外は全て手入力する必要があります。
<body>
<div id="container" style="display: none">
    <div id="header">
        <span class="IM_Button_StepBack">◀︎</span>
        アンケート
    </div>
    <div id="content">
        <table class="stepbox">
            <thead>
              <tr><td colspan="2">住んでみたい市区町村を選択してください。</td></tr>
            </thead>
            <tbody>
            <tr>
                <td><span data-im="citylist@city"></span></td>
                <td class="accessary"></td>
            </tr>
            </tbody>
        </table>
        <table class="stepbox">
            <tbody>
            <tr>
                <td>
                  <span data-im="opinion@f7"></span>
                  <span data-im="opinion@f8"></span>
                  を選択しました
              </td>
            </tr>
            <tr>
                <td>
                  この市区町村に関する感想を書いてください<br>
                  <textarea data-im="_@opinion" style="width: 90%; height: 80px;"></textarea>
              </td>
            </tr>
            <tr>
              <td><button onclick="finishSurvey()">アンケート結果を送る</button></td>
            </tr>
            </tbody>
        </table>
    </div>
</div>
</body>
テキストエリアにあるdata-im属性は、ローカルコンテキストを利用することを示しています。ターゲット指定の最初の部分であるコンテキストの指定が「_」であれば、ローカルコンテキストです。

Chromeを利用してモバイルシミュレーションで稼働させる

1ここからの作業は、Chromeを使います。ここまで別のブラウザーで作業をしていて、ここからChromeを使うこともできます。「http://192.168.56.101」で開いたページに戻り、「page16.htmlを表示する」をクリックして、ページを開きます。
2デベロッパーツールを開きます。Macだと、command+option+I(アルファベットの「アイ」)、Windowsのだとctrl+shift+Iです。
3デベロッパーツールのElementsの左にあるモバイルツールボタンをクリックするなどして、画面をモバイルシミュレーションにします。必要に応じて、ツールの設定後に更新を行います。最初のページが表示され、市区町村の一覧が見えています。
4適当なセルをタップして、次の画面に移動します。テキストエリアが見ているので、適当に入力します。
5「アンケート結果を送る」ボタンをクリックすると、ダイアログボックスに、最初の画面での選択結果と、次の画面での入力値が表示されました。
ダイアログボックスで表示する部分は、finishSurvey関数です。ここで例えば、データベースに書き込むスクリプトを記述すれば、アンケート結果の保存ができるようになります。データベース処理の記述は、『6-3 データベースへの書き込みを直接行う』で説明しています。
テキストエリアをクリックするとセルが反応してしまうなど、いくつか動作上の問題点らしきものは見られます。これらは随時改良します。

演習のまとめ

このセクションのまとめ

 ステップ動作は、ひとつのコンテキストごとに画面に表示する機能で、ある種のモバイルアプリケーションでよく見られる形式であるとも言えます。画面の構築はもちろん、タップ後の画面遷移もコンテキスト定義に定義されている順序で順番に行われます。また、戻るボタンの動作も自動的に行われます。タップ時には定義したメソッドを呼び出せるので、そこでさまざまな処理を記述できますし、遷移をやめたり順序と関係ない別のコンテキストに遷移したり、さまざまな動作を実装できます。ただし、JavaScriptのプログラミングが必要になります。