Passing Multiple Parameters to Scripts – Advanced

Wouldn’t it be nice if whenever you need a parameter you could just ask for it by name? Lets say that you have a create order script and you want to pass the script the name, street and zip code of the customer. If you were stuck with just the regular old FileMaker functions there ain’t no way that’s happening. But after you are done with this post, you will be able to get the name of your customer with the simple function: `GetParameter( “customerName” )`.

Alright, since your interest is hopefully already piqued, I’m just going to go ahead and tell you the functions right away. There are three of them. I know that sounds like a lot but if you give me a chance to explain myself, trust me your going to love it and once you get used to them you’ll never go back.

Name: PassParameter
Parameters: name, value
Formula:

"<:" & Substitute( name; ["="; "/="]; [":"; "/:"] ; [">"; "/>"]; ["<" ; "/<"]   ) 
& ":=" & 
Substitute( value; ["="; "/="]; [":"; "/:"] ; [">"; "/>"]; ["<" ; "/<"]   ) & ":>"

Name: DictGet
Parameters: dict, name
Formula:

Let( [
                  match="<:" & Substitute( name ; ["="; "/="]; [":"; "/:"] ; [">"; "/>"]; ["<" ; "/<"]   ) & ":=";
                  pstart = Position(dict; match; 1; 1);
                  start = pstart + Length(match);
                  end = Position(dict ; ":>"; start; 1);
                  len = If(end = 0; 999999999; end - start)];

                  If (pstart = 0; ""; 
                  Substitute(Middle(dict; start; len); ["/:"; ":"]; ["/="; "="] ; ["/>"; ">"] ; ["/<" ; "<" ]))
    )

Name: GetParameter
Parameter: name
Formula:

DictGet(Get(ScriptParameter); name)

These different functions are probably a little bit confusing right now so lets go through them one by one.

PassParameter( name ; value )

This custom function is your bread and butter. You use it every time you want to pass a parameter to another script. `name` is the name of the parameter your are passing and `value` is the actual text that you want to send over. So for instance if you were sending the name of your customer to a script you would use:

PassParameter( "customer" ;  "Joe Blow" )

If you want to pass multiple parameters to our order creation script would just string this custom function together using the ampersand like so:

PassParameter( "customerName" ;  "Joe Blow" ) &
    PassParameter( "street" ; "123 Any St." ) &
    PassParameter( "zip" ; 85282 )

How it works: `PassParameter()` turns the name, value pairs that you pass in into a special format so that they are easy to manage. It basically puts two special characters in front of the `name`, between the `name` and `value`, and after the `value`. Basically if you were to say `PassParameter( "customerName" ; "joe blow" )`, this function turns your text into:

<:customerName:=joe blow:>

or in the case of multiple parameters as above it would look like this:

<:customerName:=joe blow:><:street:=123 Any St.:><:zip:=85282:>

This construct of name and value pairs is what we refer to as a dictionary. This becomes important because our next function operates on a dictionary. You'll notice in the actual definition of the function `PassParameter()` that I do a couple of substitutions. I use this process to escape the different characters that I use in my delimiters so that my scripts won't break when you send in those same characters. Don't worry though, when I get them back out I remove any extra characters that I added.

DictGet( dict, name)

The `dict` parameter in this custom function is, yup you guessed it, short for dictionary. The second parameter, ‘name', is just the name of a value that you would like to get out of the dictionary. So if you were to execute the following code:

DictGet( "<:customerName:=joe blow:><:street:=123 Any St.:><:zip:=85282:>" ; "customerName" )

It would return "Joe Blow". On it's own this DictGet function doesn't seem that useful but it is the workhorse of the three functions.

How it Works: `DictGet()` actually works as a basic text parser. It takes the name that you pass in and turns it into the format you would expect in the dictionary. If the `name` passed in is "customerName", then it would be turned into "<:customerName:=". The we take that weird looking text and search for it in the dictionary. If we find the weird string in the dictionary, then we return the value for it ("joe blow", otherwise we return an empty string (""). Pretty neat huh?

GetParameter( name )

This is the function you would use whenever you are trying to access data that was passed in using the `PassParameter()` function. It's is specifically designed to operate on a script parameter that contains a dictionary. It actually just pulls the value of the `name` you give it, out of the script parameter using the `DictGet()` function. Very Simple.... not a whole lot going on here.

Now that you have a pretty good understanding you are pretty much ready to enter into the world of named parameters. Once you get used to using these little gems, I am positive that you will never be able to go back to plain old vanilla script parameters ever again. There are a couple more benefits to using these functions such as using named values in your script results, nesting parameters and accessing multiple occurrences that I will probably get into a later date. For now… I hope these functions help make FileMaker Development a little easier for you.

>More About Dictionaries: If you would like to get a little more in-depth into these functions there is a second post that adds functionality to this idea here: FileMaker Dictionary Functions.

>NOTE: We had a couple of good comments from some of our early release readers. Genx who blogs on FileMakerMagazine.com added that he thought it would be much more visually appealing and easier to use XML style tags. I can't remember the exact reason why I didn't go this route... I am pretty sure it was because I wanted to have two delimiters at each break point in the name/value pair. He did convince us to change the names of the functions from our more nondescript names ( #() #P() ) we started with so that it would be easily understood what each one did. Another useful tip came from Fitch of pre1.com, who pointed out that another similar name/value pair implementation can be seen in Back Magic from SeedCode.com

Leave a Comment

31 thoughts on “Passing Multiple Parameters to Scripts – Advanced

  1. Jesse, are you familiar with GetParamIndex(Index) custom function? I can’t take credit for it (the folks at Productive Computing introduced me to it), but I use it all the time. In essence, passing a set of values via parameter is as simple as separating them by commas. The implementation isn’t complex and any developer can do it, but what makes it powerful is the simplistic way one can format all these parameters within the ScriptParameter.

    Run a script and pass a parameter such as:

    Contacts::fullname & "," &
    Contacts::address & "," &
    Contacts::phone & "," &
    Contacts::email
    

    And then within the script itself:

    Set Variable[$$Name; GetParamIndex(1)]
    Set Variable[$$Address; GetParamIndex(2)]
    Set Variable[$$Phone; GetParamIndex(3)]
    Set Variable[$$Email; GetParamIndex(4)]
    

    The function:

    // get the Nth parameter in a comma separated string
    Let ( 
      scriptParameter = Get( ScriptParameter );
      Case(
        // get the parameter based on the index and strip all spaces and CRs
        PatternCount ( scriptParameter ; "," );
        Substitute ( TrimAll ( MiddleValues ( Substitute ( scriptParameter ; "," ; "¶" ) ; Index ; 1 ) ; 0 ; 0 ) ; "¶" ; "" );
    
        // only a single parameter was passed, just return the original ScriptParameter
        index = 1;
        scriptParameter;
    
        // trap for null results
        ""
      )
    )
    

    Once again, I DO NOT take credit for this. Productive Computing deserves that.

    I’m thinking that there’s probably a method that merges the best of yours and the simplicity of this.

    Cheers,

    Bart

  2. Great post! Just discovered this site recently, reading through these items.

    A few minor corrections to the code sample:

    Substitute is misspelled as “Subsitute”

    “clean” should be “dict”

  3. @sam: Thanks for the corrections. I have made the fixes in the page. I also cleaned up the formatting a little since my esteemed colleague Jesse is … challenged.

  4. @Sam – oops…. that was embarrassing…. Good save Sam. I would like to place all blame for that mistake on Geoff’s shoulders as he was supposed to tech edit this and totally blew it.

  5. @Bart – That is an interesting way of doing it. I actually haven’t seen this particular way of doing it before myself. We do have something similar here:

    http://sixfriedrice.com/wp/passing-multiple-parameters-to-scripts/

    The only problem with that version of the function is that it’s not robust enough to handle values with commas in them, which for some might not be a problem. I actually use these guys to pass the content of web pages and xml back and forth between some scripts so we need a bullet proof solution. The content you pass in is always the exact content you get out.

    I think the best part of this solution is the ease with which you can access the parameters…. It is a little annoying passing them in, that is for certain, but I love the ability to get them out just by asking for them by name. It’s probably because I am used to doing the same in other languages like C, Java, Ruby, PHP and Perl.

    My functions are definitely a little heinous to look at though 🙁

  6. I really like the safety of being able to store absolutely anything in these tags, you can store paragraphs of text, don’t really have to worry about escaping anything out and the formatting is still retained.

    Just one minor correction in an example you use:
    DictGet( “<:customerName:=joe blow:><:street:=123 Any St.:><:zip:=85282:>” ; “customer” )

    Should probably be:
    DictGet( “<:customerName:=joe blow:><:street:=123 Any St.:><:zip:=85282:>” ; “customerName” )

    By the way, props to whoever designed this site, nice on the eyes 🙂

  7. @Alex. Thanks for your catch. Again, I apologize profusely for my esteemed partner Geoff Coffey’s poor editing skills. As far as the design of the site goes, we can’t take full credit we had help from an excellant CSS professional who currently works for godaddy.com. His name is Lee Glasglow and you can reach him through LinkedIn

  8. For some time I’ve been using the technique of passing parameters separated by returns, and simply using FileMaker’s GetValue() function to pull out the one I want.

    Also, if I specifically want to use named parameters I tend use FileMaker’s variables along with the Let command to assign them, but I love seeing all these new techniques on show!

  9. @tim: Those are defintely the most common approaches, and they work fine in many typical cases. But they often can’t handle complex data (like newlines and quote marks) and/or are difficult to produce when calling a script.

    Jesse’s technique was developed to be easy to pass and always reliable. He has passed entire HTML pages and blocks of XML data in a single parameter using this technique, and it works flawlessly.

    One of our core philosophies at SFR is “Don’t write code that works, write code that doesn’t fail.” We try to ensure that things will keep on working even when the unexpected happens (like weird data in a parameter).

  10. @geoff, @tim – Wow geoff, it sounds like you are grumpy or something. I definitely agree that the named value pairs that we produce are air tight and since we deal with more complex data then the average user that’s why we created a heavy weight technique.

    I agree Tim the GetValue() function is EXTREMELY easy to use and will accomplish most of the tasks at hand. The Let techinque is interesting… could you expand on it?

    PS. Geoff probably wasn’t trying to sound like an old crotchety man… it just happens on occasion.

  11. Hi Geoff,

    This is very cool… for some time now what I have been doing is to use a let statement to set some global memory variables and then call a script.

    Been keeping track of these variables and making sure I don’t accidentally rename them. I guess scope comes to mind when thinking of the difference between my this approach and your approach. In my approach I can also have total isolation, meaning that what goes in comes out.

    What I like about your approach is that you can pass them between files easily.

    After all this rambling – this is what I wanted to ask you. What I find frustrating is how to handle the result. So if I call a subscript and pass it a few parameters. One of the parameters could be that the calling script can’t continue if an error is returned, or the some more serious problem happened and the file needs to be closed.

    I would like to see if you have an approach or a standard way of doing this that you and Jesse can share.

    Thanks for such great tips.

  12. @Vince – Welcome to our humble abode… I’m glad you asked that question. We actually do have a way of returning script results using the same name/value pair process. All you have to do is place `PassParameter( “error” ; 1234 )` in your exit script step and you can then access “error” on the other side using the following custom function:

    Name:  GetResult
    Parameter:  name
    Formula:  DictGet(Get(ScriptResult); name)  
    

    Just call `GetResult( “error” )`, and you have your error code back. You can of course add as many different named parameters as you want just by concatenating them together in your exit script step like so:

    PassParameter ( "errorMsg" ; "Your computer has been eaten by some linux or something!" )  &  PassParameter( "error"; 1234 )
    

    Now you can access “errorMsg” and/or “error” if you so desire. We will probably post another article soon that goes deeper into this approach. Until then, hopefully this will tie you over

  13. basically you’ve done a fairly robust implementation of parts of basic general serialized dictionary; i think “PassParameter” is much too specific a name for the set function, and there are some pieces missing to make it fully general; also, your approach turns every dictionary value into a string

    in itself, though, it isn’t especially convenient for passing parameters, which deserve to become local variables as soon as they reach the called procedure, as in almost all general programming languages; sure, a group of Set Variable steps could “fix” this, but that’s tedious and a potential source of error, so i suggest using a helper script to do that part, and keeping it apart from the general dictionary functions by packaging one use of the dictionary object as a local-variable factory; this would make parameter passing more convenient and support defining default values for optional parameters (another common feature of programming languages)

    i would thus see your dictionary as an opportunity to refine my local -setting technique (http://www.briandunning.com/cf/453) by decoupling the dictionary from the parameter handling

    so what i’d want to do with it first is to make a full set of dictionary functions:

    dict_set (dict; itemname; value) –> dict

    dict_get (dict; itemname) –> item or null

    dict_delete (dict; itemname) –> dict

    dict_names (dict) –> names valuelist

    this would create a dictionary type for any of several purposes — these dictionaries can be stored in the database and reloaded at startup, for example; and this is how i’d use such a dictionary for parameter passing in FileMaker; i’d define a local variable factory (untested off the cuff definition):

    Let (a_name = trim_val (dict_names (dict); 1);

       Case (

           is_empty (a_name);


            1;
            (
            Evaluate ("Let ($" & a_name & "=\"" &
             dict_get (dict; a_name) & "\"; 1) "
            );
            dict_to_locals (dict_delete_item (dict; a_name));

           0

       )

    )

    the convenience function, trim_val (dict; n) simply removes returns and trims the result of MilddleValues (list; n; 1)

    another convenience function, param_dict() would simply do

    dicttolocals (Get (ScriptParameter))

    then you could pass params with e.g.


    Perform Script ["myscript"; Parameter: set_dict_item (set_dict_item (""; "var1"; val1); "var2"; val2)

    and the called script would start like this (except for any default values for parameters):

    If [not param_dict())]

    // handle parameter error

    Else

    // do things with the local script vars created

    End If

  14. @garbanzito: That was a great post. We actually feel pretty much the same way about these custom functions… with one notable exception but I will get to that later. I definitely agree that PassParameter is too specific a name and in our personal copy of these CF’s we actually use then name #(). We use this because of the fact that it reminds of the hash data structure in other languages… it obviously isn’t the same but you get the point ;). As far as decoupling goes, we implemented `DictGet()` seperately from `PassParameter()` and `GetParameter()` for just this reason

    Also, we actually have a whole suite of custom functions that expand upon this idea. It’s awesome that you had the same first impression of where to go once one starts using the Dictionary concept. We have implemented DictFirst, DictRemove, DictAdd, DictToString, DictList and my personal favorite DictToXML. There are a couple of others that we use as well but those are the ones I believe you mentioned in your post.

    The one think I don’t think we agree on is the need for local variables in the script that house the same data as in the dictionary. Why not access the data directly??? The dictionary data is just as transient as the data in the variables. I also don’t like that you lose the transparency of where the data came from because they are in a let function buried in a recursive custom function that is solely used to place data you already have access to into variables.

    I think at this point though, we both are agreeing on the idea that this type of construct is beneficial but don’t agree on it’s exact implementation. Please feel free to modify our code in any way you see fit to meet your own specifications for it’s functionality. I really appreciate the critique and hope you continue to provide comments on our methods in the future.

  15. I’ve been using a custom function to pass multiple script parameters. Its a modification of a CF that I found on Brian Dunning’s website.

    The CF, which I call GSP, has a single function parameter (number) and looks like this:

    
    Let (
      [x = MiddleValues (Substitute (Get (ScriptParameter); "|"; "¶"); Number; 1);
      y = Left (x; Length (x)-1)];
    
      Case (
        PatternCount (Get (ScriptParameter); "|" ); y;
        Get (ScriptParameter)
      )
    )
    

    When I want to pass multiple parameters to the script I separate each value with the verical pipe character “|”.

    So if I wanted to pass apple, banana and orange the paramter would look like “apple|banana|orange”. The passed parameter could obviously be a calculated set of values or field names, etc. They just have to be separated with a |.

    If I wanted to use the second parameter I would call GSP (2) and return “banana”.

    It has worked very well for me and it can take an unlimited set of parameters so that it can easily be used with 1 parameter or 100 without having to modify the code.

  16. @Jesse,

    Thanks for posting this code.

    You mentioned that you have a “whole suite of custom functions that expand upon this idea” (DictFirst, DictRemove, DictAdd, DictToString, DictList and my personal favorite DictToXML).

    Would you be willing to post that code? I’ve been looking into building my own dictionary functionality, but I’d like to avoid reinventing the wheel.

    Thanks,

    Chris

  17. @Chris –

    I will write up a post for that by the end of next week. I was planning about doing it a lot earlier but became quite lazy :-).

    I appreciate your interest.

  18. I made this fucntion to do the trick :

    GetParamValue ( “City==Antwerp|Country==Belgium|TelNo==0485638536” )

    The paramters can be in any order and text constants (like “Antwerp”) don’t have to be between “”. It’s very easy and fast to use.
    The only minus is that I can’t use == nor | nor ” in my parameters, but till now that has never been a problem.

    Evaluate ( “Let ( [∏” & Substitute ( Get ( ScriptParameter ) ; [“==” ; “=\””] ; [“|” ; “\”;∏”] ) & “\”] ; ∏” & ParName & “)” )

    I use the ∏ to avoid a problem I had in teh past with certain parameter names, but I can’t remember which (!)

  19. Guys,

    I appreciate the posts, the mistakes, and the humor. I see that your blog renders some characters in the FileMaker syntax as different entities and wonder if perhaps the pages need re-saved with a BOM as UTF 8 to parse and display correctly.

    While it’s easy enough to read, and experienced developers know what is missing it might frustrate people newer to FileMaker who might actually cut and paste code from the browser.

    -Brian Loomis

  20. @Brian:

    Thanks for the heads up. Alas, the cleanup process isn’t as simple as one would hope. WordPress seems to have incorrectly interpreted UTF-8 data as Latin-1, then re-transcoded it to UTF-8. This happened with our last upgrade (using their built in import/export functions). The end result is we have valid UTF-8 content, properly encoded and displayed, which contains wacky characters.

    Fixing it requires correcting each problem case. Some day I’ll get a script written to do this for me.

    Thanks,

    Geoff

  21. Hi Everyone,

    we have several systems (a few hundred fm-systems, which date back (partially) to 1992 with more than 70 fm-files). Using a custom-function is in such cases not an option and the amount of custom-builds is so much that rebuilding isn’t an option either.
    With that in mind my collegaes and i use a different method, which is pretty easy to implement:

    We call our script with either a script or a button with a scriptparameter which consists of multiple values:

    “$value1=&quote(value_x)&”;$value2=quote(value_y)&etc.etc.

    The script that is called has a step that sets a variable $result with the following calculation:

    Evaluate( “Let( [” & Get( ScriptParameter ) & “]; ” & 1 & ” )”)

    The nice thing is that this step creates local (or global if you like with $$) variables that you can use in the script.

    Putting the “quote()” around the values ensures that the evaluate also works when one of the vars is empty. Using the quote() makes the function be read a little eassier than with escaped “\”” quotes (this of coarse is a matter of opinion 😉 )

    The downside of this method may be that all the vars are evaluated as text .. in such cases you will have to change them back to numbers, dates, or wathever you need them to be. If you are sure a value is never empty you can of coarse parse it without the quotes, a date or a number will not need to be reformatted as text then.

    If you were to use this function is several cascading script you could exit a script with in the ScriptResult:
    Get ( ScriptParamer )&”;$value5=quote(value_z)&etc.etc. and if one of the original values changes in this script, you could simply add that value again to the parameter …. when evaluating the last (identical named) value in the parameter will be the resultvariable 😉

    cheers, Menno

  22. Edited version:

    Hi Everyone,

    we have several systems (a few hundred fm-systems, which date back (partially) to 1992 with more than 70 fm-files). Using a custom-function is in such cases not an option and the amount of custom-builds is so much that rebuilding isn’t an option either.
    With that in mind my collegaes and i use a different method, which is pretty easy to implement:

    We call our script with either a script or a button with a scriptparameter which consists of multiple values:
    [code]
    “$value1=”&quote(value_x)&”;$value2=”quote(value_y)&etc.etc.
    [/code]
    The script that is called has a step that sets a variable $result with the following calculation:
    [code]
    Evaluate( “Let( [” & Get( ScriptParameter ) & “]; ” & 1 & ” )”)
    [/code]
    The nice thing is that this step creates local (or global if you like with $$) variables that you can use in the script.

    Putting the “quote()” around the values ensures that the evaluate also works when one of the vars is empty. Using the quote() makes the function be read a little eassier than with escaped “\”” quotes (this of coarse is a matter of opinion )

    The downside of this method may be that all the vars are evaluated as text .. in such cases you will have to change them back to numbers, dates, or wathever you need them to be. If you are sure a value is never empty you can of coarse parse it without the quotes, a date or a number will not need to be reformatted as text then.

    If you were to use this function is several cascading script you could exit a script with in the ScriptResult:
    [code]
    Get ( ScriptParamer )&”;$value5=”quote(value_z)&etc.etc.
    [/code]
    and if one of the original values changes in this script, you could simply add that value again to the parameter …. when evaluating the last (identical named) value in the parameter will be the resultvariable

    cheers, Menno

  23. I started out doing multiple parameters using Shawn Flisakowski’s Property Lists implementation.
    http://www.spf-15.com/fmExamples/
    Then I got sick of calling getProperty(propertylist ; propertyname) all the time: if I have a property of the form “surname=Smith”, then I can feed that to another custom function I called PropertyListsToLocalVariables(propertylist), and I get a local variable called $surname with the value “Smith” in it. The custom function actually calls a worker function recursively for each value supplied in the propertyList, so it doesn’t matter how many or how few parameters I get, they get turned into local script variables with one custom function call. One of these days i really should post them up to one of the custom function websites…..
    To make matters more interesting, I had to retrofit the custom functions to our 89 file solution, so I wrote an AppleScript that went through all the files typing them in for me. Let me know at pgort@denbigh.com.au if you want a copy of the custom functions/robot for typing them in.

  24. Peter:

    A lot of people tell us about that technique. I, for one, *prefer* to have my variables and parameters in separate name spaces. It prevents the possibility that a parameter passed to a script could impact its functioning in unexpected ways down the road. If I request a parameter called “XYZ” I know I’ll get a passed parameter, and not a variable. But I can see either side of this argument.

    I will say, though, that I agree that parameters are used so heavily that it should not be hard to type. That is why we use a function called # to pass parameters:

    #("p1", 10) & #("p2", "boo")

    and #P to fetch them:

    #P("p1") & " and " & #P("p2")

    It is certainly more characters to type than $ but still quite efficient.

    Geoff

  25. I have been very interested in your dict scheme, and have done many variations and extensions of it on my own. It is quite a powerful idea.

    But the more I think about it, the more I conclude: we’re doing pseudo-JSON. (Maybe it’s JXSMOLN) Why not just use JSON? Then for instance if we throw it into a web viewer, and maybe the web viewer uses a jquery library – it all just works.

  26. Bruce:

    I think that’s a great idea.

    When I did the very first iteration of this whole set of ideas back when 7 was released, I used a JSON-like syntax, but not really JSON (it was actually based on OSX text plist format…). But it was harder to parse and produce.

    Jesse built these from scratch with a few key goals in mind:

    1: Resilient. I should be able to put any data, any characters, and any type into the dictionary without errors. (Container data being the notable exception.)

    2: Nestable. I should be able to do #("x", 1) & #("y", #("y1", 2) & #("y2", 3)) with impunity.

    1: Decent performance (no character-by-character recursive processing)

    So the question is, can we meet these goals with JSON? I’m not sure… I’m not entirely sure how big a deal #3 is, too. I feel like it would be unacceptable to do this kind of recursion but I may be wrong.

    Geoff

  27. Hi, I came across your article and found it very useful.
    I thought I test it with a script straight away, as I am sick of globals.
    However I realized, that I need not only to pass on values to another subscript in a different file, but also to pass values back from this subscript to my main script.
    I added a Exit Script step with all parameters set as normally in the script parameters in my Subscript.

    I duplicated the GetParameter function called it ExitParameter. I simply replaced the Get(ScriptParameter) with Get(ScriptResult) and voila, I can extract all my parameters with the ExitParameter function and just continue in my main script.

    Now I am busy re-writing my scripts.
    Thanks a lot.

    kind regards

    Regine