By Daniel Wood, 11 February 2012
Script triggers are a great feature in FileMaker that allow you to initiate a script when certain actions occur such as committing a record or leaving a layout. Not only do they run when user initiated, they can also be script initiated by script steps. This can sometimes produce unexpected results and so you may wish for triggers to be disabled. This article presents a very simple and elegant method of suppressing script triggers from firing when initiated by other scripts.
So after writing the entire article and then sitting down afterwards and having a little thing, it became apparent there is in fact a far more elegant solution that follows the same technique as in the article - but without the need for any variables of any type. It will make more sense as you read the article, but for the time being just note that you could simply pass Get ( ScriptName ) as the parameter to the script trigger and check for whether it is empty or non-empty as your test. Anyways onto the article...
Because we are dealing with two types of scripts in this article, we will refer to triggered scripts as T. The main script that is executing will be called A.
Suppressing script triggers is a common issue and one that has had solutions devised in the past which we have used. The conventional method involves setting a global variable at the beginning of script A as a means to tell other triggered scripts T that script A is executing and that they should halt their own execution immediately.
While this method works, it does require a fair amount of maintenance. Not only does the global script variable have to be set in A, but if there are any exit conditions later on in the script, it must be cleared otherwise the global variable will persist with its value when future scripts are run.
You've all seen local variables and almost certainly used them in scripts to store information required during execution. So what does "local" mean? Well, the local variable only exists within the context in which it is defined. In the case of scripts, when you define a local variable then that variable will only exist while that script is running. Once the script ends, the local variable is trashed. The "script" is the context in this case. We tend to think of these as script variables, but they really aren't.
The Set Variable script step does not actually mention at any point that these are script variables, they are local variables.
Are there any other contexts in a FileMaker solution where we might be able to define a local variable? The answer is yes!
Just like the title says, local variables can be defined outside the context of a script. When this is done, the local variable exists and is available to be referenced anywhere in the current file EXCEPT when a script is running. Once a script begins to execute, the context has changed and the local variable is not available until the script ends.
So what do we know so far? We know that if we can set a local variable outside of a script, then this variable will exist only outside of scripts. Conversely we know that when a script is executing, the local variable is unavailable, and so if we try to obtain its value it will return a blank.
Ah ha!
What if we define a local variable outside of a script and then pass it to the script T as a parameter. If T is user initiated then the parameter passed will be the value in the local variable.
However if another script A is executing and triggers script T it is going to pass the local variable. Because the context is a script, the local variable is going to be blank and so script T will receive a blank parameter.
Confused? Ok lets break it down....
Part 1: Defining the Local Variable
The first step involves setting a local variable outside of the confines of a script. This is not quite as easy as it sounds. In order to do this we require a calculation to define the local variable, and we require that calculation to evaluate outside of the script.
In this article we are using 2 different examples of where we can place this calculation and have it evaluate outside of a script.
The first place we could define the local variable is within conditional formatting. These calculations are evaluated when a layout renders for the first time and when the layout refreshes thereafter. In this case we have defined conditional formatting but applied no formatting for the calculation - it is merely to define the local variable.
You could place an object on your startup layout (or a number of layouts) that contain this conditional formatting calculation. You need to make sure it is visible on the layout for it to evaluate.
The second possible location is within a custom menu name. If you have a menu item that is used in your custom menus then you can hide the definition of the local variable within the menu name as shown. Custom Menu names are evaluated when layouts load, refresh and when the menu is accessed.
Other possibilities might be to create a calculation field placed on a layout that defines the variable, or modify an existing calculation field on a layout to define the variable.
NOTE: If you use a startup script, be careful because conditional formatting and custom menus are going to evaluate while the startup script is executing, and as such the variables defined will actually become script variables!
The next step involves setting up script triggers. In this example we are using 3 layout-level script triggers.
Each of the T scripts are being passed the local variable defined in Part 1 which we have called $ScriptTrigger.
In the scripts we place a few lines of code to check the script parameter.
If the triggered script T is user initiated then it is being initiated from outside the context of a script. This means $ScriptTrigger exists with a value and as such the script is passed a non-empty parameter. The triggered script T can then execute because it knows a user has caused it to trigger.
If however script A is executing and does something that causes T to trigger then when it tries to pass $ScriptTrigger it is going to pass a blank variable. Remember, $ScriptTrigger only exists outside of a scriptural context! The variable does not exist in script A.
If you are using the script debugger to try this out you may end up with differing results. With debugger enabled, the conditional formatting and custom menu calculations will actually evaluate at some point during the process so that $ScriptTrigger gets defined within the context of script A. This does not occur when the script debugger is disabled.
This technique can very easily be reversed so that you can use script triggers in a way that they are initiated only from other scripts and not when they are user initiated - simply change the check at the beginning of the triggered scripts.
This technique should probably be accompanied by some documentation as to where you are defining your local variable outside of the script context and what the name of that variable is. You should probably also document the technique in the triggered scripts or parameters passed to them. If other developers come along in future and see a local variable being passed to a script, they may wonder what is going on, and could think it is an error and remove the variable!
Please find attached an example file. This file was used for all the screenshots in this article, and is provided to help you fully understand what is going on in this article, and to let you experiment in FileMaker with this solution.
Something to say? Post a comment...
Comments
Scott Toland 15/02/2014 7:02am (11 years ago)
I would like to see a real 'Suppress' option. One that will not fire the script if run from a script. Hopefully someday they can program that in.
Damian 15/04/2013 10:26pm (12 years ago)
Personally I tend to use:
If(PatternCount(Get(WindowName); "noTrigger")
Exit Script ["1¶Trigger exited because this window suppresses triggers"]
End If
It relies on you putting the above at the head of every trigger script but I would tend to want to have specific scripts for triggers anyway. As the only windows I would not want a trigger running in are those used by the system (rather than UI) this approach works for me.
I prefer this to using variables because you suppress the trigger where you want it suppressed, in a given window, and it only takes a single script leaking the value of a trigger suppression variable to wreak havoc across the solution.
DigitEL 15/01/2013 3:31pm (12 years ago)
Jaywill's idea has some merit when starting out a file but I have file systems with thousands of scripts and lots of branching within those so putting a parameter through all that would be more than laborious. Additionally, there are lots of uses for attaching parameters to script triggers so an 'enforced' not doing so would limit opportunities.
DigitEL 15/01/2013 3:30pm (12 years ago)
Jaywill's idea has some merit when starting out a file but I have file systems with thousands of scripts and lots of branching within those so putting a parameter through all that would be more than laborious. Additionally, there are lots of uses for attaching parameters to script triggers so an 'enforced' not doing so would limit opportunities.
Dan Shockley 15/01/2013 5:27am (12 years ago)
Matt, is there a reason you use "$$ScriptTrigger = Null" instead of "IsEmpty( $$ScriptTrigger )" to test for an empty value?
Peter Beehler 11/12/2012 4:57am (12 years ago)
Two comments.
1) great idea, I usually send in the script name in as a tag so that I can parse it out and then leave the script trigger on for some normal navigation scripts, but off for reporting and other type scripts.
2) On the original idea, instead of the new improved method. I always used to set the global variable to suppress dialogs to the server's timestamp. That way if I forget to clear the global variable, I can test the timestamp and if it's too old, run the trigger anyway.
Jaywill Sands 08/09/2012 1:40pm (12 years ago)
All I do is make sure every script thats run has a script param and my triggers never use params, then I check for the script param in my script triggers and exit the trigger if it exists. No variables needed.
Downside is you might need to have params for your triggers as well, but most cases my triggers dont need script params.
Daniel Wood 15/02/2012 1:46pm (13 years ago)
Thanks for the comments Matt - nitpicking is what its all about! I definitely prefer cutting down the amount of code so will be using that single Exit Script step in future - I do a similar thing for loops now in the Exit Loop If step, but never thought to use it in exit script too, cool stuff :)
Matt Petrowsky 15/02/2012 1:28pm (13 years ago)
Oh, and not to pick nits, but I feel compelled to throw out a suggestion for your first dialog of code. Not that using one extra Set Variable is going it kill anyone - and it's certainly a preference thing. But I would opt for the following one line of code.
Exit Script [ Let ( $$ScriptTrigger = Null ; True )]
Where Null is simply a custom function with nothing in it or simply ""
Fewer lines of code (provided they're readable) = easier to maintain/understand solution - just my opinion. ;)
Matt Petrowsky 15/02/2012 1:23pm (13 years ago)
I like the simplicity of simply passing Get ( ScriptName ) as a parameter to know if the called script is part of a branch.
As there are many who know my fondness of Custom Functions, I would be remiss if I didn't mention Jeremy Bante's trigger management methods found here. http://filemakerstandards.org/x/i4MI
Yet another way to control what FileMaker does or doesn't do. Great post Daniel!
DigitEL 12/02/2012 12:51pm (13 years ago)
Well now, elegant indeed. I was just thinking recently how FM should have provided some kind of 'Ignore Script Triggers' script step for some very complex multi-layout/record scripts I have and wondering how to do the old 'If 1 = 2' stopper kind of thing (before 'Disable' scripts came along).
Your technique would seem to have many uses beyond script trigger control.
Bravo!
You know how I like those one-to-many kind of things ;-)
EL
Daniel Wood 12/02/2012 8:55am (13 years ago)
Thanks for the comments guys. Agnes, the whole thing came about earlier last week when I was using an SQL plugin to update some fields on a parent record when a child record was being modified. However SQL was running into some record locking issues because on this particular layout when the child record was being modified, the parent was being locked by the user also (via relationships). So to remedy it I had to introduce an onRecordCommit script trigger to perform the parent record update. The problem however was I only needed this to happen when the user initiated a change to the child record.
On this particular layout there were a number of scripts that could be run to do various things, and these scripts contained a number of commit record steps, as well as navigating to other layouts - all of which caused the onRecordCommit trigger to run. The reason why I didn't want this to happen was it just made the whole thing inefficient - there was simply no need to run the triggered script 10 times during the exection of another script :)
HOnza 12/02/2012 7:49am (13 years ago)
I like how you guys can think outside of the box :-)
Another good way to use global variables created outside of the scripting environment is to provide data to a paused script via Resume buttons.
Jonathan Fltecher 12/02/2012 6:32am (13 years ago)
Nice. I gonna use dat!
Ágnes Riley 12/02/2012 6:03am (13 years ago)
Great article. It is crazy to think that just because something is defined one-way by FileMaker, it can only be used that way. I've known that we can define variables outside the script environment, but it never occurred for me to use it for suppressing script triggers.
Perhaps it would've been nice to mention some scenarios that can ignite this, as to why triggers get initiated from other places they are meant to.
No one has commented on this page yet.
RSS feed for comments on this page | RSS feed for all comments