Hi

I have been using a technique (I don't know if it is really new or not, but it is not listed on this page yet) to store StatusGlobals ( like AllowAutoEnter, ScriptTriggersBlocked) etc.

I got inspired by Matt Petrowsky's idea to use custom functions for more than just storing a simple calculation. At a certain point in a project I ran into the problem that in one of my scripts I turned the scripttriggers off ($$blockScriptTriggers = 1 ) and forgot to turn it of at the end of the script ($$blockScriptTriggers = 0).

This meant that in certain conditions the application simply failed... no more OnRecordLoad, OnLayoutLoad etc...

As it was really a pain in the ass to find the script that caused it, I came to the idea that I actually wanted to know which script lastly modified this statusGlobal. So I created for this "ScriptTriggersBlocked" status three different custom functions (SG is my "class" name for all the StatusGlobals)

  • SG_ScriptTriggersLockedSet ( _boolean )
  • SG_ScriptTriggersLockedGet -> returns a boolean
  • SG_ScriptTriggersLockedInfo -> returns more detailed info about the last modification of the status global

The custom functions uses xmlSet ( _tag ; _data ) and xmlGet ( _tag ; _data ) developed by Fabrice Nordmann, they can be downloaded here:

SG_ScriptTriggersLockedSet ( _boolean )

Let ( $$SG_ScriptTriggersLocked =
xmlSet ( "status" ; GetAsBoolean ( _boolean ) ) &
xmlSet ( "scriptName" ; Get ( ScriptName ) ) &
xmlSet ( "CurrentTime" ; Get ( CurrentTimeStamp ) )  &
xmlSet ( "LayoutName" ; Get ( LayoutName ) )
;
""
)


SG_ScriptTriggersLockedGet

Let (
_status = xmlGet ( $$SG_ScriptTriggersLocked ; "status" )
;
GetAsBoolean ( _status )
)


SG_ScriptTriggersLockedInfo

$$SG_ScriptTriggersLocked


Those custom functions saved me many times. If there is now a particular strange behavior with my scripttriggers, I just put in my dataviewer SG_ScriptTriggersLockedInfo and I get the information of the latest status change.

I use the same principle for "Auto-enter block", "Turn Audit On", etc etc

For me this is really something that has an added-value for every developer, but as I said in the beginning of my post, I don't know if this is a known technique or not... Or maybe there are better techniques...

12 Comments

  1. Have you seen our Best Practice for suppressible triggered scripts? That's one place that uses this pattern, but it could be helpful to document the pattern as an Accepted Technique.

    1. Anonymous

      Hi Jeremy

      I saw this Best Practice, and may-be my example is a little confusing. It is not really about "script triggers", but just for me a Best Practice for declaring Status Globals. In the past I always used something like $$setting_AutoEnterBlocked... which gave me a headache because I could never remember the exact name of the global variable.

      So my proposal is more about putting those global variables in a wrapped custom function to

      a) avoid typos
      b) easily find them back in my custom function list
      c) store additional information like the scriptname that set it, the time, ...

      Does this make more sense?

      Andries

      1. Yes Andries, this does make sense - and is a good idea! I just ran into this the other day.

        Jeremy, essentially, he is saying we should unify a custom function such as SetEnvironmentStatus where a reserved variable might be something like $$~DISABLETRIGGERS.SCRIPTS.LAST which indicates which script name most recently disabled script triggers (this would be added to the TriggersDisable custom function). This addresses the problem of not knowing which script modified the environment (in the rare case that you forget to enable script triggers within the same script that disabled them) (wink)

        The SetEnvironmentStatus and GetEnvironmentStatus would be be useful for tracking and documenting global environment variables which indicate the status of things - such as if Script Triggers were on or off.

        Essentially, the GetEnvironmentStatus is similar to the current Debug custom function and really is a reporting mechanism because the SetEnvironmentStatus would contain the list of reserved global status variables and each one would contains its respective status.

        Make more sense?

        1. Anonymous

          Well it is almost my idea, but instead of storing the scriptname in a separate global variable, as you propose, I would store a pseudo-xml in the environment global.

          Something like this

          $$~DISABLETRIGGERS = <status>True</status><scriptName>SomeScript</scriptName><timeStamp>06/04/2011 12:11:03</timeStamp><layoutName>someLayout</layoutName>

          I like to keep my dataviewer as "clean" as possible, and avoid overpopulating it with global variables. That is why I really like the idea to keep all the information related to a environment status into one global variable, but stored in a pseudo-xml way. With those two custom functions of Fabrice Nordmann this is really easy to achieve.

          1. With our primary goal being code clarity, the verbose nature of xml within the dataviewer, simply for the purpose of reference doesn't seem like the best of ideas.

            What is the advantage to storing it as XML? Is it going to be written out to a file?

            What was your reasoning for choosing such as structured format?

            Would something within a variable like $$~ENVIRONMENT that read like the following work?

            $$~ENVIRONMENT.DISABLETRIGGERS = True
            $$~ENVIRONMENT.DISABLETRIGGERS.SCRIPTS = "Script Name A¶Script Name B"
            $$~ENVIRONMENT.DISABLETRIGGERS.SCRIPTS.LAST = "Script Name A"
            $$~ENVIRONMENT.DISABLETRIGGERS.SCRIPTS.TIME = 06/04/2011 12:11:03
            $$~ENVIRONMENT.DISABLETRIGGERS.SCRIPTS.FILE = SomeFile.fp7
            
        2. I understood that Andries' script trigger suppression example is only an example of the pattern being described. (The current revision of the Triggers* functions already do something similar by storing the stack of scripts disabling triggers, albeit with less detail.) The timestamp-based UUID functions store some return-delimited data between calls to improve calculation speed and uniqueness, but that's not global status information in the same sense of the word.

          As a pattern, I use it, I like it, and I'm eager to see it documented in public. It's an abstract idea, though, and many people will need more examples to be able to separate the pattern from the application, in addition to a description of the pattern decoupled from any one instance of it.

          So far as we're talking about using families of custom functions to access and manipulate global status variables in a way that couples the variables to the functions that work with them, I'm all for it. I'm imagining (for example) AllowAutoEnter and TriggersAreActive families, each manipulating their own variables and with zero shared dependencies.

          I have mixed feelings about the prospect of combining all such functionality under one EnvironmentStatus tent. On the one hand, it would be nice to keep a snapshot of all status variables in one place, and it would be nice to reduce the quantity of global status variables polluting the data viewer by dumping them all in one big set of name-value pairs in one variable.

          On the other hand, SetEnvironmentStatus and GetEnvironmentStatus (as I'm imagining how they might work) are decreasingly self-documenting. Retrieving a specific status parameter for a calculation, like GetEnvironmentStatus ( "triggersAreActive" ), requires that you know to pass the string "triggersAreActive" before you can use or understand what it's doing. (In other words, functions that require enumerated parameters are something to avoid when possible.) I suppose I'm actually imagining a #GetEnvironmentStatus ( name ) function along the same lines as the other #Get functions, but that's more simple than what I think is being suggested. We might wrap that up in one more abstraction by defining a TriggersAreActive custom function as GetEnvironmentStatus ( "triggersAreActive" ), but now we've added a dependency. I abide Occam's Razor. One more dependency is one more thing to forget when importing or copy-and-pasting functions from one file to another. Good code is robust to careless (and unfamiliar) developers.

          On yet another hand, the traceability aspect of what Andries is presenting does sound awfully handy for debugging. My understanding is that custom functions for interacting with global status variables set not only the status, but start to build a sort of audit log of the status, which is very interesting despite the escalating complexity. I was wondering for a second whether it would be worthwhile to try a name-value format that includes audit/trace data, thus creating a mini-EAV format, but then I came back to my senses.

          1. My senses about name-value formats have fled again. What about an optional trace addition to the # ( name ; value ) function, that would generate something like this when yet another environmental status flag is set, and the basic name-value format otherwise:

            $variable = "value"; // 7/4/2011 17:01:00.21 ScriptName
            

            We can call the function that turns logging on #Debug! Its inverse can be #Rebug! Too much fun to resist...

            1. Oooh, a quasi-call stack for variable instantiation. I like the concept of a Trace-like feature.

              When it comes to the argument of "cluttering" the dataviewer, I've never been one to be afraid of too many variables. Provided things are classed properly, it becomes very easy to scan for the sub-section of global variables which you are looking for. My Theme Studio product has a dataviewer with hundreds of global variables, yet is very easy to find things within - due to strong naming conventions (and sorting by name).

              So, my reason for saying the above is that we could offer both, with the singular being required to maintain storage of the status information.

              The way I envisioned things working was this.

              ~SetEnvironmentStatus would be a private function called only by other custom functions (or calcs) which needed to declare the status (or change of status) of the environment they affect (e.g. TriggersDisable).

              GetEnvironmentStatus or even better ShowEnvironmentStatus (no enumerated parameters required) is a function that would reveal the environment status variables in an exploded format. Here are the examples.

              Expanded dataviewer format

              $$~DISABLETRIGGERS
              $$~DISABLETRIGGERS.SCRIPTS
              $$~DISABLETRIGGERS.SCRIPTS.LAST
              $$~DISABLETRIGGERS.SCRIPTS.TIME
              $$~DISABLETRIGGERS.SCRIPTS.FILE
              

              Condensed dataviewer format

              $$~ENVIRONMENT
              

              It's the singular $$~ENVIRONMENT variable which would contain the individual lines which could easily be instantiated for viewing in the dataviewer via the #Assign() function.

              HideEnvironmentStatus would essentially kill all the global status variables so they would not be "cluttering" up the dataviewer. A simple walk across the $$~ENVIRONMENT list would be easy enough to kill the global variables. The CustomList function could do this in a heartbeat.

              This is also something that I would migrate into from the Debug function. Being able to Hide/Show global Status variables in the dataviewer is a great solution to the fact that a crashing FileMaker will wipe any of your dataviewer Watch settings.

              With a potentially ever growing list of Environment variables it might be better would be required to prefix things with the $$~ENVIRONMENT class - to facilitate a nice sorting/viewing experience - because the variable itself would be used by the CF which uses it.

              $$~ENVIRONMENT
              $$~ENVIRONMENT.DISABLETRIGGERS
              $$~ENVIRONMENT.DISABLETRIGGERS.SCRIPTS
              $$~ENVIRONMENT.DISABLETRIGGERS.SCRIPTS.LAST
              $$~ENVIRONMENT.DISABLETRIGGERS.SCRIPTS.TIME
              $$~ENVIRONMENT.DISABLETRIGGERS.SCRIPTS.FILE
              

              would collapse to a nice clean

              $$~ENVIRONMENT
              

              Personally, I'm not into deciphering the verbose nature of reading XML within a global variable. (sad)

              Thoughts? We would certainly need to think this through.

              1. I have some naming thoughts about this. If we are going to have centralized control of global/session/environment status variables, "Status" is more concise than environment.

                It also seems to me that it would be natural to think of such functions as an extension to the # name-value functions, so we might have #Status ( name ; value ), #GetStatus ( name ), and #ShowStatus instead of ~SetEnvironmentStatus, ~GetEnvironmentStatus, and ShowEnvironmentStatus. (That's #ShowStatus, not #AssignStatus. We've been using "#Assign" as a prefix for creating variables intended to be used programmatically, which is not how we're talking about using #ShowStatus) (I don't think XML is any fun to read either, but I don't think the choice of name-value representation is material to the pattern, or at least where we seem to be going with it.)

                I'm inclined to think that the more detailed trace/audit details (timestamp, script, privilege set, file, window, layout, etc.) should be something that can be switched on and off with a #LogStatus ( trueOrFalse ) function. When it's off, #Status only stores the most recent value for each name. When it's on, #Status keeps a history of values for each name, including trace details. #AssignStatus would then create variables for the most recent value, plus variables for any trace details for the most recent value. Developers could see the history of a parameter by opening the combined $$~STATUS variable (and reading the name-value syntax of their choosing).

                I was reluctant to centralize handling before, but I think the potential burden of repeating trace logging logic outweighs Occam's Razor in this case. I'll come up with a sample implementation tonight and see where it takes me.

  2. Anonymous

    Hi,

    I needed some time to let this all come to me :)

    The reason for using this verbose xml structure is simple: it is a habit... I pass everything in a pseudo-xml structure, all my script parameters, script results etc... For me it is an easy way to pass data between procedures, which is really readable (for me it is more readable than any other delimited way (pipes, comma, ¶, ... ) of passing parameters). And we have a whole bunch of custom functions in place which uses this pseudo xml structure. This is really the only reason, and I understand that it might look verbose, so I am open to other proposals, but in that case I think we need also a best practice in passing parameters to a script and declare them in one go as local variables, because this is really a time saver with xmlSet, and xml2var (but this is another discussion).

    I agree with Jeremy that "Status" might be a better naming rather than environment. For me environment is more like "I am working on Mac, with Mac OS X 10.5, FileMaker 10 v2, ..." and Status describes better the state of the application and/or file.

    The fact to have one custom function to rule them all, is not really teasing me, because it wouldn't solve my "typo" problem. Also it is not really coherent with what is being described in "Don't pass commands as Parameters". I understand that a name of a global status is not really a command, but it makes the custom function less self explanatory. But I think we can have them (or I would implement them) as helper functions, which would be called by the other custom functions. For example to set the an enviroment/status global we could have the helper function "~SetEnvironmentStatus ( _name ; _value )" which would be called by the function "~SetDisableTriggers ( _boolean )", which would look like this: 

    ~SetEnvironmentStatus ( "~ENVIRONMENT.DISABLETRIGGERS ; _boolean ).

    The helper function would declare all the necessary global variables. I like this idea because it centralizes the "logging" part. If you want to add a parameter to be logged, there is only one function to be updated.

    I like the idea very much of collapsing the variables, and I agree CustomList can do this in a snap. Also the fact that we can enable and disable the logging is a nice feature with a high added value during developing/debugging.

    1. Anonymous

      I always forget to sign my posts...

      Andries   :)

  3. Anonymous

    Hi

    I finally found some spare time to refine this technique. I tried to adapt to the naming conventions provided by this site, but please don't shoot me if I made a mistake somewhere.

    The idea is to have 3 private custom functions that will tunnel all calls to change or update status globals. At the moment I "reserved" the namespace "sg" to define all custom functions and global variables referring to status globals.

    sgSet ( name ; value )
    Evaluate (
    "Let ( ["&
    	"$$SG." & name & ".value = " & Quote ( value ) & ";"
    	&
      	"$$SG." & name & ".script = " & Quote ( Get ( ScriptName ) ) & ";"
    	&
    	"$$SG." & name & ".timestamp = " & Quote ( Get ( CurrentTimeStamp ) ) & ""
    	&
    	"];
    	\"\"
    	)"
    )
    ~sgGetValue ( name )
    Evaluate ( "$$SG." & name & ".value" )
    ~sgGetInfo ( name )
    List (
    "value: " & Evaluate ( "$$SG." & name & ".value" );
    "script: " & Evaluate ( "$$SG." & name & ".script" );
    "timestamp: " & Evaluate ( "$$SG." & name & ".timestamp")
    )

    Those private functions can be called by other custom functions (also belonging to the SG name space) to change their values. For example:

    sgTriggersBlockedSet ( value )
    ~sgSet ( "TriggersBlocked" ; value )
    sgTriggersBlockedGetValue
    ~sgGetValue ( "TriggersBlocked"  )
    sgTriggersBlockedGetInfo
    ~sgGetInfo ( "TriggersBlocked"  )

    Is this the way to go?