こんにちは。
ライジングサン・システムコンサルティングの岩佐です。
この記事では、カンバンボードUIの実装を通して、FileMakerのソリューションにドラッグ&ドロップの操作性を組み込む方法を解説します。御存知の通り、FileMakerは標準機能としてドラッグ&ドロップの操作はサポートしていません。
しかし、唯一ファイルメーカーのレイアウトオブジェクトでドラッグ&ドロップ操作が可能なオブジェクトフィールド内のコンテンツ、スクリプトトリガ、そして非保存の計算フィールドをうまく組み合わせることで、簡易的なドラッグ&ドロップ機能を実装することが可能です。
それでは解説をスタートしましょう。
目次
1.カンバンボードUIとドラッグ&ドロップ
ドラッグ&ドロップによる操作は様々な利用シーンでより快適なユーザ体験をもたらします。今回はその中でも特にドラッグ&ドロップ操作が快適な操作感をもたらす「カンバンボード」のユーザインタフェース構築を通して、実装手順を解説したいと思います。
1-1.カンバンボードとは?
カンバンボードとは、こちらの動画のように、4つのレーン上(列)に、チケットと呼ばれる管理対象情報を記録した作業伝票を、作業の優先順位順に表示するユーザインタフェースです。アジャイルソフトウェア開発における進捗共有ではおなじみのユーザインタフェースです。
このチケットには、一般的に「誰が」「いつまでに」「何をやるか」のToDo情報が記録されています。
利用想定としては、まずいちばん左側のアイスボックスというレーンに、チケットを登録します。このときはあまり優先順位や細かい操作手順などの詳細情報は考えず、それ以上に作業漏れがないようにできるだけたくさんの作業項目を列挙していきます。
その後、それぞれのチケットにより詳細な作業情報や担当者、そして締め切りなどを設定します。これらの設定が完了したら、ひとつとなりのToDoというレーンに移動します。
ToDoのレーンは、作業伝票を優先順位順に並べます。つまり、並べられたチケットを上から順に対応していくことを想定してチケットの順番を並び替えます。
次にそのチケットの作業に着手するタイミングで、ひとつ右側のDoingに移動します。これで「今」「誰が」「何の仕事をしているのか」がひと目で確認することができます。
そして最後に、作業中の仕事が完了したら、ひとつ右側のDoneに移動します。
このようなユーザインタフェースを提供することで、一般的なToDoリストよりも、作業の偏りやボトルネックが非常に見つけやすくなり、また実際に消化したタスクの量も視覚的に確認できることで、担当者の仕事に対する動機づけも同時に満たすことができます。
1-2.カンバンボードUIに求められる機能要件
上記のようなユーザインタフェースを実現するにあたって必要となる機能要件を簡単にまとめると次のとおりになります。
- 予め準備された4つのレーンに、チケットをリスト表示したい。
- チケットには「誰が」「いつまでに」「何をする」の情報を記録したい。
- リスト内のチケットはドラッグ&ドロップで、ソート順(作業の優先順位)を変更できるようにしたい。
- レーンをまたぐチケットの移動もドラッグ&ドロップで実現したい。
- レーンをまたぐ場合も、ドロップした場所にチケットを表示したい。
- 今回、レーンの追加・削除機能は要求外とする。
1-3.ドラッグ&ドロップ操作で考慮すべき処理フロー
FileMakerに限らず、ドラッグ&ドロップ操作をプログラミングで実装するにあたって、必ずプログラマが考えなければならないことは次の3点です。
- ドラッグ&ドロップ操作で情報が書き換えられる対象物(FileMakerの場合はレコード)は何か?
- ドラッグ操作のスタート位置はどこか?(どのレコードが操作対象か?)
- ドロップした位置はどこか?(どうアップデートすればよいか?)
上記3つの情報を取得することで、何の情報(対象物)を、何から(ドラッグ元に関連する情報)、何に(ドロップ先に関連する情報)書き換えれば要件をみたすことができるのかを定義することができます。
それではこれら3つの視点を意識しながら、具体的なFileMakerプラットフォームにおける実装方法を解説して行きましょう。
2.ソリューションの全体像
今回構築するソリューションのユーザインタフェースはこのようなレイアウトです。フォームレイアウトに4つのポータルを配置し、それを「レーン」と呼びます。この4つのレーンにはそれぞれ「Icebox(アイデア)」「ToDo(未着手)」「Doing(作業中)」「Done(完了)」の4つのタイトルが設定されています。
2-1.チケットテーブル / カンバンレーンテーブル
今回はKanbanLaneテーブルと、Ticketテーブルの2つのテーブルを用意します。具体的なスキーマに関しては、こちらのGoogleスプレッドシートに、実際のソリューションから出力したDDRを元とした情報をアップロードしているので、こちらをご確認下さい。カンバンレーンテーブルとチケットテーブルの関係性は1:nです。
カンバンレーンテーブル
https://docs.google.com/spreadsheets/d/1qIJofp5EK1gd-TBGjIo1D6mM8PcRGK4Evgdj097riR0/edit?usp=sharing
チケットテーブル
https://docs.google.com/spreadsheets/d/16L8mJYj-EsbOO4xkL7LsAFdV1VCzgg2qevNFxIV_pWQ/edit?usp=sharing
今回用いるテクニックでは、チケットテーブルに、ドラッグ&ドロップ操作のハンドルとなるオブジェクトフィールド、及び後述する「ドロップ操作が完了した場所」を取得するための非保存の計算フィールドが必要となります。
具体的にはチケットテーブルの以下のフィールドを使って、ドロップ操作が完了した場所を取得しています。詳しくは後述します。
フィールド名 | タイプ | 保存 | 目的 |
dragDrop_HandleObject | オブジェクト | ノーマル | ドラッグ&ドロップ操作のハンドル |
dragDrop_HandleFileName | 計算 | 保存 | ハンドルオブジェクトが更新されたときに再評価 |
dragDrop_TargetJSONObject | 計算 | 非保存 | ドロップされた場所情報を格納するJSONオブジェクト |
2-2.カンバンボードレイアウト
こちらがカンバンボードレイアウトをデザインモードで表示した時の様子です。レーンと表現している4つのポータルが並列に並び、そのポータル上に、それぞれのレーンに表示すべきチケットのコントロールが配置されていることが確認できると思います。
ポータル上に配置されているチケット情報を表現するオブジェクトには、それぞれ以下のコントロールが配置されています。
オブジェクト名 | コントロールタイプ | 説明 |
Icebox.AccountAvatarImage | オブジェクトフィールド | チケット担当者のアバター画像 |
Icebox.TicketDescription | テキストフィールド | チケットの詳細説明文 |
Icebox.Shape | シェイプ | チケット左辺のリボン |
Icebox.InfoButton | ボタン | チケットのカードウインドウを起動するボタン |
Icebox.TicketTitle | テキストフィールド | チケットのタイトル |
Icebox.DragDropHandle | オブジェクトフィールド | ドラッグ&ドロップ操作のハンドル |
Icebox.DragDropHandle.Dummy | オブジェクトフィールド | ダミーチケットの操作ハンドル |
具体的にどのような配置、及びZオーダーになっているのかは、こちらの動画でご確認いただけます。
2-3.チケット詳細のカードウインドウ
それぞれのレーンには、チケットと呼ばれる「作業伝票」を表示しています。チケットの詳細情報、及びアップデートはチケット右上のインフォメーションアイコンをクリックすると、カードウインドウで表示されます。
チケットは、タイトル、説明文、締切日、そして担当者といった属性情報を持っています。
3.実装するにあたっての考察すべき要点
3-1.ドラッグを開始した位置のレコードを識別する
ドラッグ&ドロップ操作というのは、まず対象のオブジェクトのドラッグ操作をスタートした場所を特定しなければなりません。つまり「どのチケットを動かそうとしているのか」です。
この情報を取得するには、OnObjectEnterスクリプトトリガを使います。そしてこのスクリプトトリガを仕掛けるのは、ドラッグ&ドロップ操作のハンドルとなるオブジェクトフィールドです。実際のオブジェクト名称だと Icebox.DragDropHandle
になります。
このときに発火するスクリプトは、rsc_DragDropPortalRow_startDragです。それぞれ、スクリプトの内容と、設定されている引数は以下のとおりです。
スクリプトの内容
変数を設定 [$$DragDrop_StartDrag; 値:Get ( スクリプト引数 )]
スクリプト引数
JSONSetElement ( "" ;
[ "id" ; T01d.Tickets|1_Icebox::__id ; JSONString ];
[ "KanbanLaneID" ; T01d.Tickets|1_Icebox::_fk_KanbanLaneID ; JSONString ];
[ "SortNo" ; T01d.Tickets|1_Icebox::sortNo ; JSONNumber ]
)
処理の内容としては、単純にOnObjectEnterイベントに設定された引数を、 $$DragDrop_StartDrag
というグローバル変数にセットしているだけです。そして、設定される引数は、ドラッグをスタートした位置のレコードID、レーンを識別するレーンID、そして現在のソート番号です。(※厳密にはソートNoの必要性はありませんがデバッグ情報として取得しています。)
この仕掛によって、ドラッグ操作がどこから開始されたか、つまりどのチケットを動かそうとしているのかを、グローバル変数に取得することができます。
3-2.ドロップした位置を取得する。
次に、ドラッグを開始したチケットが、最終的にどこにドロップされたのか、つまり移動先の情報を取得します。この仕掛けは、先程と同じ Icebox.DragDropHandle
のオブジェクトフィールドに仕掛けたOnObjectModify スクリプトトリガ内で実現します。
ここでFileMakerのドラッグ&ドロップ操作の特徴的な動きを理解しておく必要があります。FileMakerのオブジェクトフィールドをドラッグ&ドロップすると、OnObjectModifyのトリガが発火する場所は、ドロップされた先のオブジェクトなのですが、アクティブなレコードの位置はドラッグを開始した位置のままです。
つまり、OnObjectModifyで発火するスクリプトで、アクティブなレコードID等をひろったとしても、それはドラッグ操作を開始した位置のレコードIDであり、ドロップ先のレコードIDではないのです。つまりOnObjectModifyイベントのみでは、ドロップ先の情報を取得することはできません。
ですので、何らかの工夫を施してドロップした場所の情報を取得する必要があります。今回は2-1で先述したとおり、ドロップした先のIDを取得する方法を、データベースのスキーマ側に仕掛けました。
具体的には、チケットテーブルに dragDrop_HandleFileName
という非保存の計算フィールドを作成し、そこでドラッグ&ドロップによるオブジェクトフィールドの値変更によって、非保存の計算フィールドが自動的に再評価されるFileMakerの特徴を使って、ドロップ先のレコード情報を取得します。
具体的に、 dragDrop_HandleFileName
には、次のような計算式が設定されています。
Let( [
~Result = GetValue(GetAsText(dragDrop_HandleObject) ; 1) ;
$$dragDrop_TargetID = __id ;
$$dragDrop_TargetJSONObject = JSONSetElement ( null() ;
[ "id" ; __id ; JSONString ];
[ "KanbanLaneID" ; _fk_KanbanLaneID ; JSONString ] ;
[ "SortNo" ; sortNo ; JSONNumber ] ;
[ "isDummyTicket" ; isDummyTicket ; JSONBoolean ] ;
[ "RecordOpenStatus" ; Get ( レコードのオープン状態 ) ; JSONNumber ]
)
];
~Result
)
この計算式は、スクリプトトリガではなくFileMakerのスキーマレベルで「非保存の計算フィールドの再評価」として発火します。発火するタイミングは、ドラッグされたオブジェクトが、移動先のオブジェクトフィールドにドロップした瞬間です。当然、ドロップした先のオブジェクトフィールドの中身(コンテンツ)が変わるので、ハンドルとなっているオブジェクトフィールドを計算値として参照しているこちらの式が再評価されます。
式の再評価は、オブジェクトフィールドの内容が変更されたレコード… つまり、ドロップ先のレコードで発生するので、そのレコードの計算フィールドで、レコードID等を含めたドラッグ&ドロップ処理に必要な情報を、グローバル変数にJSONオブジェクトとして取得します。上記の式では、レコードIDの他に、レーンを識別するためのIDやドロップした先のチケットのソート番号等を取得しています。
そして、グローバル変数のスコープは該当ファイル内のみで、他のファイルからは参照することができないので、このグローバル変数をそのまま非保存の計算フィールドとして dragDrop_TargetJSONObject
というフィールドで参照し、他のファイルからも参照可能な仕掛けを施しています。
3.3 ドラッグ&ドロップ処理に必要な情報を取得する仕掛けのまとめ
ここまで、ドラッグを開始したレコード情報の取得方法、及びドロップした先のレコード方法を取得する方法を解説してきました。特にドロップした先の情報を獲得する方法が、ある意味FileMakerチックで、スクリプトでは表現されない場所に隠されているので少し解りづらいかもしれません。しかし、別の側面から考えると、スクリプトのコーディングをすること無く、これだけの情報を簡単に取得できてしまうのも、FileMakerの良さのひとつだと思います。
4.ドラッグ&ドロップの処理プロセス
それでは、上記のような仕掛けを施したテーブルを使って、具体的にどのようなプロセスでドラッグ&ドロップが実現されるのかを、プロセスフローに従って解説していきます。
4.1 ドラッグの開始
まず、ユーザは操作対象のチケットのドラッグを開始します。この時、一般的な開発言語ではこの時点で StartDrag
等のイベントが発火するのですが、FileMakerでは標準機能としてドラッグ&ドロップをサポートしていないので、このようなイベントは発火しません。
しかし、ハンドルオブジェクトをドロップ…具体的には、マウスのメインボタンを解放したタイミングで OnObjectEnter
のスクリプトトリガが発火します。このスクリプトトリガで、「どのチケットのドラッグを開始したのか」を把握することができます。そして同時に、そのチケットはどのレーンに属しているのかも取得することができます。
4.2 ドロップイベント
ドラッグをスタートしたオブジェクトのドロップ地点情報を取得するには、先述の通り OnObjectModify
のスクリプトトリガを使います。 OnObjectModify
イベントが発火する理由は、ドラッグ&ドロップ操作のハンドルになっているオブジェクトフィールドの内容がドラッグ&ドロップ操作によって書き換えられるからです。ですので、OnObjectModify
イベントは、先の OnObjectEnter
が発火した直後に発生します。
ドロップイベントが発火したタイミングで、1-1で述べた以下すべての情報を取得することができます。
- ドラッグ&ドロップ操作で情報が書き換えられる対象物(FileMakerの場合はレコード)は何か?
- ドラッグ操作のスタート位置はどこか?(どのレコードが操作対象か?)
- ドロップした位置はどこか?(どうアップデートすればよいか?)
ですので、今回はこの OnObjectModify
イベントで発火するスクリプトに、ドラッグ元のレコードの書き換え処理、今回だとレーンIDとソート順の書き換え処理を実装しています。具体的なスクリプトは以下のとおりです。
レコードのアップデートは、2つのスクリプトで実行しています。ひとつは rsc_Kanban_DragDrop
で、もうひとつが @Tickets_UpdateTicket
です。 OnObjectModify
イベントで発火するrsc_Kanban_DragDrop
で、レコードの更新に必要なパラメータをJSONオブジェクトとして作成し、それを@Tickets_UpdateTicket
に渡して実際のレコードをアップデートしています。
4-3.レコードのアップデートで考慮すべきこと
レコードのアップデート処理で考慮しなければならないのは、レーンIDの必要性と、ソート番号の更新です。同一レーン内でのドラッグ&ドロップは、ソート順の付け替え処理のみですが、レーンをまたいだドラッグ&ドロップの場合は、レーンIDの付け替えと共に、ドラッグ元のレーン内にあるチケット、そしてドラッグ先にあるレーンのチケット双方のソート順の付け替えが必要になります。
※厳密には小数点以下の数字をうまく使うことで、ソート順の更新は必ずしも必須ではないのですが、今回は説明のしやすさを考慮してこのような形を取りました。
さらに、ソート順の付け替えは実装が容易なレコードの全置換を使っていますが、これはマルチユーザ環境下では推奨できる実装方法ではないので、その点についてはご注意下さい。
5.まとめ
この記事では、JavaScriptを使わずFileMakerの標準機能を応用することで、ドラッグ&ドロップ操作を実装する方法について解説してきました。実際には、JavaScriptを使ったほうが、見た目も操作感もより洗練され、分かりやすいものを実装できることは間違いありません。
しかし、普段FileMakerのみを使ってカスタムAppを開発しているFileMakerデベロッパーにとって、JavaScriptをゴリゴリと書きつつ、記述したJavaScriptとFileMakerとを連携させて素早くソリューションを開発するのはやはりハードルが高いのではないかと思います。
翻って、今回解説してきたとおりFileMakerでも非常にシンプルな仕掛けのみで、今回ご紹介したような動きを実現できることも事実です。開発するソフトウェアの機能要求や、どの程度のユーザ体験(UX)を実現したいのかにもよりますが、このような技をひとつ持っておくことで、開発できるソリューションの幅は確実に広がると思います。
今回も無料でダウンロードできるサンプルソリューションをご用意しておりますので、ぜひ実際に触ってみて、FileMakerの標準機能のみで実装したドラッグ&ドロップの操作感を体験してみて下さい。(ソースコードを見るには完全アクセス権が必要です。)
5-1.サンプルソリューションのダウンロード
まずは、今回ご紹介したソリューションをダウンロードして、実際に使ってみてください。
ダウンロードしたZipファイルを解凍すると、 KanbanBoard_V01.fmp12 と KanbanBoard_M01.fmp12 の2つのファイルが展開されます。KanbanBoard_V01.fmp12がUIのファイルとなるので、こちらを開いて下さい。
こちらのボタンからサンプルのカスタムAppをダウンロードしてください。
※Ver16以上のFileMakerPro/FileMakerProAdvancedが必要です。
※OSのバージョンや特定の環境下においては正常に動かない可能性もあります。
また、ダウンロードしたソリューションの中身を解析して、自分のソリューションに組み込んでいただくこともできます。
ぜひこちらから完全アクセス権の情報をご請求ください。