January 25, 201510 yr Newbie Let function question..... I am using the Let function for the first time. My goal is to end up with a local variable "$DaysOld" (which equals the "current date" minus "date of birth"). My Let function below is returning the answer that I want but how do I assign that answer to a local variable? If ( METADATA::desc ≠ "birthday" ; "" ; Let ( [ ~dob = GetAsDate ( METADATA::value ) ; ~curdate = Get (CurrentDate) ] ; ~curdate - ~dob ) )
January 25, 201510 yr You can create it as a side effect: Case ( METADATA::desc = "birthday" ; Let ( [ ~dob = GetAsDate ( METADATA::value ) ; ~curdate = Get (CurrentDate) ; $DaysOld = ~curdate - ~dob ] ; "" ) ) But why do you need a $var in what I assume is a field calculation? (In a script, you could of course use Set Variable [ $DaysOld ; … ] )
January 25, 201510 yr Author Thank you eos! No, I want to use the local variable in a layout as a merge variable: <<$DaysOld>>
January 25, 201510 yr And when do you expect the local variable to be visible? it will exist only within the scope of the script in which it is declared.
January 25, 201510 yr Author I don't understand your question Bruce. I am not using it in a script. I am using it on a layout to display data...so I do not have to create calculation fields solely for presentation purposes.
January 25, 201510 yr I am not using it in a script. […] so I do not have to create calculation fields No script, no field – in which context do you use that calculation to create the $var?
January 25, 201510 yr Layout variables must be global variables ($$). Normal practice is to run a script to set that variable. If you try to bury setting that variable in a calculation (like a conditional format calc) on a layout then: - you are hiding business logic: makes it hard to troubleshoot and maintain. Your future self will hate you for it. So will any other developer that is tasked to work on the solution - if you ever need the same logic on another layout you have to code the same calc again. If the logic ever changes, you now have multiple places to update the logic. Not good.
January 25, 201510 yr It is not good idea to use $variables within Let(). A perfect explanation exists on Tech Net here https://fmdev.filemaker.com/message/105194#105194 but since many people aren't registered at Tech Net, I'll quote the explanation here (by Comment): The Let() function purposefully creates a "sandbox" for its own (unprefixed) variables; their scope can never exceed the limits of the parentheses. For example: Let ( [ a = 1 ; b = Let ( a = 2 ; a ) ] ; a ) returns 1, even though the variable's name has been reused within the same calculation. Now, when you use $variables, you extend their scope to the entire file (with the exception of scripts). One effect of this is that when a calculation such as: Let ( $futureDate = Get (CurrentDate) + Numberfield ] ; $futureDate ) is evaluated, the value assigned to $futureDate becomes valid for all the records in the file. The only thing that keeps all records displaying the same result is Filemaker's laziness in refreshing the display. That's something I am not willing to trust, even if I don't have a practical example of a failure (so far?). I just tell my variables to keep their heads and hands inside the bus... Layout variables must be global variables ($$). Local $ variables used to work as layout (merge) variables also (in version 11) but more recent changes in layout rendering and refreshing has removed that ability.
January 25, 201510 yr Author Layout variables must be global variables ($$). Your future self will hate you for it. So will any other developer that is tasked to work on the solution Yes, but think about the added benefit of job security.
January 25, 201510 yr You want your potential customers to read that here? Do the right thing. Also on the scope of variables: http://www.soliantconsulting.com/blog/2014/01/all-variables-should-be-global-or-not
January 26, 201510 yr I believe this ( $local variables ) still actually shows as a merge variable depending on how/when you set it, though it works a little different than before. But I agree that you really have to be careful with there use. Most of the time I use them, it's strictly for UI elements. As Wim's article nicely points out, use the variable they way they were intended. And save yourself some headaches. lol Local $ variables used to work as layout (merge) variables also (in version 11) but more recent changes in layout rendering and refreshing has removed that ability.
January 26, 201510 yr Author No script, no field – in which context do you use that calculation to create the $var? Eos, I was experimenting with the technique that I read about (that if I recall correctly was credited Josh Ormond) where a local variable could be created in the conditional formatting of a text object and then used to display the result. What I found interesting about the technique was that it provided a way to display calculation results in the GUI file (when using the data separation model) without using scripts or creating a calculation field in the data file.
January 26, 201510 yr You are probably referring to Matt Petrowsky's YouTube video. Keep in mind, there are times, that it will not evaluate and refresh the way you think. It's very prone to breaking if not set up correctly. And the application of what Matt and I communicated about is primarily UI elements. If you are putting mission-critical data up on the screen, use a tried-and-true method.
January 27, 201510 yr I believe this ( $local variables ) still actually shows as a merge variable depending on how/when you set it, though it works a little different than before. But I agree that you really have to be careful with there use. Most of the time I use them, it's strictly for UI elements. As Wim's article nicely points out, use the variable they way they were intended. And save yourself some headaches. lol I can confirm that local merge variables do still work, but they appear to me to work the same way they always have. I haven't had a good excuse to try this before now, but object hiding calculations can also be used to set local merge variables, and they're less sensitive to one of the issues that made the conditional formatting version problematic. When setting merge variables with conditional formatting calculations, it was important to make sure the conditionally formatted object was below the object with the merge variable so that the variable was set before it was drawn. The quick test I ran for myself just now seems to show that we don't have to worry about that with object hiding calculations — the object that sets the variable with its object hiding calculation can be anywhere in the layout's z-order relative to the layout displaying the merge variable, and it still works fine. My guess is evaluating object hiding calculations must be one of the very first steps in drawing a layout. You can also have the object with the hiding calculation be a block of text that is conspicuous in layout mode with something like "This object sets local merge variables", have the hiding calculation always return True (1) as its final result, and we don't have to be as particular with how the object is formatted and where it's located. I feel the need to play devil's advocate against a pendulum in this thread that was swung all the way in one direction, that direction being "don't use local merge variables". There are other schools of thought and other reasonable balances of priorities. Setting variables with a Let function in a calculation rather than in a script step is inconsistent with the primary mental models of what calculations and scripts steps do and how they relate to the rest of an application's logic. Calculations typically follow a functional computation paradigm, and script steps do imperative computation — calculations return results, scripts actually do things. Setting a variable that persists outside of the calculation and its results is a side-effect of the calculation evaluation, which is inconsistent with the functional paradigm we normally associate with them. One thing to keep in mind in this discussion is that that distinction is purely for the benefit of human developers. The computer doesn't care. Different developers working on different projects can and do have different priorities that lead to different practices, and life would be boring any other way. It can be reasonable to break with the traditional dichotomy between calculations and scripts if your mental model is different (less defensible in a tool as "controlled" as FileMaker), or to achieve a result that would be much more complicated or slower otherwise (more defensible). There's a similar, but distinct, tension between prioritizing different programming paradigms in FileMaker. Some folks favor as much declarative programming as possible: try to solve any problem with calculation fields, relationships, or any other feature that FileMaker will resolve on its own as appropriate. On the upside, this takes advantage of a lot of functionality that FileMaker gives us with a minimal amount of additional labor invested. A more subtle advantage is that the definitions of how entities relate to each other (in the documentation sense) can also serve as the programming to make the application that users experience actually work. The downside is that declarative programming in FileMaker is dependent on FileMaker's decisions about when to update things. Sometimes FileMaker doesn't refresh things when we'd like it to, resulting in issues like displaying out-of-date information. Sometimes FileMaker refreshes things when we'd prefer that it didn't, resulting in performance problems. Shifting more functionality to a more imperative model — more scripting — takes over more control of when operations happen, at the cost of being responsible for making sure those operations happen. Setting layout merge variables with an object hiding calculation is closer to the declarative paradigm. Whenever FileMaker gets around to drawing the layout, your variables come along for the ride. Yet another issue is the relationship between objects and the programming logic that manipulates those objects. Keeping those two closer together usually makes software easier to understand. Further, minimizing the scope that variables apply to is associated with thinking about logic in cognitively-tractable chunks small enough to fit in human short-term memory — using smaller-scope variables limits how much you have to keep in your head at any one time while developing a solution. This could be used to argue both for and against setting merge variables with a hide object calculation. Setting a persistent variable (local or global) increases the scope that we otherwise deal with in the Let function. On the other hand, setting and using a local variable in the layout constrains the variable to a smaller scope than setting a variable in a script and displaying it in a layout. (Local variables set by a layout appear to not be accessible in a script.) Developers often like to use generalized code, using the same logic for several different situations. It has the potential to be great, and it's a fundamental part of programming, but it's often oversold. Generalization can often (but not always) come at the cost of making logic more difficult to follow by separating objects from the logic that manipulates them and being less optimal for specific situations. (No matter how good you think you are at generalized thinking, you're better at more concrete thinking. I can point you to science that says so.) We've seen the argument that you should program similar functionality to use the same code so that you only have one place to make any changes to that logic that come up in the future — but this doesn't really hold water. It's just as plausible (and, in my experience, probable) that the logic will need one change for some places it gets used, and no change or a different change for other places it gets used, so the benefit of the early extra effort to write the logic in generalized form can be diluted or even just wasted. (Yes, writing generalized code takes more effort than case-specific code. Again, science says so.) Resist premature generalization.
January 27, 201510 yr Author I have attached my test file that I have been playing around with. It's a data separation example using Local Merge Variables. I am curious to see if anyone can figure out a way to display the metadata (without adding any new fields to any of the tables) so it displays both the description and the value of (phone, email, etc.) as shown in the screen capture: Contacts-Local_Merge_Variables.zip
January 27, 201510 yr I am not using it in a script. I am using it on a layout to display data...so I do not have to create calculation fields solely for presentation purposes. In addition to all of the above, you should also be aware that local $variables declared while no script is running cease to exist while any script is in progress. So the layout with merged $variables will not display the same while a script is paused, for example (unless it remains unrefreshed for the duration).
January 27, 201510 yr Appreciate the input Jeremy. I haven't used them much since, I believe 12v2. Around that time, it broke. I had to scramble to fix it in my solutions. I am glad to hear ( and test ) that the "Hide object when..." calculation is so much more reliable. There are a few caveats that come with using a Local Variable though. They do NOT function like Global variables. As comment pointed out, they don't exist during scripts. You can pass them as variables, but it's the only way to capture their values in a script. Also, it's not multi-record friendly. So you can't use them to display different values in different records, unless you create multiple variables. Halburn - you could also use a WebViewer.
January 27, 201510 yr Author Halburn - you could also use a WebViewer. I hate using web viewers in my layouts. They always seem very glitchy with a lot of undesirable flashing.
January 27, 201510 yr In addition to all of the above, you should also be aware that local $variables declared while no script is running cease to exist while any script is in progress. So the layout with merged $variables will not display the same while a script is paused, for example (unless it remains unrefreshed for the duration).  Are you sure? I tried futzing with it for a few minutes, and I couldn't get the merge variable set by an object hiding calculation to go away. I could get the script to overwrite the variable, but the script-written value would only display if the object hiding calculation respected existing content in that variable. Pausing a script apparently triggers re-evaluation of hide calculations and drawing the merge variables. I added a $counter and found that running a script does indeed wipe out the original local variable values, but that didn't stop the hide calculation from setting new ones when the script paused. I did confirm that the script doesn't see what the merge variable value was before the script ran, presumably because it doesn't exist again until parts of the layout re-draw, and the script does see the contents of the variable after the layout re-populates it. Is there a more specific situation where merged variables don't adequately update during script execution?  In the course of this futzing around, I also found that $local variables set by a layout appear to be available to other windows. This doesn't necessarily mean window-specific merge variables can't be done. Whatever process draws the merge variables appears to always evaluate the hide calculations on the layout first. Merge variables set to the window name work how I hoped they would work. This drawing order makes intuitive sense to me; if I were drawing a layout, I'd want to start by making sure I didn't draw anything I didn't have to.  Josh, I concede that merge variables set by hide calculations in a list view is a problem. Merge variables don't support repetitions, and we don't have a way to change the repetitions by record in the merge variable if we did. Record-specific merge variables can still be done with the more finicky conditional formatting calculation as long as we remember to get the z-order right. And I found that, at least in my copy of FileMaker 13v4, these variables also seamlessly re-draw during a paused script.
January 27, 201510 yr I did want to clarify my post a bit (although we're all far past it now) ... I meant using $ in a merge variable was no dependable like it was in 11, triggered by conditional formatting. As you, Josh, we were using it extensively within Let() within conditional formatting attached to objects (refreshed by stacking order and sometimes nudged by placing under a portal edge or field to force refresh). Usually all it took was being lowest in stacking order. FMI made a lot of changes in refresh and layering between 12.0v1 and 13.02. I haven't tested recently (haven't had the need) so I am pleased to hear it is viable option even if it doesn't refresh quite as well; in fact, I have a few posts with files which behave one way on merge variables in 11 and differently in 12 and then also between 12 and 13. So little time and so much to test.
January 27, 201510 yr Author Merge variables don't support repetitions Ah, that explains some of the trouble that I was having.
January 27, 201510 yr One thing you can do, since your $fullName DOES evaluate, move all of the variables calculations into it as a single variable and place only the single variable of <<$FullName>>. Then they all refresh. I don't show repetitions in your file at all, something like: Let ( $FullName = CONTACTS::first_name & " " & CONTACTS::last_name & ¶ & List ( METADATA::value ) & "¶¶¶" & ValueCount ( List ( METADATA::value ) ) ; "" ) I'm unsure why you want to Count your related text fields, I guess to see what is not empty but ValueCount() does that since you are using List(). Anyway, maybe it'll move you forward a bit. added word 'not' in blue Edited January 27, 201510 yr by LaRetta
January 27, 201510 yr Author One thing you can do, since your $fullName DOES evaluate, move all of the variables calculations into it as a single variable and place only the single variable of <<$FullName>>. Then they all refresh. I don't show repetitions in your file at all, something like: Let ( $FullName = CONTACTS::first_name & " " & CONTACTS::last_name & ¶ & List ( METADATA::value ) & "¶¶¶" & ValueCount ( List ( METADATA::value ) ) ; "" ) I'm unsure why you want to Count your related text fields, I guess to see what is not empty but ValueCount() does that since you are using List(). Anyway, maybe it'll move you forward a bit. added word 'not' in blue Thanks for your example LaRetta. My demo file is not meant to practical, it's just a test file that I created so I could learn more about the Let function and using this Local Variable technique.
January 27, 201510 yr I believe that 'what you learned' is that it can be used, can be effective, can be difficult to force-refresh at times, and even can change how it refreshes as we move through versions. However, there ARE principles behind each 'fix' which can be important and, after all, you DID ask for a fix for a refresh issue, right? 13 now appears to be stable in refreshing and layout rendering. The principle is that you can use other layout objects to force a refresh - there are many things you can do. The question is ... do you want to use these techniques if it is critical the data display properly or timely (according to your business rules)? Just displaying a client's address is no big - other things such as Past Due notices are another.
January 27, 201510 yr Author However, there ARE principles behind each 'fix' which can be important and, after all, you DID ask for a fix for a refresh issue, right? Actually, I was not the person that brought up the refresh issue. This was not a problem for me. I was just trying to get a little bit of experience with the Let function and I thought that I would kill two birds with one stone and experiment with this technique that I had read about a while ago for using the local variables in layouts. Thanks again for your feedback and advice.
January 27, 201510 yr Are you sure? Am I sure of what? That local $variables declared while no script is running are suspended for the duration of any script runtime? Yes, I am sure of that. That a layout showing these variables will necessarily show them as undefined while a script is in progress? No, I can't be sure of that, because that depends on how exactly the layout is set up. Basically, there are three options: [a] the layout is not refreshed, and continues to display the variables as populated before the script was initiated; the layout is refreshed, and the variables are shown as missing; [c] the layout is refreshed, and the refresh causes a new set of variables (with the same names) to be declared and displayed. I suspect you were experiencing [c] when you "couldn't get the merge variable set by an object hiding calculation to go away". What really happened is that the variable did "go away" and a new variable took its place for the duration of the script.
January 27, 201510 yr I've played with these a lot over the past few years. This is what happens. $variable set via Let function outside of the script. Will only show once the layout is refreshed, or resized, or a script with a freeze window completes ( anything that causes the layout to redraw ). Start script. $variable 'disappears', and is not accessible to the script, unless you passed it as a parameter. ( you should be able to watch the data viewer, the layout isn't refreshing so it will stay on the layout ). Script completes. Original $variable returns. During the script you can set that same variable to anything. When the script ends, that $variable in the script clears, and the original $variable returns.
January 27, 201510 yr That's the script 0 concept... any local variable declared outside of a running script is really in this "Script 0". That explains the normal behavior of the variable resetting when you run another script "Script 1" and when it ends you return to "Script 0"
January 27, 201510 yr Gotcha. Sounds similar to being able to set Global fields in a table with zero records. "Record 0"?!
January 27, 201510 yr That's the script 0 concept... any local variable declared outside of a running script is really in this "Script 0". That's a potentially misleading formulation. "outside of a running script" suggests that only $variables declared inside of a running script belong to that script. That is not so: any $variables declared during the runtime of a script - no matter how - are in that set.
January 28, 201510 yr Basically, there are three options: [a] the layout is not refreshed, and continues to display the variables as populated before the script was initiated; the layout is refreshed, and the variables are shown as missing; [c] the layout is refreshed, and the refresh causes a new set of variables (with the same names) to be declared and displayed. I suspect you were experiencing [c] when you "couldn't get the merge variable set by an object hiding calculation to go away". What really happened is that the variable did "go away" and a new variable took its place for the duration of the script. I'm pretty confident that option C is definitely what FileMaker does. This is perfect, as it's the option that makes use of local merge variables most palatable. Merge variables definitely do display while a script is running, and different values can display in response to the script. One more quick test does show one behavior worth keeping in mind for anyone taking advantage of this: the layout-based calculations do not necessarily re-evaluate and reset the merge variable values after the last script exits. So if you want merge variables in a layout to reset after a script is done, the script should do the resetting before it exits.
January 28, 201510 yr What do you see with this file. The $var is set via the "Hide when...". The script sets the same variable. If you watch the data viewer, at least what I'm seeing on my laptop ( Windows 7.1 ) is that the $var resets to the original value. Not creating a new variable with a freshly calculated result. Variable Test.zip
January 28, 201510 yr I'm pretty confident that option C is definitely what FileMaker does. I'm afraid you misunderstood me (or I you). Filemaker does any one of the three options, depending on how you set this up. So if you want merge variables in a layout to reset after a script is done, the script should do the resetting before it exits. I am not sure how that will work, given that before the script exits, the script is still running...?
January 28, 201510 yr That's a potentially misleading formulation. "outside of a running script" suggests that only $variables declared inside of a running script belong to that script. That is not so: any $variables declared during the runtime of a script - no matter how - are in that set. Agreed, that is a better formulation.
January 29, 201510 yr I'm afraid you misunderstood me (or I you). Filemaker does any one of the three options, depending on how you set this up. In my testing, I only got FileMaker to do option C. Josh has demonstrated A. Can you demonstrate a circumstance where FileMaker does B? The important thing is whether FileMaker's behavior makes layout-set merge variables usable. For A and C, the answer is yes, we can work with that. B is the only problematic situation, but I haven't seen it yet, despite my attempts to find it. I am not sure how that will work, given that before the script exits, the script is still running...? I should have said that the script needs to set the variable or trigger a refresh itself before exiting to create the appearance of the layout resetting the variable.
January 29, 201510 yr You get A when nothing in the script refreshes the window. You get C when something in the script does cause a refresh or redraw. Adding a simple Freeze window to a script will then cause C to happen in my file.
February 10, 201510 yr After some more testing, I think I have a slightly more coherent and comprehensive idea of the interaction between variables set by layout and by script. I created a counter that increments itself in a Hide Object calculation. It increments on its own when switching modes (by 1) or when refreshing the window (by 2, therefore, by 1 twice) without a script. When a script starts, the value of the counter variable is apparently empty, and starts re-counting from 0 when the window is refreshed during the script. When I refresh the window again after the script, the counter resumes from where it left off before the script started. Another variable is set to different values by the layout and the script, but the calculation in the layout respects any previously set value if it finds one. This variable shows the value set by the script during and after the script, but reverts to the value set by the layout after a non-scripted refresh, i.e. the variable set by the script is gone. (Duh.) Another variable set by a calculation in the layout completely ignores its previous value. When the script sets that variable and refreshes the window, the value from the layout displays in the layout, and that value is then also what the script sees. This suggests that a layout-scoped variable persists during the script, but the script-scoped variable takes precedence during the execution of the script. However, when the script refreshes the window, the layout has an opportunity to set script-scoped variables, and those variables are then available to the script. After the script exits, the original layout-scoped variables are available again. Interestingly, if the script sets variables and doesn't refresh the layout, the GetLayoutObjectAttribute function can still return what it thinks the contents of a layout object ought to be, rather than what's actually being displayed, which I'm reporting as a bug. LocalMergeVariableTest.fmp12.zip
February 10, 201510 yr In my testing, I only got FileMaker to do option C. Josh has demonstrated A. Can you demonstrate a circumstance where FileMaker does B? I don't know of a good way to demonstrate B using a variable that's populated by a layout-related event. Your latest crop of findings summarizes the situation quite well, though. I would just add one or two points: This suggests that a layout-scoped variable persists during the script, but the script-scoped variable takes precedence during the execution of the script. The term "layout-scoped variable" is misleading, IMHO. Local $variables are not layout-scoped. If anything, they are script-scoped. In this context, the concept of "script[0]" that Ray Cologon presented for the set of variables declared while no script is running, may be useful. Also, when you say that "the script-scoped variable takes precedence", it suggests that you need the script to actually declare a competing variable in order for the non-script variable to become suspended. That's not so; all local $variables that weren't declared during the runtime of the current script are put aside for the duration. if the script sets variables and doesn't refresh the layout, the GetLayoutObjectAttribute function can still return what it thinks the contents of a layout object ought to be, rather than what's actually being displayed, which I'm reporting as a bug. I am not at all convinced that's a bug, or even undesired behavior. If there's a legitimate grievance here, it should be directed at the entire gap between what Filemaker holds as "the next screen I am going to draw" and what's currently in the graphic card's buffer - not just one detail out of the entire picture. If you're willing to accept Filemaker's paradigm as a lazy application that skimps on window refreshing (and now even more so with Refresh Object []), then you should have no quarrel with this particular aspect of it.
February 10, 201510 yr I am not at all convinced that's a bug, or even undesired behavior. If there's a legitimate grievance here, it should be directed at the entire gap between what Filemaker holds as "the next screen I am going to draw" and what's currently in the graphic card's buffer - not just one detail out of the entire picture. If you're willing to accept Filemaker's paradigm as a lazy application that skimps on window refreshing (and now even more so with Refresh Object []), then you should have no quarrel with this particular aspect of it. I just did a test now, and found that this behavior-that-I'm-calling-a-bug works the other way, too: when a script sets a local variable displayed on a layout, refreshes the window, and exits, the GetLayoutObjectAttribute function result reflects what the layout would set, even before the layout actually displays it. I endorse lazy evaluation as it is understood in other programming contexts, e.g. FileMaker's boolean logic short circuiting. FileMaker's behavior here isn't quite that; it's pre-emptive rather than reactive. I expect the GetLayoutObjectAttribute function and other design and state functions to tell me what currently is, not what may or may not ever be. One could claim that "what currently is" refers to the next screen FileMaker anticipates drawing rather than what's currently displayed, but the latter is too obviously the more sensible choice.
February 10, 201510 yr this behavior-that-I'm-calling-a-bug works the other way, too: when a script sets a local variable displayed on a layout, refreshes the window, and exits, the GetLayoutObjectAttribute function result reflects what the layout would set, even before the layout actually displays it. It seems to me as the same way, not the other way. But I guess it's relative to one's point-of view - as is this: One could claim that "what currently is" refers to the next screen FileMaker anticipates drawing rather than what's currently displayed The way I (and Filemaker) see it, Filemaker is the one (and the only one) who has the full picture of "what currently is". From time to time, Filemaker will push the current picture to the screen. This is strictly a one-way street, a fire-and-forget missile. IOW, the screen represents "what was a moment ago". What you are asking for is a massive change in concept - it would require Filemaker to keep track of two pictures instead of just one.
Create an account or sign in to comment