Script Triggers: Monitoring and Restoring Tabs

Leave a comment

1-19-2009 by Geoff Coffey

FileMaker 10′s new Script Triggers feature just keeps delivering. You might, at first, think triggers are limited to layouts and fields, but it turns out your triggers can fire with all kinds of objects. In this article we’ll show you how to run scripts when users switch tabs on your tab controls. In the end, we’ll also revisit a common FileMaker problem: tabs switch when you switch layouts. We’ll show you how to solve this problem quickly and easily using script triggers.

Triggers with Tabs

It is unfortunately not entirely obvious, but Script Triggers work with tab controls. Specifically:

  • The OnObjectModify trigger fires whenever the user switches to a different tab. This is probably the most useful trigger for tab controls.

  • The OnObjectEnter trigger fires when the tab control gets keyboard focus. This happens when it is in the tab order and you tab to it (so a tab gets the thick black border). You can see a tab control with keyboard focus here:

A tab control with keyboard focus, showing the thick black border around the active tab.

  • The OnObjectExit trigger fires when the tab control loses focus. In other words, when you tab away from it.

  • The OnObjectKeystroke trigger fires when the tab control has focus and you press any key on the keyboard.

  • The OnObjectSave trigger never fires for a tab control.

Watching the Tab Change

Once you know all that, keeping tabs on your tabs (sorry) is easy. For example, suppose you want to show a special message under some circumstances when someone switches to a particular tab. Setting this up couldn’t be easier:

  1. Write a script that does what you want.
    For example, it might show a dialog box, calculate a total, set a global, or clear a selection.

  2. In Layout mode, select your tab control, then choose Format -> Set Script Triggers…
    The Set Script Triggers dialog box appears.

  3. Turn on the OnObjectModify checkbox.
    You’ve just told FileMaker you want to run a script, so it asks you which script to run.

  4. Select the script you created in step 1.
    When you’re done here, click OK, then switch to Browse mode. You’re ready to test.

As this video attests, your script now runs as soon as the user switches to any tab:

Which Tab

If you watched the video closely, you may have noticed that the scripted dialog box showed which tab was selected. With only one trigger script, how do you manage this? You just have to ask.

FileMaker’s esoteric GetLayoutObjectAttribute function can tell you all kinds of information about objects on your layout. Of particular interest, it can tell you if a tab panel is front most. To use it, though, you first need to give your tabs object names. We’ve showed you how to do this before but here’s a quick refresher. If you’re already an object names expert, you can skip ahead.

  1. In Layout mode, click on your tab control to select it.
    When you click, the first tab gets a thick black border. This tells you that specific tab panel is selected. (You can have an entire tab control selected, without selecting any of its individual panels. You’ll see this in a moment.)

  2. Choose View -> Object Info to show the Object Info palette.
    This is where you go to set object names (among other things). Since its a palette, you can simply leave it open while you work through all your tabs.

  3. In the Object Name box in the Object Info palette, type a unique and identifying name for your tab.
    You might call it “tab1″ or “customer tab” or whatever you feel is appropriate.

  4. Click the second tab in your tab control.
    The second tab comes to the front but it is not selected. Notice it doesn’t have a thick border.

  5. Click once again on the second tab.
    This time it gets its thick border. This is important. You can only name a tab panel once you have it properly selected.

  6. Give this tab a unique name, then repeat these steps to name every tab in your tab control.
    If you want to find out if a particular tab panel is in the front, it must have a name, so you’re best of naming them all.

Once you’ve given every tab panel a name, you’re ready to start asking FileMaker who’s in front. This part is easy. Here’s the long-but-simple formula to see if a tab is in front:

GetLayoutObjectAttribute ( "myTab", "isFrontTabPanel" )

Notice that this formula only looks at one particular tab panel (in this case it is looking at the panel called myTab so be sure to put one of your tab panel object names there instead). It will return a True result if the tab panel is in front, and a False result otherwise. To determine which of all the tabs is in front, you can check them one by one:

Case(
   GetLayoutObjectAttribute ( "myFirstTab", "isFrontTabPanel" ); "First tab is in front";
   GetLayoutObjectAttribute ( "mySecondTab", "isFrontTabPanel" ); "Second tab is in front";
   GetLayoutObjectAttribute ( "myThirdtTab", "isFrontTabPanel" ); "Third tab is in front"
)

Or, in a script:

If [ GetLayoutObjectAttribute ( "myFirstTab", "isFrontTabPanel" ) ]
   # do stuff for the first tab
Else If [ GetLayoutObjectAttribute ( "mySecondTab", "isFrontTabPanel" ) ]
   # do stuff for the second tab
Else If [ GetLayoutObjectAttribute ( "myThirdtTab", "isFrontTabPanel" ) ]
   # do stuff for the third tab
End If

Restoring Tab State

Now that you know how to watch for tab changes, and figure out which tab is in front, you can take it one step further, and solve an age-old FileMaker problem in the process. Last year, we wrote up a laborious technique to remember which tab is in front before you leave a layout, then bring it back to the front when you come back. This way, if your scripts have to quickly zip to another layout to get some work done, your users don’t get distracted by tab controls that keep switching back to the first tab.

That technique worked, but it was tedious. You had to write two long and complicated scripts. Then you had to call one every time you leave the layout, and the other every time you come back. What a nuisance.

Using Script Triggers, you can save all the hassle. Now two very simple scripts can get the job done, and they can do it automatically every time you return to the layout.

Note: In fact, this new, simpler version is even better than the old way. The tab will restore itself even if you manually leave the layout and come back later, making things a touch more stable for your users.

First, you need a simple script to remember the front most tab:

Set Variable [ $$FRONT_TAB;
   Case(
      GetLayoutObjectAttribute ( "myFirstTab", "isFrontTabPanel" ); "First tab is in front";
      GetLayoutObjectAttribute ( "mySecondTab", "isFrontTabPanel" ); "Second tab is in front";
      GetLayoutObjectAttribute ( "myThirdtTab", "isFrontTabPanel" ); "Third tab is in front"
   )
]

That’s it (and its really a one line script, although we put it on a few lines here so it would be easier to read. Just set a global variable to the name of the font most tab.

Note: If you have multiple tab controls to keep track of, make sure you use a unique variable name (or repetition) for each one. Otherwise, changes to one set of tabs will impact the others.

Now attach an OnObjectModify trigger to your tab control and set it to run the script. With these two steps complete, a new global variable will always remember which tab you last visited.

Next, you need to make FileMaker restore the front most tab whenever you come to this layout. This part is equally easy. Start with a simple script:

If [ Not IsEmpty($$FRONT_TAB) ]
   Go to Object [ $$FRONT_TAB ]
End If

This script first checks to see if the $$FRONT_TAB variable has a value. If it does, it uses the Go to Object step to bring that tab to the front.

Finally, tell FileMaker to run this script whenever the layout loads. You do this with (shocker) a script trigger. Choose Layout -> Layout Setup, then switch to the Script Triggers tab. Turn on the OnLayoutLoad trigger and point it to your script.

Here’s a video showing the tab behavior with and without the triggers:

In the first version (before the fade-to-black) FileMaker switches back to the firs tab every time you leave the layout and come back. In the second, it seamlessly keeps things in order. This magic works whether your user leaves the layout manually, or you do it with script. Easy as pie.

29 Comments

  1. Bart

    That works , but the next step will make it complete. We have to take into account that $$FRONT_TAB is a global variable and thus available to the entire database file and all of the layouts. Multiple layouts with their own tab controls will need to be singled out by layout and object name.

    The way it stands now, only one object name (tab) will ever be the result of the navigation sequence.

  2. Geoff Coffey

    Bart:

    I tried to address that concern in the note above. There are a lot of ways to deal with it. In the simplest case you can just make up a new variable name for each case (maybe using a naming convention).

    If you have more than one tab control on the layout, you can repeat the above steps with a different variable name for each, and it will work perfectly.

    Of course that could be hard to keep track of. In our own work, I suspect we’ll use our dictionary functions to store all tab states in one global variable. I haven’t worked out the best structure , but probably each trigger will pass in a unique “name” for the tab control itself, and we’ll store it like this:

    
    #(Get(LaoutName), #([tab control name], [tab panel name]))
    

    Something like that, anyway. We haven’t put 10 in production anywhere yet, so I haven’t hashed this out entirely.

    Geoff

  3. Ralph Salomons

    I have a slightly different problem. I have a file that has two types of records – contacts and companies. Each record type has different portals. I have placed these on individual tab control and nested them in a master tab control with 2 hidden tabs. I would like the master tab control to have each tab come forward upon the record type field changing. This way the company record reveals one tab control and the contact record reveals the second tab control (each with their own nested tab control with portals. Any advice.

  4. TheLifeofBri

    Great tip – exactly what I was looking for!

  5. John Maloney

    Thank you. This is most helpful.

  6. Pavle Ancevski

    Great article!
    Still I can’t make it to work with tab inside the tab. Is there a limitation on function GetLayoutObjectAttribute, since if there is a tab inside the tab and you select that one (named as object and part of the case calc for the variable), the global variable doesn’t get the name of the inner tab but stays with the first tab value. So in my case when I have a 3 level tabs, getting back to the layout brings me to the last level 1 tab, but than uses the defaults for the level 2 or 3, which is partial solution for me. Is there a way to fix it that I am not aware of?

  7. Jonathan Stark

    Hi all!

    If anyone is interested, I put together a proof of concept file that handles multiple layouts and nested tab objects with a fairly short and portable script:
    http://tinyurl.com/dfmczh

    Cheers!
    j

  8. Geoff Coffey

    Jonathan:

    Looks great. This is something I’ve never figured out because I don’t think I’ve ever nested tab controls. But many many people have asked about this in response to a couple of our articles.

    Thanks for sharing!

    Geoff

  9. Michael McClure

    Geoff — Had an FTS class with Cris Ippolite last week (June ’09) in West LA, and we wrestled with this concept a bit. Alexei from FMP came in on Thursday and said layout and tab ‘memory’ was certainly doable, and she began to tell me how to do it, but it became very dense very quickly (and she talks real fast…), and in any case the FTS setting was not an appropriate time for us to go over it all. I just downloaded Jonathan’s file (thanks very much for that, Jonathan!!) and it works perfectly in virtually no time at all, and my tabs aren’t even nested, just one layer deep. Thanks, guys.

  10. Bob Stuart

    Jonathon, I couldn’t find a feedback section on your blog where I could say ‘thank you’ for an excellent technique. Implementing it in my solutions has been a huge timesaver because the management of tabs is handled in script triggers that fire when a tab control is modified or a layout is loaded. Therefore, you don’t need to memorize tabs in every navigation script. Great stuff!

  11. Loretta

    Jonathon: Thanks … this solution rocks!

  12. Ward Clark

    I’m a late-comer to this discussion because my “restore tab” logic from FileMaker 9 just stopped working in one of my databases. Rather than debug it, I took this as a sign it’s time to take advantage of a Layout Script Triggers.

    Although the tab-name technique Geoff described makes sense, I would foresee a script maintenance headache because my tabs evolve. I chose instead to build on the tab numbering technique I was using in FileMaker 9.

    1. Define a non-stored Calculation field named “tabNumber” with enough repetitions to handle a maximum number of tabs (15 as been more than enough so far):

    Let ( $$tabNumber = Get( CalculationRepetitionNumber ) ;
    $$tabNumber
    )

    2. Put an instance of the “tabNumber” field in each tab panel, choosing a single, unique repetition for each panel. For example, the second tab uses “Show repetitions: 2 through 2″. Make the field invisible.

    I place the field at the left margin of the tab panel, in the blank space to the left of the centered tabs, which keeps it in the panel but outside of the “business” part of the panel.

    3. Add a Script Trigger to the layout which performs a script that doesn’t care about tab names:

    If [ not IsEmpty ( $$tabNumber ) ]
    Go to Field [ tablename: tabNumber[$$tabNumber] ]
    Go to Field []
    End If

  13. David Crowe

    Awesome. I store the current tab in a record so that every record can have its own tab memory. This creates a new problem because “Go To Layout” is not always used to navigate between records. So I need to add the tab restore script to other scripts as well. I have tabs that show a portal of between 1 and 4 sub-records. Some records have only one sub-record with lots of details, others have more records with less detail. So it’s nice that the record “remembers” it’s own layout.

  14. Geoff Coffey

    David:

    That sounds like a nice enhancement. Have you tried an OnRecordLoad trigger? It fires both when you switch to a layout for the first time and when you switch records on the same layout. I’m not 100% sure if it fires when changing among layouts with the same base table occurrence, so you may need an OnLayoutLoad as well. But it should allow you to put your script in just one or two places and get the effect you’re after.

    Geoff

  15. Gordon K

    Thanks for the great technique Geoff, very useful.
    As regards this working with nested tabs, I think I have come up with another solution:-

    Case(
    GetLayoutObjectAttribute ( “MainTab1″, “isFrontTabPanel” ) and GetLayoutObjectAttribute ( “SubTab1-1″, “isFrontTabPanel” ); “SubTab1-1″;
    GetLayoutObjectAttribute ( “MainTab1″, “isFrontTabPanel” ) and GetLayoutObjectAttribute ( “SubTab1-2″, “isFrontTabPanel” ); “SubTab1-2″;
    GetLayoutObjectAttribute ( “MainTab2″, “isFrontTabPanel” ) and GetLayoutObjectAttribute ( “SubTab2-1″, “isFrontTabPanel” ); “SubTab2-1″;
    GetLayoutObjectAttribute ( “MainTab2″, “isFrontTabPanel” ) and GetLayoutObjectAttribute ( “SubTab2-2″, “isFrontTabPanel” ); “SubTab2-2″;
    )

    For example, if the active Tab is the second sub-tab in the second main-tab, FileMaker will return true for

    GetLayoutObjectAttribute ( “MainTab2″, “isFrontTabPanel” )

    and for

    GetLayoutObjectAttribute ( “SubTab2-2″, “isFrontTabPanel” )

    and so the calc above would return “SubTab2-2″

    Hope this is useful. btw your link to ‘http://tinyurl.com/dfmczh’ appears to be broken

    Gordon

  16. Ron Gafron

    Hi,

    I’m fairly new to Filemaker, and am currently using it to port over an old Microsoft Access runtime application. Now, even back in the days of Access97, you could create multiple-row tabs, something that as as far as I can tell you can’t do with the regular tab control in FM. From reading a couple of the blog postings here, am I right that the way to do this, ie, multiple row tabs, is to create the number of tabs you want but set their size to zero (got this from another blog posting), and then create something that looks like a set of multi-row tabs (whatever that something might be), let’s say a set of buttons set up in rows, and use script triggers to get to the correct tab page by making use of object naming in some way?

    Or am I just delusional? (well, that’s probably true most days, but I mean here and now :-)).

    Thanks.

    Ron

  17. Geoff Coffey

    Ron:

    You are correct that FileMaker does not support multi-row tabs in one tab control.

    If you want this sort of look, one way is to do what you describe. Essentially you hide the FileMaker tabs entirely, then add your own with any look, position, and orientation you want. Note that you must set the line width of the tab control to 0px for this to work properly, otherwise a zero-width tab will have to tiny borders that are still clickable.

    Also note that you can *nest* tabs, which is sometimes useful (although in general I avoid both multi-line tabs and nested tabs because I think they can be confusing for the user).

    Finally, depending on what these tabs do, sometimes a nicer interface is to have one portal, and a pop-up menu or series of buttons that twiddle the records that are shown by setting a global field. For instance, if you have a list of associated Sprockets, and you want to be able to flip between Tiny, Small, Medium, Large, Huge, and All sprockets, you can get by with one portal, a pop-up menu, and some clever relationship work.

    Hope this helps,

    Geoff

  18. Jerry Beck

    I wish I could tell you the number of hours I have tried to get this to work, it’s beyond beyond.

    Set Variable [ $$frnt_tab; Value:Case(
    GetLayoutObjectAttribute ( "insp.prgm" ;"isFrontTabPanel");
    GetLayoutObjectAttribute ( "insp.insp" ;"isFrontTabPanel");
    GetLayoutObjectAttribute ( "insp.task" ;"isFrontTabPanel");
    GetLayoutObjectAttribute ( "insp.task.mod" ;"isFrontTabPanel");
    GetLayoutObjectAttribute ( "insp.scrtch" ;"isFrontTabPanel")
    ) ]

    data viewer keeps showing me; 0 or 1, not the text value of the object named tabs, “insp.prgm”, “insp.insp” etc.

    The Attribute “isFrontTabPanel” under GetLayoutObjectAttribute supports this. “isFrontTabPanel – returns 1 (True) if the target object is the tab panel that is in front”

    What am I not seeing?

    Jerry

  19. Jerry Beck

    got it.

    Case(
    GetLayoutObjectAttribute ( “tabone”; “isFrontTabPanel” ); “tabone”;
    GetLayoutObjectAttribute ( “tabtwo”; “isFrontTabPanel” ); “tabtwo”;
    GetLayoutObjectAttribute ( “tabthree”; “isFrontTabPanel” ); “tabthree”
    )

    I need this to return to the “same tab” location from many tables and did not want to hard code each return.

    Jerry

  20. RichT

    seems like a simple way to make it work for muti layout would be with a repeating varable set using Get(LayoutNumber) Like this

    Set Variable [ $$FRONT_TAB[Get(LayoutNumber)];
    Case(
    GetLayoutObjectAttribute ( “myFirstTab”, “isFrontTabPanel” ); “First tab is in front”;
    GetLayoutObjectAttribute ( “mySecondTab”, “isFrontTabPanel” ); “Second tab is in front”;
    GetLayoutObjectAttribute ( “myThirdtTab”, “isFrontTabPanel” ); “Third tab is in front”
    )
    ]

    Then Call it back the same way ….

    If [ Not IsEmpty($$FRONT_TAB[Get(LayoutNumber)]) ]
    Go to Object [ $$FRONT_TAB[Get(LayoutNumber)] ]
    End If

  21. Robin Boast

    Sorry to bother you with this, but I have a script identical to the script in your “Which Tab” section, but for tab names, and I can’t get it to do anything in FMP 11 Advanced. I have set the trigger on my tab bar, and named my tab objects, but nothing happens. I also can’t get a field to return anything when asking for Get ( ActiveLayoutObjectName ). Please help.
    Thanks.

  22. kevin

    great technique. however, i am finding that there is a visible delay in switching to the non-default tab. I have 2 tabs with portals. the go to object script step doesn’t seem to fire until the portal is visible. any suggestions?

  23. CarlD

    Help, this trick is working kinda crappy in FM11 (Windows XP). If I include a “New Window” command in my triggered script, the layout in the new window displays the default tab. Can anyone think of a possible workaround?

  24. Scott Karlins

    Great blog post! Was wracking my brain trying to figure out how to have Form View on one tab, and List view on another tab.

    Worked a treat!
    Thank you, Scott

  25. Jeff Miller

    Using the above techniques, is there a way of going to Layout mode and returning to the SAME tab you left in Browse rather than the “default” tab?

  26. Oolfanska

    To anyone looking for the blog & file mentioned by Jonathan Stark, 4-1-2009, I tracked it down at http://jonathanstark.com/blog/2009/04/01/using-script-triggers-to-restore-active-tabs-in-filemaker/ .

  27. Armin Stross-Radschinski

    The script from Jonathan Stark is awesome simple to implement. But there is still something to Do if you have the same layout open in different windows depending of your experienced user workflow.

  28. Fiona

    Ok, I’m going to sound like such a rookie when I say this but, I need a solution to the nested tabs problem and I’ve downloaded Jonathan Stark’s file but if wont let me view scripts – how am I supposed to see how it works? Is this a Filemaker Advanced thing…because I don’t have it? If so can someone tell me how he solved the problem! I’ve spent so many hours trying to get this to work.

    Thanks,

    Fiona

  29. Llorenc Sole

    If (like me) you only have one tab set per layout there is a simple way to do this with two on-line scripts (but only since FM12):

    onTabSwitch event script => Set variable[$$track_tab[Get(LayoutNumber)];Value:GetValue(GetTriggerTargetTabPanel);2]
    onLayoutEnter event script => Go to object[Name of object: $$track_tab[Get(LayoutNumber)]]

    You only have to uniquely name all the tabs and attach the events to the layouts and tab sets desired. The scripts need not be modified when adding or removing tabs or modifying their names.

    Llorenc

Tell Us What You Think

*
* (will not be published)