By Daniel Wood, 15 December 2010
When writing a script to carry out a particular task, ask yourself
"What conditions need to be true for this script to run properly?"
Sometimes, a script will do something which can potentially fail, and cause a cascade effect or errors, IF the initial conditions were incorrect. It is always a good idea to check for suitable conditions before doing anything in the script.
Updating a record
Consider a script that carries out a number of various operations on the current record. What do we need for the script to run properly? The answer is - a record. If your found set consists of 0 records, then why bother running the script? This is a potential scenario, for example, if you do a search which yields no results, your found set will be empty. If a user then tries to run the script, chances are pretty much every script step is going to fail.
This can be avoided by inserting the following few lines of code at the beginning of your script:
If [ Get ( FoundCount ) = 0 ]
Exit Script []
End If
This simple If statement will check if you have at least one record in your found set, otherwise it will exit immediately.
In fact, it is good practice to follow this guideline when writing all of your scripts. You should first carry out any checks or tests to ensure conditions are right for the script to begin executing. Exit the script immediately if any of the conditions are not right. After your error checking, just begin the script as usual - if execution reaches the point beyond your tests, you know that the tests all passed.
Structuring your error checking
Consider the following example - you have a script that is programmed to duplicate the current record, and do some various actions on the new record such as setting fields, BUT ONLY if the value of the 'status' field is "Completed".
Quite often, this script is written in the following style:
If [ Status â "Completed" ]
Show Custom Dialog [ "Error" ; "Cannot duplicate if record is completed" ]
Else
Duplicate Record
Set Field [ a ; "a" ]
Set Field [ b ; "b" ]
etc.....
Show Custom Dialog [ "Done" ; "Record Duplicated" ]
End If
While there is nothing particularly wrong with this style, it can make for a script that is difficult to read. Now, consider the alternate style, of error checking, followed by carrying out the action:
/* Check for suitable conditions */
If [ Status â "Completed" ]
Show Custom Dialog [ "Error" ; "Cannot duplicate if record is completed" ]
Exit Script []
End If
/* If script execution gets here, all tests successful */
Duplicate Record
Set Field [ a ; "a" ]
Set Field [ b ; "b" ]
etc.....
Show Custom Dialog [ "Done" ; "Record Duplicated" ]
The main difference is readability - all of your error tests are at the start of the script, and can quite easily be separated for ease of reading. You can group many checks by using "Else If" , or you can keep them individual using separate If/End If blocks.
The other difference is indenting of script steps - the majority of script steps are in the duplication area of the script, it is nicer to have the majority of the script not indented, rather than nested inside an If statement.
Checks within your script execution
Checking for suitable conditions may not necessarily only occur at the beginning of your script, sometimes they are required mid-way through your scripts execution.
Taking the first example one step further, lets assume this time that the record we are duplicating has potentially many child records that are displayed in a portal on the layout. When we duplicate the parent record, we also need to create duplicates of all of the children records also.
Our first check still stands - we can only duplicate when the record status is Completed. Even if the record has no child records, we still need to duplicate the parent record. So what happens when it comes time to duplicate the children?
/* Check for suitable conditions */
If [ Status â "Completed" ]
Show Custom Dialog [ "Error" ; "Cannot duplicate if record is completed" ]
Exit Script []
End If
/* If script execution gets here, all tests successful */
Duplicate Record
Set Field [ a ; "a" ]
Set Field [ b ; "b" ]
Set Variable [ $ParentID ; Parent::ID ]
/* Now, duplicate the children records */
Go to Related Record [ show only related records; from table "Child" ; using layout (Child) ]
Loop
Duplicate Record/Request
Set Field [ Child::Parent ID ; $ParentID ]
Omit Record
Go to Record/Request/Page [ First ]
Omit Record
Exit Loop If [ Get ( FoundCount ) = 0 ]
End Loop
Go to Layout [ Original Layout ]
Show Custom Dialog [ "Done" ; "Record Duplicated" ]
Here, we test for the parent records condition, then we duplicate the parent record. Next, the script uses the go to related record step to make the found set be that of the children records, on the children layout, it then loops through the records, duplicating them and linking them to the new parent record, before returning to the original layout.
NEVER DO THIS !!!!
The script above fails to test whether there are any children records before it executes the duplication part! If there are no children records, then the Go to Related Records script step will NOT switch layout.
As a result, after the go to related record step, the layout & found set will remain exactly what it is on the Parent layout - the script continues and starts to duplicate parent records - duplicating every single parent record in the found set ! - This is a classic example of failing to check for error, and a script cascading out of control.
It is crucial that you script defensively, checking for any potential issue before it happens!
Now, consider the same script, with a small modification before the Go to Related Record code:
/* Now, duplicate the children records */
If [ not isEmpty ( Child::Child ID ) ]
Go to Related Record [ show only related records; from table "Child" ; using layout (Child) ]
Loop
Duplicate Record/Request
Set Field [ Child::Parent ID ; $ParentID ]
Omit Record
Go to Record/Request/Page [ First ]
Omit Record
Exit Loop If [ Get ( FoundCount ) = 0 ]
End Loop
Go to Layout [ Original Layout ]
End If
Show Custom Dialog [ "Done" ; "Record Duplicated" ]
Here, we are making use of the fact that a relationship exists between the parent & child records. We are doing a test to ensure that there exists at least one child record through the relationship. This is done by using the isEmpty function, checking the primary key field on the children table. A primary key field is guaranteed to have a value in it for every record. So by checking for the existence of a value through the relationship - we are actually checking for the existence of a record. All we care about is if there is at least one record, so this test is sufficient.
If there is a record through the relationship, then the block of code for duplicating child records can execute, otherwise the If statement contents is not executed at all.
Earlier, I noted that you should try to avoid indenting large blocks of code if at all possible - unfortunately this is one time where it is not possible when doing a check like this (FileMaker lacks the ability to "go to" a specific part of a script, thus bypassing a block of code).
One alternative you could employ however, is to create a new script which is responsible for duplicating child records, and your code can then be tidied up as such:
/* Now, duplicate the children records */
If [ not isEmpty ( Child::Child ID ) ]
Perform Script [ "Duplicate Child Records" ]
End If
Show Custom Dialog [ "Done" ; "Record Duplicated" ]
Final Thoughts:
The more defensive you are with your scripting, the less problems you will have later on. It is a good idea to ensure at the beginning of your script that you test for the conditions (or lack of) that need to be present for the script to execute correctly, such as:
Create yourself a section at the beginning of your script to place these checks, you can use comment script steps to detail each test, and to keep the tests separate from the actual execution of the rest of the script.
Checking for tests at the beginning of the script not only saves the script executing incorrectly later on, it also saves time by not running the script if conditions are not right. While some scripts might run without trouble even if conditions are not right, it will save time to not run them at all rather than run them and have them do nothing of any use !
Error and condition checking is not restricted to just the beginning of your scripts, it may be necessary to test for conditions mid-way through a scripts execution. If possible, try to test for the condition before running the script action - this will save you post-action error checking. Better to not do the action at all, rather than do it, and then have to check if something went wrong, and restore anything that may have happened.