Live Formatting Modus Operandi
We recently posted an article about a magical FileMaker development technique called “Live Formatting” where we use an input mask to show the user how the data entered will be formatted and to ensure that it follows that format consistently.
The technique has obvious utility for phone number fields, but can be useful for other structured data like federal ID numbers or even product codes. In this previous article, we describe how to implement the technique (5 easy steps); in this article, we’re going to dive into the details of how it works.
So, How does it work?
Recall that there are three components that make up this technique: a custom function, a handful of scripts, and some global variables that need to get set.
The Mask custom function (CF) takes three parameters: “input” is a string reduced to only its digits, if any, “template” is a text string containing formatting characters interspersed with instances of the “placeholder” character, and “placeholder” is the character in the “template” that identifies positions where digits are expected. By default, the CF’s output applies Bold Red formatting to any placeholder characters not (yet) replaced by a digit. When template ends in a placeholder and the number of digits in input exceeds the number of placeholders in the template, the CF applies Bold Red Strikethrough formatting to the excess digits.
The script “Startup” (configured to run OnFirstWindowOpen) instantiates $$fieldName variables with the mask format applicable to the corresponding field. This makes the development process simpler, as the mask format is identified when the triggered script runs instead of receiving it as a parameter. That’s handy for somewhat trivial reasons: there’s no need to copy and paste the parameter as each script trigger is initially configured for the field, and—in the event that a format change is desirable—there is no need to revisit every iteration of the relevant field(s) on every layout to make the change.
OnObjectEnter and OnObjectKeystroke
Question: What happens when the user clicks into a mask formatted field and starts typing?
Answer: It depends! Is the field empty or is there data already? What key is the user typing? Is the user using a modifier key like shift? What about a paste operation?
The OnObjectEnter script inserts the mask template for the user and puts the cursor in front of the first placeholder character when the field object is empty and the field is not identified in the Startup script as one that should prepopulate the area code—in that case, it inserts and highlights that value. Where the field contains at least one digit, the script respects the cursor’s insertion point as long as there are no placeholder characters to its left. When there are, it locates the cursor in front of the left-most placeholder to await the next digit’s entry.
The OnObjectKeystroke script is the most complex script in the demo file, with multiple $scriptVariables declared within one or two Let functions so that all potentially relevant data is available in subsequent steps. OnObjectKeystroke is a “Pre Event” trigger, so this script identifies the character typed and determines whether the keystroke is in the set that modifies the field’s content, the set that affects cursor position and/or the ActiveSelection, or the “neither” set that is ignored. Keystrokes that change the field contents are numeric digits, backspace when the cursor is immediately preceded by a digit, and forward delete when the cursor is immediately followed by one. Arrow keys may move the cursor. When paired with a modifier key, they may change the ActiveSelectionSize, ActiveSelectionStart, or both.
A Couple Things to Keep in Mind
Individual keystrokes that would modify a field’s content do so via Set Field within the OnObjectKeystroke script and thus won’t trigger the OnObjectModify script. OS key combinations—like those that invoke Copy, Paste, or Select All—do not trigger the OnObjectKeystroke script. The same is true for FileMaker key combinations that navigate through or modify the current found set. Drag/Drop is also not a trigger event, unless the field receiving the “drop” is already in focus.
If users might populate “masked fields” via Drag/Drop, add the option to Auto-Enter a Calculated value and uncheck the “Do not replace existing value (if any) checkbox. The example file models the formula (it’s abstracted so that it can be copy/pasted to all relevant fields) that will handle the dropped string.
The OnObjectModify script is needed because Cut and Paste may be invoked by menu and the OS key combination is ignored by OnObjectKeystroke. This script also has multiple $scriptVariables declared within a Let function. If a field has focus when a text string is dropped via Drag/Drop, this script fires.
The OnObjectExit script applies some cleanup to the field’s contents. When there are no digits, it empties the field entirely. When there’s a mix of digits and placeholders, indicating an incomplete entry, it sets the field to the result of the Mask Custom Function without applying substitution to the Bold Red placeholder characters
UI/UX and The Weight of The World
The OnObjectKeystroke script inserts a typed digit into the mask framework by replacing the next “placeholder” character as the user types it. It ignores typed alphabetic and punctuation characters. When the cursor is between digits, it inserts the typed digit at that position, moves the insertion point to the right so it precedes the next expected digit’s position, and pushes all following digits to the right into positions identified by the placeholders within the mask’s template.
The OnObjectKeystroke script trigger allows granular control over the user experience but carries significant baggage. It must account for a wide variety of keystrokes and a range of expected behaviors in response. It’s simple when the user types only digits, but what should the user see when (and where should the cursor go in response to) the RightArrow key when the character to the right of the cursor is a placeholder? Or when RightArrow key is combined with the Shift, Ctrl/Option, or other modifier key? Shift plus Home or End in Windows OS? Or an OS level key combination to Cut or Paste?
Identifying the range of key combinations relevant to text selection is a significant effort all by itself. When there’s a text selection, the selection method (double-click, Ctrl/Option-Shift-RightArrow, Ctrl/Option-Shift-LeftArrow) determines whether the Shift-RightArrow combination will extend or reduce an active selection. It’s certainly possible to setup an environment in which the text selection method is identified, but how valuable is that?
Let’s reduce that question’s context. A Backspace or Delete key normally deletes the character immediately preceding or following the cursor location. That’s great when that character is a digit, but what if that character is one of the mask’s formatting characters? Because those provide visual feedback and structure, deleting one seems wrong. The demo file uses a model that seems consistent with what a touch typist might expect: it moves the insertion point to the other side of a formatting character. If one formatting character sits between the cursor and the next digit, two Backspace (or Delete) keystrokes are needed. The first will move the cursor past the adjacent formatting character, the second will delete the digit.
A Notable Exception
When the Phone mask is set to add an extension, the cursor stays put after the tenth digit until another digit is typed. Typing the eleventh digit reveals the extension formatting characters, appends the digit, and sets the cursor to its right. At that point, a Backspace will delete that eleventh digit, remove the extension formatting characters, and put the cursor to the right of the tenth digit again. Currently (October 2020) there’s even less consistency among web sites for handling extension digits.
What behavior is ideal when an Arrow is typed? In the demo file, the LeftArrow key moves the insertion point left by one character but appears to stop responding once it reaches the left side of the first digit or, when the field contains no digits, the first placeholder. The RightArrow key moves the insertion point past each character to the right until it precedes a placeholder, and then appears to stop responding
Is That The Ideal Behavior?
One alternative is that all formatting characters are ignored and the Backspace, Delete, LeftArrow, and RightArrow keys operate as if the field contains only digits. In that scenario, typing a deletion key would delete the adjoining digit even when the cursor location is separated from it by one or more formatting characters. That’s consistent with the digit entry behavior, which moves the cursor to the point preceding the next digit’s position and skips past intervening formatting characters as each digit is typed. That consistency might be attractive but must be balanced against a user’s muscle memory and expectations based on prior experience.
And finally, there’s the question of mirroring the text selection behavior in response to key combinations. Doing so would require a lot of development time and effort tracking minutia. The method by which text was selected in the first place can impact whether a key combination will extend or reduce the size of the selection. And once that work is complete, there are still unanswered questions: Are the OS text selection rules ideal for this use case, and if not, what rules should replace them?
It is, therefore, left to the developer to determine the User Experience when implementing the demo file’s techniques, and to grapple with whether the benefit of a new user experience is worth either the loss of default behaviors or the extensive effort to retain them.
Try it out, and feel free to share your thoughts about any of the questions this article raises. Perhaps community feedback will lead to a consensus that we can incorporate in a future version of the technique.