Thursday 4 September 2014

Parameter mechanisms in FileMaker coding.

FileMaker only lets the developer use a single parameter to a script (what we called "procedures" back in Pascal in CompSci 101 ), so developers wind up using some kind of structure mechanism to send data into the parameter, then inside the script, parse the structure to get the values they want.

The first idea, and in most ways the simplest, is to just delimit each “value” with a carriage return or other delimiter character or characters.  This idea, while it works, is also somewhat limiting in that the developer must always construct their parameter very carefully with all the intended values in the right order.

Going through various trials and errors, I ended up going for “Property Lists”, as implemented by Shawn Flisakowski, here.  You should really download and play with the "Property Lists" to get a handle on how this works.  Using PropertyLists we wind up with parameters that look like this:

Surname=Nurk
Firstname=Fred

This has some nice advantages.  You can construct your parameter with any number of values, and in any order. If you need to extend an existing script to add some extra functionality to it, you can just add more properties, and it won’t disturb your existing code. Also, if you are watching in the debugger, you can see what values have been assigned to which property names.

It also has a couple of relatively minor disadvantages : 
1) you should substitute any “return”, “equals”, and "semi colon" characters in the values you are putting in so as not to confuse the mechanism. Fix : modify the “Add Property” and “Get property” custom functions to handle this. We created two functions, EncodeForProperty(thetext) and DecodeFromProperty(thetext).
2) All values you pass in will be converted to text.  If you pass in a date, you get a silent date-to-text coercion happen, which has implications because it does not obey the region settings of the computer you are on, but the region settings the file was originally created in.  Fix : do a coercion of your own, getastext(getasnumber(datevalue)) when you “add property” and then do getasdate(getasnumber(datevalue)) to get it back inside your script.

This worked very well. Later on though, I had to write a routine that tested for the existence of about 20 possible properties.  The script that was executing was never going to have more than 4 or 5 of them passed in as a parameter, but it could be any combination of those 20 possible values.  Once I had written the code 

if not isempty ( get property ( property list ; property name ) )
  set field thingummy to get property ( property list ; property name ) )
end if

20 times, I realised I was making life hard for myself.  So I took another look, and if you take the statement 

Surname=Nurk

and put a dollar sign in front of it

$Surname=Nurk

then put a double quote in after the equals sign and at the end

$Surname="Nurk"

then wrap it in a let statement

Let($Surname=“Nurk”;get(lasterror))

then wrap the whole lot in an “Evaluate()” function call, escaping the quotes

Evaluate ( "Let($Surname=\"Nurk\";get(last error)")

our parameter value just turned into a script local variable.  Sounds like a lot of work doesn’t it?  However you can do it all with custom functions.

First, the single value case:
Function: ParameterToLocalVariable(property)=
If ( not ( IsEmpty ( property ) ) ;
Let([
propWithLeadingDollar = If ( Left ( property ; 1 ) = "$" ; property ; "$" & property );
propWithQuotes = Substitute ( propWithLeadingDollar ; "=" ; "=\"" ) & "\"";
propWrappedInLetStatement = "let(" & propWithQuotes & ";\"\")";
propInstantiated = Evaluate (propWrappedInLetStatement)
];
Get ( LastError )
)
; "0") // outside if

Second, the multiple value case:
Function: PropertyListToLocalVariables(propertyList)=
If ( not ( IsEmpty ( propertyList ) ) ;
 Let([
 countProperties = ValueCount ( propertyList )
 ];
 If ( countProperties > 1 ; 
   PropertyToLocalVariable ( GetValue( propertyList ; 1 ) ) & PropertyListToLocalVariables(RightValues(propertyList ; countProperties-1 ))
 ; 
   PropertyToLocalVariable(GetValue(propertyList;1)) 
 )  // inside if
) // let

 "0"
) // outside if

Finally, we want an overall function to call at the start of the script that is going to take care of all of this for us, and return a boolean for whether or not it succeeded:

Function: ParameterConvertedToVariablesOK()=
Let(
 t = PropertyListToLocalVariables ( Get ( ScriptParameter ) )
;
 isempty ( Get ( ScriptParameter ) ) 
 or 
 (
  ( GetAsNumber( t ) = 0 ) and ( PatternCount ( t ; "?" ) = 0 )
  )
 )

If there is no script parameter, return true.  If the conversion to script local variables fails, one of the ParameterToLocalVariable(property) calls will return a “?” in the result, causing the function to return false, otherwise all the script local variables get created from the parameter in a single function call.  Your script code looks like this:

Script “Make Person Record (properties surname firstname)” = 
if (ParameterConvertedToVariablesOK)
 new record
 set field “People::Surname” to $surname
 set field “People::First Name” to $firstname
 commit record [no dialog]
end

Any FileMaker Developer reading this will realise I have left out the EncodeForProperty(thetext) and DecodeFromProperty(thetext) functions that handle the “return”, “equals” and "semi colon" characters in the value, but I leave that as an exercise to the reader. (As Bugs Bunny would say, “ain’t I a stinker?” ;-)

This mechanism has been used by us a Denbigh since soon after FileMaker 7 came out, and it’s been one of the best design decisions we could have made.  As we have extended the functionality and capabilities of Denbigh Admin over the years, this mechanism has made life a lot easier for all of us.

No comments:

Post a Comment