FileMakerでファイルを作るときに使い回す定番の小ネタ・小技があります。今回はソートボタンのスクリプト。
何がしたいかというと、リストのタイトル部分をクリックしたらソートされるようにしたいだけです。大概のリスト表示があるプログラムでよく見かけます。クリックでソートするだけでなくトグルで昇順降順が入れ替わります。
ポイントは二点あります。
- ソートを実行するスクリプト
- ボタンクリックごとに昇順・降順を切り替える仕組み
- 表示を司る仕組み
すいません。三点でした。ということでソートのスクリプトについてです。
ここではかつて二つの方法を記していました。
その1は汎用スクリプト1個 + 個別にソートのスクリプトを作りまくる方法、
その2はスクリプト一個(か2個)のみで済む代わりにフィールドを2個追加する方法を記しました。
時を経て、決定打的な三つめの方法を追加しました。
その3は、偉人から知恵を授かったので試してみたらすこぶる良い調子だったんでこれで決め打ちかという方法です。フィールドを使います。
はっきり言ってその1は昔使っていて今は使っていない駄目な例です。失敗例のひとつとしてしばらく残していましたが、役目を終えたと考え削除しました。
その2、または現時点で最良と思われる その3にお進みください。
その1 個別にソートスクリプトを作りまくる方法
その1は、汎用的なソートスクリプトを一個作り、さらに個別ソートのスクリプトを作りまくる方法です。以前この方法を好んで使っていましたが今は使わなくなりました。しばらくこの部分を残していましたが、惜しみなく削除しました。
その2 専用フィールドを作成する方法
その2は、ファイルメーカーのヘルプにも載っているやり方で、専用フィールドを作成して利用する方法です。
ヘルプに載ってるままではちょっと実用に耐えないので、少し工夫が必要です。また、大きな弱点もあります。
追記: その3の方法を追記しました。今となってはその2の方法も面倒ですので、最良の方法をお探しの方はすっ飛ばして その3へ ↓
材料
グローバルフィールドを一つ、計算フィールドを一つ追加します。
例としてグローバルフィールドを「GLBfieldName」、計算フィールドを「sortContent」と名付けてみます。
あとはソートのスクリプトが一つあれば良いです。実際の運用では、このソートスクリプトを発動させるための「ソートボタンのスクリプト」をもう一つ追加すれば良いでしょう。
理屈
グローバルフィールド GLBfieldName は、ソートする「フィールド名」を記入する入れ物です。ここにフィールド名を入れることで「おいお前、このフィールド名の内容でソートするんだぞ」と命じることになります。
計算フィールド sortContent は「あいよ、おまいさん。こうかい?」と、グローバルフィールドで指定したフィールド名の「フィールド内容」をそっくりそのまま転記してクローンと化します。
例えばグローバルフィールドに「氏名フィールド」と書いたら、計算フィールドは実際の「氏名フィールド」の内容をレコード分転記して「氏名フォールド」と同じになります。
スクリプトは、常に計算フィールド sortContent でソートするように仕込みます。個別にソートスクリプトなど作る必要ありません。sortContentのソート指定一個ですみます。sortContent の中身がその都度変わるのだという仕組みです。
計算フィールドの計算式
理屈の通り、sortContent フィールドにフィールド内容を計算して転記するわけですが、基本は FileMaker のヘルプに載っているとおり、GetField ( GLBfieldName ) の一行でOKです。
この簡単な1行でフィールド内容は転記されますが、このままではソートに問題があって駄目ですので改良します。どういう問題か、どういう改良か、それは以下です。
結果のタイプ
計算式では、計算結果のタイプを「テキスト」や「数字」など事前に指定しなければなりません。
でも実際にソートしたいフィールドの内容はテキストだったり数字だったりします。
計算フィールドの計算式の結果を「テキスト」にすると、数字のソートが上手くいきません。1 から 10 の数字だとすれば、1の次に10が来てその次に 2 が来てしまいます。 結果を 「数字」にすると、当然ながらテキストが返りません。
FieldType
取りあえず計算結果は「テキスト」にしておきます。その上で、ソートフィールドが数字や日付の場合に備えましょう。
備えるのは良いのですが、その前にソートする指定フィールドのタイプをまず調査しなければなりません。大丈夫。FieldType という関数があります。
FieldType ( ファイル名 ; フィールド名)
これを使うと指定フィールドの情報がスペース区切りのテキストで返ってきます。スペースを¶に置換して GetValue すると2行目がフィールドタイプです。ですので具体的に次のように書きます。
GetValue ( Substitute ( FieldType ( get(ファイル名) ; GLBfieldName ) ; " " ; ¶ ) ; 2 )
結果は「text」「Number」「Date」「Timestamp」「Container」といった文字列になります(Container はオブジェクト)
タイプが Number だったら、無理矢理テキストとしてソートできる形にします。つまり 0 を追加して桁数を増やします。
Right ( "0000000" & フィールド内容 ; 8 )
例えばこんな風に8桁にしてしまえば、テキストフィールドとしてソートしても期待通りの結果となるでしょう。桁数はご自由に。
日付フィールドの場合は、各々お好きに工夫します。私の場合は書式を揃えているのでそのままテキストとしてソートしても問題ありません。秒でズレますが細けえことは気にしません。気にする場合は、タイムスタンプでもテキストとして桁数が揃うように整形します。
計算フィールドはその都度本物フィールドから転記してくるだけのクローンフィールドですから、ソートしやすいように整形しまくっても大丈夫、本物データに何の影響もありません。
Let 関数
ということで、sortContent フィールドの計算式は具体的こうなりました。太字のGLBfieldName はグローバルフィールドです。
Let ( [ content = GetField ( GLBfieldName ) ; fieldtype = GetValue ( Substitute ( FieldType ( get(ファイル名) ; GLBfieldName ) ; " " ; ¶ ) ; 2 ) ; content = case ( fieldtype = "number" ; Right ( "0000000" & content ; 8 ) ; content ) ]; content )
GLBfieldName にフィールド名を記入することで、sortContent にはソートできる形でフィールド内容が転記されます。あとはスクリプトでソートするだけです。
ソートするスクリプト
ソートスクリプトは、計算フィールド sortContent フィールドをソートさせれば良いだけです。
ダイアログでsortContent フィールドを指定します。
しかしこれだけでは貧弱ですね。状態把握のためのグローバル変数の作成をを交えつつ、次のようなスクリプトを作成しました。
ソートスクリプトの動き
まず、$$sort を読んで一行目のフィールド名がグローバルフィールドに書かれた指定フィールドと同じかどうか調べます。つまり現在のソートと、今からソートしようとしているソートが同じフィールドなのか、違うのかを最初に見ます。
同じなら、現在昇順なのか降順なのかを調べ、現在と逆の行動を起こします。現在が昇順ならば降順、降順ならば昇順でソートします。
ソートを実行するとグローバル変数 $$sort を更新し「フィールド名 ¶ ソート順」を記録します。
$$sort のフィールドと今からソートしようとしてるフィールドが別であるなら、新たに指定フィールドでソートすることになります。デフォルトのソートです。デフォルトを昇順・降順、どちらにセットしておくかはお好み次第です。この例ではデフォルトを降順としています。
そしてソートしたあとは $$sort を更新、「フィールド名 ¶ ソート順」 を記録します。
ソートボタンのスクリプト
GLBfieldName にフィールド名を記入すればそのフィールドでソートされるようになりました。
実際の運用ではフィールド名を手入力しません。してもいいけど。普通しません。大抵はボタンに仕込みたいです。GLBfieldName にフィールド名を記入し、ソートスクリプトを実行するスクリプトを作りましょう。
ソートボタンに割り当てるスクリプトを追加しました。
ソートボタンにセットするスクリプト引数
ボタンの設定で、スクリプト引数にフィールド名をセットします。フィールド名は ” ” で囲みます。ここで書いたスクリプト引数が、グローバルフィールドにそのまま入る仕組みです。”” で囲まないと、そのフィールド名のフィールド内容が書かれてしまいます。フィールド名としてセットしたいので必ず括ります。あとはソートスクリプトを実行させ、最後にレコードの先頭に移動させています。
ソートするスクリプトとボタンにセットするスクリプトをこのように分けることで、他の処理と併用しても面倒なことになりませんのでおすすめです。
普通のボタンじゃなくボタンバーを使うことによってボタンタイトルを計算式で作れます。これは半ば必須かもしれません。ソート順を表示できますから。
ボタンバーのラベル
ボタンバーでは計算式でラベルを作れますので重宝します。例として、次のようなラベルの計算式を挙げておきます。
Let ( [ fieldName = "ファイル名" ; label = "ファイル名" ; asc = TextSize ( TextFont ( " ▵" ; "Apple Symbols" ) ; 11 ) ; desc = TextSize ( TextFont ( " ▿" ; "Apple Symbols" ) ; 11 ) ; none = TextSize ( TextFont ( " " ; "Apple Symbols" ) ; 11 ); GLBfld = GetValue ( Substitute ( $$sort ; ": " ; ¶ ) ; 1 ) ; GLBsort = GetValue ( Substitute ( $$sort ; ": " ; ¶ ) ; 2 ) ; sort = Case ( GLBfld ≠ fieldName ; none; GLBsort = "ASC" ; asc ; GLBsort = "DESC" ; desc ); disp = If ( GLBfld = fieldName ; label & sort ; label & none ) ]; disp )
最初の fieldName は、ボタンがターゲットとするフィールド名、スクリプト引数と同じです。
次の label は、表示させるテキスト。
asc, desc, none はテキストの後ろに付ける矢印や三角。この例では小さな三角を指定しています。小さな三角はフォントやサイズの指定をしないと表示サイズが定まらなかったので指定しています。
あとは 現在の $$sort(1行明がフィールド名、2行目が昇順・降順 )を読んで、case で表示を決定しています。
FileMakerのヘルプにも載っているやり方でした。この方法はソートの度に大量の転記が発生しますからレコードが多いとちょっとエグいです。
その3 フィールドそのものを使う方法
その3の方法はすごいです。こんなこと思いつきもしませんでしたが、FileMaker の講演ビデオを見て知りました。早速取り入れます。
参考になったビデオ→ 効率的な カスタム App開発の基本手法を考察する – 2023年版
是非上記ビデオをご覧ください。以下、参考ビデオを自分なりに飲み込んで作った例にすぎません。ただし、参考ビデオでは触れられていない重要なポイントがひとつあります。その件にも触れておきます。
材料
- ソートボタン … ボタンではなくソートしたいフィールドを使用
- スクリプト … 汎用的なスクリプト 1個
- グローバル変数 … ソート状態を保存するグローバル変数
- ボタンバー … 昇順降順を表示するためのボタンバー
概要
ソートボタンの代わりにソートしたいフィールドそのものを使います。ボタン化もしません。オブジェクトトリガ OnObjectEnter をセットし、ソートのスクリプトを走らせます。
ソートスクリプトは、スクリプトステップ「レコードをフィールド順でソート」が基本です。あとは昇順降順を使い分けるため、グローバル変数を作ったり解釈したりします。
それ以外には表示用に昇順降順を表示するボタンバーを作り、それを隠すためのフィールドを配置して見た目を整えます。
詳細
ボタンのグループはこんな仕上げになります。
分解するとこういうものが配置されています。
では上から順に説明します。
※ 尚、このレイアウト方法は配置するオブジェクトが多すぎるので、後に別のやりかたに変更しています。うざいので「目隠し用」なんか使わなくなりました。いずれ追記できればと思いますが・・
テキスト(ラベル)
単なるテキストです。ラベルとして使用します。
ソートするフィールド 目隠し用
ソートしたいフィールドと同じフィールドです。すぐ下のボタンバーを隠したり見せたりするためだけにここに置いています。
グローバル変数に、現在ソートしているフィールド名とオーダー(昇順か降順)を記録しており、そのフィールド名と己が同じならこのフィールドの背景を透明にします。
↑↓を表示するためのボタンバー
何もしないボタンバーを配置して名前だけ計算式で作っています。グローバル変数 $$_order にASCと記録されていれば ↑ 、DESC と記録されていれば ↓と表示します。矢印より三角とか別のが判りやすいのかな。いずれにしても、アイコンではなくテキストが望ましいです。名前欄ですから。
ソートするフィールド
これが主人公、ソートしたいフィールドです。これをボタン代わりに使いますが、ボタン化もしません。フィールドのままです。
ただしボタンみたいなデザインにはします。例えばフォントサイズを1にして色を背景色に合わせフィールドの外に追い出し、スクロールをオンにします。このあたりは参考ビデオでおっしゃっている通りの措置です。それに加えて押したときの背景色なんかを整えます。
押したときに凹んだように見える背景色をセットするわけですが、色を黒にして不透明度10%とか、そういう配色をするのがよろしいです。不透明な色をセットすると、押したときに上に配置したラベルテキストが消えたように見えます。
さて、このフィールドに、OnObjectEnterトリガをセットします。OnObjectEnterですから、クリックした途端にスクリプトが発動し、フィールド内容に関与しません。
ソートスクリプトとグローバル変数
ソートのスクリプトは、スクリプトステップ「レコードをフィールド順でソート」を使用します。
「レコードをフィールド順でソート」は、指定したフィールドでソートするステップです。フィールドを指定しない場合はアクティブフィールドが採用されるということです。そうだったんですか。
フィールドをボタン化せずにボタンのように使用することをこれまで思いつかなかったので、「レコードをフィールド順でソート」を使えない奴。と思ってました(以前これをやろうとしてフィールドをボタン化したら動かなかった)
このステップでは昇順または降順をセットするので、分岐して配置します。現在昇順ソートされていれば降順に、降順ソートされていれば昇順にソートします。
if [ 現在のソート = "ASC" ] レコードをフィールド順でソート[ "降順" ] else if [ 現在のソート = "DESC" ] レコードをフィールド順でソート[ "昇順" ] else // 記録がない場合、お好みのデフォルト順を記載しておく レコードをフィールド順でソート[ "昇順" ] end if
「現在のソート」をグローバル変数 $$_order に記録することにしましょう。
if [ $$_order = "ASC" ] レコードをフィールド順でソート[ "降順" ] 変数を設定 [ $$_order ; "DESC" ] else if [ $$_order = "DESC" ] レコードをフィールド順でソート[ "昇順" ] 変数を設定 [ $$_order ; "ASC" ] else // 記録がない場合、お好みのデフォルト順を記載しておく レコードをフィールド順でソート[ "昇順" ] end if
単純化して書くとこんな感じです。
これだけでは足りないので、現在ソート対象となっているフィールド名も記録します。ソートしようとしたとき「現在ソートされている対象と同じなら昇順降順を入れ替え、そうでないなら改めてデフォルトのソート」の動きにしたいからです。
ソートスクリプトの最初に、今クリックしたフィールドを記録します。
変数を設定 [ $nowField ; 値: Get ( アクティブフィールド名 ) ]
それから、グローバル変数 $$orderBy に記録された「現在のソート対象フィールド」と比較します。
変数を設定 [ $nowField ; 値: Get ( アクティブフィールド名 ) ] if [ $$_orderBy = $nowField ] if [ $$_order = "ASC" ] レコードをフィールド順でソート[ "降順" ] 変数を設定 [ $$_order ; "DESC" ] else if [ $$_order = "DESC" ] レコードをフィールド順でソート[ "昇順" ] 変数を設定 [ $$_order ; "ASC" ] end if; else // 異なる場合(記録がない場合含む)お好みのデフォルト順 レコードをフィールド順でソート[ "昇順" ] 変数を設定 [ $$_order ; "ASC" ] 変数を設定 [ $$_orderBy ; $nowField ] end if;
こんな感じでどうでしょう。
見た目
さて見た目を整えます。さっきの絵をもう一度貼っておきましょう。
最下のフィールドをボタンみたいにデザインしたあとは、そのすぐ上にボタンバーを設置して矢印を表示させます。
ボタンバーは名前に計算式を使えるので、ラベル代わりにもよく使ってます。
名前欄の計算式は次のようにしました。
If ( $$_order = "ASC" ; "↑" ; If ( $$_order = "DESC" ; "↓" ; "" ) )
$$_order が ASC なら ↑、DESC なら ↓ それ以外なら空欄です。矢印じゃなく別の文字のほうがカッコいいかもしれませんがとりあえず。
で、このボタンバーはすべてのソートボタンで同じように表示されてしまうので、現在のソート対象以外では隠す必要があります。
隠す方法は、フィールドをもう一項追加して上にかぶせてしまいます。背景色などを下のフィールドと合わせておき、条件付き書式で調整します。
フィールドの「隠す」では上手くないです。なぜかというと、グローバル変数の値に応じて即時に反映させる必要があります。フィールドの「隠す」は変数の対応がリアルタイムでなく、再表示を必要としますから却下。条件付き書式でやります。
条件付き書式の計算式がこれまた雑ですいません。フィールド名のところにフルフィールド名が入るのかフィールド名だけなのか確認するのが面倒だったので PtternCount を使いました。
PatternCount ( GetFieldName ( Self ) ; $$__orderby )
ここで Self を使っているのがミソですね。このために、ソートさせるフィールドと同じフィールドを使いました。でないとフィールド名を指定しなければなりませんから。
(図ではJSONの文字も見えますがグローバル変数をどうするかって話でして、ここでは詳しく言いません。どうでもいい話です)
この計算式には罠があります。GetFieldName には、関連テーブルのフィールドで正常に動作しません。もし関連テーブルのフィールドをソートをする場合はGetFieldName ではない個別対処を行うしかありません。汎用性を棄てます。
ということで、このフィールドで現在のソート対象以外のフィールドではボタンバーを隠します。
ソートボタンラベルが完成しました。必要なだけ複製し、フィールドをソートするフィールドに変更します。最上位のテキスト部分にラベルを書いたらできあがり。
重要ポイント:関連テーブルでレコードがないとき
さて重要なポイントを指摘しておきます。
関連テーブルのフィールドをソート対象にする場合、そのフィールドに内容がない場合はソートできません。
関連テーブルでは、レコードがなければフィールドをクリックしてもフィールド名をゲットできないし OnObjectEnter トリガのスクリプトも発動しません。何もできないんですね。
これを回避する方法は、リレーションシップでレコード作成許可を与えるしかありません。
許可を与えていれば、空のフィールドもフィールドとして認識してくれます。
レコード作成の許可を与える与えないは結構重要な設定だと思いますので、許可を与えないテーブルに対して、ソートのためだけに設定を変更するリスクを取れるか取れないか、そこんところが重要な判断になります。
関連テーブルかつレコードがない可能性がありかつレコード作成の許可を与えたくない場合のみ、他の方法を使うほかありません。その2の方法や、諦めて単独ボタン・単独スクリプトを用意するとかですね。
関連レコードのフィールドをソート対象にしたいとき、目隠し用の条件付きも使えないし、対処は個別具体的な対応以外にありません。この事は大きな弱点ですが、Claris社がGetFieldNameのバグを直してくれること以外に解決方法がありません。