Originally published in July, 2006.
Welcome back to FileMaking. This month, we’re going to continue our investigations of FileMaker’s scripting capabilities that we began last month. Now we’ll take a look at two features of ScriptMaker that were introduced in recent versions of FileMaker.
Most programming languages have a construct that is very similar to FileMaker scripts. These are known as subroutines, methods, or handlers, depending on the language one is programming in. Regardless of the name, most languages have a feature that comes with these subroutines: information can be passed to the subroutine when it’s called and information can be retrieved from the subroutine (in which case it’s often then called a function). When information is given to a subroutine, the information is called a parameter or argument. The information returned by a function is the result. Parameters and results make subroutines much more powerful. Rather than simply executing code, the routine can make complex decisions and behave very differently depending on the parameters passed, and once the subroutine is finished executing, it can communicate back to the calling program what it has accomplished.
A simple example: suppose that while writing an AppleScript program, you wish to calculate the length of the hypotenuse of a right triangle. To refresh your high school trigonometry, the hypotenuse is the longest side of a right triangle, and its length can be calculated by squaring the lengths of the other two sides, adding these two squares together and taking the square root of the sum.
One could write a simple AppleScript handler that is passed the lengths of the two sides as parameters and returns the length of the hypotenuse (note that raising a number to the power of 0.5 is equivalent to taking that number’s square root).
on HypotenuseLength(a, b) return ((a ^ 2) + (b ^ 2)) ^ 0.5 end HypotenuseLength
Such a function could then be called elsewhere in the program with something like this:
set hypt to HypotenuseLength(3, 4)
After being called, hypt would have a value of 5 (3×3 + 4×4 = 9 + 16 = 25, the square root of which is 5).
Until FileMaker 8, this kind of functionality, while possible, was difficult at best. Translating the above function to FileMaker 6 would require three global fields: one for each of the parameters and one for the result, and would look something like this:
Set Field[gResult, ((gParam1 ^ 2) + (gParam2 ^ 2)) ^ 0.5]
If another script wished to make use of this “function,” it would do so like this:
SetField[gParam1, 3] SetField[gParam2, 4] Perform Script[Hypotenuse Length] SetField[Length, gResult]
As you can see, while you can do it, generally you don’t, because it has so much overhead to get parameters into Pre-7 FileMaker scripts and get results out.
Script parameters were introduced with FileMaker 7, and script results with FileMaker 8, allowing us to now create (almost) honest-to-goodness script functions in FileMaker using ScriptMaker. I say “almost” because, as yet, you can’t simply assign the results of a script by calling it, as you can in a more traditional programming language, and strictly speaking, FileMaker scripts can only take a single parameter.
The single parameter limitation is resolved with the use of custom functions. If you’ll recall from a previous column, FileMaker Pro 8 Advanced allows the creation of custom functions that, from within a script, operate just like FileMaker’s built-in functions. By defining a format for a string to contain multiple parameters, we can use a custom function to extract those multiple parameters from within a script.
There are many solutions to this, but here is what I use: whenever I need to pass a parameter to a script (even if it’s only a single parameter), I use the format "Parameter1 = "Param1"; Parameter2 = "Param2"". Remember that a string is defined in FileMaker as a set of characters between double quotes. The " pair of characters indicates a single double-quote character within a FileMaker string, rather than closing the string as the last double-quote does. This is a common technique in programming called “escaping,” where we are using the backslash to escape the normal meaning of the double-quote. Therefore the above FileMaker string has a value of Parameter1 = "Param1"; Parameter2 = "Param2".
What you may notice is that this format could be evaluated as the setting of variables in a Let() function, which is exactly how we’re going to use it.
We’ll define a new custom function called GetParameter() which will take a single parameter, ParameterName. It’s defined as follows:
Evaluate( "Let ( [ " & Get( ScriptParameter ) & " ]; " & ParameterName & " )" )
This is a rather advanced use of FileMaker functions to create a custom function, so let’s go through it slowly. First of all, let’s assume that we have a FileMaker script called HypotenuseLength that takes a script parameter. And assume that we have called that script and passed it a parameter of "a = "3"; b = "4"". For the moment, know that if we call Get( ScriptParameter ) from within that script, we will be returned that string. Given all of that, let’s see what the GetParameter() function will do with it if passed the parameter "a".
We have two substitutions to make to figure out the result of our custom function. The first is the part that says Get( ScriptParameter ) and the second is ParameterName. Since we passed "a" to the custom function, substituting it for ParameterName results in:
Evaluate( "Let ( [ " & Get( ScriptParameter ) & " ]; " & "a" & " )" )
Not too difficult. Now let’s substitute the value of Get( ScriptParameter ). Remember that this function returns the parameter pass to the script it is called within, so Get( ScriptParameter ) will return "a = "3"; b = "4"":
Evaluate( "Let ( [ " & "a = "3"; b = "4"" & " ]; " & "a" & " )" )
We can see from that (if you look at it carefully) that we have one long string (returned from the concatenation of a number of strings) which is being passed to the Evaluate() function. Let’s concatenate the strings to simplify the expression.
Evaluate( "Let ( [ a = "3"; b = "4" ]; a )" )
So, the string we are passing (after taking into account the escaped double-quotes) is Let ( [ a = "3"; b = "4" ]; a ). This is a valid FileMaker expression in itself. What we have done is used FileMaker functions to build a FileMaker function. We then use the Evaluate() function to, well, evaluate this expression. Our original expression becomes:
Let ( [ a = "3"; b = "4" ]; a )
The expression uses the Let() function to set two variables, a and b. It then returns the value of the variable a, which in this case is "3", which is the final result of our original expression.
When we do this manually, it takes quite a bit of thinking to figure out what is going to happen. The fortunate thing is that we don’t have to do this ever again. The function works, and if we pass a parameter to a script in the manner described, our GetParameter() custom function can always extract whichever parameter we want.
Now that we have the means to pass and extract multiple parameters in scripts, we can rewrite our FileMaker script HypotenuseLength as follows:
Exit[((GetParameter("a") ^ 2) + (GetParameter("b") ^ 2)) ^ 0.5]
The FileMaker script step Exit exits the execution of the current script and returns control to the calling script. It has an optional parameter that, if specified, the results will be retrievable with the Get( ScriptResult ) function. While the above refinement doesn’t simplify our original script very much (it’s still a single line of code), the calling and retrieving of the script is much simpler.
Perform Script[Sub-scripts, HypotenuseLength; Parameter: "a = "3"; b = "4""] Set Field[ Length, Get( ScriptResult )]
You might be thinking that, after all this discussion, the task we’ve performed would be better done with a custom function itself, and you’re absolutely right. I’ve used a very simplistic example to demonstrate the concepts. In contrast, let me give you a real-world example.
In building FileMaker solutions, I often provide buttons that let the user navigate to the first, previous, next, and last record in a found set. Before FileMaker 7, this entailed not only four buttons, but also four scripts, one for each of the desired destination records. These scripts were very simple, and, aside from some standard steps that I tend to include in all of my scripts, were a single script step, such as Go to Record/Request/Page [ First ] for navigating to the first record in the found set.
Scripts multiply easily in complex FileMaker solutions. I’ve worked with solutions that had hundreds of scripts. Any time two scripts can be combined because of similarities, one should probably do so, and the four scripts needed in pre–FileMaker 7 are all very similar. They all go to a record using the Go to Record/Request/Page script step, only the parameter they pass to that script step differs. Therefore, in my more recent solutions, I’ve combined those four scripts into a single script, called Go to Record( WhichRecord ). This new script takes a single parameter that has one of four values, and looks like this:
If[ GetParameter( "WhichRecord" ) = "First" ] Go to Record/Request/Page[ First ] Else If[ GetParameter( "WhichRecord" ) = "Previous" ] Go to Record/Request/Page[ Previous ] Else If[ GetParameter( "WhichRecord" ) = "Next" ] Go to Record/Request/Page[ Next ] Else If[ GetParameter( "WhichRecord" ) = "Last" ] Go to Record/Request/Page[ Last ] End If
When I want to call this script, I attach a button to it and have the button pass the appropriate parameter, such as "WhichRecord = "Previous"".
So far we’ve covered some very heavy material in this column, so I’m going to give you some time to absorb this and post questions if I haven’t been clear in any way. But before I leave you, let me share some scripting tips that I’ve picked up over the last decade of working with FileMaker.
My first tip is: organize your scripts. Even with the script consolidation capabilities that script parameters provide, complex FileMaker solutions will still have dozens of scripts. I organize my scripts with two techniques. First of all, I create dummy scripts that have no script steps to act as headings and dividers in the “Define Scripts” dialog box.
Every script that begins with an equal sign or is simply a dash is empty and is not meant to be run. It exists solely to provide structure and organization to the “Define Scripts” dialog box. Every script falls into one of the following sections: Script Menu (for those scripts that are to appear in FileMaker’s “Scripts” menu), Developer (scripts that are useful to me but not necessarily to end users), Startup / Shutdown (scripts that execute automatically when the file is launched and closed), Navigation (scripts that take the user from one layout to another), Operations (scripts that perform complex business logic on records), Reports (scripts that generate reports), Sorting, Printing, Searching, Messages (scripts that display generic dialog boxes to the user), Miscellaneous, and Help (scripts that handle the contextual help system of the program). Such a group of headings works for me; perhaps something else will work for you. But regardless, keep your scripts organized. When there are dozens of scripts, it will make it easier to find the one you want.
If you take another look at the screenshot above, you’ll see that some of the scripts are named differently from others. Some of the scripts are indented with spaces and some have part of the name in parentheses and brackets.
In my development standards, scripts that are indented are those that are generally called by other scripts. And, generally, they appear directly below the script that calls them. So you can see from the script list that Check Layout Records is called by Developer Setup, which also calls Open Routine. Open Routine in turn calls Record Logged In User ID as well as two other scripts.
Parentheses, however, mean that that script takes a parameter, and the words in the parentheses are the names of the parameters. Sometimes, a script can take a parameter but doesn’t have to have one. In that case, the parameter name is also placed with brackets to indicate that it is optional. So in the figure, you can see that Set Window( [PrintFlag] ) is a script that takes one optional parameter, while Set Next Serial Value( WhichTable, NextSerialValue ) takes two parameters, and both are required.
Finally, every script I write calls a script called Script Start. Also, every script I write begins with a comment that documents what the script’s purpose is and other useful information. Because I don’t want to put these two things into every script manually, I have a Script Template script which is never itself called, but is always the start of a new script. When I wish to have a new script, I never create it from scratch. I always create it by duplicating the Script Template script and then change the comments to those appropriate for the script.
I think this may have been the most advanced column yet in this series, and I hope that I’ve done a good job with this tutorial. Take some time to absorb this before we move on next time to more practical applications of scripts in FileMaker. Until then, happy FileMaking!