By Daniel Wood, 6 May 2011
In this article the term "self-aware window" refers to a FileMaker window that can know in real-time whether or not it is the currently selected window, and act accordingly. We introduce a technique for allowing individual windows to calculate this, and some examples of what windows could do with that knowledge.
NOTE: This technique only appears to work on a Mac - sorry Windows, back in the box!
There are a number of different wee bits and pieces that are used to tie this all together, so lets get stuck in with the first...
One of my favorite functions, it can do some pretty cool things. In this case, one of the unique properties of the WindowNames function is that the currently active window name always appears first in the list of window names returned by the function. To try this, you can open a few windows, and put the following function in the data viewer:
WindowNames ( Get ( FileName ) )
When you click from window to window, you'll notice that all though the function returns all window names open for that file, the one you click on is always moved to the top of the list.
Now, why is this useful? Well, for one it gives us a test we can perform. If we can get each window to independently evaluate the WindowNames function, and compare its own window name to that which appears first in the list, then we can tell whether or not that window is currently selected.
It's no mystery that web viewers can do a lot more than just show web pages as may have been originally intended. Over the years they've been used for all kinds of things, from simply displaying a web page, to used in plugins, to used as a substitute for calculation fields, the list goes on.
In this situation we are going to make use of the fact that a web viewer is essentially an on-layout calculation engine. What sets the web viewer calculation engine apart from say perhaps a regular calculation field, or conditional formatting, is that the calculation in a web viewer is always evaluating. That is to say, a web viewer does not require a manual or automatic refresh of a window to update its display.
Consider conditional formatting, or an unstored calculation on a layout, these things in order to update their display require you to resize the window, run a refresh window script, enter into the field, and so on. Something must trigger the refresh. Web viewers are different. They do not require a refresh in order to update, which is great for us!
Better yet, a calculation in a web viewer will re-evaluate even when:
All of these points make the web viewer calculation engine a perfect tool for us. If we can put our WindowNames checking calculation in a web viewer on layouts, then each window can become "self-aware" because the calculation will re-evaluate when the current window changes
The idea here is that we put a check in the web viewer to test whether the current window is the selected one. We do this by comparing the Get ( WindowName ) function with that of the first value returned from the WindowNames function. If they match, then that window is the currently selected window
As a point of note - you can have two windows with the same name, so pay attention if using this technique that the window names appear unique otherwise it may yield incorrect results.
Below we have a calculation defined in a web viewer:
What we are doing here is constructing a data URL. This format allows us to display text direct within the web viewer instead of referencing a web site URL.
Most of the calculation is concerning itself with setting up the data URL and formatting the resulting text, but the key line in all of this is:
If ( windowSelectionStatus ; "Selected" ; "Not Selected" )
Here we have defined a custom function called windowSelectionStatus. If it returns true, we have the web viewer display the word "Selected", otherwise "Not Selected". No prizes for guessing what the windowSelectionStatus function does :)
Here is the calculation for this custom function, and it's basically what we have talked about to this point. We obtain the first window in the list of WindowNames, and compare it to the current windows name, returning true if they match, false otherwise.
Every web viewer that evaluates this calculation is acting in its own window, and so the result of the calculation and thus the subsequent value shown in the web viewer is completely independent of any other window - and it updates in real time!
If you were to place a web viewer on your layouts, and open up multiple windows, you would see the web viewer change its resulting text in real time as you click from window to window, without the assistance of a refresh.
While this is all nice and well, it's pointless if there is no real application for the technique. Perhaps you just want to show something different in the web viewer depending on whether the window is selected, in which case that's fine, the technique to this point is fine.
This article came about because we were working on an issue in a client database, where the user was getting into a record locking situation when having multiple windows open on screen at once. Often, they would end up with two windows open on the same record, either a data record, or their Home record in an interface/data solution. The user may be modifying a record in one window, only to change to another, and upon trying to edit the same record, be presented with the following dialog:
The message is pretty self explanatory as to what has just occurred. Perhaps we can make use of this self-aware window functionality to allow windows to tidy up after themselves when deselected.
Wouldn't it be nice if when a window was deselected, it was able to commit its record right away. That way, the record is not left uncommitted and subsequent locking errors can be avoided.
Now, in order to commit a record, we have to run a script, and in the web viewer we only have access to the calculation engine, so at this time we have to make use of a plugin that is capable of running a script from the calculation engine. There are various plugins out there which are capable of doing this so the choice is yours. In the example file supplied with this article, we include examples for Digital Fusion's Reactor plugin, and MonkeyBread Software's MBS Plugin.
Ideally we want the window to commit itself when:
Well, we already know how to test for the first case. The second case can be tested for using the Get ( RecordOpenState ) function - any result that is non-zero means we should run the commit.
Below is the new custom function defined called commitWindow. As you can see, it is setup in a similar way to the earlier function to test a windows active status.
There are a couple of points of difference. The first is that we also check Get ( RecordOpenState).
The second is that the result of this function is a call to a plugin function capable of executing a script. The above is taken from the example file, so you can see a couple of different examples of function calls for the plugins used. In both cases the script run is called "CommitRecord".
The function is then placed inside the web viewer as a calculation. Because we are only concerned with running a script, and not with what the web viewer shows, there is no need for the function to return anything worth displaying in the web viewer, its job is to run a script and that's all it does.
Now where is the fun in that! While it is true you can set an onTimer running in all of your windows to monitor the windows active status and commit accordingly, there are issues!
Firstly, onTimers require the script to continually be running after defined intervals. In order to safely commit the record fast enough before the user tries to modify in another window, the internal has to be sufficiently low. The smaller the interval, the more frequently the script runs. Perhaps this doesn't add overhead, but the idea of a script running in every window every 5 seconds doesn't sit well with me :-) The web viewer method - while requiring a plugin - is more passive. It only executes the script when it absolutely needs to, and in doing so, the effect is instant.
Secondly, there is a peculiar bug/feature/issue with onTimer scripts that we noticed in writing this article.
Each window has its own onTimer running, that is known. When the script is eventually run, "parts" of the script evaluate from the context of the window from which it was triggered, however other parts of the script execute in whatever the current window is. We haven't fully explored this, but in trying to produce an onTimer method, we found that each window can successfully determine whether it is the selected window via an onTimer. So, each window is able to evaluate windowNames, and Get(WindowName) from their own context.
The problem however comes when the Commit Records/Requests step is run. This script step is always run from the context of whatever the current window is.
So, if you have two windows open A & B, and you modify a record in window A, then navigate to window B, the onTimer that runs in window A will determine successfully that it is uncommitted, and no longer the active window, but the commit record step is executed in the newly selected window B - confused?
To put it another way - in order for an onTimer implementation to work, the script must first navigate back to the window from which the script was executed, run the commit and then navigate back to the window that was (and now is) the currently selected window! (couple that with the fact script is still continually running...)
In short, yes you could work a solution using onTimer, in conjunction with the windowNames first value method. Both are possible, but both come with their own pros and cons, choose wisely :)
We have included an onTimer scenario in the example file so you can see this issue in action, and perhaps modify the file to work correctly, have a play and see what you come up with.
Please find attached an example file. This file was used for all the screenshots in this article, and is provided to help you fully understand what is going on in this article, and to let you experiment in FileMaker with this solution.