Pop: Goes the…um…Variable
We start our return to semi-regular writing with a small-but-awesome custom function: Pop. If you grew up in the American mid-west like me you might think I’m talking about [soda|soda pop|coke|soft drinks]. But I’m actually talking about stacks. In the computer science world, pop means to take something off the top of a list, and it is exceptionally handy to make it easy in FileMaker scripting.
Looping Through Lists
Imagine you have a list of values. (I know, what a weird concept in a database).
Isabel
Sophia
Mamie
Geoff
Jesse
And you want to do something with these items in a script. Typically, for example, you might want to make a new record for each item in the list. You probably already know that FileMaker has several functions designed to make working with return-separated lists easier. For instance, suppose the list above was in a variable called $names. Here are some functions you might use, and their results:
GetValue($names, 3) => returns "Mamie"
LeftValues($names, 2) => returns "Isabel¶Sophia¶"
MiddleValues($names, 3, 2) => returns "Mamie¶Geoff¶"
RightValues($names, 2) => returns "Geoff¶Jesse¶"
Writing our script using these functions isn’t particularly difficult. Here’s the form we normally use:
# assume $names has a return-separated list of names
# ...
Loop
Exit Loop If [ValueCount($names) = 0]
Set Variable [$name, GetValue($names, 1)]
New Record/Request
Set Field [The Name Field; $name]
# set other fields, do more with $name, or whatever*
Set Variable [$names, MiddleValues($names, 2, 999999)]
End
Note: You might wonder whey I have the
Exit Loop Ifstep right after the start of my loop. I do this a lot. It means my loop won’t run even once if the $names variable happens to be empty, which saves me anIfstep around the loop.
Pop: For the Lazy Programmer In Your Life
We write scripts like this a lot. We pull the first item out of the variable, use it or store it, and then re-set the variable to remove that first item again. Although it is minor, this is non-ideal for a few reasons:
The code is kind of strange. What does this code do? Why all those magic nines?
Any time you write the same couple of lines over and over again, your inner-time-saver-alert should start buzzing.
Call me lazy, but I’m always happy to save a few clicks and/or keystrokes.
And so, we wrote Pop. Here it is:
Pop(variableName) :=
Evaluate(
"let([
top = getValue( " & variableName & " ; 1 );
" & variableName & " = middlevalues( " & variableName & " ; 2 ; 999999999999999 )];
top)"
)
In a nutshell, this function takes the name of a variable as its parameter. It returns the first value in the variable and removes that value from the variable all in one step.
Note: Pay attention to the previous sentence. The parameter you pass to
Popis the name of a variable, not the variable itself. You want this:Pop("$variable"), not this:Pop($variable).
For the curious, I’ll explain how it works in a moment. But first let’s see it in action. We can now re-write our loop like this:
Loop
Exit Loop If [ValueCount($names) = 0]
Set Variable [$name, Pop("$names")]
New Record/Request
Set Field [The Name Field; $name]
# set other fields, do more with $name, or whatever
End
Instead of using GetValue to get the first item in the list, and then removing it later, we do it all in one step using Pop. In fact, since Pop is a function instead of a script step, we can use it right inside other expressions, so we can simplify our loop even more:
Loop
Exit Loop If [ValueCount($names) = 0]
New Record/Request
Set Field [The Name Field; Pop("$names")]
# *set other fields, do more with $name, or whatever*
End
This time, we get the value, remove it from the variable, and put it in the field all in one step. As a function, Pop can be used in other places too, like inside larger expressions:
Substitute(
$something;
[Pop("$to_remove"); ""];
[Pop("$to_remove"); ""];
[Pop("$to_remove"); ""]
)
Or in recursive custom functions:
If (ValueCount($whatever) = 0; ""; Pop("$whatever") & AmazingFunction
It is often really handy to be able to combine fetching and removing into one tiny expression.
How It Works
The Pop function uses an oft-overlooked FileMaker power-function called Evaluate. This is another one of those meta-features in FileMaker. Evaluate lets you process calculations inside your calculations.
When you write a calculation, you combine functions, values, fields, and operators to produce some useful result. Normally that result is some new data to display or use in your scripts. As you no-doubt know, FileMaker’s calculation engine provides some very powerful tools to process input and produce interesting results.
When you use Evaluate, the result of one calculation can be a calculation formula itself. Here’s a trivial example:
Evaluate("1 + 2")
In this case, the parameter we’re passing to Evaluate is the text value “1 + 2″. Since this is a valid FileMaker calculation, Evaluate will, well, evaluate it, and the final result of the formula will be 3.
Of course that example is silly. If I wanted to add 1 and 2 in a calculation, I’d just write it like this, and save a lot of trouble:
1 + 2
But since this is a calculation, you can get as complicated as you want:
Evaluate(Choose($something, "Average", "Min", "Max") & "(1, 2, 3)")
That equally-ridiculous formula will return either an average, minimum, or maximum of the values 1, 2, and 3 depending on the value of $something.
But why?! Why would I ever do that?
Well, you wouldn’t. But using those techniques, you can do all kinds of other cool things, like, oh, for instance, write Pop. All Pop does is generate a new calculation formula that looks like this:
Let(
[top = GetValue($my_variable, 1);
$my_variable = MiddleValues($my_variable, 2, 99999999)];
top
)
In other words, it makes a calculation that removes the first item from a variable and returns it. Since we don’t know the name of the variable ahead of time, we have to work the variable name into the calculation using a calculation. (I know, meta…). Once we’ve built this little formula, we use Evaluate to run it for us.
Easy as pie (wrapped in cake, wrapped in an enigma).
Why Would I Ever Use This
If you’re like me, right now you’re thinking this is a lot of work for a very little payoff. It saved a line or two in my loop. But I encourage you to try it out the next time you need to process lists of values. Like all custom functions, it is easy to abstract the complicated part away in your database and never worry about it again, calling on it here and there with an easy name. And that, truth be told, is the core power of programming.
Using Global Variables for List Selections
A funny thing happened while I was stumped over the question of how to allow one of our clients to select and deselect multiple items in a list. Multiple people would be using the list at the same time. I was struck by the fact that I’ve been making something more complicated than it needs to be for as long as I’ve been working with Filemaker. After toying with various methods which included adding fields to the records in the list, join tables, global fields and horribly convoluted scripting it occurred to me that you can actually write a relatively simple custom function to handle it all. It will add or subtract a value from a list that is stored in a global variable and then simply do some conditional formatting to highlight the selected items in the list. No additional fields or scripting are needed, the global variable is session specific and once you are done you’ve got your list of data ready to go as is.
Here are all the components of this process and how they fit together:
AddOrRemoveFromList(list, value) – The custom function that will maintain the values in the list for you.
$$selectedItems – The global variable that stores the list I want to operate against.
Selected Item – The actual record that you are adding to the list.
First I’d like to explain the custom function. Basically, that function checks a list that you the value you pass in. If the function finds that value, it removes the value and returns the new list sans value. If it doesn’t find the value, it returns to you the list with the value appended to the end of it. Here is the what AddOrRemoveFromList(value, theList) contains:
let(
[newList = Substitute(¶ & theList & ¶, ¶ & value & ¶, ¶)];
if( PatternCount( ¶ & theList & ¶; ¶ & value & ¶) = 1 ; Middle(newList, 2, length(newList) – 2); theList & if(not isempty(theList); ¶) & value)
)
The reason for all those extra ¶’s is for encapsulation and searching. When you think about what a list actually is, it turns into this: apple¶orange¶pear¶applejuice. If you want to search that list for apple right now you would be editing both the values apple and applejuice. To get around that we put an extra ¶ before the first entry in the list and after the last entry so our list now looks like ¶apple¶orange¶pear¶applejuice¶. So, if we now search for ¶apple¶ we would only be working with the actual apple value. To get the correct list format returned after that you just need to use the middle function to strip the first and last ¶ from the list.
So, we now have a custom function that will handle adding and removing values from a list. The only thing we have to do to make a selection is to say where we want to store the list. Using a global variable has the advantage of being easily accessible and being completely separated from your data structure, so you don’t need any additional fields or records to keep track of what the user wants selected. So, if you choose to use a global variable, the only step you need to take is to set the variable $$selectedItems using the custom function. Here’s what that step looks like:
If you are unformiliar with our parameter passing system, please take a look at this. So, we now have the selected items being added to the desired list automatically. So, let’s let the user see what they’ve selected. Using conditional formatting we’ll just apply that same list checking we used in the custom function. The formula should be:
PatternCount( ¶ & $$selectedItems & ¶; ¶ & item & ¶) = 1
“item” in the above example should be whatever you passed into the list. No
Script Triggers: Filter as You Type
For years, FileMaker developers have been devising various filter techniques. The idea is that you type all or part of a name, part number, description, etc… and a list of results filters down to show relevant matches. As handy as these techniques are, they always stop just short of perfect because, before FileMaker 10, you had to exit the field before the filter would take effect. Using FileMaker 10’s powerful Script Triggers, you can make the impact of your filtering more immediate.
Note: We’ve never done this before, but the idea, scripts, and sample file for this technique come directly from FileMaker luminary and Six Fried Rice reader David Graham of Bit Tailor. We’re publishing it here (with David’s permission) because it is an excellent example of Script Triggers solving an age-old problem in an elegant way.
There are as many ways to filter lists in FileMaker as there are developers implementing it. The beauty of this technique is that it doesn’t matter if you prefer exploded keys and filtered portals, aggregate text fields and a find, or (my personal favorite) scripted multi-request finds. Whatever you want, when it is fast, clean filtering you’re after, it is always better if the results appear as you type. This sort of thing was essentially impossible with FileMaker 9. Once a user started entering data in a field, you were hands-off until they finished.
Some people dreamed up horrifying solutions with looping scripts and all manner of wackiness. These techniques, in my opinion, never worked well. Consequently, we were always resigned to the “press Enter when you’re done” model. But when the results appear as you type, the user gets immediate feedback, only needs to go as far as necessary to get the result she wants, and doesn’t have to click back into the field to correct or make a change. So it is clearly a better model. See for yourself:
David’s technique relies on the handy OnObjectModify trigger on the filter field to kick off a script every time the field changes. This includes:
- When you add a single letter to the field by typing a key.
- When you remove something by pressing Delete or Backspace.
- When you cut text form the field or paste it in.
You may be tempted to use the OnObjectKeystroke trigger for something like this, but OnObjectModify gets you more bang for the buck: It handles cut and paste, and doesn’t require all the complexity of keystroke triggers.
The Code
I’ll let you explore David’s sample file for the full scoop, but the core of his technique can be found in two places. First, he uses an exploded key filtering technique, which I won’t cover her, except to say that the idea is to process some text data and produce a multi-line key that includes partial match values. In other words, if the value is “Test” then the key would match “T”, “Te”, “Tes” and “Test.” In this way, you can type all-or-part of the value and see the results. Look at David’s ExplodeToMultikey custom function to see how he accomplishes this.
If you prefer, you can use scripted finds and a list view layout to do your filtering instead.
But the interesting part is this very simple trigger script:
Set Variable [$currentObject; Get ( ActiveLayoutObjectName )]
Commit Records/Requests []
Go to Object [ $currentObject ]
Set Selection [ Start: Length (Get(ActiveFieldContents)) + 1]
That’s it. This script really just does two things:
- It commits the record, causing anything you’ve typed in the filter field to take effect.
- It returns you to the field, so if you type again, your keystrokes will go right on the end of the field.
In this way, as you type, your changes reflect immediately. Very cool.
Note: If you were using finds instead of exploded keys, you would add a a few more steps to your script to switch to Find mode, build a find request or two, and then perform the find.
You can download the working file right here.
Thanks to David for the great technique!
