Datagestuurd scripten

De Slechte Dispatcher

Door Jan.

Hallo lezer,

Ik ben Jan, en ik schreef zonet een blog over dispatcher scripts in FileMaker. Ik licht kort toe wat ze zijn, waarom ik denk dat ze geen ideale oplossing bieden, en welke techniek dan wel gebruikt kan worden om het gewenste resultaat op een elegantere manier te bereiken.Dit alles zal gedemonstreerd worden aan de hand van enkele voorbeelden. Je vindt onderaan het bestand met praktische implementaties van die voorbeelden.

Dispatcher script

Wat is het?

Gewoonlijk is de taak van een dispatcherscript het kiezen van een uit te voeren codetak op basis van bepaalde criteria. De uitvoering bestaat meestal uit de evaluatie van de criteria, gevolgd door een reeks  If ... Else If ... End If statements.

Stel je een tabel voor met de naam Message. Elke record vertegenwoordigt een bericht dat moet verzonden worden volgens een van de beschikbare verzendmethoden. De methode wordt bepaald door het veld Message::_messageTypeId. Het volgende voorbeeld gebruikt dispatcher-logica en doorloopt de records en verstuurt ze een voor een:

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

Problemen met dispatcherscripts

Ik heb twee grote problemen met deze manier van coderen.

Ten eerste is er meer moeite nodig om nieuwe berichttypes aan het systeem toe te voegen. Wanneer iemand er één toevoegt, moet hij niet alleen het script voor het verzenden van berichten coderen; hij moet ook onthouden, of erger nog, uitzoeken, dat het dispatcherscript moet worden aangepast om de berichten van dit type verzonden te krijgen. Laten we ook niet vergeten dat er meerdere scripts kunnen zijn die zich bezighouden met het verzenden van berichten van dit type.

Dan is er het debuggen. Het is eenvoudig als auteur om iets over het hoofd te zien, vooral als er maar een paar branches zijn. Het is gemakkelijk om het niet op te merken. Als je echter het dispatcherscript van iemand anders moet debuggen, zul je je snel realiseren hoe frustrerend het debuggen is als je niet weet welke branch als eerste wordt uitgevoerd. Ik moest ooit een door iemand anders geschreven systeem bijwerken, en telkens als ik op een menu-item klikte, kwam ik in een dispatcherscript met meer dan 40 branches. Dat wil ik nooit meer meemaken.

Datagestuurde scripting

Als u dezelfde pijn voelt als ik, biedt datagestuurde scripting de uitkomst. Maar vooraleer we in de techniek zelf duiken, zijn er enkele voorwaarden.

Script op naam uitvoeren

In versie 17 heeft FileMaker de mogelijkheid geïntroduceerd om Perform Script [ Specified: By name ;   ; Parameter:    ] te gebruiken. Deze verandering had grote gevolgen en was essentieel voor een goede datagestuurde scripting.

Interne script IDs

Wanneer je Perfom Script By Name wil uitvoeren, is het geen goed idee om de naam van het script op te slaan en direct te gebruiken. Als u later de naam van het aangeroepen script wijzigt, worden uw verwijzingen verbroken. In plaats daarvan moet u werken met interne script-ID’s.

Er zijn verschillende manieren om te ontdekken wat je interne script ID is:

  • Bestandskopie opgeslagen als XML
    Wanneer u een kopie van uw bestand opslaat als XML, kunt u zoeken naar de naam van uw script, en u vindt de ScriptReference node. Die ziet er dan zo uit:
    <ScriptReference id="21" name="Dispatcher.Message.SendSubmitted" UUID="64A4A810-4C5F-4518-8043-DFB3B8E61750"></ScriptReference>
    Het id-attribuut is wat u zoekt.
  • FileMaker berekening
    U kunt een berekening ontwerpen, idealiter in de vorm van een aangepaste functie die de ID van een script teruggeeft op basis van zijn huidige naam. Het zou er zo uitzien:GetValue( ScriptIDs( Get( FileName ) ) ; List.ValuePositionSimple( ScriptNames( Get( FileName ) ) ; scriptName ; 1 ) ) In dit voorbeeld is List.ValuePositionSimple een aangepaste functie die de positie van een bepaalde waarde in een lijst met waarden teruggeeft, en scriptName is een plaatshouder voor de naam van het script waarvan we de ID willen weten. Gewoonlijk is dit een parameter van de aangepaste functie die deze berekening bevat. Beide zijn te vinden in het bijgevoegde voorbeeldbestand.
  • MBS Plugin
    Als je de MBS Plugin hebt geïnstalleerd op je MAC, zal de Script Workspace de script IDs onmiddellijk tonen.

Instellen van de gegevens

Nu we alle hulpmiddelen hebben om met scriptnamen te werken, kunnen we de gegevensstructuur voorbereiden en vullen met de scriptreferenties. We voegen een nieuw veld toe aan de tabel MessageType: sendMessageScriptID. Vervolgens specificeren we voor elk record in deze tabel (een berichttype) de ID van het script dat moet worden uitgevoerd wanneer het bericht moet worden verzonden. Een tabelweergave zou er zo uit kunnen zien:

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

Zoals u kunt zien, heb ik ook een niet-opgeslagen berekeningsveld opgenomen dat de werkelijke naam van het script toont. Het is handig om een directe visuele bevestiging te krijgen dat de ID de juiste is.

Datagedreven loop

Tenslotte kunnen we het dispatcher-script herschrijven en overschakelen op datagestuurde logica:

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.

Zoals u kunt zien, vereist deze loop geen bewerking naarmate nieuwe berichttypen worden toegevoegd. Er zijn ook geen branches, dus de debugging brengt u rechtstreeks naar het script voor het verzenden van berichten. Het is vermeldenswaardig dat de scriptstap Perform Script By Name een andere aangepaste functie gebruikt om de scriptnaam te wijzigen voordat het script daadwerkelijk wordt aangeroepen. De reden hiervoor is de mogelijkheid om scripts aan te roepen in andere FileMaker-bestanden die gekoppeld zijn als Externe gegevensbronnen. Daarom moet de scriptnaam worden opgegeven als <data source name>::<script name> . Het lijkt erop dat als u een script in hetzelfde bestand wilt aanroepen, u gewoon <script name>  gebruikt en alles werkt. Helaas is dat niet het geval als uw scriptnaam het teken : bevat. Het verwart FileMaker, en de aanroeping van uw script mislukt. De oplossing is de scriptnaam vooraf te laten gaan door :: , en dat is precies wat de aangepaste functie Design.Script.ExecutableName doet. De definitieve scriptnaam wordt dan bijvoorbeeld ::Message.SendViaSMTP.

Datagedreven scripting problemen

Laten we eerlijk zijn en opmerken dat deze aanpak ook zijn eigen ongemakken heeft. Zo blijken de verzendscripts niet te verwijzen naar enige oplossing analyse-instrumenten. Iemand zou later tot de conclusie kunnen komen dat ze niet worden gebruikt en ze verwijderen, waardoor de functionaliteit volledig wordt verbroken.

Dan kan gevolgen hebben op de prestaties. Hoe meer scripts of lay-outs uw oplossing bevat, hoe meer tijd het kost om de naam van het doel te berekenen op basis van zijn ID. Wanneer er duizenden scripts zijn, is de vertraging al merkbaar vanuit het oogpunt van de gebruikers. Een van onze oplossingen heeft meer dan 1700 scripts en de tijd om de naam van een script te berekenen bedraagt ongeveer 3/10 van een seconde.

Ik geloof echter nog steeds dat dit een veel betere aanpak is dan de dispatcherscripts.

Voorbeeldbestand

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.

Alle in dit artikel gebruikte voorbeelden zijn te vinden in het bijgevoegde voorbeeldbestand. Naast de berichten-verzend loop vind je daar ook een navigatiemenu, dat ook datagestuurde scripting gebruikt. Alleen slaat het in dit geval de lay-out-ID’s op en gebruikt die voor de navigatie naar de bestemmingslay-out. Het bestand is toegankelijk met account Admin, en wachtwoord admin.