Chapter 5
さまざまなユーザーインターフェース構築
このセクションでは、定義ファイルやページファイルの設定だけで可能な機能のうち、これまでに説明していない機能について説明をします。最初は一覧と詳細を行き来するユーザーインターフェース、続いて電子メールの送信、異なるクライアント間で編集結果を連動させる方法、JavaScriptで作られたユーザーインターフェース部品を利用する方法を説明します。
5-1マスター/ディテール形式のナビゲーション
一覧と詳細を切り替えて表示するようなユーザーインターフェースはよくみられます。業務系システムでは多くの場合、こうした動作が基本です。INTER-Mediatorではこうした動作を2つのコンテキストで実現して自動的に切り替えるユーザーインターフェースを用意します。そこまでの作業は、特別なプログラムコードを書かなくても、定義ファイルとページファイルの設定だけで可能です。また、一覧と詳細の切り替え時にプログラムを記述すれば、より高度なユーザーインターフェース構築も可能です。
マスター/ディテールあるいは一覧/詳細
データを一覧表示し、さらに特定のレコードについてより多くの情報を表示するといった形式のユーザーインターフェースは一般的なテクニックです。その場合、異なる画面であるだけに、2つのページを作成しておき、それぞれの機能動作を組み込むということが一般的かもしれません。しかしながら、INTER-Mediatorでは、ひとつのページに一覧表示のためのコンテキストと、詳細表示のためのコンテキストを両方を用意して、切り替えることなどが可能です。
iOSには、UISplitViewControllerというクラスがあり、iPadの「メール」などにみられるように、一覧と詳細を同時に、あるいは個別に表示できる仕組みがあります(図5-1-1)。INTER-Mediatorの機能は、このスプリットビューをヒントにしています。
FileMakerを利用するときに、レイアウトを2種類作成し、同じTOを2つのレイアウトに割り当てることで、一覧(図5-1-2)と詳細表示(図5-1-3)の切り替えがスムーズに行われます。切り替えるために、なんらかのスクリプトは必要ですが、一覧で選択した結果は、TOで記録されるので、レイアウトを切り替えるだけで、一覧で選択されているレコードを詳細レイアウトでも表示することができます。
これらのユーザーインターフェースを本コースでは、「マスター/ディテール形式」あるいは「一覧詳細形式」と総称することにして、「一覧表示側」「詳細表示側」という用語で、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キーは記述するとしたら、一覧表示側のコンテキストに指定をしてください。
一覧表示側 | 詳細表示側 |
---|---|
master | detail |
master-hide | detail-top |
detail-bottom | |
detail-update |
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タグ要素 |
一覧と詳細の切り替え時に呼び出されるメソッド
一覧表示と詳細表示で、コンテキスト内のリンクノードについては、データベースの内容がそれぞれ表示されますが、それ以外のなんらかの処理を追加したい場合には、いくつかのメソッドを利用することができます。少ない作業で確認ができるので、このセクションの演習で実際にプログラムを追加して動作を紹介しておきます。
演習一覧と詳細を利用したユーザーインターフェース
iPadのようなユーザーインターフェースや、一覧と詳細が切り替わるユーザーインターフェースを実際に作成してみましょう。また、JavaScriptを利用した高度なカスタマイズも紹介します。
2つのコンテキストを定義ファイルに定義
viewとtableは「person」とします。
viewとtableは「person_layout」とします。
viewとtableは「person」とします。
viewとtableは「person_layout」とします。
db-classは「PDO」のままでかまいません。dsnに「mysql:host=localhost;dbname=test_db;charset=utf8mb4」と入力します。そして、userに「web」、passwordに「password」と入力します。
db-classを「FileMaker_FX」に書き換えます。databaseは「TestDB」、userに「web」、passwordに「password」、serverに「192.168.56.1」、portに「80」、protocolに「http」、datatypeに「FMPro12」と入力します。
ページファイルの作成と表示
<!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>
<body>
<div id="IM_NAVIGATOR"></div>
<table style="float: left; margin-right: 20px;">
<tr>
<td>
同一ページでのマスター/ディテール形式のユーザーインターフェース
<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>
一覧表示側に対して作用するページネーション
レコード削除に対する詳細側の動作
<table>
<tr>
<td>
<div data-im="person_list@name"></div>
<div data-im="person_list@mail"></div>
</td>
<td></td>
</tr>
</table>
一覧と詳細が切り替わるユーザーインターフェース
<tr><th>memo</th>
<td><textarea data-im="person_detail@memo"></textarea></td>
</tr>
</table>
<br clear="all"/>
</body>
</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>
<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>
演習のまとめ
- コンテキスト定義のnavi-controlに「master」「detail」をそれぞれ同一のテーブルから定義した2つのコンテキストに記述することで、一覧と詳細を表示するユーザーインターフェースが作成できます。
- 一覧および詳細のコンテキストでは、その他の設定にも注意を払う必要がありますが、通常、一覧は複数レコードを表示、必要に応じてページネーションと連動させます。詳細は1レコードを表示するようにします。
- 「master-hide」を指定すると、一覧と詳細が切り替わるユーザーンタフェースを作成できます。
- 一覧から詳細、あるいは詳細から一覧に移動するときに、作成しているページ側で定義したプログラムを実行することができます。これを利用して、例えば「master-hide」を利用しているときに、一覧と詳細で異なる見出しを表示することなどが可能です。
一覧と詳細の切り替え時に呼び出されるメソッド
以下、マスター/ディテール形式のページにおいて使用可能な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-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メソッドで、プロパティ名の配列を得て、その最初の要素から、主キーフィールド名とその値を分離して得ています。
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 | 新たなレコードを作るアクションを起こした後にメールを送信する |
設定の上で若干柔軟性が低いと思われるかもしれません。例えば、フィールドAを更新したときだけメールを出したいといった場合です。そのようなときには、フィールドAの更新を行うときのコンテキストを新たに定義し、そこにメール送信の設定を行います。そして、フィールドAの更新を、例えばボタンを押して行うなどして、ボタンを押したときに新たなコンテキストの更新処理をJavaScriptで記述するという手法を使います。このように、メールの送信のための別のコンテキストを用意するといった手法で、柔軟にメール送信の仕組みが組み立てられると同時に、条件設定的な複雑な設定やプログラムを導入することなくメール送信が利用できます。
メールの内容に関する設定
あるコンテキストで、新規にレコードを作ったときにメールを送るのであれば、send-mailキーのnewキーの値にさらに連想配列を定義して、その連想配列を、表5-2-2に示すキーの要素を追加します。表にあるすべてのキーを設定する必要はありませんが、送信者と送信先、そして本文の3つはなんらかのキーで指定は必要です。
キー | 値に設定する内容 |
---|---|
from-constant | 送信者やアドレスを文字列で指定 |
to-constant | 送信先を文字列で指定 |
cc-constant | Cc先を文字列で指定 |
bcc-constant | Bcc先を文字列で指定 |
subject-constant | 件名を文字列で指定 |
body-constant | 本文を文字列で指定 |
from | 送信者名や送信者アドレスが含まれるフィールド名 |
to | 送信先が含まれるフィールド名 |
cc | Cc先が含まれるフィールド名 |
bcc | Bcc先が含まれるフィールド名 |
subject | 件名が含まれるフィールド名 |
body | メール本文が含まれるフィールド名 |
body-template | 本文のテンプレートとなるファイルのファイル名 |
body-fields | テンプレートに差し込むフィールドの順序をカンマで区切る |
f-option | UNIXでSMTPサーバーを経由しない場合にtrueを指定すると、fromの指定が有効 |
body-wrap | 右端の折り返しのバイト数(指定がないと72バイト)。0だと折り返ししない |
メールの作成方法は、この後に演習を通じていくつかの事例を示しながら解説をします。表の中のf-optionは、UNIXマシンのsendmailコマンドを使ってメールを送るときに、送信者を指定したのにもかかわらず、送信者が、www(あるいはApacheの稼働ユーザー)になってしまうようなときに指定をしてください。OSに組み込まれているメール送信コマンド等の動作に依存しますが、より確実に送信者の指定ができるはずです。
body-wrapは、長い行の折り返しを、Shift-JISのバイト数で指定します。なお、折り返しを入れますが、比較的追い込みを積極的に行うアルゴリズムを組み込んであります。
演習Post Onlyモードのページでメールを送信する
Post Onlyモードでは、アンケートなどで使われることが多いと思われます。そのとき、投稿した上で、確認のメールを出すということはよく行われます。そうした場面を想定して、3種類のメールの送信方法を紹介します。
コンテキストを定義ファイルに定義
db-classは「PDO」のままでかまいません。dsnに「mysql:host=localhost;dbname=test_db;charset=utf8mb4」と入力します。そして、userに「web」、passwordに「password」と入力します。
db-classを「FileMaker_FX」に書き換えます。databaseは「TestDB」、userに「web」、passwordに「password」、serverに「192.168.56.1」、portに「80」、protocolに「http」、datatypeに「FMPro12」と入力します。
Post Onlyモードのページファイルの作成
<!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>
送信者、送信先、本文がすべて一定のメール
以下の演習では、2つのメールアドレスを使用します。それぞれ、「メールアドレス1」「メールアドレス2」と記述します。手順では筆者の新居が所持する2つのメールアドレスが書かれていますが、そのまま記述しても、皆さんのお手元にメールは届きません。ご自分のアドレスに置き換えてください。もし、メールアドレスをひとつしか持っていない場合には、そのひとつのアドレスを記載してください。
データベースから得られた宛先を送信者にする
メール本文にテンプレートを使用する
Finderで、「移動」メニューから「サーバーへ接続」を選択し、サーバーアドレスとして、「smb://192.168.56.101」を指定して、「接続」ボタンをクリックします。その後に、ユーザー名「developer」、パスワード「im4135dev」でログインをして「webroot」という共有ディレクトリを指定します。
エクスプローラーのアドレスの枠に「¥¥192.168.56.101¥webroot」と入力して、Enterキーを押します。ユーザー名「developer」、パスワード「im4135dev」でログオンします。
演習のまとめ
- Post Onlyモードを利用してレコードを新規作成するときに、メールを送信する方法を演習を通じて学びました。
- 送信者と件名は固定でしたが、宛先や本文を固定あるいはフィールドの値を利用する方法を説明しました。
- 本文をテンプレートから抜き出してきて差し込む方法を学びました。本文をテンプレートとなるテキストファイルに記述する方法が実用的な意味で汎用性が高く、さまざまな文面を手早くシステムに組み込めて便利に使えるでしょう。
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 | メール送信時に認証で使用するパスワード |
定義ファイルエディターで実際に設定する場合は、ページの冒頭にある「Show All」ボタンをクリックして、全項目を表示します。すると、図5-2-1のように、Optionsの最後に設定項目が表示されるようになります。定義ファイルへの設定を直接行う場合は、リスト5-2-1にあるように、オプション領域に記述を行います。
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サーバーへ中継する。
- サーバーにメール送信機能はあるが送られない。その場合はサーバーの動作で何か設定が必要かもしれない。あるいはどこかでファイアウォールによってSMTP通信が遮断されていることが多い。
- 文字化けする。その場合は定義ファイルやテンプレートのファイルがUTF-8でエンコードされているかどうかを確認する。
- 送信者が指定通りにならない。その場合は、UNIX系OSなら、f-optionキーの値を「true」にする。
- SMTPサーバーを指定したが送られない。原因は別掲します。
SMTPやあるいは認証のトラブルはさらに複雑な設定が絡みます。うまく行かない場合には、以下のような原因が考えられます。
- サーバー名がまちがっている。あるいはSSLないしはTLSなのに、URLにホスト名しか記述していない。Gmailは「ssl://smtp.gmail.com」です。
- INTER-Mediatorのサーバーと、SMTPサーバーの間で、利用するポートの通信ができるのかどうかをよく確認する。
- ポート番号が違っている。Gmailの送信機能は465です。そのほか、25なのか、587なのか、よく確認する。
- SMTP認証の場合、PLAIN方式をサポートしているかどうかを確認する。
- ユーザー名、パスワードが違っている。正しいと思っている方こそ、絶対に違っています!と言い切れるほど、この手の間違いは多い。Gmailのユーザー名は、@を含むメールアドレス全てがユーザー名です。
- SMTPサーバーが送信者の制限を行っている。例えば、一般にプロバイダーは、契約しているドメインの送信者のメールしか送れません。任意のアドレスを送信者にできないのが一般的です。
- あなたの利用しているSMTPサーバーがブラックリストに入っている。未だにけっこう無意味な「怪しいホスト名リスト」にあるメールを拒否する設定をしている会社が、いくらかはあります。これは、送信できているけれども、受け入れてもらえないだけです。INTER-Mediatorやあるいは関連ライブラリの問題ではありません。
- Gmailでは、自分のアカウントのアカウント情報にある「安全性の低いアプリケーションのアクセス」の設定を「有効」にします。多くの場合、この設定が「無効」になっており、外部からのSMTPサーバー利用はできない状態です。Googleのサイトでも記載されています。
このセクションのまとめ
コンテキストを通じて、データベースから検索した後、データベースの内容を更新した後、新しいレコードを作成した後に、メールを送信することができます。メールの宛先や本文などを定義ファイルで指定できます。メールの本文や宛先は、データベースから得られたレコードのフィールドの値を利用できます。メールの本文をテンプレートとしてテキストファイルで用意して、そこにフィールドを埋め込むといったメールの作成方法も可能です。メールの送信には、同一サーバーにある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のアカウントおよびそこで作成したAppのコード
- ページファイルでのPusherライブラリの読み込み
- サーバー側でPHPライブラリのインストール
- 定義ファイル等でのPusher Appのコードの設定
- 管理用テーブルの作成(決められたテーブル名およびフィールド名)
定義ファイルでの設定
Pusherでのサービスを利用するためには、そのサービスの利用のためのコードを、INTER-Mediatorで作成するアプリケーションに記述する必要があります。定義ファイルのIM_Entry関数の第2引数に、puhserというキーで連想配列を記述します。それぞれ、Pusherが指定するapp_id、key、secretを同名のキーとともに記述すれば、INTER-MediatorはPusherの利用を開始します。定義ファイルのPHP記述では、リスト5-3-1のようになります。
IM_Entry(
array(
/* コンテキストの定義 */
),
array(
:
'pusher' => array(
'app_id' => '1234',
'key' => '9876543210',
'secret' => '9876543210',
),
),
array('db-class' => 'PDO', ....),
false
);
定義ファイルで設定する場合は、図5-3-1のように、Optionsのところに設定項目があります。この設定項目は、Show Allボタンをクリックすることで表示され、通常は表示されていません。
管理用テーブルの作成
INTER-Mediatorはどのクライアントにどのテーブルのデータが配布されているのかを、データベースに記録します。そのために表5-3-1のようなテーブルが必要になりますので、アプリケーションでクライアント間同期の機能を利用する場合には、これらと同一名および同一フィルードを持つテーブルを作成してください。PDO対応のデータベースエンジンの場合は、INTER-Mediator/dist-docsファイルにあるサンプルデータベースのスキーマ(sample_schema_*.txtファイル)に、CREATE TABLE等で記述されたSQLがあるので、それを利用できます。なお、registeredcontextテーブルのidフィールドと、registeredpksテーブルのcontext_idフィールドでリレーションシップが設定されています。
テーブル名 | フィールド名 | 型 |
---|---|---|
registeredcontext | id | INT AUTO_INCREMENT, |
clientid | TEXT | |
entity | TEXT | |
conditions | TEXT | |
registereddt | DATETIME | |
registeredpks | context_id INT | |
pk | INT |
演習Pusherをアプリケーションに統合する
Pusherを利用する方法と、既存のアプリケーションに組み込む方法を説明します。組み込むアプリケーションは、『4-4 計算プロパティの設定』で作成したものです。フィールドの値の変更や、レコードの追加や削除ができれば統合できますが、このアプリケーションは、リレーションシップを適用したレコードが展開されています。リレーションシップで結合している内部のエンクロージャー/リピーターでも機能するところを確認するのが、このアプリケーションを選択した理由です。なお、INTER-Mediator-Server VMのデータベースには、同期を実現するために必要なテーブルの定義はすでにできているので、この演習ではその作業は不要ですが、作成するアプリケーションではテーブルの定義が必要になることが一般的でしょう。
Pusherに入会する
Pusherに入会する方法をすべての手順は説明しません。サイト上で必要な情報を入力して、アカウントを取得してください。
PusherのAppを用意する
Pusherでは、リクエストと通知を出すためのひとつの領域を「App」と呼んでいます。原則として、ひとつのWebアプリケーションで、ひとつのAppを利用することを想定しています。
サーバーにPusherのライブラリを読み込む
サーバー上にはPusherのPHPのクラスとして定義されたライブラリが必要です。INTER-Mediator-VMには入っていませんが、INTER-Mediator-Server VMではないサーバーでのインストールも視野に入れてインストール方法を説明します。
「ターミナル」アプリケーションのウインドウで、「ssh developer@192.168.56.101 」とコマンドを入力し、Password:と表示されたら「im4135dev」とキータイプしてreturnキーを押します。
TeraTermやPuttyなどのアプリケーションを利用するか、Cygwinを利用するなどして、VMにssh接続することができます。
$ php -i | grep include_path
include_path => .:/usr/share/php:/usr/share/pear => .:/usr/share/php:/usr/share/pear
$ cd
$ git clone https://github.com/pusher/pusher-http-php.git
$ cd pusher-http-php/
$ cd lib
$ sudo cp Pusher.php /usr/share/php/
定義ファイルにPusherの設定を行う
『4-4 計算プロパティの設定』で作成したアプリケーションが、page09.htmlおよびdef09.phpで作成されたものとします。もし、異なる番号で作成されているのなら、該当する番号に読み替えてください。
ページファイルに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>
クライアントの動作を確認する
演習のまとめ
- 複数のクライアントで編集結果のリアルタイム同期を、Pusherというサービスをベースに実装してあります。サービスに申し込み、コードを取得して、そのコードを定義ファイルに記述します。
- INTER-Mediatorのアプリケーションにおいては、ページファイルにJavaScriptのライブラリの読み込みが必要です。
- INTER-Mediatorが稼働するサーバーにも、PHPで作られたライブラリの追加が必要です。
- INTER-Mediatorで利用するデータベースにはいくつかのテーブルが仕様に従って定義されている必要があります。
- これら、ソフトウェアの追加だけで、同期ができるようになります。
このセクションのまとめ
複数のクライアント間で、編集やレコード作成、削除の結果をリアルタイムに反映させる仕組みを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つのコンテキストを定義ファイルに定義
db-classは「PDO」のままでかまいません。dsnに「mysql:host=localhost;dbname=test_db;charset=utf8mb4」と入力します。そして、userに「web」、passwordに「password」と入力します。
db-classを「FileMaker_FX」に書き換えます。databaseは「TestDB」、userに「web」、passwordに「password」、serverに「192.168.56.1」、portに「80」、protocolに「http」、datatypeに「FMPro12」と入力します。
ページファイルの作成と表示
<!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>
演習のまとめ
- アダプターが用意されていれば、JavaScriptのコンポーネントは、data-im-widget属性を要素に記述するだけで利用できます。
- そのJavaScriptコンポーネントを利用するためのスタイルシート読み込みやSCRIPTタグによるプログラムの読み込みが必要です。これらは、定義ファイルの読み込みより先に記述するのが一般的です。
- 定義ファイルの読み込み後に、アダプターのJavaScriptファイルを読み込みます。
演習ファイルアップロードのコンポーネントを利用する
INTER-Mediatorに組み込まれているファイルのアップロードのコンポーネントの利用方法を説明します。なお、アップロードにおいては、いろいろな準備が必要ですし、アップロードしたファイルを参照する方法も知っておく必要があります。これらをまとめて、この演習で説明をします。なお、この演習は、前の演習『日付選択を行うコンポーネントを利用する』の続きで行います。
また、PHPの環境上の制限で現在の標準設定では1.5MB程度が上限となり、INTER-Mediator-Server VMはその設定を変更していません。それ以上のファイルをアップロードしようとしても、制限を超えているというメッセージが表示されてアップロード作業は完了しません。アプリケーションを実際に作るとき、精細な写真を貼付したい、あるいは動画を保存しておきたいときなど、業務上、どうしても大きなファイルを添付しなければならないという場合は、PHPの環境設定ファイルを書き直すなどの対応が可能です。方法は、『9-3 INTER-Mediatorを利用する開発プロセス』で説明します。
定義ファイルにファイル保存位置を指定する
ページファイルの修正
<!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>
アップロードした結果の確認
ファイルがアップロードされた結果を、INTER-Mediator-Server VM上で確認をします。
「ターミナル」アプリケーションのウインドウで、「ssh developer@192.168.56.101 」とコマンドを入力します。初回に接続するときに限り、「本当に接続してもよいか?」という趣旨のメッセージ
Are you sure you want to continue connecting (yes/no)?
TeraTermやPuttyなどのアプリケーションを利用するか、Cygwinを利用するなどして、VMにssh接続することができます。
$ cd /tmp
$ ls -l
total 4
drwxr--r-- 3 www-data www-data 4096 Jul 28 01:34 testtable
$ 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
# 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
アップロードしたファイルの履歴を残す
ここまでの方法では、あるレコードのあるフィールドに対して、アップロードしたファイルのうち、最後のファイルへのパスだけがデータベースに残っていました。さらに発展させて、ファイルをアップロードした履歴も残すようにします。
<!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>
# 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
# 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ページの中で表示する方法を説明します。なお、この方法は、アップロードのコンポーネントを使わないでアップロードしたファイルについても適用できる手法です。
:
<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>
演習のまとめ
- ファイルのアップロードのためのコンポーネントが用意されており、data-im-widget属性に「fileupload」を指定すると、利用できます。ただし、アップロードできるファイルサイズの上限はINTER-Mediator-Server VMでは約2MBまでです。この制限を大きくすることもできます(『9-3 INTER-Mediatorを利用する開発プロセス』を参照)。
- data-im-widget属性は、テキストを保存するフィールドにバインドしたタグ要素に指定します。そのフィールドにはアップロードしたファイルへのパスが設定されます。
- アップロードされたファイルは、media-root-dirキーで指定したパス以下、コンテキスト名、レコードを特定する条件、フィールド名と続くディレクトリが作られ、そこに保存されます。
- アップロードされたファイルのファイル名に4桁のランダムな値が付与され、同一ファイル名を持つファイルがアップロードされても上書きされないようにしています。
- アップロードの履歴を別テーブルに残すこともできます。
- アップロードの履歴を別テーブルに残すこともできます。
ファイルアップロードのその他の機能
ファイルのアップロード履歴を、演習ではfileuploadコンテキストのテーブルに残していました。このテーブルの構成としては、pathという名前のテキスト型フィールドが必要ですが、その他は自由に設定可能です。relationキーによるリレーションシップが定義されていますが、演習のような設定をした場合には、fileuploadコンテキストのテーブルには、外部キーとなるf_idフィールドが必要です。また、この演習では設定していませんが、レコード作成日時を自動的に設定するタイムスタンプのフィールドを確保し、現在の日時を既定値にすれば、ファイルをアップロードした日時が分かります。
演習ではファイルアップロードをドラッグ&ドロップでできることを確認しましたが、ドラッグ&ドロップに対応していないブラウザーの場合には、従来形式のフォームによるファイルのアップロードができるようになっています。ただし、リスト5-4-1のようなJavaScriptのプログラムを、例えばページ合成を行う前に実行されるメソッドに記述することで、ドラッグ&ドロップ対応のブラウザーでも、従来のフォーム形式の画面が表示されます。
IMParts_Catalog["fileupload"].forceOldStyleForm = true;
ファイルアップロードのコンポーネントは、プログレス表示にも対応していますが、既定の状態ではプログレス表示は行いません。コンポーネントのプログレス表示機能を有効にするには、リスト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 |
種類 | 値とファイル名 |
---|---|
コンポーネント | CodeMirror |
data-im-widgetの値 | codemirror |
アダプター | codemirror_im.js |
ページファイル | codemirror_MySQL.php |
定義ファイル | include_MySQL.php |
種類 | 値とファイル名 |
---|---|
コンポーネント | jQuery DatePicker |
data-im-widgetの値 | jquery_datepicker |
アダプター | jquery_datepicker_im.js |
ページファイル | jquery_datepicker_MySQL.php |
定義ファイル | include_MySQL.php |
種類 | 値とファイル名 |
---|---|
コンポーネント | jQuery DatePicker |
data-im-widgetの値 | jquery_fileupload |
アダプター | jquery_fileupload_im.js |
ページファイル | fileupload_jQuery_MySQL.html |
定義ファイル | include_MySQL.php |
注釈 | 利用方法は、標準のfileuploadと同様にパスを記録するフィールドとバインドして利用します。 |
メディアファイルの内容の取得
定義ファイルは、通常はフレームワーク自体をページファイルに送り込むことや、データベースアクセスに利用しますが、他にもさまざまな機能があります。そのうちのひとつが、ファイルの内容を取り出す仕組みです。HTMLでは、IMGタグによる画像や、PDFファイルへのリンクといった用途に使うことを想定しており、パスを与えて、そのファイルの中身をMIMEタイプなどとともにクライアントに返すといった動作を行います。基本的にはリスト5-4-3のような記述を行います。mediaというキーでパラメーターを指定するということです。
一般的な記述:定義ファイルへのパス?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-2)。
フィールドvc1を選択して、右下の「オプション」ボタンをクリックして、フィールドオプションのダイアログボックスを表示します。このダイアログボックスの「入力値の自動化」を選択した状態で、「計算値」のチェックボックスをオンにします(図5-4-3)。
そして、計算値として図5-4-4のような式を入力します。この式は、どんなフィールドでもそのままで指定すればかまいませんので、TestDBからコピーして利用すればよいでしょう。この計算式のポイントは、フィールドに何も入力されていないとき、つまり新規にレコードを作成したときなどに、Base64でエンコードされた結果をもとにして、元データを得てフィールドに保存しています。つまり、クライアントからBase64でデータを送り込み、この式を通じて元に戻しているのです。
続いて定義ファイルの変更点について説明します。コンテキストは、testtableを利用して、fileuploadは利用しません。testtableコンテキストのFile Uploadingの下の設定は、fieldにオブジェクトフィールドのフィールド名(ここでは「vc1」)を指定します。contextは空欄にし、containerをtrueにします。その他は変更の必要はありません。
ページファイルをリスト5-4-4のように修正します。フィールド名がvc1に変わっただけで大きな違いはありません。ファイルアップロードのコンポーネントをdata-im-widget属性で指定するタグ要素は、vc1フィールドにバインドします。そして、vc1フィールドに入力されている画像を表示するには、vc1フィールドの値をmedia=の後につなげます。オブジェクトフィールドは、カスタムWeb経由でデータを得ると画像等のバイナリデータではなく、フィールドに入力することが可能なURLの一部分が得られます。それを定義ファイルの引数に与え、定義ファイルから先のINTER-Mediatorの内部で正しいURLを構築して画像データなどを得ています。
<!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>
ページファイルを表示して、画像ファイルをドラッグ&ドロップします。直後に右側に画像は出てきませんが、更新すると、ドラッグ&ドロップした画像が見えるようになります。
なお、更新しないと画像が見えない点については、将来にバグ修正する予定です。
このセクションのまとめ
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キーと同一のテーブルがあるといった状況を想定してください。
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属性によってターゲット指定が記述されています。ここで、Ⓑのセルは行見出し用コンテキスト、Ⓒのセルは列見出し用のコンテキストが展開されます。したがって、これらのセルでは、コンテキスト定義に存在するコンテキスト名およびその中に存在するフィールド名を記述する必要があります。そして、Ⓓのセルには、交差セル用コンテキストに対応したターゲット指定を記述しますが、セルを埋めるルールは、この後の『クロステーブル生成の仕組み』で説明をします。なお、Ⓐのセルは、ページ合成後もテーブルの左上のセルとして配置されます。
<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タグは、ページファイルにあったもではなく、プログラムで単に生成したものです。Ⓐのセルは単に複製して子要素にするだけですので、この中にターゲット指定を記述しても、データベースの内容を展開することはありません。
続いて、テーブルの1行目に列見出しを作成します。図5-5-3のように、1行目のTRタグ要素をエンクロージャー、Ⓑのセルをリピーターとして、ページ合成を行います。ここではⒷのセルの中身を解析して、ターゲット指定の設定を集めて、一番多く使われているコンテキスト名を決定し、そのコンテキスト名の定義に基づいてデータベース処理を行います。つまり、通常のエンクロージャー/リピーターの展開が行われます。そして、コンテキストオブジェクト自体も生成されます。もちろん、クエリー結果のレコードの個数だけⒷのセルが複製されて、その中ではフィールドの内容がタグ要素に合成され、データベースのデータがセルに見えることになります。もちろん、複数のリンクノードを記述して複数のフィールドを指定しても構いません。なお、内部にさらにエンクロージャー/リピーターが見つかれば展開は行いますが、一般には処理速度を考慮すべきところであり、1回のデータベースアクセスで必要なデータを取り出すようにデータベース側を設計しておくことが求められます。
次にテーブル2行目から、行見出しの合成を行います。図5-5-4のように、TBODYタグをエンクロージャーとし、ⒸのセルをTRタグで包んだ要素をリピーターとして通常通りの手順で合成を行います。したがって、Ⓒのセルに含まれるリンクノードのターゲット指定によってコンテキスト名が決まりデータベースアクセスして、取り出されたレコードのフィールドの値が2行目以降の1セル目に合成されます。Ⓒのセルを包むTRタグ要素も、元からページファイルにあったものではなく、単にプログラムで生成したものです。Ⓒのセル内に複数のリンクノードを設けても構いませんし、さらにその中にエンクロージャー/リピーターのセットがあれば展開を進めるのも同様です。
そして、2行目の2列目以降、列見出しのレコード数分Ⓓのセルを付加します。最初は単にセルを付加して、行列をセルで埋める作業を行います。そして、Ⓓのセルを解析してコンテキスト名を求めてコンテキストを決定し、データベースアクセスを行います。クエリー結果のレコードを順番に合成するのではなく、順番に調べて置き場所がテーブル内にある場合、その交差セルに対して合成、つまりリンクノードの指定に応じてフィールドの値をタグ要素内に埋め込む処理を行います。したがって、交差セル用コンテキストで得られた結果でも、対応する行あるいは列がなければ無視されます。この処理はクロステーブルだけで行われており、エンクロージャー/リピーターによる展開とは異なる処理が組み込まれています。したがって、エンクロージャーに相当するノードはないため、図の中ではエンクロージャーに対しては「規定なし」と記載をしました。なお、Ver.5.4-dev現在の実装では、交差セルへの合成は、単に合成するだけなので、同一のセルに2回以上合成すると、それらの数値の文字列がつながって見えるだけです。加算等は実装されていませんので、コンテキストから得られるデータは集計結果になっているものを利用するようにしてください。
最後の交差セルを埋める部分はデータを見ながら説明をしましょう。サンプルファイルはSamples/Practice/crosstable.htmlおよびcrosstable.phpにあります。VMを利用している場合には、ブラウザーで「http://192.168.56.101」に接続し、そこにある「サンプルプログラム」のリンクをクリックして、サンプルプログラムの一覧を表示します。そこにあるPracticesにある「cross table」の項目をクリックして、動作を確かめることができます。図5-5-6は、そのサンプルを動かした結果です。このサンプルのページファイルは、このセクションで示したページファイルの記述のサンプルと同一のものです。定義ファイルには、item、customer、salesummaryの3つのコンテキストが指定されていて、それぞれ、列見出し、行見出し、交差セルのコンテキストです。
以下、3つのコンテキストの具体的な値をもとに、クロステーブルの動作を検討しましょう。列見出しのコンテキスト「item」と、行見出しのコンテキスト「customer」に関して、クエリー結果のデータを示すと、表5-5-1と表5-5-2のような結果になります。それぞれの値が、セルの中に表示されているのが分かります。
id | name |
---|---|
25 | Onion |
26 | Parsnip |
27 | Peppers |
28 | Potato |
29 | Pumpkin |
30 | Peas |
31 | Rhubarb |
32 | Shallots |
33 | Spinach |
34 | Squash |
35 | Sweet Potato |
id | name |
---|---|
250 | Danio Food, Co. |
251 | Darter Food, Co. |
252 | Dartfish Food, Co. |
253 | Dealfish Food, Co. |
254 | Death Valley pupfish Food, Co. |
255 | Deep sea anglerfish Food, Co. |
256 | Deep sea bonefish Food, Co. |
257 | Deep sea eel Food, Co. |
258 | Deep sea smelt Food, Co. |
259 | Deepwater cardinalfish Food, Co. |
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キーでの指定から得るようにしました。
id | dt | item_id | customer_id | qty | unitprice | total |
---|---|---|---|---|---|---|
1 | 2010-01-01 00:00:00 | 38 | 549 | 7 | 528 | 3696 |
2 | 2010-01-01 00:04:45 | 24 | 632 | 16 | 781 | 12496 |
3 | 2010-01-01 00:14:10 | 30 | 251 | 8 | 373 | 2984 |
4 | 2010-01-01 00:27:02 | 32 | 496 | 46 | 331 | 15226 |
: | : | : | : | : | : | : |
94 | 2010-01-01 22:02:29 | 16 | 860 | 4 | 472 | 1888 |
95 | 2010-01-01 22:24:34 | 27 | 255 | 41 | 297 | 12177 |
: | : | : | : | : | : | : |
クロステーブルで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」を適用した状態と同じにする |
テーマの設定は、以下のいずれかの方法で行えます。IM_Entry関数はもちろん定義ファイルに記述するもので、このテーマの設定は、定義ファイルを参照しているページにだけ適用されます。params.phpファイルに指定すると、そのファイルを参照しているINTER-Mediator全体に適用されるので、例えばサイト全体をまとめて設定したいときに利用できます。
- IM_Entry関数の第2引数(オプション設定)で、themeキーによる値にテーマ名を指定する。
- params.phpファイルに、$themeName変数に対して文字列でテーマ名を指定する。
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-1のようにヘッダー部にstyleタグ要素を追加して、ボタンに対する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変数も記述します。
$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ファイルの中身をマージして返します。
[定義ファイル名]?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-2)。ここで、まず、市区町村のリストもテーブルで構築していますが、スクロールすることを確認してください。その時、タイトルバーとページ下部の表示は画面に固定され、その間でスクロールされるという典型的なモバイルアプリケーションの動作になっていることを確認してください。この動作はINTER-Mediatorとは関係なく、CSSの設定で可能です。これについても、あとで説明します。また、ヘッダーの左側に、◀︎ボタンが表示され、これをタップすると、東京都などの最初のリストが表示されることが分かります。つまり、これは「戻る」ボタンであり、最初の画面では表示されていないことも確認してください。
「市区町村」をタップしたあとは、町域名の一覧が表示されます。この時、前に選択した市区町村に含まれる町域名だけが表示されていることを確認してください。つまり、前のステップの選択肢が、次のステップの一覧表示に対して影響を与えている、つまり検索条件を与えるということができています。そして、最後は、郵便番号、都道府県、市区町村、町域名が一覧され、そこから先にはステップ動作での移動は行いません(図5-7-3)。戻ってまた別の場所を選択することもできます。郵便番号が見える画面では、いずれのセルをタップしても画面遷移は行いませんが、デバッガのコンソールにオブジェクトの内容が出力され、これまでの4つのステップで、何をタップしたのかが記録されていることが分かります。ここで、記録されている状況を例えばデータベースに書き込むなどすることになることも多いかもしれませんが、その作業はJavaScriptでプログラムを記述する必要があります。
ステップ動作のためのコンテキスト定義
定義ファイルの内容を参照しましょう。定義ファイルは、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コンテキストが選択され、このコンテキストを利用しているページファイルの一部分が更新され、データベース処理が行われてクエリー結果をリピーターに合成します。そして、「次へ」という動作の時にスタック動作を行うグローバル変数に情報を残すので、「戻る」ことも自動的に行えるようになっています。
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属性に必ず前述の名前を指定します。それが、ページ内の見えている場所に配置されていれば構いません。通常はひとつの要素だけで十分と思われますが、複数あってもかまいません。
<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変数のオブジェクトとして定義します。引数はありません。
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-2に示すキーで、メソッド名を定義できます。いずれも、INTERMediatorOnPage変数のオブジェクトとして、キーに対する値を名前に持つメソッドを定義します。before-move-nextstepキーのメソッドは前に示すように返り値により動作を定義できますが、残り2つのキーに対するメソッドは、引数も返り値も不要です。例えば、あるステップだけ、特定のフッターが必要な場合には、あらかじめdisplayスタイルをnoneにしておいて画面には見えないようにしておき、just-move-thisstepキーの値の名前のメソッドでフッターを表示します。また、just-leave-thisstepキーの値の名前のメソッドでフッターを非表示にします。
コンテキスト定義でのキー | メソッドの呼び出しタイミング |
---|---|
before-move-nextstep | セルをタップしたとき |
just-move-thisstep | コンテキストのページ合成を終えた直後 |
just-leave-thisstep | 次のコンテキストに移行する直前 |
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メソッドと同じかどうかを判定して、ボタンから次のステップに移動するのか、セルをタップするのかを判別することができます。
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ステートメント |
---|---|
prefecture | SELECT MIN(id) AS pref_id, f7 AS pref FROM postalcode GROUP BY f7 |
city | SELECT MIN(id) AS city_id, f8 AS city FROM postalcode GROUP BY f8 |
town | SELECT MIN(id) AS town_id, f9 AS town FROM postalcode GROUP BY f9 |
wrapup | SELECT * FROM postalcode |
まず、最初の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_id | pref | 注釈 |
---|---|---|
1 | 東京都 | SQL文で得られた結果 |
101 | 埼玉県 | appending-dataキーで追加された結果 |
102 | 神奈川県 | appending-dataキーで追加された結果 |
103 | 千葉県 | appending-dataキーで追加された結果 |
ここで「東京都」をタップしたとします。すると、リスト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_id | city |
---|---|
1 | 千代田区 |
447 | 中央区 |
605 | 港区 |
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にしています。
#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の高さを求めて設定をしています。なお、このプログラムは、ページデザインを変更した場合など、ヘッダーやフッターの状況によって作り変えが必要になります。
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にして、座標位置や幅を数値で与えています。
.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ではできません。
定義ファイルエディターを開き最初のコンテキストを入力
2つ目のコンテキストを入力
ページファイルの修正
<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>
<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>
<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>
<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>
Chromeを利用してモバイルシミュレーションで稼働させる
演習のまとめ
- navi-controlキーの値がstepあるいはstep-hideのコンテキストに対応したエリアをそれぞれ1画面として、画面が切り替わるステップ動作のページを作成することができます。ページ内は、データベースの内容を表示することができます。
- クラスがIM_Button_StepBackのspanタグ要素が「戻る」ボタンになります。オブジェクトの配置をするだけで、ボタンが適切に動作します。
- 画面遷移の時に呼び出されるメソッドをbefore-move-nextstepキーで指定します。画面でセルをタップしたときに呼び出され、次の画面のデータベースアクセスの条件設定などができます。
- ページ内にテキストエリアなどを配置した場合、ローカルコンテキストとしてバインドしておけば、後から値をプログラムで取り出すことができます。
このセクションのまとめ
ステップ動作は、ひとつのコンテキストごとに画面に表示する機能で、ある種のモバイルアプリケーションでよく見られる形式であるとも言えます。画面の構築はもちろん、タップ後の画面遷移もコンテキスト定義に定義されている順序で順番に行われます。また、戻るボタンの動作も自動的に行われます。タップ時には定義したメソッドを呼び出せるので、そこでさまざまな処理を記述できますし、遷移をやめたり順序と関係ない別のコンテキストに遷移したり、さまざまな動作を実装できます。ただし、JavaScriptのプログラミングが必要になります。