FileMaker 7 and now FileMaker 8 have each introduced features that allow you to write scripts that are more flexible and extensible than in the past. FileMaker 7 introduced script parameters, a means of passing inputs into a script. FileMaker 8 completes the picture by adding script results, the capability for a script to output a piece of data after it's finished executing. Together, we can think of these features as constituting a system for script input and output.
The capability to move data in and out of scripts is desirable because it means that scripts can be written more abstractly and thus can be reused. By "abstractly," we mean that scripts are written to solve general problems rather than specific ones. Using script input/output saves you time, reduces the number of scripts that are necessary in your files, and makes your scripts easier to maintain.
That being said, the use of script input/output is completely optional. FileMaker developers did quite well for years without script parameters and script results, and we can think of no scenario in which you couldn't still muddle through without them. Script input and output represent a considerable advance for FileMaker Pro scripting; the extent to which you want to take advantage of that depends on the needs of your users and the scope of your files.
Much of what there is to say about script input/output applies equally well to script parameters (inputs) and script results (outputs). We'll discuss script parameters first, and then delve into a consideration of script results.
Before we get into the details of how and why to use script parameters, a short example will give you a concrete sense of what script parameters are all about and why you want to learn this. Imagine that you want to create several navigation buttons that take users to a specified layout. One way to do this is to create a separate script that's hard-coded to go to a particular destination. You'd need as many scripts as you have destination layouts, and every time you wanted to add a new destination, you'd create a new script.
Another way to accomplish this task is to create a generic "Go to" script that navigates to a layout specified by the script parameter that was passed to it. Then, when setting up the buttons themselves, you would simply call the "Go to" script, specifying the destination as the parameter. This approach has the advantage of requiring only a single script. To add another destination in the future, you simply specify the new destination as the parameter. There is no need to add a new script or to edit the original "Go to" script.
It's clear from this example that extracting hard-coded values from a script and placing them instead into script parameters has a tangible benefit. Keep this example in mind as you read further about script parameters.
Specifying Script Parameters
Script parameters can be defined in several places: as part of a button definition, as an option for invoking a subscript within the Perform Script script step, or as part of the definition of a custom menu item. Figure 15.1 shows the first of these: the dialog for specifying which script should run when a button is clicked. The interface for specifying a subscript is exactly the same; it too gives you a place to specify a parameter when calling a script.
Figure 15.1. When attaching a script to a button, you can also specify an optional script parameter, which is passed into the script.
At the bottom of this dialog, you have the option of specifying a script parameter. The parameter can be some text string you type into the space provided, or you can enter a calculation formula as the parameter. Clicking the Edit button brings up a standard calculation formula dialog box. If you use a calculation formula as your script parameter, when the button (or subscript) is triggered, the formula is evaluated and the results are used as the parameter.
The actual string or formula you use as your parameter depends completely on what you're trying to accomplish. Later in this section, you'll see some sample applications of script parameters.
Only scripts triggered by buttons or called as subscripts of other scripts can have script parameters passed to them. Scripts triggered through the Script menu, by an external source (such as via Custom Web Publishing), or as startup/shutdown scripts (under File, File Options) cannot have script parameters passed to them.
Retrieving a Script Parameter
The Get(ScriptParameter) function can be used to retrieve the value of the parameter passed to a script. If no parameter was specified, this function simply returns an empty string. The value of the script parameter can be accessed anywhere from within the script in this way. It can't be changed or altered in any way, and it expires as soon as the script is complete.
Any subscripts called by a script can have their own independent script parametersthey do not inherit the parameter of the script that calls them. As an example, say that the string abc was designated as the parameter to be passed to a script called Main Script. Assume further that Main Script called a subscript called Child Script as its second step, and that the parameter xyz was specified as part of the Perform Script step. Within Main Script, Get(ScriptParameter) will always return abc. Within Child Script, Get(ScriptParameter) will always return xyz.
The parameter passed to a script can be the result of a calculation, so by using Get(ScriptParameter) as the parameter, you can pass a script's parameter down to the subscripts it calls, as shown in Figure 15.2.
Figure 15.2. If you want a subscript to inherit a parameter, set the parameter to Get (ScriptParameter).
Passing Multivalued Parameters
The interface for specifying script parameters allows for only a single value to be passed to a script. For many situations, this is sufficient to achieve the desired outcome. Other times, however, you will find that you want to be able to pass multiple parameters to a script. Although this isn't directly possible, there are several methods to achieve such a result.
Parsing a Text Array
The simplest way to pass multiple values in a script parameter is to specify a delimited array as the script parameter. For instance, if you wanted to send a parameter that contained the values Fred, 123, and Monkey, you could send the string Fred|123|Monkey, or even Fred¶123¶Monkey.
The delimiter you use (here we've used pipe characters and carriage returns) is up to you; just choose something that you know won't be found in the data you're passing.
To retrieve a portion of the passed parameter, use the built-in text parsing functions of FileMaker Pro. If you've used carriage returns as your array delimiter, the GetValue function is the easiest way to extract a particular value. Say that you want to grab the third value (Monkey). From within your script, anytime you wanted access to this value, you would use the following formula:
GetValue (Get (ScriptParameter) ; 3 )
For more on text parsing functions, see Chapter 8, "Getting Started with Calculations," p. 217, and Chapter 14, "Advanced Calculation Techniques," p. 391. See also FileMaker 8 Functions and Scripts Desk Reference, Chapter 6, "Calculation Functions," for a detailed function reference.
The nice thing about using delimited lists to pass multiple values is that you can set them up very easily. Even if some of the values are derived as calculated results, it's still quite easy to set up a formula that concatenates all the appropriate pieces together. For instance, if you wanted to pass the current layout name and the current time as the two values of your script parameter, you would use the following formula:
Get (LayoutName) & "¶" & Get (CurrentTime)
The main drawback of this method is that the burden is on you, the developer, to know what each position in the array represents. Does the value Monkey represent a favorite animal, a password, or a Halloween costume? There's nothing in the parameter itself that offers any assistance. This can (and should!) be clarified with script and/or calculation comments.
Using the Let Function
Another method for passing multiple values in a script parameter involves the Let and Evaluate functions. If you have a good understanding of those functions, you'll likely appreciate the elegance of this technique.
For more on the Let and Evaluate functions, see "Logical Functions," p. 392.
Imagine that you pass as your script parameter the following string:
"First Name = "Fred"; Favorite Number = 123 ; Favorite Animal = "Monkey""
What you have here is a set of name/value pairs, separated by semicolons. Immediately you can see one of the benefits of this method over the previous one: When you pass both names and values, the parameter becomes more meaningful. In 6 months when you need to troubleshoot or enhance your script, you won't have to rack your brain to remember what the elements in your parameter represent. Another benefit of this method is that the order of the values doesn't matter. They'll be retrieved by name rather than by their position within the parameter.
You'll notice that within the parameter, there are backslashes before all the internal quotation marks. This process, known as escaping your quotes, is necessary anytime you want to pass a string that contains internal quotes. For this technique, you need to escape the quotes surrounding any text values in your parameter; numeric values (such as the 123) do not need quotation marks and hence don't need to be escaped.
You might recognize that the parameter specified previously is structured similarly to the first parameter of a Let function. This isn't a coincidence. Recall that the Let function allows you to set variables within a calculation formula. Imagine you had the following formula:
Let ([First Name = "Fred"; Favorite Number = 123 ; Favorite Animal = "Monkey"] ; Favorite Animal)
This formula sets three variables (First Name, Favorite Number, and Favorite Animal) and then returns the value of the Favorite Animal variable. It would, in fact, return Monkey.
By combining the Let and Evaluate functions, you can build a formula that pulls out a named value from within a script parameter. The Evaluate function executes a dynamically constructed calculation formula. Therefore, within your script, anytime you want to retrieve the value of the Favorite Animal, you would use the following formula:
Evaluate ( "Let ([" & Get(ScriptParameter) & "]; Favorite Animal)")
As you can see, a string containing a Let function is dynamically assembled from the value of the script parameter. The Evaluate function is then used to execute it. To return one of the other variables within the script parameter, you would simply need to change the end of the formula to reference the proper variable name.
If you foresee a need to do much parsing of multivalue script parameters, you should consider creating a custom function to simplify the process even more. That way, you won't have to remember the syntax for the Let and Evaluate functions every time you need to retrieve a parameter value. Figure 15.3 shows the definition for a custom function called GetParam.
Figure 15.3. The custom function GetParam abstracts the script parameter parsing routine even more.
For more on creating custom functions, see "Custom Functions," p. 420.
The GetParam function takes a single argument, paramName. The formula for the function is the same as the Evaluate formula shown previously, but with the paramName inserted in the place of the hard-coded parameter name:
Evaluate ( "Let ([" & Get(ScriptParameter) & "]; " & paramName & ")")
Now, within your script, to retrieve the value of the Favorite Animal, you just need the following formula:
GetParam ("Favorite Animal")
This final abstraction provided by the GetParam custom function certainly makes the parameter parsing more convenient. After it's in place, you can pass and retrieve multivalued script parameters with ease.
Passing Structured Data Elements
The final method in this discussion for passing multivalued script parameters involves creating your own structured data elements. It's really a hybrid of the other two methods, in that it requires standard text parsing to retrieve an element (like the first method), but the elements are meaningfully named (as in the second method).
The syntax you create for naming elements is up to you. We generally prefer an XML-like structure because it's easy to use and organize. For instance, to pass the same three values discussed in the preceding section, you might specify the following as your script parameter:
This is, of course, simply another way of specifying element names and values. But you don't need to worry about escaping any quotes, as you do with a string that will be used in an Evaluate statement. To retrieve the value of a particular element of the script parameter, you would need to use standard text parsing functions. This is best accomplished with the creation of a custom function; you then need to write the parsing logic just once. The following formula could be used as the definition for such a custom function; the function's only parameter is paramName:
Let ( [ openElement = "<" & paramName & ">"; closeElement = "" ; startPos = Position (Get(ScriptParameter) ; openElement ; 1; 1) + Length (openElement); endPos = Position (Get (ScriptParameter) ; closeElement ; 1; 1)] ; Middle (Get(ScriptParameter) ; startPos ; endPos - startPos) )
If this function were called GetParamXML, the value of one of the script parameter elements could then be retrieved with the function GetParamXML("First Name"). The custom function is hard-coded to parse out a value from a script parameter.
You could easily turn this into a more generic XML parsing tool by passing a text string in which to search as another parameter.
Strategies for Using Script Parameters
Using script parameters can greatly reduce the number of scripts in a file and can make your database much easier to maintain. You should consider using script parameters in several common programming scenarios.
The firstand most importantreason for using script parameters is to add a layer of abstraction to your scripts, thereby making them more modular and reusable. Rather than writing scads of single-purpose scripts, if you can generalize your scripts by using script parameters, you will need fewer scripts and your solution will be easier to maintain.
Practical Script Parameter Examples
Elsewhere in this book are several examples of script parameters being used to modularize scripts. For instance, in Chapter 10, "Getting Started with Reporting," one of the reporting techniques involved turning the column headings on a list view into buttons that would sort by the values in that column. All the buttons called a single script, passing in a different script parameter. Without script parameters, that routine would have required separate scripts for each column, each with a single hard-coded sort specification. Not only would that take longer to set up, but if the behavior ever needed to be modified, changes would need to be made to multiple scripts rather than a single, abstract script.
Script parameters were also central to the navigation routine described in Chapter 13, "Advanced Interface Techniques." There, a generalized navigation script was passed a destination layout as a script parameter.
You will know if you have encountered a situation that can potentially be simplified and strengthened by using script parameters if you find yourself writing several scripts that do basically the same thing, differing only in some specific value. In place of that specific value, use Get (ScriptParameter), and then have the buttons or other scripts that trigger the script specify the particular value.
For example, say that you've developed a system that contains a calendar, and that one of your layouts shows all the scheduled appointments for a given week. You'd like to be able to place a button above each of the seven days of the week (Sunday through Saturday) that users can click when they want to create a new appointment on that particular day. Assume that you have a field that contains the date of the Sunday of the week. Therefore, a script that would create a new appointment on Wednesday would do something like the following:
New Record/Request Set Field [Appointments::AppointmentDate ; SundayDate + 3]
The scripts for creating appointments on the other days of the week would differ from what's shown in the preceding formula only by the constant that's added to the SundayDate. You could therefore write seven scripts, link them to your buttons, and move on to your next task.
We hope you can already see how and why script parameters can be used here. In the sample script, if you change the + 3 to + Get (ScriptParameter), you need only a single script to do the work of the seven that would be required without script parameters. Each of the seven buttons calls the generic version of this Add Appointment script, passing as a parameter an integer from 0 to 6 to differentiate them from each other. By using this method, you've replaced seven hard-coded scripts with a single generalized one.
Passing Data Between Files
Another situation in which script parameters can be beneficial is for passing data between files. Using script parameters for this purpose saves you from needing to create extra fields and relationships in your files.
As an example, imagine that you have a file called transactions and another called TRansactionArchive (each with a single table with the same name as the file). You periodically archive old transactions into the archive file, but occasionally you have a need to pull a record back from the archive into the main production file. Further, you'd like to avoid placing a table occurrence from the archive file in the main file because the two need to be able to function independently.
Because you can call scripts in another file without having a relationship to that file, script parameters make an ideal transfer mechanism for moving data between unrelated files. In the sample scenario, you might set up a script in the transactionArchive file that calls a subscript in the TRansaction file, passing a multivalued parameter (using one of the methods described in the preceding section) that contains the pertinent data elements from the transaction. In the transaction file, then, your subscript would create a new record and populate it, using the parsed-out parameter data.
In this example, importing the record from one file to the other would have been another solution within the defined constraints. Nonetheless, this example still clearly demonstrates the role that script parameters can play in moving data around. It's certainly preferable to copying and pasting data, or even parking data in global fields for later retrieval (both of which were common techniques with versions of FileMaker before version 7).
A final strategy for using script parameters is as a means of protecting subscripts from inadvertently being called improperly. Imagine that you have a pair of scripts, Script A and Script B, and that Script B is called as a subscript from Script A. As it stands, nothing would prevent another script from calling Script B, or even from Script B being called directly. In some cases, there may be undesirable consequences that can occur if subscripts are called directly.
To protect Script B, and ensure that it's called only as a subscript of Script A, you can pass a script parameter that authorizes Script B to run. It really doesn't matter what value you pass. You can check at the beginning of Script B to see whether the script parameter is set correctly. If it isn't, exit the script or show a warning dialog to the user.
Of course, any user who has access to the scripts in question can discover what parameter Script B expects, so this method isn't intended to be used as a security measure. It merely protects against accidental execution of subscripts (and direct invocation from the Web).
Script results, a new addition in FileMaker 8, are, if you like, the flip side of script parameters. A script parameter lets you feed data into a script; a script result lets you pass data back out of a script. In the past, you might have done this by putting some data into a global field for other scripts to look at later. In FileMaker 8, you could opt to put the data into a global variable instead (variables are discussed in the following section). But the best choice is generally to use a script result.
To return a result from a script, you'll use the Exit Script script step, which has some new options in FileMaker 8. It's now possible, when using the Exit Script script step, to specify a result to be returned when the script exits. Much as when specifying the value for a Set Field or Set Variable script step, you can create a calculation expression that defines the result to be returned.
That takes care of how to return a script result. To access the returned result, you'll then need to use FileMaker 8's new Get(ScriptResult) function, a sort of a twin to Get(ScriptParameter). Get(ScriptResult) will hand back whatever result was returned by the most recently completed script or subscript.
Let's consider a full example. As we've suggested, one of the main reasons to use script input/output is to increase the reusability of your scripts. Consider a solution with a large number of reports. When allowing users to print reports, it's common to first display the report in Preview mode, pause the script, and then, on resuming, pop up a dialog box asking whether the user wants to print the report. The task of prompting the user for print confirmation may happen over and over again in a report-intensive solution. Using script results, you can write a single script to query the user, and then return the user's choice as a script result. Here's what such a script might look like:
[View full width]
Show Custom Dialog [ Title: "Print Confirmation"; Message: "Would you like to print the report?"; Buttons: "Yes", "No" ] Exit Script [ Result: Let ( [ msg = Get(LastMessageChoice) - 1; choiceText = Choose ( msg ; "Yes"; "No" ) ]; choiceText ) ]
Notice the difference in the Exit Script step. As part of this step, the script specifies that a calculated result be returned from the script. The calculation looks at the numeric result of the dialog box choice, converts it into text using the Choose function, and returns the corresponding text result.
To use this script's modular functionality, it needs to be called from another script. A script to display and optionally print a single report might look like this:
Go to Layout [ "Report" ] Sort Records [ Specified Sort Order: Reporting::Region; ascending ] [ Restore ] Enter Preview Mode Pause/Resume Script [ Indefinitely ] Perform Script [ "Print Confirmation Dialog" ] If [ Get ( ScriptResult ) = "Yes" ] Print [ ] [ No dialog ] End If Go to Layout [ original layout ] Enter Browse Mode
This script performs all the usual sort of management common to previewing reports: navigating to a layout, sorting the records in some way, entering Preview mode, pausing for the user to look over the report. When the user resumes the script, though, the script goes straight into the confirmation subscript. Thereafter, the outer script uses Get(ScriptResult) to determine the result of the confirmation dialog, and prints the displayed report, or not, accordingly.
With such a script, instead of having a dozen print dialogs coded all over your system, you now have just one. If a user reports a problem with the print dialog, you now know where to start looking. And any changes or improvements made to the print confirmation process immediately benefit all reports that use this functionality.
Consider next a similar, but more complex, example. Suppose that your solution has a fairly complex dialog box that it needs to display on several occasions. For various reasons, FileMaker's built-in custom dialog tool is not quite enough for the task, so you'd like to design your own custom layout and pop it up in a new window as a kind of "super custom dialog."
Let's pretend your dialog is a typical software licensing dialog: It has a long software license in a scrolling text area and, underneath, buttons reading Agree and Disagree, as shown in Figure 15.4.
Figure 15.4. Using script results, you can create a modular custom dialog routine.
You'd like to create a script to show this dialog and then report whether the user clicked the Agree or Disagree button.
This sounds pretty straightforward, but something's not obvious. How do you determine which button the user pressed? With a standard dialog, you can use Get(LastMessageChoice); but this is a home-brew dialog, and FileMaker won't help you here.
Earlier in this chapter, you learned that you could attach a script parameter to a script when calling a script from a button. The Agree and Disagree buttons could each call a script, one with a parameter of Agree, the other with a parameter of Disagree. Still, that's not clearly helpful. This amounts to pushing data further down, into another subscript, when what you really want to do is pass the data backward, back up to whatever script invoked the license confirmation to begin with.
Here's where a little cleverness comes into play. Suppose that you write a one-line script, which we'll call Kicker, that looks like this:
Exit Script [ Result: Get(ScriptParameter) ]
All this script does is take whatever parameter is passed to it and "kick" it right back out as a script result (hence the quirky script name).
Now suppose that each of the two confirmation buttons calls the Kicker script; one with a parameter of Agree, the other with Disagree. Suppose further that each button is set to resume the currently executing script. And finally suppose that the overall license confirmation dialog script looks like this:
New Window [ Name: "Confirm Software License" ] Show/Hide Status Area [ Hide ] Go to Layout [ "License" (License) ] Adjust Window [ Resize to Fit ] Pause/Resume Script [ Indefinitely ] Close Window [ Current Window ] Exit Script [ Result: Get(ScriptResult) ]
The script creates a new, named window, and performs a number of basic window management tasks: hiding the status area, navigating to the layout containing the license text and the Agree/Disagree buttons, and resizing the window to fit its contents. The script then pauses, waiting for user input.
When the user clicks either button, the corresponding script parameter (Agree or Disagree) is passed down into the Kicker script. Kicker immediately kicks the value back as a script result, which means that the user's choice is now accessible via Get(ScriptResult). The buttons are set to resume the paused script, so after the user has clicked a button, the main script goes on executing: It closes the license window and then returns the user's choice (again!) as a script result to whatever outermost script invoked the license dialog.
This example may seem like a small bit of magic. How was it possible to move all that data around across as many as three scripts, without ever storing it anyplace? Well, that's some of the power of script input and output. Note also that the number of script steps it takes to accomplish all this is very small. Note finally that script input/output is not limited to operations that are one level deep. Here we took the result of a button press, passed it out to a calling script, and then turned right around and passed it "upstairs" once more. After you've mastered having script A call script B and get something back, you'll begin to see occasions when it makes sense to have A call B, have B call C, and have the result of C passed back along the chain until it comes back to A again. If you go more than three or four levels deep with this kind of data passing, you may be creating something more complex than is necessary. Still, it's important to realize that in some circumstances you may need to go a couple of levels deep. Not only is this possible with FileMaker's script input/output, but it's both powerful and straightforward.
Final Thoughts on Script Input/Output
The script input and output capabilities of FileMaker 8 represent a major advance in the capability to construct streamlined, reusable routines within a FileMaker solution. Mastering the use of these techniques is critical to getting the most out of FileMaker 8. We recommend that you study these features carefully, and that you look fairly aggressively for opportunities to use them. Anytime a script does similar work with different input values, consider using script parameters. Anytime a script may be better structured as a tool that does some work, and then reports on the results, consider reporting those results via a script result. Your solutions will become cleaner, simpler, and more elegant.
Part I: Getting Started with FileMaker 8
Using FileMaker Pro
Defining and Working with Fields
Working with Layouts
Part II: Developing Solutions with FileMaker
Relational Database Design
Working with Multiple Tables
Working with Relationships
Getting Started with Calculations
Getting Started with Scripting
Getting Started with Reporting
Part III: Developer Techniques
Developing for Multiuser Deployment
Advanced Interface Techniques
Advanced Calculation Techniques
Advanced Scripting Techniques
Advanced Portal Techniques
Debugging and Troubleshooting
Converting Systems from Previous Versions of FileMaker Pro
Part IV: Data Integration and Publishing
Importing Data into FileMaker Pro
Exporting Data from FileMaker
Instant Web Publishing
FileMaker and Web Services
Custom Web Publishing
Part V: Deploying a FileMaker Solution
Deploying and Extending FileMaker
FileMaker Server and Server Advanced
Documenting Your FileMaker Solutions