Loop, but just once.


A loop is a standard structure in programming. It is designed to process a block of code a number of times until a condition says to stop. A loop has three essential parts: a starting point, the code to repeat and an exit condition.

A loop may work through a set of records, a set of fields in a record, or a block of text such as JSON. It may also be used to retry an operation, build a list, or check a condition until it becomes true.

try/catch

Another programming structure available in many programming languages is try/catch. This is used to handle unexpected problems without letting the whole process fail abruptly.

The standard work code is put in a try block. If everything succeeds in that block, the program continues on. If something fails, the program jumps to the catch block. Code in the catch block can clean up, record diagnostic information, show a message, or return an error result.

FileMaker try/catch

FileMaker does not have a try/catch script step. To implement this script pattern we need to improvise. The single-pass loop has been suggested and used in the Claris community. The earliest FileMaker attribution is a 2012 article by Brent Durland of Soliant Consulting, who explicitly credits Dawn Heady with the idea. The pattern is also clearly described in an article by Direct Impact Solutions.

In FileMaker, the single-pass loop pattern places the script work inside a Loop, exits early on an error, and performs cleanup and error handling after the Loop.

At the end of the loop there is an Exit Loop If step that always evaluates to True. This ensures that the loop is always single pass.

How does a single-pass loop work?

Let's consider a script that prints a report.


Show Custom Dialog [ "Report Type" ; 
    "Simple or Detailed?" ]
Set Variable [ $messageChoice ; 
    Value: Get ( LastMessageChoice ) ]
If [ $messageChoice = 1 // simple ]
  Go to Layout [ "itinerary simple" (itinerary) ]
Else If [ $messageChoice = 2 // detailed ]
  Go to Layout [ "itinerary detail" (itinerary) ]
Else If [ $messageChoice = 3 // cancel ]
  Exit Script [ ]
End If
Enter Find Mode 
Set Field [ itinerary::status ; "active" ]
Set Error Capture [On]
Perform Find [ ]
Set Error Capture [Off]
If [ Get ( FoundCount ) = 0 ]
  Show Custom Dialog [ "Error" ; 
      "No active itinerary items." ]
  Go to Layout [ original layout ]
  Exit Script [ ]
End If
Sort Records [ Restore ; With dialog: Off ]
Print Setup [ Restore ; With dialog: Off ]
Allow User Abort [Off]
Enter Preview Mode [ Pause: Off ]
Print [ Restore ; With dialog: On ]
Allow User Abort [On]
Enter Browse Mode [ Pause: Off ]
Go to Layout [ original layout ]

This script has a couple of exit points and doesn't account for all errors that might occur. With the exit point on finding no active itinerary items, the clean up is to go to the original layout. This clean up is repeated code from the end of the script.

A single pass loop will give us a single error handling block and a single clean up block. These will always be run.

Here is the script rewritten as a single pass loop:


# single pass loop
Loop
  Show Custom Dialog [ "Report Type" ; 
    "Simple or Detailed?" ]
  Set Variable [ $messageChoice ; 
    Value: Get ( LastMessageChoice ) ]
  Set Variable [ $step ; 
    Value: "Navigate to report layout" ]
  If [ $messageChoice = 1 // simple ]
    Go to Layout [ "itinerary simple" (itinerary) ]
    Exit Loop If [ 
      Let ($error = Get ( LastError ); $error ≠ 0)]
  Else If [ $messageChoice = 2 // detailed ]
    Go to Layout [ "itinerary detail" (itinerary) ]
    Exit Loop If [ 
      Let ($error = Get ( LastError ); $error ≠ 0)]
  End If
  Set Variable [ $step ; 
    Value: "Cancel report request" ]
  Exit Loop If [ $messageChoice = 3 // cancel ]
  Set Variable [ $step ; 
    Value: "Find active itinerary items" ]
  Enter Find Mode [ Pause: Off ]
  Set Field [ itinerary::status ; "active" ]
  Set Error Capture [On]
  Perform Find [ ]
  Exit Loop If [ 
    Let ($error = Get ( LastError ); $error ≠ 0)]
  Set Error Capture [Off]
  Set Variable [ $step ; 
    Value: "Sort itinerary records" ]
  Sort Records [ Restore ; With dialog: Off ]
  Exit Loop If [ 
    Let ($error = Get ( LastError ); $error ≠ 0)]
  Print Setup [ Restore ; With dialog: Off ]
  Allow User Abort [Off]
  Enter Preview Mode [ Pause: Off ]
  Set Variable [ $step ; 
    Value: "Print itinerary report" ]
  Print [ Restore ; With dialog: On ]
  Exit Loop If [ 
    Let ($error = Get ( LastError ); $error ≠ 0)]
  Allow User Abort [On]
  Exit Loop If [ True ]
End Loop
# error handling
If [ $messageChoice = 3 ]
 Show Custom Dialog [ "Report Cancelled" ; 
    "The report was not produced." ]
Else If [ $error = 401 ]
 Show Custom Dialog [ "No Records Found" ; $step ]
Else If [ $error = 1 ]
 Show Custom Dialog [ "User Cancelled" ; $step ]
Else If [ $error ≠ 0 ]
 Show Custom Dialog [ "Error" ; 
    "Error " $error & " at: " & $step ]
End If
# cleanup
Allow User Abort [On]
Set Error Capture [Off]
Enter Browse Mode [ Pause: Off ]
If [ Get ( FoundCount ) = 0 ]
  Show All Records
End If
Go to Layout [ original layout ]

While being more verbose than the original script, it is also far more functional. The script can centralise the handling of the errors it explicitly checks for. The clean-up is the same for all possible script runs.

Throughout the script the $step variable has been used to be able to identify the section of the script where an error occurred. This is included in the custom error dialogs.

In most cases the loop will exit if any error occurs for the previous step. There are six (6) exit points in the loop (highlighted in bold). The error number is captured in the $error variable and reported in the custom error dialog if required.

The single pass loop script pattern allows for comprehensive error capture and reporting. It has efficient error handling and cleanup. While it may be a distinct shift in thinking from your standard scripting method, it is certainly worth consideration. But also be aware that not all scripts need to be single-pass loops.


Are you coming to Reconnect.Christchurch, 15-16 October?
Get all the details and purchase tickets at Reconnect.Christchurch.

Are you late to the ScriptLogic party? If you have missed out on some of these emails, catch up here: Email Archive

If you know someone else who you think might like this email, you can send them this link to subscribe: Subscribe to ScriptLogic Daily