By Daniel Wood, 22 September 2022
This is what we are trying to get rid of. That situation where you lock yourself out of a record because you already have it open in another window. We've all seen it, and it can be highly frustrating.
The issue is more prevalent in solutions that employ session/interface-based approaches, where the user may be editing records through a particular record that defines their session. Unless managed then spawning new windows can result in the user ending up on the same record.
There is no great reason why a user should ever see this dialog - a user can only ever interact with one window at a time. It makes little sense to leave a window in an uncommitted state while they move to another window - well at least in my experience!
So come with us on a journey to find a solution to this timeless problem, you may learn a thing or two along the way.
This article is based on our example file which also includes examples of the solution you can try out, along with additional information. We highly recommend downloading and checking it out while you read. We spend a lot of time on these!
Download the example file here
Back then the Web Viewer behaved slightly differently than it does today. It had a particular quirk in how it evaluated its URL calculation. The calculation would evaluate not only when the web viewer was in focus, but also when not in focus, and even more importantly even when the window which the web viewer was in was not the active window.
A young Weetbicks saw this as an opportunity to exploit and exploit he did. If a Web Viewer was evaluating a calculation when the active window changed, we could use it to write a calculation that determined if a window had just lost its focus, and if so run a script using a plugin (Reactor at the time) to commit the window.
This was the code:
Let ( [
~windows = WindowNames ( Get ( FileName ) ) ;
~active = GetValue ( ~windows ; 1 ) ;
~this = Get ( WindowName )
If ( ~this ≠ ~active and Get ( RecordOpenState ) ; ReactorRunScript ( Get ( FileName ) ; "Commit" ; "" ) ; "" )
The beautiful thing about this code is that the calculation would evaluate from the context of the Web Viewers window - that being the one that just lost focus.
Fast forward to some time in the future - perhaps around FileMaker 14 (we are not quite sure) and this technique no longer worked. The Web Viewer now only evaluated its calculation when loaded, or when it gained or lost focus.
It's now 2022 and we were still looking for a solution. Now, there obviously has been a solution right in front of our noses for many years - an OnTimer script. Why not just install an OnTimer in every window that checks whether the window is active, and issue a commit if it is not?
This does work. The issue we have however is that it is an active approach. We are forced to run a script every 1-2 seconds to do this check. This can become really annoying especially if trying to do development or debugging.
We did however use this technique recently in our article on how to prevent users from becoming IDLE in a record, and quietly commit them. This was using an OnTimer.
Here we wish to use a passive approach - something that sits in the background and only rears its head when required. This was what the Web Viewer provided us in 2010, and now we need a new solution.
Our earlier Web Viewer approach was concerned with determining when a window had lost its focus. Now however we switched our attention to finding something that might evaluate on a layout when a window gained focus as this seemed more likely than the former approach.
We tried most things but it seemed in this day in age no layout object would reevaluate its calculation simply by losing or regaining window focus.
The only element of a layout we could find that would actually evaluate a calculation when a window gained focus is that of a Custom Menu. When a window gains focus a Custom Menu can be evaluated to determine its visibility and its name. This happens every time a window gains focus.
That was the breakthrough required! Credit must go to our friend Steve Senft-Herrera who put us on the right track here and suggested the Custom Menu could be the way to go.
Now it seemed so simple:
And that's exactly what we did.
We force a commit in a calculation by using a plugin. These days we prefer BaseElements Plugin by Goya. Note that the issue of window record locking is mostly a desktop issue hence why we are not focussing our efforts on iOS and WebDirect as both of those platforms have solutions to this issue through different design choices.
The code is pretty simple:
BE_ScriptExecute ( "Commit" )
We put this into a custom function so we can easily modify the code if need be.
It's not just window switching that forces a custom menu to load. The following will also do this:
We only wish to run on a window change, so we needed a way to suppress all the other situations.
The solution in our script is to set a global variable to the active window name but only do so if the active window name is different from that stored in the variable currently. Only when they are different can we be sure a new window has been selected.
Now that we are only concerned with when the active window changes, the rest of our script can commit the previous window as follows:
Uh-oh, we have a problem! We know our script will run every time a window is given focus, and steps 1 and 3 both switch windows and will trigger our script.
We wish to avoid our script firing on both of these script steps.
To do this, we set a global variable - $$SUPPRESS - used to suppress our script triggering for the duration of when it is set.
This $$SUPPRESS variable is checked in our menu calculation and if it is set will not trigger our script to run again.
So let's look now at the calculation that drives everything from the Custom Menu. We put this in a Custom Function simply so we can easily edit the code.
If ( $$SUPPRESS ; "" ; Let ( ~commit = BE_ScriptExecute ( "Commit" ) ; "" ) )
First, if the suppress variable is set we do nothing and return an empty string.
If it is not set then we wish to initiate our Commit script which we do using a Let statement. We return an empty string.
This custom function can be placed in any calculation in a Custom Menu, such as its name or visibility. We prefer to place it in an existing common Custom Menu that would appear in any Menu Set, such as within the File menu. Because the function returns an empty string we do not need to worry about it impacting the rest of a calculation for name or visibility.
We have come a long way since 2010 and while the Web Viewer technique back then was actually a bit nicer (and if it worked today would be even nicer with FileMaker.PerformScript instead of a plugin required) we have adapted and found a viable solution to this issue.
Can you improve on this technique or offer any valuable insight or feedback? We'd love to hear from you in the comments.
As mentioned above, we always include an example file with our articles to help you learn and explore. We spend a lot of time on these so please do check it out.
Download the example file here
No one has commented on this page yet.
RSS feed for comments on this page | RSS feed for all comments
Dan Roaenstein 30/09/2022 9:23am (6 months ago)
Brilliant as usual!
(Would be great to get away from having to use a plugin)
Daniel Wood 30/09/2022 8:30am (6 months ago)
Thanks for your comments all. Daniel, you are quite right in your assertion about $$SUPPRESS across separate files and your solution to this is very nice.
Another minor change to make would just be in the commit script because it's currently only dealing with window names from the current file. You would just remove the parameter to WindowNames function to work with window names across all files.
Also in the Select Window script steps, you would need to un-check the "Current File Only" option.
Alessandro 30/09/2022 1:29am (6 months ago)
Brilliant deep Analysis and multiple side approach to an old limitation.
This is an excellent example where is located the Energy and Power inside the "FMP" ecosystem: into "The FileMaker Developers".
Mikael 29/09/2022 10:37pm (6 months ago)
Thank you so much for this!
I am currently building a large session based solution, and have been looking for a way to solve just this problem. This is invaluable for me, and I am so happy you did the heavy lifting and shared it with us.
(Though I would love to have a trigger under File setup like "OnWindowFocus" and have suggested that).
I also remember FM6, (I have been developing in FM since 1986), where the window losing focus automatically was committed by FM itself. Oh those happy and simple days... ;-)
Daniel Farnan 29/09/2022 4:07pm (6 months ago)
However, I see a single flaw - if the new window belongs to a different file then the window selection process will fail and the $$SUPPRESS variable will potentially have an unknown value. To handle multiple-file solutions I would change the window selection steps and store the suppression status value in a variable managed by the Base Elements plugin (BE_VariableSet and BE_VariableGet functions).
Still: wow. Thank you so much to everyone involved in this.
Cath 29/09/2022 8:56am (6 months ago)
Good job Daniel.
Josh 29/09/2022 8:09am (6 months ago)
Incredible insight. Thanks to all who worked on this.