Persistant Global Fields

Suppose you have a script which needs access to a global field value. How do you handle it? I've tried a few ways, and each has limitations that bother me.

  1. Table named Global with fields with Global storage. Pros: global fields are accessible from anywhere. Cons: global field values can only be updated when running in single-user mode. This method is basically useless for multi-user use for files hosted on Server.

  2. Table named Global with a non-global field, containing exactly one record, and a relationship using the "X" operator to the current context. Pros: the value is persistant and can be updated on the server. Cons: requires adding new TOs and relationships to every context where it's needed.

  3. Table named Global with a non-global field, containing exactly one record. "PrintHeader.Persistant". Another field which is a global calc field with global storage, e.g "gPrintHeader = PrintHeader.Persistant". Pros: the value is persistant and can be updated on the server. Does not require a TO. Cons: requires a second field.

  4. Table named Global with a non-global field, containing exactly one record. "PrintHeader.Persistant". Another field which is a global field e.g "gPrintHeader". A script run at Open sets all these global values. Pros: the value is persistant and can be updated on the server. Does not require a TO. Cons: requires a second field, requires a script, and the value only updates when the script is run.

  5. In the script, navigate to the Globals table, grab the value in a variable, then navigate back. Pros: doesn't require any extra fields or TOs. Cons: extra navigation increases complexity and reduces speed.

  6. Avoid globals: pass all needed values into a script as a script parameter. Pros: this may be cleaner object-oriented design. with new JSON functions, parameter parsing is safer. Cons: requires more setup and complexity to call the scirpt and deal with parameter.s

Your thoughts?

For security reasons, I tend to go with two tables.

One contains one record and contains all the default values needed by the solution. Each field in this table is non-global. Only the developers and, optionally, select administrators get to change the values in this table.

The second contains one record and contain almost all of the global values needed by the solution. I say almost all global values because some, notably those used in relationships, must be located in specific tables. The global values are initialized by a script when the file opens. The values can also be initialized or modified at other moments in the solution's use by the same and / or other scripts.

You already mentioned some of the pros and cons: default values are persistent and modifiable during runtime; access to global values require one or few TOs; a script is needed to transfer default values to globals. I would add the following: access security of default values; ability to modify globals during runtime based on session needs; field references break less than variable names.

Regarding #6, I disagree that passing all values via parameters to scripts removes the need for global values. There is no way for scripts to hold persistent or session values. Persistent values require storage that can be satisfied with files or fields. Session values requires storage that can be satisfied with fields or variables.

That said, I use #6 a lot because I use MVC (model-view-controler, a design pattern) a lot. Model scripts are the only thing in MVC that is suppose to access and modify tables / files / structures. This approach increases complexity in exchange for robustness, ease of future modifications and flexibility.

Hope this helps.

1 Like

Just had to find a specific solution yesterday.

  1. I use for fix values: Shut down Server, open local, set Value, quit and restart Server

One problem I tried different approaches was to set "Today" date to trigger invoices "Reminder" status. This was solved with MBS PlugIn 'MBS ("FM.ExecuteFileSQL…' function, same kind of function I found yesterday (thanks Christian) to populate a VirtualList scenario on Server. Both process use non-global fields.

Set Today is:
MBS ("FM.ExecuteFileSQL"; ""; "UPDATE "RECHNUNGEN" SET "heute" = CURRENT_DATE ")

populate Value on Server:
setRec = MBS ("FM.ExecuteFileSQL"; ""; "UPDATE "Intermediates" SET "MD_receiver" = ? "; 9; 13; $$MD_receiver)

In my design, I find that most of my scripts are initially being called from one context, so it's easy to set up a TO / relationship to the Globals table. But many of the sub scripts (and sub-sub-sub scripts) are not in that same context, so passing the globals in by parameter works well.

Looking back at my own code, I see that I'm using method 4 in many cases, and #3 in some cases. I think #3 is generally better than #4, so I wonder why I'm doing it that way. Maybe older versions of FM didn't support #4 ?

  1. Use ExecuteSQL to set a variable. Pros: as long as you script the SQL using Get(FieldName) (so that the script is not hard-coding field or table names) this is a pretty good solution too. Pro: ExecuteSQL is read-only, so it provides some protection from changing the value by mistake.
    I think I don't use this much mostly out of habit, because I was using FM long before this function existed...

This is my go-to for getting preferences...

@xochi Global fields and variables are really session variables. Persistent information needs to be stored and just because I think wording matters, I would be more likely to refer to what you are talking about as Constants or System Settings (or System Prefs).

As you pointed out, if what you are looking for is read-only with updates that are only performed by the developer, you could use the following:

  1. executeSQL: Possibly via a custom function. The table does not need to be a single record with many fields, you could have multiple records setup with just two fields (key-value). You could add other fields (or not) to enforce record level access. I never liked controlling field level access in FileMaker. Just make sure you handle the error when someone asks for something they have no read access to.
  2. executeFileMakerDataApi: This requires a script. Pretty much everything said about executeSQL applies to this as well.
  3. Store data into multiple custom functions
  4. Store data into a single custom function holding a dictionary and ask for the one item you need

Just know that if you store anything into a custom function, a DDR will reveal the contents. If you store data in a Prefs table, a DDR will not expose the data. On the flip side, unexpected scenarios where your prefs data gets modified could happen if your setup is not airtight, but it could leave a trace for you to investigate. Modifying a custom function pretty much requires a user with full access to go in there and thinker with stuff, but you rely on the author of the modification to leave a trace.

What are you using global fields for?
(The approach will likely vary by use case.)

  1. Storing system wide icons?
  2. Passing / Displaying Styled Text?
  3. Passing Containers without losing meta data?
  4. Other (there are lots more)

What are your use cases?

It's a mix: just storing plain text data such as URL to the SFTP server, a custom header for a print to PDF script, Dates (what is the current payroll date), and also a few container fields for icons, logos, etc. Some of these techniques clearly won't work (or won't work well) with container data, in which case it's best to have a regular old field.

When using container fields for icons, logos, and even strings that display/print on layout, I use the 2 table approach with a global table that get loaded, as needed, on system initiation from a 1 record resource table.

For values that do not need a UI, I am more likely to store those in a Script function and read them into a Script as needed.

In some cases, I will set a bunch of global fields on a layout to use as an easier to read sudo data viewer.

3 Likes

I always use this in multi-user databases (which is every database):
• A Prefs-table with normal fields
• A Globals-table with the same fields but global
• In the start-up script (or whenever the values need to be updated) the fixed values are copied to the global fields.
This worked for me always without meeting any problem, even for lots of simultaneous users

4 Likes