Data-driven scripting

By Jan.

The Evil Dispatcher

Hello reader,
I am Jan, and I want to talk about dispatcher scripts in FileMaker. I will shortly explain what they are, why I believe they are not an ideal solution for the given task, and finally, what technique can be used to achieve the desired result in a more elegant way. I will demonstrate this all by showing some examples, and finally, a file is attached with practical implementations of those examples.

Dispatcher script

What is it?

Typically, the task of a dispatcher script is to choose a code branch to be executed based on some criteria. The implementation would mostly consist of the criteria evaluation followed by a set of If ... Else If ... End If statements.

Imagine a table named Message. Each record represents a message to be sent using one of the available sending methods. The method is determined by the Message::_messageTypeId field. Using dispatcher logic, the following example loops through the records and sends them one by one:

Loop
	Set Variable [ $messageTypeID ; Value: Message::_messageTypeID ]
	If [ $messageTypeID = ZK.MessageTypeID.SMTP ]
		Perform Script [ Specified: From list ; “Message.SendViaSMTP” ; Parameter: ]
	Else If [ $messageTypeID = ZK.MessageTypeID.GraphAPI ]
		Perform Script [ Specified: From list ; “Message.SendViaGraphAPI” ; Parameter: ]
	Else If [ $messageTypeID = ZK.MessageTypeID.SMS ]
		Perform Script [ Specified: From list ; “Message.SendViaSMS” ; Parameter: ]
	Else
		# To be handled as an Error.
	End If
	# Using Exit Loop If to again prevent an unjustified error message in the server Event Log.
	Exit Loop If [ Get ( RecordNumber ) = Get ( FoundCount ) ]
	Go to Record/Request/Page [ Next ; Exit after last: On ]
End Loop

Dispatcher script issues

I have two major issues with this method of coding.

First of all, it increases the effort required to add new message types to the system. Whenever someone adds one, they not only need to code the script for message sending; they also have to remember, or worse, figure out, that the dispatcher script must be modified in order to get the messages of this type sent. Let’s also not forget that there might be multiple scripts dealing with message type dispatching.

Then, there is debugging. It’s easy to overlook if you’re the author of this script, especially when there are only a few branches. It’s easy not to notice. However, if you have to debug someone else’s dispatcher script, you will quickly realize how frustrating the debugging process is when you don’t know which branch will be executed first. I once had to update a system written by someone else, and every time I clicked on a menu item, I was taken to a dispatcher script with over 40 branches. That is something I never want to have to go through again.

Data-driven script

If you feel the same pain as I do, data-driven scripting comes to the rescue. But first, there are some prerequisites we must address before diving into the technique itself.

Perform Script By Name

In version 17, FileMaker has introduced the possibility to use Perform Script [ Specified: By name ;   ; Parameter:    ]. This change had great implications and was essential for proper data-driven scripting.

Internal script IDs

When you want to Perfom Script By Name, it is not a good idea to store the script name and use it directly. If you later change the name of the called script, your references get broken. You should work with internal script IDs instead.

There are several ways to find out what the ID of a script is:

  • File copy saved as XML
    When you save a copy of your file as XML, you can search for the name of your script, and you will find the ScriptReference node. It will look like this:
    <ScriptReference id="21" name="Dispatcher.Message.SendSubmitted" UUID="64A4A810-4C5F-4518-8043-DFB3B8E61750"></ScriptReference>
    The id attribute is what you are looking for.
  • FileMaker calculation
    You can design a calculation, ideally in the form of a Custom Function that returns the ID of a script based on its current name. It would look like this:GetValue( ScriptIDs( Get( FileName ) ) ; List.ValuePositionSimple( ScriptNames( Get( FileName ) ) ; scriptName ; 1 ) )In this example, List.ValuePositionSimple is a custom function returning the position of a certain value in a list of values, and scriptName is a placeholder for the name of the script we want to know the ID of. Typically, it would be a parameter of the custom function containing this calculation. Both of these can be found in the attached sample file.
  • MBS Plugin
    If you have the MBS Plugin installed on Mac, the Script Workspace will show the script IDs directly.

Setting the data

Now that we have all the tools to work with script names, we can prepare the data structure and populate it with the script references. Let’s add a new field to the MessageType table: sendMessageScriptID. Then, for each record in this table (a message type), we specify the ID of the script to execute when the message is to be sent. A table view might look like this:

__idnamesendMessageScriptIDzzzSendMessageScriptName_cu
1SMTP19Message.SendViaSMTP
2GraphAPI20Message.SendViaGraphAPI
3SMS14Message.SendViaSMS

As you can see, I have also included an unstored calculation field showing the actual name of the script. It is helpful to get a direct visual confirmation that the ID is the correct one.

Data-driven loop

Finally, we can rewrite the dispatcher script and switch to data-driven logic:

Loop
	Set Variable [ $sendScriptID ; Value: Message_MessageType::sendMessageScriptID ]
	Set Variable [ $sendScriptName ; Value: Design.Script.Name( "" ; $sendScriptID ) ]
	If [ IsEmpty( $sendScriptName ) ]
		# To be handled as an Error.
	Else
		Set Variable [ $sendScriptExecutableName ; Value: Design.Script.ExecutableName( $sendScriptName ) ]
		Perform Script [ Specified: By name ; $sendScriptExecutableName ; Parameter: ]
	End If
	# Using Exit Loop If to prevent an unjustified error message in the server Event Log.
	Exit Loop If [ Get( RecordNumber ) = Get( FoundCount ) ]
	Go to Record/Request/Page [ Next ; Exit after last: On ]
End Loop

As you can see, this sending loop requires no editing as new message types are added. Also, there is no branching, so the debugging takes you straight to the message sending script. It is worth noting that the Perform Script By Name script step uses another custom function to modify the script name before actually making the call. The reason comes from the ability to call scripts in other FileMaker files linked as External Data Sources. To do so, the script name must be specified as <data source name>::<script name>. It would seem that if you want to call a script in the same file, you just use <script name> and all is well. Unfortunately, not so in cases where your script name contains the : character. It confuses FileMaker, and the call to your script fails. The fix is to prepend the script name with ::, and that is exactly what the custom function Design.Script.ExecutableName does. The definitive script name would then be, for instance, ::Message.SendViaSMTP.

Data-driven scripting issues

Let’s be fair and note that this approach comes with its own inconveniences as well. For instance, the sending scripts appear not to be referenced to any solution analysis tools. Someone might later come to the conclusion that they are not used and delete them, breaking the functionality completely.

Then, it can have an impact on performance. The more scripts or layouts your solution contains, the more time it takes to calculate the name of the target based on its ID. When there are thousands of scripts, the delay is already noticeable from the users’ perspective. One of our solutions has over 1700 scripts and the time to calculate a script name is around 3/10 of a second.

I still believe, though, that this is a much better approach than the dispatcher scripts.

Sample file

All the examples used in this article can be found in the attached sample file. Next to the message-sending loop, you will also find a navigation menu there, which uses data-driven scripting as well. Only, in this case, it stores the layout IDs and uses them for navigation to the destination layout. The file can be accessed using account Admin, and password admin.