FileMakerでよく使う定番の小ネタがあります。タグ的なフィールドの仕組みもその一つ。キーワードとかタグとか言われているメタデータ的なアレです。レコードにタグ付けを行い、素早くフィルターして絞り込めるようになります。
サンプルファイルをダウンロードできます。このファイルに即した内容となっております。
タグ的なるもの
ここで取り扱うタグ的なものは、専用テーブルで展開するような立派なデータではなくて、フィールド内のテキストだけで済ませるシンプルなものです。
この投稿ではタグということで進めますが、キーワードであっても意味は同じとします。
仕組み
先にどういう仕組みかざっくり書いておきます。
二つのフィールドを使います。入力・表示用のフィールドと、それを計算で改行区切りのリスト化するフィールドです。データとしては改行区切りリストのほうがメインで、リストであるから値一覧に登録し、検索でも使います。
入力されたテキストを改行リストに変換するために、入力にはルールが必要です。カンマ、または改行、またはカンマ+改行、そのあたりを区切りのルールとして定め、計算式で改行リストを作るんです。このルールにスペースを含めないほうが良いです。スペースを含む言葉が分解されちゃいますからね。
ここは好き好きな部分でもありますね。「#」を付けることで区切りたい人もいるかもしれません。要は、改行区切りに計算できさえすれば何でもいいわけです。
表示と入力
ここでは、表示はカンマ区切り、入力はカンマ区切りでも改行区切りでもどっちでもOKとします。
テキスト入力で登録しますが、登録済みのリストからポップアップやチェックボックスで追加や削除を行いたいところ。
ポップアップでは、タグ欄に入力済みの言葉を選べば取り除き、入力されていない言葉を選べば入力される仕組みです。
チェックボックスではチェックを付けたり外したりするだけ。
フィルタ
タグやキーワードはフィルタしてなんぼです。簡易フィルタ機能を備えましょう。ポップアップまたはチェックボックスでフィルタします。後ほど。
複数レコードに対応した処理
タグに関する高度な機能を最後のほうに追記しました。タグ名を変更したり、まとめて追加したり削除したりします。
仕組み
二つのフィールドと一つの値一覧
まず二つのフィールドで成り立ちます。tagsフィールドとtagsDispフィールドです。tagsフィールドは計算式フィールド、tagsDipsはテキストフィールドですが計算式が含まれます。
レイアウトに表示するのは tagsDisp で、入力と表示を司ります。tags は、tagsDips から計算によって作り出され、データ登録の要となります。
tagsDisp
tagsDispは表示と入力に使います。入力の際のルールは、タグの区切りをカンマ(またはカンマ+スペース)または改行とします。テキストフィールドですが計算式を仕込みます。
どういう計算式かというと、内容を一旦リストに変換してからカンマ区切りに戻します。
- カンマ(またはカンマ+スペース)を改行(¶)に置換してリスト化
- ユニークなリストに変換
- 改行をカンマ区切りに置換して戻す
- 先頭や末尾にカンマが残れば取り除く
カンマ、カンマ+スペース、改行、いずれの方法で入力しても、結果はカンマ+スペースで区切られたテキストになります。
一旦リスト化してからカンマ区切りに戻すだけですが、なぜそんな手順を踏む必要があるのか。一旦リスト化することで重複を避けられるし、区切り入力の誤りがあれば確認できます。
くどくど書くとこのような計算式です。
Let ( [ tags = Self ; tags = Substitute ( tags ; [ " , " ; ¶ ] ; [ ", " ; ¶ ] ; [ " ," ; ¶ ] ; [ "," ; ¶ ] ) ; tags = UniqueValues ( tags ) ; tags = Substitute ( tags ; ¶ ; ", " ) ; tags = If ( Right ( tags ; 2 ) = ", " ; Left ( tags ; Length ( tags )-2 ) ; tags ) ; tags = If ( Left ( tags ; 2 ) = ", " ; Right ( tags ; Length ( tags )-2 ) ; tags ) ]; tags )
tags
tagsは計算式フィールドで、 tagsDisp の内容をリスト化したものです。tagsDisp は一旦リスト化してカンマ区切りに戻しますが、こちらはリスト化までで終了です。
レイアウト上に表示する必要はありませんが、検索のためにレイアウトの外に配置しておくのが望ましいかもしれません。
無駄なことやってるように見えますが、この無駄こそが大事です。この改行区切りテキストの用途は、値一覧に登録することです。
計算式はこうなりました。
Let ( [ tags = tagsDisp ; tags = Substitute ( tags ; [ " , " ; ¶ ] ; [ ", " ; ¶ ] ; [ " ," ; ¶ ] ; [ "," ; ¶ ] ) ; tags = UniqueValues ( tags ) ; tags = SortValues ( tags ) ; tags = tags & ¶ ; tags = Substitute ( tags ; [ ¶ & ¶ ; ¶ ];[ ¶ & ¶ ; ¶ ] ) ]; tags )
最後に改行を一つ加えています。検索のためですが、ここで入れなくても検索スクリプトで入れればいいかなとも思います。
値一覧 tags
値一覧をひとつ作成し、対象フィールドを tags(改行区切りのリストのほう) に指定します。
(FileMakerでは以前からときどきボタンが黒く潰れたり文字が読めなくなることがあります。原因は知っていますが作りの雑さはもう諦めてます)
入力補助のスクリプト
ポップアップによる入力
まず入力用のグローバルフィールド「入力用グローバル」を一つこしらえて、フィールドの隣に配置、スタイルをポップアップメニューにして値一覧 tags を表示させ、スクリプトトリガをセットします。
スクリプトトリガはこんな感じ。
OnObjectEnter に「ウインドウ再表示」を割り当てています。このスクリプトは「ウインドウ内容の再表示」ステップだけが記されています。触った途端に更新されるようにそうしています。グローバルフィールドを使い回すので。それだけの理由です。
OnObjectExit には、フィールドへ移動(行き先指定なし)と再表示の2ステップのスクリプトをセット。ポップアップのフィールドがいつまでもアクティブになっているのが不細工ですからこうしています。
肝心なのは OnObjectModify にセットした「値一覧からpopup入力」スクリプトです。
値一覧からpopup入力
スクリプトの流れは次の通り。
変数 $item に Get(アクティブフィールド内容) … 選んだアイテムを保存しておきます。
$item が tags フィールドにあれば削除し、なければ追加します。まず tags に $item があるかないかを見極める必要があります。
例えば「key」「key2」「key3」とタグがあった場合、$item が「key」なら、「Key」のみがヒットしなければなりません。「key2」も選んでしまうようなのは駄目ですね。
こういうときどうすればいいのか悩ましいかもしれません。最終的には FilterValues を関数を使うことに落ち着きました。
FilterValues ( TagLike::tags ; $item )
こんな感じで、この関数の結果があるかないかで分岐します。if のあと、こうします。
IsEmpty ( FilterValues ( TagLike::tags ; $item ) )
空の場合は追加します。空でなければ、削除します。
追加する
$item を tagsDisp フィールドに追加します。tagsDisp が空なら $item をセットするだけ、空でなければ、tagsDisp & ¶ & $item と、改行を加えてから追加します。あとはtagsDisp フィールドの計算式が勝手に整えてくれます。
削除する
tagsDisp から $item を見つけて削除します。こうしてみました。
tagsDisp をリスト化して最後の行にも改行を入れておきます。置換で $item & ¶ というふうに改行を含めて検索して空に置換します。
Let ( [ tL = Substitute ( TagLike::tagsDisp ; [ ", " ; ¶ ] ) & ¶ ; tL = Substitute ( tL ; $item & ¶ ; "" ) ; tL = Substitute ( tL ; [ ¶ & ¶ ; ¶ ];[ ¶ & ¶ ; ¶ ] ) ]; tL )
フィールド設定やこのスクリプトで、リストの最終行に改行を加えることを二重にやってしまっているようなので、最後に焦って改行の連続を消しています。みっともないままですいません。
以上、ポップアップから選んで追加したり消したりできるようになりました。
チェックボックスによる入力
キーワードやタグって、ポップアップよりチェックボックスでの入力が望ましいと思えるかもしれません。
ポップアップと同じく、グローバルフィールド「入力用グローバル」に表示させましょう。表示するのは、改行テキスト即ちリストであるところの tags フィールドです。「そのレコードのtags」です。
チェックボックスはアイテムを見渡せるし操作が簡単ですが、その分レイアウトの場所を取ります。また、アイテムが増えたときにスクロールできない欠点もあります。ポップオーバーでは多少緩和されますが、アイテムが大量に作られるような想定だと、やはり専用テーブルを用いた方法が良いかもしれません。
チェックボックスのためのスクリプトは2つ必要で、一つ目はグローバルフィールドにそのレコードの現在のtagsを表示させます。二つ目はチェックを tagsDisp に反映させます。
チェックボックス表示のスクリプト
グローバルフィールドにtagsフィールドを表示さます。何かと汎用性にこだわる筆者は、ここでもスクリプト引数を使います。スクリプト引数に対象のフィールド名をフルで書いておくことで、同じ目的のフィールドが増えても引数を変更するだけで対応できます。ここでは “TagLike::tags” と括って書きます。それから、ウインドウ内容の再表示を必ず追加します。
チェックボックス内容を表示させるこのステップは、もしレイアウト上に直接置くなら(上図の上) OnRecordLoad に、ポップオーバーで一手間かけるなら(上図の下)ポップオーバーのウインドウの OnObjectEnter に仕込みます。
チェックボックスのスクリプト
グローバルフィールドの OnObjectModify にセットするチェックボックス反映のスクリプトです。
まずその前に例によってスクリプト引数には、入力用であるDispのフィールドを指定しておきますね。“TagLike::tagsDisp” です。汎用性のためです。
変数 $item に Get(アクティブフィールド内容) を指定します。見た目はチェックボックスですが、実際の内容はチェックを付けたアイテムの改行テキスト即ちリストです。
次にフィールド設定です。スクリプト引数に指定したフィールド名、ここではtagsDisp ですが、その内容をすべて $item で置き換えます。
チェックしたもの外したもの、それらを一個ずつ判断するのではなく、フィールド全部を置き換えるわけです。単純な話です。
フィルタ
タグやキーワードを付けるのはフィルタするためですね。簡易なフィルタを実施しましょう。グローバルフィールドを一つこしらえてフィルター用に使います。
基本、検索するだけです。検索対象となるフィールドは改行テキストの tags フィールドです。レイアウト上に表示する必要はないけど、レイアウトの外にフィールドを置いておきましょう。検索するとき、検索モードで「フィールドを名前で設定」スクリプトステップを使うからです。フィールドがないと検索できません。
ポップアップによるフィルタ
ポップアップメニューで選んだタグを検索します。一つのタグを検索するだけのシンプルな仕組みですが、検索語の書き方に一工夫いります。
例えば「タグ」「タグ1」「タグ2」というタグがあったとして「タグ」で検索すると全部ヒットしてしまいます。また、検索モードで「==タグ」としても駄目ですね、複数行のタグあればイコールにならないのでヒットしません。「”タグ”」と囲っても上手くいきませんでした。「タグ」と同じ結果になってしまいます。これは困ったな。
検索って難しいですね。「 *”タグ”」これで何とかなりました。「”タグ”*」これでも何とかなりました。どうも腑に落ちず納得できないんですが、現時点で最良と思えますので採用しています。もっと良い書き方をご存じの方は教えてください。
というわけでスクリプトはこんなのですが、これをトリガ OnObjectModify にセットしています(例によって、対象フィールド名をスクリプト引数としています)
チェックボックスによるフィルタ
チェックボックスでは複数の値を選択できますね。この複数の値をどのように検索するか、ここがややこしいところになります。
つまり、複数の値をチェックしたときに「チェックしたタグをすべて含むレコードを検索」するのか「チェックしたタグのいずれかを含むレコードを検索」するのかです。絞り込みか、追加か、即ち and 検索か or 検索かですね。
最初、この投稿では絞り込みだけを前提に書いていました。でもやっぱり追加もいるでしょう、ってことで、スクリプトを二つ作ることとなりました。
全てを含む = 絞り込み = and 検索
まずは「絞り込み」です。スクリプトを次のようにしました。
- チェックされたアイテムが1個なら普通に検索
- チェックが二つ以上なら、最初のアイテムで検索した後、次のアイテム以降は絞り込み検索を繰り返す
最初にチェックアイテムをリスト化して行数を測ります。
検索の指定って難しいですね。フィールドに対して *”テキスト”と書くことで多分上手く検索できますが、何だか腑に落ちてはいません。
いずれかを含む = 追加 = or 検索
「いずれかを含む」検索は絞り込みより簡単です。
絞り込みでは、値のリストを作ってまず最初の一つで検索、ループで残りを「絞り込み検索」としました。
or 検索ではそういうややこしいことをせず、複数の値がある場合、ループで「新規検索条件」とフィールド設定を追加していけば良いだけです。
詳しくは載せませんが、ファイルをダウンロードしてスクリプトを参照してください。
高度な編集
こっそり更新し、高度な編集機能を付け加えました。特定のタグを指定し、次のような操作が行えます。
- 対象レコードに指定したタグを追加する
- 対象レコードから指定したタグを取り除く
- 指定したタグを削除し、適用されているすべてのレコードから取り除く
- 指定したタグの名前を変更する
ポップアップから選ぶなり手動で記入なりして selectedTag フィールドに目的タグを書き入れ、指定タグとします。この指定タグに関するスクリプトを三つ加えました。
対象レコードすべてに追加する
指定タグを対象レコードすべてに追加するスクリプトはすごく簡単です。「フィールド内容の全置換」を使って、tagsDisp フィールドに「改行+指定タグ」を追記するだけです。
tagsDisp が空であることに備えたり細かい調整はしますが、改行¶ と指定タグを追加することで、あとはフィールドの計算式が上手くやってくれます。
対象レコードから取り除く
全置換でフィールド「tagsDisp」を計算します。tagsDipsをリスト化して、最後に改行を加え、指定タグ&¶ を “” に置換してからカンマ区切りに戻し、余計なカンマを削る調整をします。
次の「すべてから取り除く」と同じような処理ですが、対象レコードのまま置換するだけなので単純です。
すべてのレコードから取り除く
指定タグをレコードから取り除くのはもう少し複雑になります。同じく全置換を使いますが、レコードが多すぎるといけないので事前に検索して指定タグを含んだレコードのみを対象レコードとしておきます。
全置換では、tagsDisp に対して次の手順で計算させます。
まずtagsDispの内容を検索置換を使って改行リストに変換します。リストの最後にも ¶ を付け加えておきます。そのリストに対して「指定タグ+¶」を検索し空 “” に置換します。
最後に、置換の結果冒頭に ¶ が入ってしまう可能性があるので調整しておきます。
以上終わり。あとはフィールドの計算式が上手くやってくれます。
スクリプトは以上ですが、最初に検索を入れているので対象レコードが変わってしまいます。「対象レコードを保存」「保存した対象レコードを復帰」のスクリプトを用意していないなら、作業全体を新規ウインドウでやって最後閉じればいいですね。
名前を変更する
名前を変更するスクリプトは「取り除く」とほぼ同じスクリプトです。「取り除く」では、指定タグ+¶ を空 “” に置換しましたが、こちらは $変更後テキスト に変更します。
おっと。そうですね、最初に $変更後テキスト が必要ですね。フィールドを作っても良いし、ダイアログの「入力」を使って促してもいいでしょう。ここでは、ダイアログを使っています。
以上、高度な編集機能でした。
ここで使用したファイルをダウンロードできます。お試しあれ。