In addition to the look and feel of a system and its architecture, nearly every FileMaker solution we've ever seen contains some degree of navigationbuttons and scripts designed to help users move from layout to layout. The considerations for navigation are fairly involved and warrant in-depth exploration. From this point forward in the chapter, we will discuss the nuts and bolts of various interface mechanics; assembling all these elements into one solution will then define your user interface.
Navigation implementations can come in many forms, and depending on how you define navigation, your navigation system can include various other functional elements as well. You might work with multiple windows, you may want to build a portal-only view into your data, you may want to separate your data files from your interface files, and so on. Internally, your system might need to perform validation checks in the course of navigation, run security routines, manage audit trails, and more. In FileMaker 8, scripts depend on layout context to establish table context. This then means that your users won't be the only ones needing to navigate around the system: Scripts will need to switch to different layouts to establish context, and return to the original to maintain the user experience. Although this issue is not strictly related to your user interface, you will likely still need to consider the needs of your internal system (routines that run without user control) when approaching navigation. Likewise, users won't want to only navigate. For example, at times they'll be interested in also manipulating found sets or perhaps seeing record selections made, automatically changing modes, or having navigation include a sort command. Before building a navigation system, consider what functional aspects it needs to include.
In addition to the wide variety of functions navigation performs, its presentation can vary as well. Tabbed interfaces are common, which boil down to simply a series of buttons that have Go To Layout scripts attached on a layout. Other options are different visually, but ultimately you'll be attaching navigation scripts (or a script) to various buttons. Navigation has two inseparable elements: its visual presentation and its functionality.
This chapter explores the behind-the-scenes techniques you'll find necessary to build navigation systems; these techniques are intended to be relatively independent from any particular graphical scheme, so you can adapt them to whatever particular visual scheme you choose for your system.
Tabbed navigation is one of the classic ways to get around a FileMaker database. Regardless of whether you make your buttons actually look like tabs, the basic idea is that you have a series of buttons on each layout, one of which is in a "current" selected state, and that users can see this omnipresent navigation element as they use the system (see Figure 13.4). Again, we're not advocating one visual approach over another; this technique can be applied to a wide range of styles.
Figure 13.4. The tab look was created by simply laying a one-pixel line along the bottom of the Products button that needed to appear as a front or current tab.
FileMaker 8 introduced the Tab Control object that allows developers to add tab panes to a layout in order to show more information on that layout. Using a Tab Control is a great way to extend screen real estate and make interface refinements, but they exist on only one layout. When we speak of navigation, we're talking about moving from one layout to another. The Tab Control is a fantastic addition to FileMaker that has dramatically reduced the number of layouts necessary in most solutions, but it isn't a navigation control.
To learn more about the Tab Control object, see "Working with the Tab Control Object," p. 117.
To create the layout shown in Figure 13.4, first place three gray curved-corner rectangles above a white square rectangle. These will become your gray "inactive" tab buttons. A fourth curved rectangle in white serves as the "currently selected" tab. Then it's a matter of overlaying objects, using the Arrange menu's Send Forward and Send Backward commands, to put the gray buttons behind the white rectangle, and the white button in front. Then finally create a white line (that isn't visible against white objects) to hide the bottom edge of the button.
After you have your layout visually set up as you'd like, simply set the tabs as buttons with the appropriate actions or scripts attached. Implementation methods vary widely. At the simplest level, you need only create a button for each layout in your system and attach a Go To Layout button behavior to it. This basic approach isn't the best way to manage buttons in a database solution, but it's a perfectly serviceable method.
You should try to abstract things a bit more. First off, never attach anything other than scripts to buttons to avoid having to rework a great many layout objects down the road in case something changes. It's better to attach a single script to what then becomes multiple buttons on various layouts within your system. Second, because navigation routines are almost always the same regardless of destination (ultimately it's only the destination layout that is different) and will very likely evolve over time with a given solution, we tend to recommend that you manage navigation in a single script and centralize your logic to a reasonable degree. If you can rely on one central place for your navigation routine, it is relatively easy to add functionality to your database or to, say, change one layout from an old version to a new updated version.
Finally, although we've presented here one approach to building a navigation scheme that looks like a series of tabs, we are presenting only one simple example. We'd hardly suggest that this is the only visual approach you can or should take.
For discussion on scripting best practices, see Chapter 9, "Getting Started with Scripting," p. 247.
Navigation Mediator and the Global Nav Script
We touched on using a single script for navigation in the preceding section. By using script parameters you can drive most, if not all, of your navigation through a single script. The layers of abstraction here could potentially become fairly complex, so we recommend keeping an eye on your goal: creating a simple, single routine for handling navigation. This routine might be called from a button on a layout or from another script. It needs to work both for situations in which a user wants to shift to another layout (and stay there) and for situations in which the system itself needs to move to a layout to establish context.
The basic technique makes use of script parameters: Attach a single Nav script to all your navigation buttons and set a parameter for each button that corresponds to its destination layout.
For more information on script parameters, see Chapter 15, "Advanced Scripting Techniques," p. 435.
You could certainly pass a layout name as a parameter and, within the Nav script called, use the Go To Layout script step combined with a Get(ScriptParameter) calculation, but that still means you have hard-coded layout names attached to buttons. We recommend against that practice because it would break if you ever had to change the name of any of your layouts.
To create a single navigation script, plan on never changing the script parameters you attach to buttons after your layouts have been created. Use a navigation code schemein which, for example, cust_detail might correspond with the Customer Detail layout. You could just as easily use nav1 as your code that corresponds with the Customer Detail layout, or even button1.
The point here is that you use codes of your own choosing to identify which button has been clicked by a user and then match each code to a destination layout. That way if you ever need to change the destination, you need only do so in one place; all navigation steps that used that code will now point to the new layout.
There are three general ways you can create this matching between navigation codes and their respective layout names. The most straightforward is to simply create a branching If/Else script. Another way is to use a Case statement to set a script variable, and then use that variable's value in a single Go to Layout step. This method is used here in an example of a central navigation system using multiple navigation codes:
Navigate ( navCode ) # purpose: Move the focus of the current window to a new layout # dependencies: requires a navCode (text script parameter) that # is recognized within this script # Set Variable [ $navCode; Get (ScriptParameter) ] Set Variable [ $layoutName; Case ( $navCode = "cust_list"; "Customer List"; $navCode = "cust_detail"; "Customer Detail"; $navCode = "prod_list"; "Product List"; $navCode = "prod_detail"; "Product Detail"; $navCode = "cont_list"; "Contract List"; $navCode = "cont_detail"; "Contract Detail"; $navCode = "ord_list"; "Order List"; $navCode = "ord_detail"; "Order Detail"; $navCode = "menu"; "Main Menu"; $navCode = "about"; "About this Database"; "unrecognized navCode parameter" ) ] Go to Layout [ $layoutName ] Exit Script [ ] #
This approach has several benefits, not the least of which is the fact that controlling your navigation occurs within this single place and doesn't require that you work with multiple fields, multiple scripts, and so on.
The goal of each of these methods is to centralize the mapping of navigation codes to layout names. In one example, this association took place within a script. Another possibility would be performing the matching within a custom function. With either choice, the mapping is stored in a single place. Yet another approach would be to move your layout and navCode information into its own data table. Each row of the table would store, at a minimum, a navigation code and the name of the associated target layout. You can add additional fields to each navigation record, of course, and this would allow you to track other attributes about your layouts and would enable further customization.
There are downsides to putting control information in a data table, however. First, if ever someone saved a clone of your database, that vital information would be missing. Second, you'd likely have to relate your navigation table occurrence (or occurrences) to a number of other table occurrences within your system, potentially cluttering your Relationships Graph significantly. The goal of code abstraction is to compartmentalize the logic of your system; if by building an abstracted navigation routine you burden your work with a good deal of overhead, it may defeat the purpose.
Branched Layout Navigation
One interesting possibility this approach also offers is the potential to branch your navigation routines based on differing states within your system. Imagine building two layouts and driving users to them by either preference or privilege: You could offer two different List views, for example, and your navigation system could direct users as appropriate. Here's an example of what the Case statement from the preceding Navigate script could become:
Case ( $navCode = "prod_list"; "Product List"; $navCode = "prod_detail"; "Product Detail"; $navCode = "cont_list"; "Contract List"; $navCode = "cont_detail" and $$contractPref = "Full"; "Contract Full Detail"; $navCode = "cont_detail" and $$contractPref = "Lite"; "Contract Lite Detail"; "unrecognized navCode parameter" ) ]
This example drives home the point of the exercise. Imagine building this navigation scheme and having your users request exactly that which we've described: Some users want to see the full version of a contract, and other users need to access a "lite" version. If you had built your navigation with separate, multiple scripts or attached Go to Layout script steps to buttons without the benefit of scripts, the alterations your database would require would undoubtedly be fairly numerous. In this abstracted navigation approach, you need only modify and test this one Navigate script, and need not modify your layouts or buttons at all.
Note that you could also add security privilege checks to your navigation system and gracefully let your users know when they cannot access a given area of your system, rather than having FileMaker dutifully take them to a screen with <No Access> showing and no means of getting back to the layout from which they came.
A script that more gracefully manages cases in which a user tries to navigate to a restricted layout might look like this:
Navigate ( navCode ) # purpose: Move the focus of the current window to a new layout # dependencies: requires a navCode (text script parameter) that # is recognized within this script # Set Variable [ $navCode; Get (ScriptParameter) ] Set Variable [ $layoutName; Case ( $navCode = "cust_list"; "Customer List"; $navCode = "cust_detail"; "Customer Detail"; $navCode = "prod_list"; "Product List"; $navCode = "prod_detail"; "Product Detail"; $navCode = "menu"; "Main Menu"; $navCode = "about"; "About this Database"; "unrecognized navCode parameter" ) ] # # Check for access If [ Let ([ layoutValues = LayoutNames ( Get(FileName)); filteredLayout = FilterValues ( layoutValues; $layoutName ) ]; Case ( IsEmpty (filteredLayout); 1; 0 ) ) ] Show Custom Dialog ["No Access"; "Sorry, you do not have access to that area."; Button: "OK" ] Exit Script [ ] End If # Go to Layout [ $layoutName ] Exit Script [ ]
The test for access depends on the fact that the LayoutNames function will return a list of only those layouts to which a person has access. By filtering the list and testing to see whether the result is empty, your script can check for access and, if the user does not have access, exit the script before getting to the Go to Layout step.
One of the most dreaded questions we used to hear while working with FileMaker Pro 6 and earlier was "Where's the Back button?" It's not that Back buttons were technologically that big a deal...it's just that they used to be a lot of work, and they added unwelcome overhead to database solutions. In FileMaker 8 they're a snap, and the ceiling on storage limits introduced in FileMaker Pro 7 makes overhead a complete nonissue. This is a great example of how FileMaker Pro 7 dramatically extended the horizon forward for all of us.
FileMaker has two layers of navigation (a subtlety many novice users don't immediately grasp): navigation from record to record and navigation from one layout to another. Web browsers aren't troubled by such things, nor in their stateless world is there a distinction between data and page. The metaphor of a Back button isn't a perfect fit in FileMaker, but users will immediately understand what it does. A Back button can help users distinguish more easily between navigating from record to record using the book icon (or your replacement for it) and navigating from layout to layout.
The approach given here leverages the abstracted navigation routine described in the preceding section. Our goal is to track each instance of navigation that a user makesby saving them to a navigation history variableand then be able to trace backward along those historical entries. The navigation script with a Back button capability looks like this:
Navigate ( navCode ) # purpose: Move the focus of the current window to a new layout # dependencies: requires a navCode (text script parameter) that # is recognized within this script # Set Variable [ $navCode; Get (ScriptParameter) ] Set Variable [ $layoutName; Case ( $navCode = "back"; GetValue( $$navHistory; 2); $navCode = "cust_list"; "Customer List"; $navCode = "cust_detail"; "Customer Detail"; $navCode = "prod_list"; "Product List"; $navCode = "prod_detail"; "Product Detail"; $navCode = "menu"; "Main Menu"; $navCode = "about"; "About this Database"; "unrecognized navCode parameter" ) ] # # Manage history variable If [ $navCode = "back" ] # If navCode is "back", remove first value in stack Set Variable [ $$navHistory; RightValues ( $$navHistory; ValueCount ( $$navHistory ) - 1 ) ] Else # If navCode is not "back", add the layout name to the history stack Set Variable [ $$navHistory; $layoutName & "¶" & $$navHistory ] End If # Go to Layout [ $layoutName ] Exit Script [ ]
To get the back functionality to work, you'd need to create a "back" button on all the applicable layouts and pass the navCode "back" into the Navigate script.
The script assumes that the $$navHistory variable has a list of layout names in it, with the most recently visited in the first position. As you navigate to different layouts, you build a stack of layout names in the $$navHistory value list. When the Back button is used, the $layoutName variable is set to the second value in the $$navHistory list (the one just before the current layout), and then later in the script that value is removed so that the second value becomes the first.
Note that the script throws away old layout names as the user moves backward down the value stack. You could modify the script further still to offer a "forward" button. Instead of removing layout names from the history values, create an additional variable, an integer, to keep track of the position within the stack on which the user is currently.
Last, we'd recommend building some more error-checking into your script. The example given here doesn't check whether the $$navHistory variable is empty.
Remember the distinction between navigating through layouts and navigating through records. The example we've provided here does not preserve and restore found sets. Although users will be familiar with how Back buttons work on the Web, you're not actually reloading previously viewed pages; the found set a user is working with will not change to match any prior state without significant scripting on the part of the developer.
Controlling the Tab Control Object Using a Script
In addition to controlling how users navigate from layout to layout, there are some cases in which you may want FileMaker to take a user automatically to one of the specific panes on a Tab Control object (which may be different from its default pane). For example, if you had a Tab Control in a contacts database that included, say, people's phone number information on its second tab, you might want to link directly to that pane from elsewhere in your system. Imagine elsewhere a portal of company employees on a company layout that perhaps shows a column of their office extensions and a button next to it used as a hyperlink. Your users could click the hyperlink and arrive at that person's contact record and, as an added nicety, have the second pane of the Tab Control showing that person's phone information as well.
FileMaker 8 doesn't include any programmatic access to a Tab Control, but there's a simple technique that works perfectly well.
To review the Tab Control, see "Working with the Tab Control Object," p. 117.
First, create a Tab Control object with as many panes as are necessary for your layout. Second, define a global repeating field in your database and name it something like "tab controller." Place this field on each pane of the Tab Control, but in doing so, select repetition 1 through 1, 2 through 2, and so on such that each pane shows only one unique repetition.
You can now write a script that makes use of the Go to Field script step. Add the following to the scripts that navigate to these tab panes:
Go to Field [resourcesTable::tabController]
You can change the repetition number (referenced in the brackets) to dictate to which of the tabs the user will go.
Finally, we recommend that you turn off the capability for users to click into these fields; users have no need to alter data in them. We also recommend that you format the fields so that they're not visible on the layout. They need to be there, but the users don't need to have entry access for this technique to work. (They do, however, need to have security privileges that permit them access to the field, or you can write your navigation scripts to run with full access.)
Other Navigation User Interfaces
There are many other approaches you could use for navigation, but in general they are variations on the themes we've mapped out so far in this chapter. Scriptshowever they're controlledcall Go to Layout script steps and send the user to a new layout. From there it becomes a question of how to visually present navigation to the user.
There are a wide variety of user interfaces for navigation as well. The sample file we've used in this book places navigation on a Tab Control pane. In an extreme case, we once ran into a database that used the four corners and edges of the screen for navigation. A degree of luck was involved because the system lacked any labels or indication that there were any buttons at all. The person who designed it said that the people in their organization had just learned where to click. Some navigation schemes are better than others, no doubt. The following are a few approaches other than the one described here.
One approach FileMaker Pro 8 Advanced makes possible is to offer an entirely menu-driven user interface. You could create a menu of layouts, or perhaps subdivide your solution into areas using submenus.
Another approach some developers choose is to create navigation portals. Each portal row contains a button and some attribute (the equivalent of the navCodes from the previous example) that then determines what script or layout should be the result of a button click. This could work with script parameters passing a layout name, or it would fold in nicely with the navCode approach as well. The advantage to this approach is that you can control your navigation interface through a relationship, dynamically showing various related records as you'd like.
In other cases, perhaps a simple pop-up menu and Go button can be used. In this instance, a user would make a navigation choice from a menu directly on a layout, and then click a Go button, initiating a script that would use the navigation choice to determine which script to perform or destination layout to target. It could make this determination using a series of If/Else statements, or the system could be set up for some kind of data-driven navigation, as in the navigation example earlier in the chapter. In this case the user's choice would be a key that would point to a data record fully describing the chosen navigation option and its logic.
Yet another idea is to establish a separate layout as a kind of navigation palette, and populate that layout with buttons that give access to system features and areas; scripts could handle the opening and maintaining of that window. The downside of this approach is that your users need to click twice to use it: once for the window to come forward, and once again to activate a navigation button/script; however, this is a great approach for people with different monitor resolutions (allowing those users with large monitors to place their navigation window wherever they choose), and it makes navigation a distinct process, separate from other functions.
Regardless of which visual and presentation approach you prefer, we encourage you to think ahead and try to bake in some flexibility and room to grow when dealing with navigation scripts. They are more often than not some of the most interwoven and omnipresent script routines in your solutions.
Part I: Getting Started with FileMaker 8
Using FileMaker Pro
Defining and Working with Fields
Working with Layouts
Part II: Developing Solutions with FileMaker
Relational Database Design
Working with Multiple Tables
Working with Relationships
Getting Started with Calculations
Getting Started with Scripting
Getting Started with Reporting
Part III: Developer Techniques
Developing for Multiuser Deployment
Advanced Interface Techniques
Advanced Calculation Techniques
Advanced Scripting Techniques
Advanced Portal Techniques
Debugging and Troubleshooting
Converting Systems from Previous Versions of FileMaker Pro
Part IV: Data Integration and Publishing
Importing Data into FileMaker Pro
Exporting Data from FileMaker
Instant Web Publishing
FileMaker and Web Services
Custom Web Publishing
Part V: Deploying a FileMaker Solution
Deploying and Extending FileMaker
FileMaker Server and Server Advanced
Documenting Your FileMaker Solutions