It’s been almost 10 years since the release of FileMaker 7, and with it, the introduction of custom functions to the developer arsenal. Over the last year or so, I’ve been making ever greater use of this feature, and thought that perhaps sharing the uses I make of custom functions might prove educational to others.
I’m going to provide a few explanatory examples, so keep in mind the following conventions that I use when working with custom functions:
All custom functions have a four-letter namespace prefix, such as
devp for functions specific to developer needs,
txtp for text processing functions,
appl for application-specific functions.
All parameters and local variables in custom functions begin with an underscore and use the underscore to separate words, resulting in names like
When sharing the code for a function here, the first line will display it as it would be called but with the arguments that would normally be sent replaced with the named parameters from the function’s definition, followed by a colon. The lines that follow are the source code of the function itself, as in:
devp.MyFunction( _param_1; _param_2 ): _param1 & _param_2
I tend to think of these functions as fulfilling the original purpose of custom functions, replacing common calculation code with a function, thereby abstracting the purpose. Once you have created the custom function (and have tested it sufficiently), you don’t have to remember how it works, and if you later find a better way to accomplish the task, you can replace the code in the custom function and all calls to the function will automatically take advantage of the update.
For example, it’s somewhat common to need to know if a number is an integer, and the simple code for this would be something like
Int( _number ) = _number, so I have a custom function for this purpose.
nump.IsInteger( _number ): Int( _number ) = _number
A more complicated example is a replacement for the built-in
Trim function, which removes spaces from the beginning and end of a text string. Unfortunately,
Trim ignores whitespace, and most of the time that I want to remove spaces, what I actually want is to remove all beginning and ending whitespace, including returns and tabs.
For this I have
txtp.Supertrim( _text ): // Speedily (and with no recursion) remove leading and trailing white space, // including spacing, tabs and returns, from a text string. // // Created by Debi Fuchs, firstname.lastname@example.org Let( [ // Determine value of original string with ALL whitespace removed. _text_2 = Substitute( _text; [ " "; "" ]; [ " "; "" ]; [ " "; "" ]; [ "¶"; "" ] ); // Determine position of first non-ws character in original string. _first_char = Position( _text; Left( _text_2; 1 ); 0; 1 ); // Determine position of last non-ww character in original string. _last_char = Position( _text; Right( _text_2; 1 ); Length( _text ); -1 ) ]; // If any non-whitespace characters exist return appropriate middle portion of // original Text. Case( _first_char; Middle( _text; _first_char; _last_char - _first_char + 1 ) ) )
By the way, I think that there are two spaces in there (note I didn’t write this function) because one is a normal space and the other is a non-breaking space, but I haven’t confirmed this.
Unlike script and layout organization, FileMaker doesn’t yet (as of version 12) offer custom function folders, so a couple of years ago I begin using the same techniques for custom function organization that I used to use for script and layout organization, which is to create custom functions which serve solely as dividers of sections of custom functions.
As I mentioned with regards to my custom function naming standards, every custom function begins with a four-letter namespace prefix, but each of these four-letter prefixes has one custom function that serves as the header for that prefix. Since the actual functions are named with a prefix, followed by a dot, and then the function name, I use the same prefix but follow it with an underscore, the name of the section, and then enough underscores to serve as a dividing line:
appl_____ Application Functions ___________________________________________________,
devp_____ Developer ___________________________________________________,
txtp_____ Text Processing ___________________________________________________. Since the underscore sorts before the period, sorting my custom function list by function name effectively places these “folder” functions before all of the functions with the same prefix.
Some constants appear in almost every solution. The empty string (
"") is an easy example, but also constants returned by various
Get functions (for example,
3 when evaluating
Get( SystemPlatform ) while the system is running on iOS).
If I can at all avoid using constants within calculations outside custom functions, I do. So instead of using
"" for an empty string, I have
The unfortunately cryptic constants returned by various
Get functions results in calculations that are initially ambiguous. Consider if you came across the following calculation (and hadn’t been recently reminded what the
Case( Get( SystemPlatform ) = 3; // Do something )
One solution to this is to include a comment after the test.
Case( Get( SystemPlatform ) = 3; // Are we running on FileMaker Go? // Do something )
What I do instead is create a custom function just for this purpose.
My code for the same test would read:
Case( Get( SystemPlatform ) = sysk.FileMakerGo; // Do something )
In fact, I take it a bit further by creating yet another custom function:
sysk.PlatformIsGo: Get( SystemPlatform ) = sysk.FileMakerGo
which gives my final Case function as
Case( sysk.PlatformIsGo; // Do something )
My goal here is to make the code more readable, but also easier to write, as I never have to look up what is returned by
Get( SystemPlatform ) in order to perform the correct test.
In a similar vein, I use simple constant custom functions for error numbers.
Each solution has its own constants as well, especially tab names, but also layout names. One principal of programming, and a common theme of my custom function use, is to store a piece of information once. With tab names, this is unfortunately not possible, but we can store it only twice, once on the actual tab, and once in the custom function. Then, when we need to test if we’re on a particular tab or if we need to script the navigation to a particular tab, we use the custom function instead of the sting constant.
Both this use and the previous use of custom functions provide an additional check against typos. If I need to navigate to the object named
"mainmenu_budget_shown", but in my script type in
"mainmen_budget_shown" (omitting the
"main menu"), FileMaker won’t know that I’ve made any mistake. If I do something similar with the custom function, however, and attempt to save a calculation with
app.TabMainMenBudgetShow (again, omitting the
"u"), FileMaker will complain and force me to correct the error.
Here’s where I get to the final stage of custom function use, creating functions that go beyond simple code replacement or constant replacement and perform advanced functionality while dramatically increasing the code readability.
The easiest example, I think, is my
mdfk.ModifierKeyIsActive function, which looks like this:
mdfk.ModifierKeyIsActive( _key ): // The ModifierKey parameter should be an integer between 1 and 5. These can be // accessed via the kModifier* custom function constants. Returns whether or // not the given modifier key, as specified by it's power of two equivalent, // is held down at the time the function is evaluated. // // EXTERNAL REQUIREMENTS: The BitIsSet custom function. Easiest to use with the // presence of the kModifier* custom function constants. // // Written by Charles Ross mdfk.BitIsSet( Get( ActiveModifierKeys ); _key )
In turn it’s using the
mdfk.BitIsSet function, which has the following calculation:
mdfk.BitIsSet( _number; _bit ): // Returns whether or not the binary bit in the given number is turned on. i.e., // mdfk.BitIsSet( 16; 5 ) would return True as 16 is 10000 in binary. Similarly, // mdfk.BitIsSet( 18; 2 ) would also return True as 18 is 10010 in binary. This // calculation is thanks to Mikhail Edoshin and is found at // http://edoshin.skeletonkey.com/2005/11/custom_function.html. // // Written by Mikhail Edoshin Mod( Div( _number; 2 ^ ( _bit - 1 ) ); 2 )
I won’t go into the details of this one, which looks deceptively simple on the surface (I’d point you to Mikahils’ article, but it no longer seems to be served), but the end result of these two functions (and constant functions such as
mdfk.ModifierCapsLock, etc.) is that my test for modifier keys looks like this:
Case( mdfk.ModifierKeyIsActive( mdfk.ModifierAlt ) and mdfk.ModifierKeyIsActive( mdfk.ModifierShift ); // Do something )
The above is precisely equal to the following:
Case( Get( ActiveModifierKeys ) = 5; // Do something )
While my version is much longer, it’s much easier (without resorting to comments) to tell what the test is really trying to determine.