リレーショナルデータベースを使う応用として、カレンダー形式のスケジュール管理の実例を示しましょう。コンテキストをいくつも定義するといったことを利用することで、FileMakerのレイアウトあるいはカスタムWebでのブラウザ上で、カレンダー形式でのスケジュール表示が可能になります。
カレンダーといえば、1ヶ月分の日付が表になっているものですが、特徴としては、1行が1週間となっており、左から、日曜、月曜…土曜のように並んでいます。こうした当たり前の形式ではありますが、カレンダー形式に表示するのはちょっと難しいということもあり、データベースを使ったアプリケーションではあまり見ないかもしれません。最適なデータベース設計(スキーマ)がどんなものかということと、実際にカレンダーのように表示するという方法がアプリケーションの機能に依存するからです。もちろん、予定を単に一覧するだけでいいということも多いのですが、開いている日程を見ながら先の予定を考えるようなときには、カレンダー形式は便利です。
ここで、カレンダーを実現するのにキーポイントになるのは、エンティティつまりデータベースのテーブルでの1レコードに相当するものに何を持ち込むのかということです。まず、明白に、「スケジュール」というエンティティが存在するのはすぐにわかることです。つまり、何月何日の何時から何時までどんなスケジュールが入っているのかということを1レコードにします。
加えて、カレンダーをじっと眺めると見えてくるのは、「日」が1つのエンティティであるということです。「3月23日」や「8月25日」という「日」が1つのレコードです。もちろん、「年」も含めます。「2006年8月25日」という「日」が1つのレコードで、しかも現実の世界でも1つしか存在しないものです。つまり、あらかじめ、「日」を1レコードとするテーブルを用意しておくことで、カレンダーの1日分のボックスをまかなうデータ表現が可能だということになります。
もちろん、そこから、1週間を1つの行に入れるということなどが必要になりますが、これはリレーションをうまく設定すると同時に、レイアウトの機能をうまく使うということや、ユーザインタフェースをうまく用意しておくということが必要になります。
ここで作るデータベースは「カレンダー.fp7」というファイルに保存することにします。作成の初期段階ではもちろん、使用しているコンピュータの中に保存しますが、カスタムWebでの公開時にはFileMaker Serverで公開します。なお、このサンプルは、FileMaker 8でないと使えない機能が随所に出てきます。カレンダー形式の表示という点については、FileMaker 7でも可能ですが、実際に作ってみる場合にはバージョンの違いも注意してください。
まず、データベースファイルを作ると、同名の「カレンダー」という名前のテーブル定義が作られていますが、当初はこちらのテーブルは使わないでおきます。あとから、グローバルフィールドを確保するために使います。このテーブル以外に、1件のスケジュールを1レコードとして管理する「スケジュール」テーブル定義、そして1日を1レコードとして記録する「毎日」テーブル定義を作ります。
まず、「スケジュール」テーブル定義のフィールドですが、図のようにしました。ここでは、識別のために、いちおうidフィイールドを用意してこのフィールドに自動的に連番を入れるようにします。ただし、ここで紹介するアプリケーションではこのidフィールドは特には使いません。「日付」「開始時刻」「終了時刻」のフィールドはそれぞれそのままの意味です。ここでは、日をまたがったスケジュールは存在しないものとしています。これはこのアプリケーションの制約です。スケジュール管理ソフトでは、たとえば、3日にわたるスケジュールでは日をまたがって帯がのびるようなユーザインタフェースが可能ですが、FileMakerだけでそれを実現するはかなり困難なので、ここでは日をまたがらないという制約をつけることにします。
スケジュールの説明は「概要」「詳細」としましたが、以後は「概要」フィールドだけを使います。実際には用途にあわせてフィールドを追加することになるでしょう。ここでは「カレンダー形式」という部分にしぼって解説を行います。
続いてテーブル定義の「毎日」のフィールドです。まず、「日付」フィールドとして、実際の日を入力するフィールドを日付タイプで確保します。このテーブルの場合、1日は唯一の存在なので、「日付」フィールドがキーフィールドになると考え、連番を入れるidフィールドは作らないでおきました。ただし、この「日付」フィールドへの入力を確実に行うために、後で紹介しますがスクリプトを作成します。また、間違えないようにするために、場合によっては「日付」フィールドのオプションとして、「ユニークな値」を入力制限しておくのもいいかもしれません。
「祝日休日」は「春分の日」などの文字を入れることにします。これはどうしても手作業になります。なお、このフィールドが空欄なら祝日ないしは休日でないと判断することにします。もちろん会社の創立記念日で休業のような、データベースを使う範囲のローカルな行事を書き込んでもかまいません。
「年」「月」「日」「曜日」の計算フィールドは、単に「日付」フィールドの値からそれぞれの情報を関数で取り出しているだけです。Webに展開するときに使うのですが、こうした関数で求めた結果だけのフィールドを用意しておくと便利です。これらのフィールドの計算結果は「数値」になります。曜日は、1が日曜日となり順々に割り当てられて7が土曜日になります。
さらに「経過2日目」〜「経過7日目」までの計算フィールドに注目してください。「日付」フィールドの値の次の日、その翌日と、「日付」からスタートして、1週間分の日付がこれらのフィールド自動的に得られます。
この「経過N日目」フィールドを説明するのに、データベースのスキーマを解説しなければなりません。このデータベースのテーブル間の関連は実にシンプルで図の通りです。つまり、あるスケジュールは、特定の日のものとなるので、逆に1日が複数のスケジュールを持つという1対多の関係があるというだけです。
ここで、実際のデータ、つまりコンテキストを考えてみます。それぞれの「日」に対するスケジュールがあるのですが、この「日」を7日分集めることで、「1週間」という単位のひとかたまりのものを作ることができます。この1週間は、つまりは「日付」フィールドの値に+1〜+6日のそれぞれの日を加えた日付です。その日付を生成するために「経過N日目」という6つのフィールドを用意している訳です。
「毎日」と「スケジュール」のテーブル定義から作られたテーブルは、それぞれの「日付」フィールド同士で結合が可能です。ここまではリレーショナルデータベースの基本的な定義です。ところが実データを見る限りは「経過2日目」のスケジュールと、「経過5日目」のスケジュールは、それぞれ別々のものです。つまり、「経過2日目」と「経過5日目」に対するスケジュールは異なるコンテキストであると考えます。一般的なリレーショナルデータベースは、こうしたコンテキストの違いを個別に定義はしないでアプリケーションを作るときにプログラム等で組み込みを行います。一方、FileMakerでは、こうしたコンテキストがそれぞれ存在するということをリレーションシップとして定義を行うことが可能です。
それでは、リレーションシップを実際に定義しましょう。まず、初期状態では、「カレンダー」「スケジュール」「毎日」というテーブルが存在しています。これらはとりあえず、そのままおいておき、新しいテーブルを用意してリレーションの定義を行います。つまり、これから作ろうとしているレイアウトなどに対応したテーブルをその都度作るという手法で進めていきます。
まず、カレンダーの1日が1レコードに対応するテーブルを用意しますが、「毎日」テーブル定義をもとにした「毎日_カレンダー」テーブルを追加します。そして、1日目〜7日目に対応するスケジュールは、それぞれ別のテーブルをコンテキストとして用意しなければなりません。まず、「スケジュール」テーブル定義をもとにした「スケジュール_1日目_カレンダー」というテーブルを用意します。そして、「スケジュール_1日目_カレンダー」を選択して、左から3つ目のボタンをクリックするなどして複製を作ります。合計6つの複製を作り、それらの名前を変更します。もちろん、「スケジュール_1日目_カレンダー 2」は「スケジュール_2日目_カレンダー」のようにコンテキストに合致した名前に変更します。
必要なテーブルを用意したら、リレーションを定義します。「毎日_カレンダー」テーブルの「日付」フィールドと、「スケジュール_1日目_カレンダー」テーブルの「日付」フィールドを結合します。同様に、「毎日_カレンダー」テーブルの「経過N日目」フィールドと、「スケジュール_N日目_カレンダー」テーブルの「日付」フィールドを結合します(N=1〜6)。
それぞれのリレーションは、ポータル上に展開し、そこでデータの入力を行いたいので、リレーションの設定をそれぞれ図のように変更しておきます。リレーションをダブルクリックして表示されるダイアログボックスで7つのリレーションいずれも図のように設定をしておきます。すなわち、「スケジュール_N日目_カレンダー」テーブル側に新たにレコードを作成できるように許可を与えます。そして、「スケジュール_N日目_カレンダー」テーブルのレコードを時刻順に並べ替えるソートの定義も行っておきます。
「毎日」テーブルには、1日1レコードを作ることになります。いろいろな方法はあるかとは思いますが、何かしらのマクロを用意しておいて、定期的に実行するのが適切かと思います。ここでは、図のような「複数レコード作成」というスクリプトを作りました。このスクリプトは、「毎日」テーブルに全くレコードがない状態であれば、2006年1月1日以降のレコードを作ります。もし、すでにレコードが存在する場合は、いちばん最後の日のレコードを選択した状態でスクリプトを実行します。実行時に選択しているレコードの「日付」フィールドの翌日からのレコードを作ります。使用するときには注意をしてほしいのですが、もちろん、いろいろと凝れば自動的に追加も可能です。ここではサンプルを簡単にするために完全なスクリプトではないことをお許しください。なお、スクリプトは、ある日から100日分のレコードを作りますが、スクリプトを修正すればその日数は変更できます。スクリプトは変数を使っていますので、FileMaker 8以降でないと同じようには作成できません。
「毎日」テーブルを表示する「毎日」レイアウトが最初から作られているので、それを利用して、日のレコードを作りましょう。レイアウトを「表形式」で表示しておくのが便利でしょう。マクロを何度か動かして、2006年分のデータを作りました。レコードを追加するときには、いちばん最後の日のレコードを選択してからスクリプトを実行することを忘れないでください。
レコードが作られれば、「祝日休日」のフィールドを埋めます。もちろん、用途によっては不要かもしれませんので、この作業は必須ではありません。(なお、以下のサンプルでは、実際のレイアウトなどで「祝日休日」の表示は行っていません。)もっとも、スクロールして入力のようなベタな作業を嫌う場合には、休日や祝日を別のテーブルで管理して、リレーションを定義するというのがいちばんスマートな方法でしょう。これについては紹介はしませんので、チャレンジとして残しておきます。
基本的なデータベースの定義ができたので、早速カレンダー形式に表示や入力が可能なレイアウトを作っていきましょう。以下、「新規レイアウト/レポート」のウィザードでのポイントとなる部分だけを示します。
1最初のダイアログボックスでは、「レコードを表示」は「毎日_カレンダー」テーブルを選択します。レイアウト名は「カレンダー」を選択し、タイプとしては「表レイアウト/レポート」を選択します。
2「表レイアウト/レポート」なのか「集計レポート」なのかを選択するダイアログボックスでは、「表レイアウト/レポート」を選択します。
3「フィールドの指定」では、「日付」と「経過N日目」の合計7つのフィールドを必ず含めるようにします。(図では「年」「月」「日」がありますが、これは使いませんのでなくてかまいません。)
4「レコードのソート」では、「日付」フィールドをソート対象フィールドとして指定します。もちろん、昇順でソートします。
5レイアウトのスタイルは自由に選択してかまいませんが、ここでは紙面で見やすいように「デフォルト」を選択しました。
6「ヘッダおよびフッタの情報」はすべて「なし」でいいでしょう。
7「レポートのスクリプトを作成」では、「スクリプト作成する」を選択します。実用面ではスクリプトを利用しないと、正しくカレンダー形式に常に表示される訳ではありませんので、ここで定義しているレイアウトを表示するためのスクリプトは必ず作成します。
まず、作成したレイアウトの設計画面は次の図のようなシンプルなものです。要は日付が表示されるフィールドが横に並んでいればいいわけです。それがリスト表示になっていればかまいません。
この状態で、一度ブラウズ表示してみます。もちろん、レコードに対しての検索は行っていないので、すべてのレコードが表示されます。図のように、1行目を見れば、1月1日から7日まで並んでいていいのですが、2行目は1月2日から、3行目は1月3日からとなっており、カレンダーのようにはなっていません。ここで考えるのは、1月1日を基準にすると、2行目は「1月8日から7日間」、3行目は「1月15日から7日間」といったような状況です。
想定する状況を汎用的に考えれば「日付」フィールドが日曜日だけのレコードだけを残せばいいことになります。これは、検索条件として設定可能です。もし、カレンダーを月曜日スタートにしたい場合でも同様に検索条件として設定可能です。
「カレンダー」レイアウトを表示するためのスクリプト「カレンダー」を修正しましょう。まず、スクリプト後半のプレビューモードに切り替えたり、もとのレイアウトに切り替える作業は不要なので、これらのステップは不要です。「ブラウズモードに切り替え」は最初にあるのがいいかもしれませんが、これは場合によっては不要です。念のために残しておきます。ソートについては「日付」でのソートが設定されているので、そのまま残しておきます。そして、レイアウトを「カレンダー」に切り替え、その後に「検索実行」のステップを追加して、さらにレコードをソートするステップを配置するようなスクリプトにします。
「検索実行」ステップの条件については、設定して記録しておきます。ここでは、「毎日_カレンダー」テーブルの「曜日」フィールドが「1」、つまり日曜日のレコードだけに絞り込むという検索条件を設定しました。
こうして、スクリプトの「カレンダー」を実行してみてください。すると、1行目は1月1日から、2行目は1月8日から、3行目は1月15日からというように、日付の並びを見るだけだとカレンダーの形式になってきています。
こうして作った「カレンダー」レイアウトに、ポータルを追加して、各日のスケジュールを表示します。ボディ領域の高さを高くして、まず1つポータルを追加します。最初に「日付」フィールドとリレーションを設定してある「スケジュール_1日目_カレンダー」をポータルの関連レコードとして指定をします。ほかの設定は任意ですが、リレーションシップのところでリレーションの設定でソートの設定を行っているので、ここではソートの設定をしなくても時刻順に表示されます。
ポータルの中には、関連レコードの「開始時間」「終了時間」「概要」を配置することにします。
図のように、レイアウト内にポータルが1つ作られました。ここではサイズなどは適当に設定しています。ほかに6つのポータルを作らないといけないので、ここでは、1つのポータルヲそれなりに仕上げて、それを複製し、設定を変更するという手順をとるのがいいでしょう。実際には正しいデータが表示されるかなどを確認しながら作業を進めることになると思われます。
このポータルの部分をたっぷり作ると、7つも横に並べるとかなりの場所が必要になるので、小さく作る必要があります。ここでは、フォントを10ポイントにして大きさを調整しますが、時刻の間のところには「〜」のテキストを配置することにします。ポータルの大きさも調整しました。
こうして、ポータルとその中のオブジェクトを選択して、Ctrl+Dキーなどで複製を作ります。ここで、複製を作った後、複製のセットが選択された状態になるので、そのままぴったりの右側の位置まで矢印キーを使って移動させます。その後、さらにCtrl+Dキーを5回押せば、移動した先の間隔で、選択したものの複製を作るので、図のようにうまくきれいに並んだ状態でポータルを作ることができます。
なお、複製したポータルやフィールドは設定そのものも複製されているので、ここからは地道に1つずつ設定を変えないといけません。以下の図のように、2つ目のポータルはダブルクリックをして関連レコードとして「スケジュール_2日目_カレンダー」を選択します。左から3つ目は「スケジュール_3日目_カレンダー」、4つ目は「スケジュール_4日目_カレンダー」、5つ目は「スケジュール_5日目_カレンダー」、6つ目は「スケジュール_6日目_カレンダー」、7つ目は「スケジュール_7日目_カレンダー」をそれぞれポータルの設定ダイアログボックスで選択し直します。そして、ポータルの位置にあわせて、「日付」〜「経過7日目」のフィールドの位置を調整します。
同様に、複製したポータルの中にあるフィールドについてもそれぞれダブルクリックして、表示するフィールドの設定を変更します。「データを表示」の下のドロップダウンリストは、そのフィールドが配置されているポータルの関連レコードに対応するテーブル名を選択し、その下のリストで対応するフィールドを選択します。ただ、フィールド名は全部同じなので、結果的にはテーブルを選択し直すだけでかまいません。しかしながら、この設定の変更を3×6=18の複製したフィールドに対して行います。
日付を表示する「日付」〜「経過7日目」のフィールドについては、ここでは月と日だけの表示でかまわないので、「書式」メニューの「日付」で表示されるダイアログボックスで、図のように「カスタム」を選択して、月と日だけが表示される設定に変えておきます。また、ポータル内の時刻を表示するフィールドでは、時と分だけでいいので時刻書式のダイアログボックスで形式を選択しておきます。
「カレンダー」レイアウトを表示します。場合によっては「カレンダー」スクリプトを表示してください。すると図のように、1月1日からのカレンダーが表示されます。たまたま、2006年の1月1日は日曜日なので、いちばん左に配置されています。ここで、図では、1月17日のポータルのところに、時刻と概要を入力して、スケジュールを入力しました。そして、レイアウトを「スケジュール」に切り替えると、「カレンダー」レイアウトで入力した新しいレコードが追加されているのが分かります。
「スケジュール」レイアウトは表形式で表示されていますが、id=2のレコードが新しく作られたものです。「カレンダー」レイアウトのポータルでは、「開始時刻」「終了時刻」「概要」だけを入力しましたが、関連フィールドになっている「日付」はリレーションシップで結合に使っているフィールドなので、自動的に結合元の「毎日_カレンダー」テーブルの「日付」フィールドの値が入力されている訳です。
こうしていくつかの日付のポータルのところに、新しくスケジュールを入れていきます。図では、1月17日のところは2つのスケジュールを入力しました。いくつか入力したスケジュールは、「スケジュール」レイアウトで一覧で確認すると確かに追加されていますし、正しい日付になっています。
1月26日に「全体集会」というスケジュールが入っています。ここのポータルに入力したときの動作を考えてみましょう。ここのポータルは「スケジュール_5日目_カレンダー」テーブルを展開したものです。コンテキストとしては、「毎日_カレンダー」テーブルの「経過5日目」フィールドと結合しています。「スケジュール_5日目_カレンダー」テーブルにレコードが追加されたとき、その元のレコードの「経過5日目」フィールドの値である「2006/01/26」が、「スケジュール_5日目_カレンダー」テーブルの「日付」フィールドにコピーされ、リレーションシップの定義に従ったデータの関連付けが行われる訳です。これが、コンテキストを実現しているのです。
「カレンダー」レイアウトでは、全体を見ながらのスケジュール入力は可能ですが、「詳細」フィールドの入力ができません。また、実際にアプリケーションとして使う場合にはほかにもフィールドを入れたいと思うこともあるでしょう。その場合、ポータルの中にボタンを配置して、そのボタンをクリックすることで別のレイアウトを表示し、そこで、「概要」以外のデータの入力や編集、あるいはレコードの削除などの機能を組み込むと良いでしょう。
リレーショナルデータベースの機能を利用して、スケジュールの重複を検知できるようにしてみましょう。ここで、スケジュールの重複というのは、開始時間と終了時間の間に別のスケジュールが入っている場合とします。あるスケジュールの終了時間と別のスケジュールの開始時間が同じ場合は重複していないと見なすようにします。
方針としては、「スケジュール」テーブル定義をもとにしたテーブルにリレーションを設定しますが、テクニック的には自己リレーションということになります。しかしながら、コンテキスト的に言えば、スケジュールのテーブルから、あるスケジュール(レコード)と重複しているレコードをリレーションシップの機能を使って検索するという方が状況に近いと言えるでしょう。
重複しているという条件判断を一気にはできないので、ここでは次の4つのパターンに分けて条件設定をリレーションに対して行うことにします。パターンAは、開始時間や終了時間がもとのスケジュールと同じものも含みます。よって、元になるスケジュールと開始時間は同じで、終了時間がよりは早いものは、パターンBではなくパターンAであるとみなすことにします。
もとになるスケジュール 開始時間 ◎——————● 終了時間
パターンA ◎———●
パターンB ◎————●
パターンC ◎————●
パターンD ◎———————————●
スケジュールの重複チェックのための定義をリレーションシップに追加しますが、ここでのデータの関連付けは、新たなコンテキストが必要です。明らかに、カレンダー形式に表示するという目的とは違うデータ利用なので、コンテキストが違うのです。
「スケジュール」テーブル定義をもとにしたテーブルを作りますが、ここでのコンテキストは、元になるスケジュールを中心になります。まず、そのスケジュールを「スケジュール_重複チェック元」というテーブルとして定義します。もちろん、「スケジュール」テーブル定義をもとにしたテーブルです。そして、4つのパターンを検知するためのテーブル「スケジュール_重複チェック先A」〜「スケジュール_重複チェック先D」の4つのテーブルを「スケジュール」テーブル定義をもとに作成します。
まず、パターンAの判断を行うリレーションを定義します。パターンAの判断は、まず日付が同じで、かつ開始時刻が同じか後で、かつ終了時刻が同じか前であるという条件になります。実際の手順としては、「日付」「開始時刻」「終了時刻」のそれぞれのフィールドをドラッグして結合します。その直後は前の図のようになりますが、リレーションを定義してからその設定を編集するのが一般的な手順でしょう。テーブル間を結んでいる結合線をダブルクリックして「リレーションシップ編集」のダイアログボックスで設定を変更します。初期状態としては以下のようになっています。
ここでの作業では、左右が同じテーブル定義をもとにしているので、どっちがどっちなのかをよく確認しながら作業を行いましょう。ここでは左側が「スケジュール_重複チェック元」になっています。そして、中央の定義のうち、2行目を選択し、上のフィールドリスト間のドロップダウンリストで「≦」を選択して「変更」ボタンをクリックします。そして、3行目を選択して上のフィールドリスト間のドロップダウンリストで「≧」を選択して「変更」ボタンをクリックします。結果的に次の図のようになればいいのですが、スムーズな手順を示せば以上の通りです。条件が正しく設定されていることを確認してOKボタンをクリックします。
次はパターンBです。パターンBは、同じ日付で、かつ開始時間がもとの開始時間よりも前ながら、終了時間が開始時間よりも後のものです。ここで、もとのスケジュールでは「終了時間」のフィールドは判断に使わないことを確認しておきます。つまり、元のスケジュールの「開始時間」をまたがったスケジュールということです。
作業としては、「スケジュール_重複チェック元」と「スケジュール_重複チェック先B」の間でフィールド間をドラッグしますが、それぞれの「日付」と「開始時刻」をドラッグし、さらに前者の「開始時刻」と後者の「終了時刻」をドラッグします。そして、リレーションシプ編集で次の図のように条件を設定します。なお、この図では、なぜか右側に「スケジュール_重複チェック元」がきています。こうなっても、左右を間違えないように設定をします。
次はパターンCです。パターンCは、同じ日付で、かつ開始時間がもとの終了時間よりも前ながら、終了時間がもとの終了時間よりも後のものです。ここで、もとのスケジュールでは「開始時間」のフィールドは判断に使わないことを確認しておきます。つまり、元のスケジュールの「終了時間」をまたがったスケジュールということです。
作業としては、「スケジュール_重複チェック元」と「スケジュール_重複チェック先C
」の間でフィールド間をドラッグしますが、それぞれの「日付」と「終了時刻」をドラッグし、さらに前者の「終了時刻」と後者の「開始時刻」をドラッグします。そして、リレーションシプ編集で次の図のように条件を設定します。
パターンDはパターンAの時刻に関する条件が逆と考えればいいでしょう。「スケジュール_重複チェック元」と「スケジュール_重複チェック先C」の間のリレーション設定は次の図のようになります。
なお、本来は、≧と≦ではなく、>と<で時刻の判断をすべきです。そうしないと、開始時間と終了時間がいずれも同一の場合、パターンAでもパターンDにも合致することになります。ただ、そうなっても実はあまり支障はありませんし、後で説明するように実はパターンDは考慮しなくてもいいということが分かるので、ここはあまり気にしないでおくことにします。
最終的にはリレーションシップの定義として、「スケジュール_重複チェック元」を中心に4系統のリレーションが定義されます。ところで、この4つのパターンはそれぞれ異なる判断をするのだからコンテキストは別であると考えられます。それならば、1つの「スケジュール」テーブルから4つにリレーションを設定するのではなく、1つの「スケジュール」と別の「スケジュール」のリレーションを4つ定義するのが正しくはないかとも考えられます。この考えはもちろん正しい考え方ですが、ここでは、そうしたコンテキストの完全な分離をしなくても問題はありません。なぜなら、この4つのコンテキストはすべて「スケジュール_重複チェック元」テーブルを基準に考えるからです。「スケジュール_重複チェック先A」などのテーブルからのコンテキストは考えないので、ここは分離しなくてもさほど混乱はないはずです。また、分離してしまうと、計算式の定義のフィールドを1つにまとめることができないので、ここではまとめた1つのコンテキストとして4つのリレーションを扱うことにします。
続いて重複があるかどうかをチェックする計算フィールドを定義します。ここでのポイントは、リレーション先に重複するテーブルが存在するかどうかを数えることです。つまりは、Count関数を使う訳です。フィールドの定義で「スケジュール」テーブル定義に計算フィールドを新しく追加します。ここではフィールドの名前を「重複あり」としました。フィールドを作成するときに計算式を指定しますが、このときに、「次のコンテキストからこの計算式を評価する」というドロップダウンリストで、「スケジュール_重複チェック元」を選択します。コンテキストをこのテーブルを基準に評価するということを、式の方に指定する必要がある訳です。
そして、右側のリストからCount関数を探してダブルクリックして入力を行います。引数のフィールドは左側のリストから選択しますが、まずはパターンAということで、関連テーブルの「スケジュール_重複チェック先A」のidフィールドを指定します。フィールドはidではなくてもなんでもかまいません。数を数えるだけなので、データが存在するフィールドであればいいので「開始時間」でもかまわないです。
そして、式は最終的には、パターンA〜パターンDまでの重複数を計算して合計するようにします。
以下のように、既存のレコードに意図的に重複のあるレコードを追加しました。どのパターンなのかは「詳細」フィールドにメモを入れてあります。どうも、「重複あり」の計算結果が2なら重複がなく、3なら重複があるように思えるところです。なんで、2や3や5なのかということも含めて検証をしたいところです。
それでは、リレーションの先にどんなレコードが見えているのかを調べるために、新たにレイアウト「重複チェック」を作成します。このレイアウトは、「スケジュール_重複チェック元」テーブルの内容を表示するようにし、タイプは「標準」で作成します。フィールドはとりあえず全部配置すればいいでしょう。
そして、4つのポータルを配置して、それぞれに「スケジュール_重複チェックA」から「スケジュール_重複チェックD」のテーブルを割り当て、ポータル内にはどのレコードなのかが分かるように「開始時間」「終了時間」「概要」のフィールドを配置しておきます。
このレイアウトを表示して、実際にどのようなリレーションの結果になるのかを見てみましょう。まず、重複のないスケジュールを表示すると、パターンAとDの2つのポータルにレコードが見えています。このレコードは自分自身のレコードです。条件を考えれば、自分自身が「重複である」と判断するのはもっともなことです。ここで、必ず自分自身はじゅうふくするとはんだんされるので一定しているので、「重複あり」が「2」なら重複するスケジュールはないと判断できるはずです。
ここでは、重複スケジュールがあるかどうかの判断ができればいいので、大きな問題はありません。しかしながら、場合によっては重複するスケジュールがどれかを明示したい場合もあるかもしれません。その場合は、自分自身のスケジュールを排除したいと考えることになります。そのためには、パターンAのリレーションシップの定義で、idフィールドが異なるという条件をさらに付け加えればいいでしょう。
次の図は、パターンAの重複がある場合です。パターンAとパターンDに自分自身のスケジュールがリストアップされていますが、さらに、パターンAのポータルの2行目に重複のある別のスケジュールがリストアップされています。これらのレコード数を数えた結果が「重複あり」フィールドの結果ですので、3レコード分、つまり「3」という数字が見えています。
パターンB、Cでも同様に、自分自身以外に重複のあるレコードが、それぞれの関連テーブルのポータルで登場しており、「重複あり」フィールドの値は「3」となっています。
パターンDの重複がある場合、パターンAとDに自分自身が見えているのはいいとして、重複するスケジュールのレコードは、Dだけでなく、BもCにも見えています。結果的に「重複あり」フィールドは「5」となっています。このことは、パターンBとCの判断をすれば、パターンDの判断はしなくてもいいことにほかなりません。
以上のように、重複のチェック方法としては問題はないと同時に、効率化のためには、パターンDは使用しない方がいいという結論になるかと思います。よって、リレーションシップのグラフから「スケジュール_重複チェックD」のテーブルは削除して、以下のように、A、B、Cの3つのパターンでの判断を行うようにしました。そして、「重複あり」フィールドの計算式も変更しました。
そして、「スケジュール」レイアウトで「重複あり」の結果を見てみます。図のようになりますが、結果的には重複ありの値が2以上であれば、重複レコードがあると判断できるということになります。ここでは、「重複チェック」フィールドでの結果は確認しませんが、前に示した結果から、パターンDに対応したポータルの部分がなくなったと考えればいいでしょう。
実際にレイアウトに重複の有無を表示すとき、数字の1なら重複がなく…というようなルールではちょっとあまりかっこうよくありません。そこで、重複があれば★マークが出るように仕込みます。そのために、「スケジュール」のテーブル定義に新たに「重複表示」フィールドを定義します。単に「重複あり」のフィールドの値が2より小さい場合にはからの文字列で、そうでない(つまり重複スケジュールがある)場合には文字列の「★」を戻すようにしています。そのフィールドを、「カレンダー」レイアウトの各ポータルの行に配置をします。ポータルの右側に配置するようにしました。こうすると、レイアウト表示したときに、重複期間のあるスケジュールが入力されている箇所に、★マークが見えるようになり、ぱっと見て分かりやすいでしょう。
「カレンダー」レイアウトはこのままだと、すべての日程のカレンダーを表示します。これだと、データがたまってくると使いづらくなるので、月ごとのカレンダーを表示する機能を考えます。
ここで、それじゃあ2005年5月のスケジュールを表示したい場合には、5月1日から31日までというような単純な期間に絞り込みをすればいいというわけではありません。1週目の最初の日曜日は4月30日ですし、最終週の日曜日は5月28日です。つまり、ここでは日曜日をカレンダーの1列目にしたいので、最初の日曜と最後の日曜を求めるということをしなければなりません。
これが求まれば、この期間含まれる日曜日のレコードに絞り込んでソートをすれば、その月のカレンダーが出てきます。
ただ、スクリプトの「検索実行」ステップで変数的な処理ができればいいのですが、定数でなければいけないので、ここではまず、検索するための最初の日曜日と最後の日曜日を記録するグローバルフィールドを定義します。グローバルフィールドはどのテーブルにあっても同じことなので、ここでは「カレンダー」テーブルの定義に、「最初の日」「最後の日」としてタイプが日付のグローバルフィールドを定義しました。
さらに、「毎日」テーブルに、その日が範囲内にあるのかどうかを判断するための計算フィールド「カレンダー範囲内」を「毎日」のテーブル定義に追加します。計算式は以下の通りです。つまり、2つのグローバルフィールドの範囲内の日付なら1、それなければ0になるというものです。
IF ( 日付 ≧ カレンダー::最初の日 and 日付 ≦ カレンダー::最後の日 ; 1; 0 )
そして、「カレンダー」スクリプトを修正しますが、何年の何月を表示するのかということをスクリプト引数で指定するようにします。たとえば「2006/5」のように、年と月をスラッシュで区切って引数を指定するというようにしておき、スクリプトでスラッシュの前後の文字列を得て、望むデータをそれぞれのグローバルに設定をするようにします。マクロは次のようなものですが、最初の4つのステップの式は別に書き出しておきます。また、「検索実行」ステップの条件として、「カレンダー範囲内」フィールドが1であるというものを追加しました。
スクリプトステップの式
1: $year = Left ( Get ( スクリプト引数 ) ; Position ( Get ( スクリプト引数 ) ; "/" ; 1 ; 1 ) - 1 ) 2: $month = Right ( Get ( スクリプト引数 ) ; Length ( Get ( スクリプト引数 ) ) - Position ( Get ( スクリプト引数 ) ; "/" ; 1 ; 1 ) ) 3: 最初の日 ← Date ( $month ; 1 ; $year ) - DayOfWeek ( Date ( $month ; 1 ; $year ) ) + 1 4: 最後の日 ← Date ( $month+1 ; 1 ; $year ) - 1 - DayOfWeek ( Date ( $month+1 ; 1 ; $year ) – 1 ) + 1
この「カレンダー」スクリプトの呼び出し方法ですが、たとえば、ボタンに固有の年月の呼び出し機能を割り当てたいのであれば、ボタンの動作としてスクリプトの実行を指定し、「カレンダー」スクリプトを実行するようにします。このときの「オプションのスクリプトパラメータ」で、「"2006/12"」のようにダブルクォートで文字列をかこって指定して、文字列としてスクリプトの引数として与えられるようにします。たとえば、このボタンだと、「2006年12月」とラベル表示をしておき、クリックすると12月が表示されるようにします。もちろん、月ごとのボタンを用意するというのが一つの解決策でしょう。
なお、この方法で2006年5月のカレンダーを出したとき、4月30日や6月1日も見えています。通常のカレンダーだと、該当していない月の日は小さく表示したり薄く表示したりしていますが、このアプリケーションではここまでところでは特にそれらしい表示はしていません。
FileMaker Server AdvancedのカスタムWebの機能を使って、カレンダー表示を行ってみます。ここでは、カスタムWebの詳細については詳しくは説明しませんので、ポイントとなるところと、リレーションの絡みだけを説明します。
まず、一般にはデータベースというものは、レイアウト上に出すとしてもWebに出すとしても構造上は同じと考えていいのですが、現実的にはそれぞれ特徴と柔軟性の持ちどころが違ってきます。特に、カスタムWebでの大きな違いは、表示に使うデータの形式です。レイアウト上に展開するデータは、ベーブルをもとにした表形式のデータですが、Webページ生成に使われるデータは、XML形式のツリー構造を持ったデータです。ここは非常に大きな違いで、結果的に違ったアプローチをとることができます。
カレンダーは例によって7日分が1行なので、レイアウト上に展開するときには、1レコードに7日分のデータを展開できるようにリレーションの定義を行いそして、7日ごとにレコードを絞り込んでカレンダー表示をできるようにしました。
XMLでは、ツリー構造になっているデータを上下に移動して取り出すことができます。つまりこれは、XPathという規格に従った処理をすればいいわけで、ある1日のレコードに注目しているとき、翌日のデータは1レベル上がって次のレコードに移動するということで得られる訳です。
この仕組みを利用すれば、単に1日につき1レコードを割り当てた、レコードリストを、7日ごとに表示すればカレンダーができあがるということになります。このとき、レイアウト上に展開したときのように、1日後、2日後のレコードにアクセスするためのリレーションは、前述のように必要はありません。従って、Webのカレンダー表示に使うテーブルは、以下のようにシンプルです。「毎日_Web」「スケジュール_Web」という2つのテーブルをリレーションシップに用意し、日付でリレーションをとってあります。
Webでの公開を行うためには、そのための専用のレイアウトを作っておくのが便利です。レイアウトにないフィールドは使用するテーブルにあっても使えないので、デザインはいい加減でもいいので、ともかくレイアウトを作り使用するフィールドを配置しておく必要があります。ここでは「web_calendar」という名前のレイアウトを作りますが、ポイントになる設定を図で示します。
できあがったレイアウトにポータルを配置して「スケジュール_Web」テーブルの内容を表示できるようにしておきます。以下、ポータルの設定のポイントを図で示します。
実際のカレンダー表示では、通常は月ごとにと考えるかもしれませんが、そのためのインタフェースはちょっと複雑です。ここでは、データの入力はFileMaker Proで行い、Webはもっぱら確認だけのためにあるとするのなら、必ずしも1ヶ月単位での表示が使いやすいわけではありません。たとえば、会議室のスケジュールをこうした方法で管理してWeb公開するのなら、過去がどうなっているかはあまり問題ではなく、近々、どれくらいスケジュールが埋まっているのかが問題です。ということで、ここでは、今日を含む週から、適当に、100日間後までというような表示を考えます。そのため、以下のように、「毎日」テーブル定義に、フィールド「今日から何日後」を定義します。このフィールドは計算フィールドで画面に見えているような式を設定します。
Web公開をするためには、どうしても、データベースをFileMaker Serverで公開する必要があります。そのため、公開可能とするためのアクセス権の設定が必要になります。データベースに最初から存在するAdminに対してパスワードをつけるのがまずは基本作業となります。
ここでは、Webアクセスのためのアカウントは、既定の「ゲスト」を使うことにします。まず、ゲストをアクティブにして、利用できるようにします。
そして、「ゲスト」に対応づけられているアクセス権セットの「閲覧のみアクセス」の拡張アクセス権で、「XSLT Web公開によるアクセス」にチェックを入れます。これで、カスタムWebからの参照時には、認証なしにデータベース利用ができるようになります。もちろん、書き込み権限は与えられていませんが、ここで紹介する範囲では読み込みだけで必要なデータは得られます。
さらに、FileMaker ProからFileMaker Serverで公開しているデータベースへのアクセスができるようにします。実際にはユーザを作るなどしますが、ここでは、少なくともAdminはFileMaker Serverへのアクセスができるようにしたいので、関連づけられている「完全アクセス」のアクセス権セットにある拡張アクセス権で「FileMakerネットワークによるアクセス」のチェックを入れておきます。
ここまで準備をして、XSLファイルを作成しますが、一度、FileMaker Site Assistantを利用して、ひな形となるサイトを作ってしまいます。レイアウトとしては、「web_calender」を適当に選んでこれを使った一覧が出るなどしておけばいいでしょう。そして、自動的に作られるerrorreply.xsl、utilities.xslのファイルをそのまま活用します。そして、以下のcal.xslを使用しますが、このファイルはrecordlist.xslをベースに改造したものです。
実際のレイアウトに対して、ブラウザから参照すると、それなりにカレンダーで見えています。複数月を表示するので月の変わり目に境界線を引きたいところですが、実際のデータに応じた処理をしながら、スタイルシート情報のカスケード機能をうまく使ってこうした表示をできるようにしました。ダブルブッキングのスケジュールはそれぞれが赤字で表示されるようにしました。なお、ソースのcal.xslでは、「祝日休日」フィールドに入れた祝日名などがカレンダーに出てくるようにもなっています。
指摘するまでもないことですが、これでカレンダー表示の大枠は可能ですが、実際のアプリケーションとしては詰めはまだまだ甘いところもあります。実際の使用形態に合わせて機能を作り込むことになりますが、基本的な考えはここまでの方法から始めることで、カレンダー形式の表示という機能は組み込めるはずです。
<?xml version="1.0"encoding="UTF-8"?> <xsl:stylesheet exclude-result-prefixes="xsl fmxslt fmrs fmq" version="1.0" xmlns:fmq="http://www.filemaker.com/xml/query" xmlns:fmrs="http://www.filemaker.com/xml/fmresultset" xmlns:fmxslt="xalan://com.fmi.xslt.ExtensionFunctions" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <?xslt-cwp-buffer buffer-content="false" ?> <?xslt-cwp-query params="-db=%e3%82%ab%e3%83%ac%e3%83%b3%e3%83%80%e3%83%bc&-lay=web_calendar&%e4%bb%8a%e6%97%a5%e3%81%8b%e3%82%89%e4%bd%95%e6%97%a5%e5%be%8c=-6..1200&%e4%bb%8a%e6%97%a5%e3%81%8b%e3%82%89%e4%bd%95%e6%97%a5%e5%be%8c.op=bw&-grammar=fmresultset&-encoding=UTF-8&-find"?> <!-- --> <xsl:param name="request-query"/> <xsl:param name="client-user-name"/> <!--ユーティリティ/エラースタイルシートを含めます--> <xsl:include href="utilities.xsl"/> <xsl:include href="errorreply.xsl"/> <xsl:output encoding="UTF-8" indent="yes" method="html"/> <!--このセッションで検索条件を保存する変数--> <xsl:variable name="stored-find"> <xsl:call-template name="store-find-request"/> </xsl:variable> <xsl:template match="/fmrs:fmresultset"> <xsl:choose> <!--[エラー応答] の表示--> <xsl:when test="$_errorcode != $_errorcode-none and $_errorcode != $_errorcode-no-records"> <xsl:call-template name="display-error-reply"/> </xsl:when> <!--[レコード一覧] の表示--> <xsl:otherwise> <html> <head> <title>カレンダー</title> <style> h1 { text-align: center; padding: 4px; background-color: #BBBBBB; } h2 { background-color: #DDDDDD; font-size: 14pt; } td { vertical-align: top; padding: 0;} .firstcolumn { background-color: #CCCCCC; text-align: center;font-weight: bold;} .sunday { background-color: #FFAAAA; } .saturday { background-color: #AAAAFF; } .firstday { border-left: 2px solid black; } .firstweek { border-top: 2px solid black; } .weekenddate { text-align: right; } .weekdaydate { text-align: right; background-color: #BBBBBB; } .item {font-size: 10pt; } .sc-time { font-size: 9pt; color: #555555; } .doublebook { color: #FF0000; } .natinalday { font-size: 9pt; } </style> </head> <body bgcolor="#ffffff" text="#000000"> <!-- <xsl:value-of select="fmxslt:url_encode('今日から何日後','UTF-8')" /> --> <h1>カレンダー</h1> <table border="1" cellspacing="0" cellpadding="2" bordercolor="#BBBBBB" width="100%"> <tr> <td class="firstcolumn" width="70">年/月</td> <td class="sunday" align="center">日</td> <td align="center">月</td> <td align="center">火</td> <td align="center">水</td> <td align="center">木</td> <td align="center">金</td> <td class="saturday" align="center">土</td> </tr> <xsl:for-each select="/fmrs:fmresultset/fmrs:resultset/fmrs:record"> <xsl:variable name="curPos" select="position()" /> <xsl:if test="fmrs:field[@name='曜日']/fmrs:data = 1"> <tr> <td class="firstcolumn" > <xsl:choose> <xsl:when test="fmrs:field[@name='日']/fmrs:data < 8"> <xsl:attribute name="class">firstcolumn firstweek</xsl:attribute> </xsl:when> <xsl:otherwise> <xsl:attribute name="class">firstcolumn</xsl:attribute> </xsl:otherwise> </xsl:choose> <xsl:value-of select="fmrs:field[@name='年']/fmrs:data"/>年<br /> <xsl:value-of select="fmrs:field[@name='月']/fmrs:data"/>月 </td> <xsl:call-template name="show-one-day"> <xsl:with-param name="oneday" select="../fmrs:record[$curPos]" /> </xsl:call-template> <xsl:call-template name="show-one-day"> <xsl:with-param name="oneday" select="../fmrs:record[$curPos+1]" /> </xsl:call-template> <xsl:call-template name="show-one-day"> <xsl:with-param name="oneday" select="../fmrs:record[$curPos+2]" /> </xsl:call-template> <xsl:call-template name="show-one-day"> <xsl:with-param name="oneday" select="../fmrs:record[$curPos+3]" /> </xsl:call-template> <xsl:call-template name="show-one-day"> <xsl:with-param name="oneday" select="../fmrs:record[$curPos+4]" /> </xsl:call-template> <xsl:call-template name="show-one-day"> <xsl:with-param name="oneday" select="../fmrs:record[$curPos+5]" /> </xsl:call-template> <xsl:call-template name="show-one-day"> <xsl:with-param name="oneday" select="../fmrs:record[$curPos+6]" /> </xsl:call-template> </tr> </xsl:if> </xsl:for-each> </table> </body> </html> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="show-one-day"> <xsl:param name="oneday" select="-1" /> <td> <xsl:attribute name="class"> <xsl:choose> <xsl:when test="$oneday/fmrs:field[@name='祝日休日']/fmrs:data != ''"> <xsl:text> sunday</xsl:text> </xsl:when> <xsl:when test="$oneday/fmrs:field[@name='曜日']/fmrs:data = 1"> <xsl:text> sunday</xsl:text> </xsl:when> <xsl:when test="$oneday/fmrs:field[@name='曜日']/fmrs:data = 7"> <xsl:text> saturday</xsl:text> </xsl:when> </xsl:choose> <xsl:if test="($oneday/fmrs:field[@name='日']/fmrs:data = 1) and ($oneday/fmrs:field[@name='曜日']/fmrs:data != 1) "> <xsl:text> firstday</xsl:text> </xsl:if> <xsl:if test="$oneday/fmrs:field[@name='日']/fmrs:data < 8"> <xsl:text> firstweek</xsl:text> </xsl:if> </xsl:attribute> <div class="date"> <xsl:attribute name="class"> <xsl:choose> <xsl:when test="($oneday/fmrs:field[@name='曜日']/fmrs:data = 1) or ($oneday/fmrs:field[@name='曜日']/fmrs:data = 7)"> <xsl:text>weekenddate</xsl:text> </xsl:when> <xsl:otherwise> <xsl:text>weekdaydate</xsl:text> </xsl:otherwise> </xsl:choose> </xsl:attribute> <span class="natinalday"> <xsl:value-of select="$oneday/fmrs:field[@name='祝日休日']/fmrs:data" /> <xsl:text> </xsl:text> </span> <xsl:value-of select="$oneday/fmrs:field[@name='日']/fmrs:data" /> <xsl:text>日(</xsl:text> <xsl:value-of select="fmxslt:convert_datetime(fmxslt:get_fm_date_format(), 'E', $oneday/fmrs:field[@name='日付']/fmrs:data)"/> <xsl:text>)</xsl:text> </div> <xsl:for-each select="$oneday/fmrs:relatedset[@table='スケジュール_Web']/fmrs:record"> <div class="item"> <span class="sc-time"> <xsl:value-of select="fmxslt:convert_datetime(fmxslt:get_fm_time_format(), 'H:mm', fmrs:field[@name='スケジュール_Web::開始時刻']/fmrs:data)"/> <xsl:text>〜</xsl:text> <xsl:value-of select="fmxslt:convert_datetime(fmxslt:get_fm_time_format(), 'H:mm', fmrs:field[@name='スケジュール_Web::終了時刻']/fmrs:data)"/> </span> <xsl:text>:</xsl:text> <span> <xsl:attribute name="class"> <xsl:if test="fmrs:field[@name='スケジュール_Web::重複あり']/fmrs:data > 1"> <xsl:text>doublebook</xsl:text> </xsl:if> </xsl:attribute> <xsl:value-of select="fmrs:field[@name='スケジュール_Web::概要']/fmrs:data"/> </span> </div> </xsl:for-each> </td> </xsl:template> </xsl:stylesheet>