FileMaker のソリューションを作るときに、それぞれ自分なりの Standard な作法(流儀)を持っている。
それぞれに独特の好みがあり、「どんだけ○○やねん!w」と首を傾げることがないではないが、カシコの開発者の流儀を読むことは、中級レベルの開発者にとって、もう一つ上のレベルへのスキルアップへの早道だと思う。

同様のことを自分で実現している方法や、同様のことを別々の開発者がどうやっているのか…など比較して、どういう条件であれば有用なのか、どういう場面ではかえって意味が無いのか…を考えることはためになると同時になにより楽しい。

命名規則(Name Convention)やスクリプトの使い方、もちろん、データベース構造にも、個々の開発者の流儀が出てくるわけだが、まずは、スクリプトを見ていくことにする。

ということで…本稿では、Seed Code の SeedCode SQL Explorer を例にとって、SeedCode というか… Jason Young というか…の 作法を学んでいこうと思う。
ファイルは、FMGateway のサイトからダウンロードできる。

まず、最初にファイルを開くと走るのが、Upon Opening Hierarchy というスクリプトだ。
※ 先に FileMaker Pro Advanced を立ち上げて、デバッガをオンにしてからファイルを開く

Upon Opening Hierarchy



SeedCode SQL Explorer: Housekeeping: Upon Opening Hierarchy
#
#==============================================
# Function: Startup script for file
# Parameters: none
# Notes: This script runs when the hierarchy starts up and should be called as the opening script in file options.
# Author: SeedCode
# Version: 1.0
#==============================================
#   、
#Set File Name
変数を設定 [ $$sc_HierarchyFileName; 値:Get ( ファイル名 ) ]
#
#Clear Global Values
スクリプト実行 [ 「ClearGlobals」 ]
#
#Make sure Virtual List records are there.
スクリプト実行 [ 「Create Rows」 ]
#
#Create Array of color codes used for color coding tables and their fields.
変数を設定 [ $$sc_ColorList;
値:"6802379¶11068584¶16636347¶16628667¶13358314¶16774105¶14416052¶13411788¶16770466¶129018 59¶6802379¶11068584¶16636347¶16628667¶13358314¶16774105¶14416052¶13411788¶16770466¶12901 859" ]
#
#Go To Home layout.
レイアウト切り替え [ 「Queries」 (SQLWizardHome) ]
スクリプト実行 [ 「Adjust_Window { HideStatusArea ; Lock }」; 引数: "HideStatusArea = 0" ]
#

順に見ていこう。

#Set File Name
変数を設定 [ $$sc_HierarchyFileName; 値:Get ( ファイル名 ) ]

どうやら、$$sc_HierarchyFileName というグローバル変数に 開いたファイルのファイル名を入れているようだ。

#Clear Global Values
スクリプト実行 [ 「ClearGlobals」 ]

ClearGlobals というサブスクリプトを実行している。
名前からして、グローバルな何かをクリアするのだろう。グローバルといってすぐに思い出すのは、グローバルフィールド・グローバル変数だが、たぶんグローバルフィールドではなかろうか。

ClearGlobals



SeedCode SQL Explorer: General: ClearGlobals
#
#==============================================
# Function: Utility script for clearing virtual list arrays and the global fields that set the number of rows to show in the respective portals.
# Parameters: none
# Notes:
# Author: SeedCode
# Version: 1.0
#==============================================
#
フィールド設定 [ SQLWizardHome::HierarchyGlob; "" ]
フィールド設定 [ SQLWizardHome::RowsToDisplay1; "" ]
フィールド設定 [ SQLWizardHome::RowsToDisplay2; "" ]
変数を設定 [ $$sc_Display[2]; 値:"" ]
変数を設定 [ $$sc_Display; 値:"" ]


あに図らんや、
  • SQLWizardHome::HierarchyGlob
  • SQLWizardHome::RowsToDisplay1
  • SQLWizardHome::RowsToDisplay2
というグローバルフィールドの他に、
  • $$sc_Display
というグローバル変数の繰り返し番号1と2についてもクリアしている。

グローバル変数はファイル依存なので、ファイルを開くときにキックされるスクリプトのサブスクリプトとして、「グローバル変数をクリアする」というのは釈然としないが、使い回す価値があるからサブスクリプトにしているのだろうから、他のスクリプトから呼び出される場面も考えられているからなのね。

また、(変数に複数の値を入れたい場合、改行区切りで入れることが多いが)このように変数にわざわざ繰り返しを設定しているところをみると、値自体に改行を持つものが入るのかなーと。
よくよく見ると、Function(機能)の説明に、
Utility script for clearing virtual list arrays and the global fields that set the number of rows to show in the respective portals.
とある。
なるほど、Virtual List の配列の元とするグローバル変数 をクリアしたいのね。だから、繰り返しなんだ。
VL のクリアが目的なら、たしかに、(ファイルを開いたときだけでなく)ファイルを開いた後の操作中にも、同じ VL を別用途で使う前にも、その前に使っていた VL をクリアするときにも使うものね。

さて、Upon Opening Hierarchy に戻ると、今度は、Create Rows というサブスクリプトに飛ばされる。

#Make sure Virtual List records are there.
スクリプト実行 [ 「Create Rows」 ]

Create Rows


SeedCode SQL Explorer: Housekeeping: Create Rows
#
#==============================================
# Function: Part of start-up routine to make sure virtual list display rows are populated to the specified number.
# Parameters:
# Notes:
# Author: SeedCode
# Version: 1.0
#==============================================
#
#Default Number Row Records
変数を設定 [ $sc_NumberRows; 値:1000 ]
フィールド設定 [ SQLWizardRows1::DragDrop1; " " ]
#
ウインドウの固定
#
レイアウト切り替え [ 「dev_Rows」 (SQLWizardRows1) ]
全レコードを表示
If [ Get ( 対象レコード数 ) = $sc_NumberRows ]
レイアウト切り替え [ 元のレイアウト ]
現在のスクリプト終了 [ 結果: "" ]
End If
#
対象レコード削除[ ダイアログなし ]
変数を設定 [ $sc_c; 値:1 ]
Loop
新規レコード/検索条件
フィールド設定 [ SQLWizardRows1::RowNumber ]
変数を設定 [ $sc_c; 値:$sc_c + 1 ]
Exit Loop If [ $sc_c > $sc_NumberRows ]
End Loop
#
レイアウト切り替え [ 元のレイアウト ]
現在のスクリプト終了 [ 結果: "" ]

さて、Function(機能)の説明にこうある。
# Function: Part of start-up routine to make sure virtual list display rows are populated to the specified number.
「スタートアップルーチンの一部として、Virtual List を表示するための row を特定の数、確実に生成しておく」
のが目的らしい。

#Default Number Row Records
変数を設定 [ $sc_NumberRows; 値:1000 ]
フィールド設定 [ SQLWizardRows1::DragDrop1; " " ]
#
ウインドウの固定
#
レイアウト切り替え [ 「dev_Rows」 (SQLWizardRows1) ]

VL用のレコードを、予めいくつ作っておくのかという数字が、$sc_NumberRows にセットされた 1000 ちうことね。
SQLWizardRows1::DragDrop1 というフィールドに、半角スペースをセット。
これ何でしょう?現時点ではわかりません。

SQLWizardRows1 という TO がコンテキストであるレイアウト「dev_Rows」に移動。
そのまえに、ウインドウの固定が入れて、余計な画面遷移を出したくないのね。

全レコードを表示
If [ Get ( 対象レコード数 ) = $sc_NumberRows ]
レイアウト切り替え [ 元のレイアウト ]
現在のスクリプト終了 [ 結果: "" ]
End If

TO「SQLWizardRows1」のすべてのレコードを表示した後の条件が…
Get ( 対象レコード数 ) = $sc_NumberRows
$sc_NumberRows は、さっき 1000 をセットした。
SQLWizardRows1というTOのレコードも現在 1000 なので、この条件式は真。
真だと、元のレイアウトに戻ってスクリプト終了。

なるほど、SQLWizardRows1 ってのが VL のテーブルなのね。
そこに、予め 1000 のレコードを作っておくためのスクリプトだもんね。
ちうことは、現在の VL のレコード数が1000 レコードじゃない場合の処理が次に来るわけね。

対象レコード削除[ ダイアログなし ]
変数を設定 [ $sc_c; 値:1 ]
Loop
新規レコード/検索条件
フィールド設定 [ SQLWizardRows1::RowNumber ]
変数を設定 [ $sc_c; 値:$sc_c + 1 ]
Exit Loop If [ $sc_c > $sc_NumberRows ]
End Loop
#
レイアウト切り替え [ 元のレイアウト ]
現在のスクリプト終了 [ 結果: "" ]

やっぱり。
全レコード削除して、$sc_c ちうカウンタを上げながら、新規レコードを $sc_NumberRows の数(1000)作るっちうことね。

んで、また、最初の Upon Opening Hierarchy に戻る。

#Create Array of color codes used for color coding tables and their fields.
変数を設定 [ $$sc_ColorList;
値:"6802379¶11068584¶16636347¶16628667¶13358314¶16774105¶14416052¶13411788¶16770466¶129018 59¶6802379¶11068584¶16636347¶16628667¶13358314¶16774105¶14416052¶13411788¶16770466¶12901 859" ]
#
#Go To Home layout.
レイアウト切り替え [ 「Queries」 (SQLWizardHome) ]

テーブルやフィールドの文字列用のカラーコーディングに使うカラーコードの配列を作るわけですね。
そして、このファイルのホームレイアウトである SQLWizardHome へ。

スクリプト実行 [ 「Adjust_Window { HideStatusArea ; Lock }」; 引数: "HideStatusArea = 0" ]

最後に、HideStatusArea = 0 という引数で、Adjust_Window { HideStatusArea ; Lock } というスクリプトを実行。

Adjust_Window { HideStatusArea ; Lock }


SeedCode SQL Explorer: General: Adjust_Window { HideStatusArea ; Lock }
#
#==============================================
# Function: Utility script for adjusting current window (to fit)
# Parameters: Hide Status Area ( Yes or No ) ; Lock
# Notes:
# Author: SeedCode
# Version: 1.0
#==============================================
#
#
#Evaluate Patameters
スクリプト実行 [ 「Parameters to Local Variables」; 引数: Get ( スクリプト引数 ) ]
変数を設定 [ $parameters; 値:Evaluate ( Get ( スクリプトの結果 ) ) ]
#
#Hide and Lock if specified with parameters.
If [ $sc_HideStatusArea = "Yes" ]
If [ $sc_Lock ]
ツールバーの表示切り替え[ ロック; 隠す ]
Else
ツールバーの表示切り替え[ 隠す ]
End If
Else If [ $sc_HideStatusArea = 0 ]
ツールバーの表示切り替え[ 表示する ]
End If
#
#Adjust window
ウインドウの調整[ 収まるようにサイズ変更 ]
#

カレントウインドウを調整するスクリプトね。

はじまってすぐにParameters to Local Variables というスクリプトに飛ばされる。
コメントに “Evaluate Parameters" とあるから、スクリプト「Adjust_Window { HideStatusArea ; Lock }」に渡されたスクリプト引数( “HideStatusArea = 0" )を Evaluate するスクリプトに飛ばされるわけね。

Parameters to Local Variables


SeedCode SQL Explorer: General: Parameters to Local Variables
#
#==============================================
# Function: Neat little utility to transform script parameters into local variables of the same names.
# Parameters: The script parameter you wish to transform, sent as Get ( ScriptParameter )
# Notes: After calling this script, call the following in your script to instantiate the variables:
: SerVariable [$parameters ; Value:Evaluate ( Get ( ScriptResult ) )]
: Note that this script namespaces your variables with $sc_
: Script parameters sent in here must follow the format: name = value ; name2 = value 2 with even spaces around =s and ;s.
# Author: SeedCode
# Version: 5.23.1
#==============================================
#
#> > make semicolon = safe?
#
If [ IsEmpty ( Get ( スクリプト引数 ) ) ]
変数を設定 [ $parameters; 値:"empty" ]
Else
変数を設定 [ $parameters; 値:
Let ( [
n = Substitute ( Get ( スクリプト引数 ) ; "¶" ; "\¶" ); prefix = "$sc_" ;
e = PatternCount ( n ; " = " ) = 0 ;
string = Substitute ( n ;
[ " ; " ; "\" ; " & prefix ] ;
[ " = " ; " = \"" ] )
];
"Let ( [ " & prefix & string & "\" ] ; \"\" )"
)]
End If
現在のスクリプト終了 [ 結果: $parameters ]
#

なにやら楽しそうだぞ!

「スクリプト引数を、同名のローカル変数に変身させる」だと。
Note を見ると…
このスクリプトをコールした後で、あんたのスクリプト内で、変数のインスタンス生成するために以下をコールしなさいな…と。

変数を設定 [ $parameters; Value:Evaluate ( Get ( ScriptResult ) ) ]

Get ( ScriptResult ) を Evaluate したものを $parameters という変数にセット…ね。
$sc_ ってのを(頭につけて)、名前空間とする(ローカル変数名のネームコンフリクトをさける)
このスクリプトに送られるスクリプト引数は以下の形でなければならない。
name = value ; name2 = value 2( = や ; の前後がスペースでも)

にゃるほど。

#> > make semicolon = safe?
#
If [ IsEmpty ( Get ( スクリプト引数 ) ) ]
変数を設定 [ $parameters; 値:"empty" ]
Else
変数を設定 [ $parameters; 値:
Let ( [
n = Substitute ( Get ( スクリプト引数 ) ; "¶" ; "\¶" );
prefix = "$sc_" ;
e = PatternCount ( n ; " = " ) = 0 ;
string = Substitute ( n ;
[ " ; " ; "\" ; " & prefix ] ;
[ " = " ; " = \"" ] )
];
"Let ( [ " & prefix & string & "\" ] ; \"\" )"
)]
End If

セミコロンが安全かどうか?
スクリプト引数が空値の場合、$parameters には、"empty" というテキストをセットする。
スクリプト引数になにがしかの文字が入っていれば、$parameters に以下の式の結果をセットする。

Let 式の内部を確認していく。
(仮に、スクリプト引数が "hoge = ほげ¶ほげ ; nan = なんちゃら" だったとき)
n は…

hoge = ほげ¶ほげ ; nan = なんちゃら

あれ?一緒じゃん…と思うなかれ。
じっさいには、スクリプト引数は…

hoge = ほげ
ほげ ; nan = なんちゃら

なのである。このスクリプト引数内の¶をいったんエスケープして、Value(xxx = yyy ; xxx2 = yyy2 のようなときの、yyy や yyy2 にあたる部分)自体が改行を含む場合も破綻しないように、後に続く式内でも使えるようにしているわけだ。

e は、n に “=“ が無ければ 1(True)…ということだから、0(False)。

string の結果は…

hoge = "ほげ¶ほげ" ; $sc_nan = "なんちゃら

$sc_ という文字が片方(後方)にのみついていて、後方のダブルクォーテーションも欠けているが、この後、
"Let ( [ " & prefix & string & "\" ] ; \"\" )"
と整形してやると…

Let ( [ $sc_hoge = "ほげ¶ほげ" ; $sc_nan = "なんちゃら" ] ; "" )

という Let から始まる Let計算式 が生成される。

現在のスクリプト終了 [ 結果: $parameters ]

このスクリプトが終わって、このスクリプトをコールしたスクリプトに戻ったときに、Get ( ScriptResult ) で呼び出せるように、スクリプトの結果に $parameters を入れて終了。

そして、Adjust_Window { HideStatusArea ; Lock } に戻ってくる。

変数を設定 [ $parameters; 値:Evaluate ( Get ( スクリプトの結果 ) ) ]

Get ( ScriptResult ) を Evaluate したものを、$parameters にセット…と見えるが、結果をデータビューアで見ても、$parameters という変数は存在しない。
直前にコールされた Parameters to Local Variables の説明にもあったように、Parameters to Local Variables というスクリプトでは Let で始まる『式テキスト』を生成する。
Parameters to Local Variables が終わると、Get ( ScriptResult ) として、Adjust_Window { HideStatusArea ; Lock } 内で使用するローカル変数を作成する。

通常、変数をセットするには、スクリプトステップ「変数を設定」でシンプルに使いたい変数名をセットする。
しかし、ここでは、変数を設定スクリプトステップで、$parameters という変数を設定する…フリをしながら、実際には…

Get ( ScriptResult ) …つまり、Let ( [ $sc_HideStatusArea = "0" ] ; "" ) という式を Evaluate することで、
$sc_HideStatusArea という変数 に 0 を設定している。

この Let ( [ $sc_HideStatusArea = "0" ] ; "" ) というテキストは、直前のスクリプトステップでコールした外部スクリプト「Parameters to Local Variables」によって生成された。
そして、Parameters to Local Variables の実行時に予め渡された

HideStatusArea = 0

というスクリプト引数が元になっているが、その元を辿ってみよう。
遡ると、このテキストは、大元のスクリプト「Upon Opening Hierarchy」によって、Adjust_Window { HideStatusArea ; Lock } にスクリプト引数として渡されたものなのだ。

大元の Upon Opening Hierarchy スクリプト内で、ウインドウの調整をさせるスクリプト「Adjust_Window { HideStatusArea ; Lock }」を呼び出す際に、スクリプト引数で、HideStatusArea = 0 と渡せば、「ステータスエリアを隠さない」というオプションを指定して実行することができる。
よく見れば、スクリプト名「Adjust_Window { HideStatusArea ; Lock }」は、Adjust_Windows というスクリプトの内容を表すテキストの後に中括弧で括られて、{ HideStatusArea ; Lock } とある。
ウインドウの調整をさせるスクリプトに「ステータスエリアは隠すの/隠さないの?」「ロックするの?/しないの?」というふたつのオプションが指定可能である…ということが、スクリプト名を見ればわかるようになっている!

Upon Opening Hierarchy では、ステータスエリアの表示について「隠す」というオプションを指定しているだけだが、別のスクリプトから呼ばれたときに、「ウインドウをロックする」というオプションを指定することも、もちろん可能だ。

HideStatusArea = 0 ; Lock = 1

をスクリプト引数に入れて、Adjust_Window { HideStatusArea ; Lock } に渡せば、Adjust_Window { HideStatusArea ; Lock } は、まず、今 Upon Opening Hierarchy からもらったばかりのスクリプト引数と共に、Parameters to Local Variables をコールする。
Parameters to Local Variables は結果、

Let ( [ $sc_HideStatusArea = "0" ; $sc_Lock = "1" ] ; "" )

という計算式を生成し、Adjust_Window { HideStatusArea ; Lock } に、Get ( ScriptResult ) として返す。
Adjust_Window { HideStatusArea ; Lock } は、それを、 変数を設定スクリプトステップを使って、$parameters をセットするのだが、
その計算式で、Let ( [ $sc_HideStatusArea = "0" ; $sc_Lock = "1" ] ; "" ) を Evaluate することで結果として…

  • $sc_HideStatusArea という変数が 0、
  • $sc_Lock という変数が 1で、セットされる。

Dictionary関数と比べてみる

Dictionary関数 と呼んでいる 連想配列を作る関数群をよく使うが、Dictionary関数に頼らずとも、Parameters to Local Variables という汎用の D.R.Y. なスクリプトに、Label1 = Value1 ; Label2 = Value2 というスクリプト引数を投げてやることで、同様のことを実行しているわけか…どう違うのか…。

Dictionary関数使用の場合

投げるスクリプト引数は…

SetProperty ( "Label1" ; Value1 ) & SetProperty ( "Label2" ; Value2 )

結果を引き出すときは…

変数を設定 [ $Label1; 値:GetProperty ( Get ( ScriptResult ) ; "Label1" )]
変数を設定 [ $Label2; 値:GetProperty ( Get ( ScriptResult ) ; "Label2" )]


…もしくは、
GetScriptResult ( "Label" ) := GetProperty ( Get ( ScriptResult ) ; "Label" ) をカスタム関数に追加登録して、

変数を設定 [ $Label1; 値:GetScriptResult ( "Label1" )]
変数を設定 [ $Label2; 値:GetScriptResult ( "Label2" )]

Parameters to Local Variables でLet計算式を生成して、あとでEvaluateという手法なら…

投げるスクリプト引数は…

"Label1 = Value1 ; Label2 = Value2"
まあ、こっちの方が読みやすいっちゃ読みやすいね。

結果を引き出して使うときは…複数の変数を設定するにも一挙に1ステップ

変数を設定 [ $parameters; 値; Evaluate ( Get ( ScriptResult ) )

Evaluate はコスト高だし、予期せぬ動きにならないとも限らないという面もなきにしもあらず。
なので、これだけならさして違いはないというか、どっちでも、お好きな方で…なのだが、

以下のような条件の場面では、「Let計算式を作って Evaluate する」という SeedCode 方式がすこぶる便利だろう。

  • 変数名もスクリプト引数で渡すLabel名も同じでOKの場合
  • やたらと膨大な変数を一気に100とか登録したい場合
  • 変数名は Label1、Label2… のように Loop で作りたい…という場合

変数名もスクリプト引数で渡すLabel名も同じでOKの場合

まず、スクリプト引数を作る段階ですでに、後で使うときの変数名は、自動的にスクリプト引数のラベル名になるように作るので、変数を作る際に、変数名を「なんだっけかな?」と悩む必要が無い。
※ スクリプトごとに、スクリプト引数内で使うラベル名をスクリプト名の命名規則(中括弧で括り、デリミタはセミコロンとか)にすれば「このスクリプトをコールするときどんなスクリプト引数つけるんだっけ?」というのがわかりやすくなる。

やたらと膨大な変数を一気に100とか登録したい場合

個人的には、スクリプト引数で指定するときの式は、さほど変わらないように思うが、Get ( ScriptResult ) を使って、山ほどの変数をセットするときには、Evaluate 一発で設定できた方がどう考えても楽チン。

変数名は Label1、Label2… のように Loop で作りたい…という場合

Loop で回して…(Virtual List を使うとき、VLの元となる変数を作成する場合など、ラベル名は、特定の文字列に続けて繰り返し番号だけをインクリメントしたいとかは「あるある」な話)という場合、「スクリプト引数を作る」ためだけのスクリプトが明らかに著しく作りやすい。

さて、残りをやっつけるか…w

#Hide and Lock if specified with parameters.
If [ $sc_HideStatusArea = "Yes" ]
If [ $sc_Lock ]
ツールバーの表示切り替え[ ロック; 隠す ]
Else
ツールバーの表示切り替え[ 隠す ]
End If
Else If [ $sc_HideStatusArea = 0 ]
ツールバーの表示切り替え[ 表示する ]
End If
#
#Adjust window
ウインドウの調整[ 収まるようにサイズ変更 ]

思った通りなので、何も言うことはない。