Update 28 April 2014: Make sure to read the illuminating comments following the article, with various suggestions to make your code even less brittle.
The other day I was working with an OnRecordCommit script trigger — let’s call it “Trigger Script” — and, not surprisingly, I wanted this script to run whenever a record in a certain table was committed. Except… well… not exactly always… you see, there was this one other script— let’s call it “Other Script” — which had a Commit Record step right smack in the middle, and in that particular circumstance I definitely did not want Trigger Script to execute.
Luckily, we can include a parameter when a script is triggered, so I added the highlighted script parameter to the OnRecordCommit trigger as follows:
…which evaluates as 0 (zero) when the current script is “Other Script”; otherwise it evaluates as 1. Next I added some new steps at the top of Trigger Script to instruct it to bail out if necessary.
…and problem solved. But… not optimally, because what happens if I realize “Other Script” isn’t the ideal name for that script, and decide to rename it?
[Yes, I realize that I could have used a $$variable to pass the “don’t execute” information between the scripts — but if I’d done that, I wouldn’t have an excuse to talk about hard-coded object references.]
Many years ago I heard Albert Harum Alvarez use the term brittleness to refer to code that breaks if you modify a referenced object, for example: let’s say your script instructs FileMaker to go to a certain (hard-coded) layout number, but then later you change the order of your layouts without remembering to update the script… two words: “kah-blooey!” (Don’t ever hard-code layout numbers in your code; they’re way too volatile. Seriously.)
More often though, brittleness can occur when we refer to objects by name, via functions such as, but not limited to: Get(LayoutName), Get(ActiveFieldName) or, as per my script parameter above, Get(ScriptName). Well it turns out that a Belgian FileMaker developer named Fabrice Nordmann has written a custom function, FM_Name_ID, that can translate almost any FileMaker object name into its corresponding internal id… and vice versa.
That’s right, this one CF translates in both directions: input an ID, you get back a name; feed it a name, out pops an ID. I’ve been working with internal layout ids in various ways for many years, but it never occured to me that a single custom function could be used for both encoding and decoding. It’s a great idea, and I wish I would have thought of it.
You can find the syntax for the custom function here:
www.fmfunctions.com/fname/FM_Name_ID
…and a detailed writeup by Fabrice here:
www.1-more-thing.com/FileMaker-Avoid-Hard-coding.html
So, to cut to the chase, I was able to remove the hard-coded script name reference from my script parameter and replace it with…
…which, admittedly, is not as readable, but is certainly more robust. As Fabrice suggests, it’s a good idea to add a comment like “// Other Script”… though truth be told, it’s not much work to throw FM_Name_ID ( 341 ; "S" ; "" ; "" )
into the Data Viewer if you can’t remember what script you were referring to.
One of the things I love about custom functions is the open-source nature of them, and if I’m going to use a CF, I want it to have as few arguments as possible, and I also want to be able to use it without having to give it much thought. So, I decided to spawn a few “baby” (single-purpose) versions from Fabrice’s mothership CF for my own use, which incidentally, you can find, along with Fabrice’s original CF, in today’s demo file: CF-Sandbox.
The baby versions are FMNID_Layout, FMNID_Table, FMNID_Script, etc, and here’s the final version of the parameter:
Incidentally, I did include one original CF in today’s demo file; it’s called FNO, which is short for “field name only” — basically, it’s the GetFieldName function minus the “table::” portion.
I wrote it to faciliate working with the Get(ActiveFieldName) function…
Before: If ( Get(ActiveFieldName) = “organization” ; …
After: If ( Get(ActiveFieldName) = FNO ( customers::organization ) ; …
…and now I can rename “organization” to “business”, “department”, “division”, or what have you, with a 100% clean conscience.
Post script: 19 April 2011
In the comment section below, Geoff Gerhard offers an alternative method to solve this problem. I have updated this article’s demo file to include his approach, which I actually prefer to mine.
Thanks for sharing your thoughts on this–I’ve been thinking about the issue of “brittleness” in the context of data/meta-data/structural/procedural abstractions and had not seen Fabrice’s CF.
One quick comment: the use case for your FNO CF excludes the possibility that two or more fields on a layout have the same name but come from different Table Occurrences. If, for example, the active field name is subsidiaries::organization, the “After” example evaluates to true. The following calc does not require a CF and surmounts that potential problem:
( Get ( ActiveFieldTableName ) & “::” & Get(ActiveFieldName) ) = GetFieldName ( customers::id )
Very nice, Geoff. Your example definitely solves a problem that could arise from time to time, and one that I’d overlooked. Thanks for taking the time to write that up.
On further consideration, I like your approach a lot more than mine… my approach was “dumb down GetFieldName with a CF”… yours is “make Get(ActiveFieldName) smarter”. So, I’ve added another CF called GATF to the demo file. GATF is simply your…
Get ( ActiveFieldTableName ) & “::” & Get(ActiveFieldName)
And I also added an example showing how to use it.
Hey Kevin,
Always nice to see good code on the interwebs! I’ve been personally using Fabrice’s function since it was named ObjectId. To simplify your code even more, we’ve created a best practice suggestion over at filemakerstandards.org.
http://filemakerstandards.org/display/cs/Layout+references
Essentially, you reserve the use of the FM_Name_ID() function for internal use (a private function) within the Custom functions area and then create wrapper functions around it. The code can become quite readable and easy to maintain. Something as clear as this reads great!
If [ LayoutIsCustomerDetails ]
# Do layout specific steps here...
End If
—
For more information about my work with FileMaker, visit my dedicated web site for FileMaker Tutorials
Kevin
Thanks for this
I have extended mine to take account of repetitions
Let ( [
_rep = Get ( ActiveRepetitionNumber ) ;
_end = If ( _rep = 1 ; "" ; "[" & _rep & "]" )
] ;
Get ( ActiveFieldTableName ) & "::" & Get ( ActiveFieldName ) & _end
)
Hi Kevin,
Another great post with lots cool stuff.
I like that you broke Fabrice’s brilliant code into separate (single-purpose) versions.
You might want to split the version for Layouts into 2, i.e…..
LayoutName -> LayoutID
LayoutID -> LayoutName
… for 2 reasons:
1. Layouts could be named using a number (not by me!) which would break
2. The code would be easier to read by mortals ;-)
There is also the edge case to trap for that someone (again not me)
might use the same LayoutName more than 1 time in the same file.
Thanks again.
Tony White
Hi Tony,
Thanks for taking the time to comment. You are correct as usual.
Regards,
Kevin