By Daniel Wood, 3 May 2022
At Digital Fusion we have spent years implementing solutions using all manner of navigational techniques.
Over that time we have refined and settled on our own methods for managing navigation within a solution - be it solution-wide, within a layout, or within within parts of a layout.
In this and upcoming articles we will share some of these techniques and frameworks, beginning with a simple framework used for layout section navigation.
This portal-based approach allows us to produce a vertical navigation control that lets the user navigate to a nominated tab control, or panel control.
We use it in situations where a tab control would simply have too many tabs to be a usable means of navigation, or where other controls such as button bars would just end up with far too many segments.
Simply put, a section is a specific tab control section or panel section within their respective objects. A common layout structure we have is to create a hidden sliding panel object and make it the size of the content area of a layout. Each panel will contain a different set of fields or functionality for that layout.
In fact this is no different than simply putting a tab control/panel on a layout and using their built in navigational controls. What we are doing instead is hiding those controls and building our own portal based control to navigate them.
You may have come across other approaches such as using a button bar control to handle tab/panel navigation. While being a perfectly reasonable approach, it does suffer a few setbacks:
Based on these factors and our own desire to make a far more dynamic and reusable control that can cater to larger navigational structures we opted instead for a portal approach.
Note however that this does not mean other approaches are invalid - it all depends on your scenario. We still use tab controls all the time because they are perfect for smaller layout section navigation. Our portal approach is for times when a tab is not the best suited control for the job.
By building a navigation control using a portal, each item in our navigation control is a record in a table. We call this the NavigationItems table (more on this later).
With this approach our records can be setup to contain any number of different properties to aid us in navigation and display such as:
The portal can then have a script run from within such that depending on the row you select the script can obtain the relevant information needed to navigate, and do so.
The underlying table that our Navigation control is based on is the NavigationItems table. As mentioned above it contains a number of useful bits of information such as display name, target object, a script to run, an icon and so forth. Two key pieces of information in this table are the type and item_type fields.
Think of the type field as a way to identify similar records belonging to a single navigation control. For example if you have two controls in your solution - one for the setup screen and another for contacts screen - then your records will have values of "setup" and "contacts" for this type field.
The other important field is item_type. This helps us categorise records into one of three sub-types.
The three subtypes of records defined by the item_types field are:
Header records act as titles for other items and are a way to help group similar items in the navigation. They can also be styled to appear different to navigation items.
Separators are usually blank records that act as separators between groupings of items. You don't necessarily have to have them as blank either - through putting objects in the portal and hiding them on rows that aren't separators, you can define any style of separator you want.
The last type is "item" and these are the actual clickable navigation items which will have a name, target object, and potentially a script to run.
Next it's time to create and configure your navigationItem records. This part is simple, just assign them all the same type, categorise them based on item_type, and then fill in all the other required fields.
Headers will at the very least require a display name, while separators require no additional information.
Items will require a name, target object, and possible post-script to run. In addition to this you can specify an icon, a prefix text, and some indentation if required.
One other important field in the setup is the order field. For simplicity sake all our records are sorted based on this numerical order. Make sure you set the correct order for which you want all your navigationItem records to appear in the control.
Right, now it's time to actually add a portal to the layout for this control.
First you'll need a relationship to the NavigationItems table from your layout context. This could be a relationship to all records where you will make use of Portal-Level filtering to restrict records to just those of a particular type. Alternatively you could program the type predicate into the relationship so you only find those specific records via your relationship. For simplicity sake we use portal filtering as our NavigationItems table often doesn't have many records,and so portal filtering performance issues are negligible.
With the relationship established, add the portal to the layout and add into it your display name field.
Make sure to sort the portal by the order field.
In addition to any display fields added, there are two additional objects that are important to add to the portal:
For the button we want to only show this on the rows where item_type = "item", which we achieve through a hide conditions.
The highlight should only be shown on the row which is the selected row which is also done using a hide condition - more on this later.
There are many ways to display your navigation control. You have control over how you format the items, the headers, separators etc.
Usually we would make use of layout styles and apply these to objects in the portal. We do this for the hover and navigation button.
For the display name we would usually also use styles but in the interests of simplicity for the example file conditional formatting has been utilised to format headers differently. To do this with styles we would have the display name field added twice into the portal - one styled for items and one for headers - and hide the relevant objects based on the item_type of that record.
Utilising styles allows you to achieve a system-wide consistency of design with your navigation controls.
We have a single script that carries out navigation to a desired layout section. This script accepts a JSON block as a parameter.
This JSON block contains information about the selected record, such as its id, name, type, item_type, target, and script name.
The information within the JSON is parsed out and used to navigate and run any required script.
In addition to carrying out the navigation, the JSON is then stored in a global variable so that we track what the currently selected item is. This selected item is how we determine back in the portal which row should be highlighted.
Storing of the selected item is done using a global variable. This global variable is dynamically generated in the script and it's name is constructed using the type of the navigation control being interacted with.
For example if you are selecting an item for the "contacts" navigation control, then the global variable name will be $$NAV.CONTACTS.
However it's not quite that simple! FileMaker is great at allowing users to create multiple windows - each with their own contexts, found sets, and states (such as selected tab or panel). For this reason, we need to actually track the selected item at the window level, meaning if a user had 5 windows open then we need to know what the selected section is for each of those five windows!
Turns out there's a rather nice and simple way to address this issue. We take the window name (which we assume to be unique) and turn it into an MD5 hash number using the following formula:
GetAsNumber ( GetContainerAttribute ( Get ( WindowName ) ; "MD5" ) )
Think of this as a way to map a text string - the window name - into a numerical equivalent. We then use this number as the repetition number of the global variable to store that windows state in - easy!
Standard layout section objects such as tab controls and sliding panels all have a default section. For tabs you can nominate it, and for panels it is the first panel. When the user first enters the layout, the default tab/panel is chosen.
Because we are overriding this behaviour with our own navigation control, we need a way to define what our controls default section should be. There are three potential candidates:
All three are viable options and which option you choose depends upon your use case. In our example file and in our own solutions we have opted for the first option of just selecting the top-most item from the control, regardless of whether one was previously selected.
You can find this achieved in our example files navigation script. In a situation where the user is not actively determining the selected item - such as entering a layout - then we pass through just the type to the navigation script as JSON:
JSONSetElement ( "" ; "type" ; "contacts" ; JSONString )
The script detects that no item was selected when we only have a type passed, and will instead run an executeSQL query to retrieve the first record whose item_type is "item'. It will then select it and navigate to it in the usual manner.
This is just scratching the surface of what you could achieve with this simple framework. At the end of the day all we are doing here is showing records in a portal - something any developer can understand - and with a few extra bits of information to help navigate, we are using these as a means to navigate to a panel control - nothing complicated here at all.
Which is why there are so many ways you could build upon this to do more, such as:
In fact we're going to talk about taking this further and utilising NavigationItems for other purposes in future articles...
In this article we've covered a way in which we achieve navigation within a layout using a portal based navigation control. We do this because often layouts (such as a preferences screen) may have a large number of sections where a standard tab control may not work. However there are other forms of navigation that a solution needs.
One such control is a navigation bar that helps the user navigate to all the main screens in a solution. At Digital Fusion we call this the navigation bar and we favour a horizontal bar across the top of layouts.
In the next article in this series we talk about how we build this navigation bar to be completely contextless and able to be pasted and work on any layout. Not surprisingly we are utilising the NavigationItems table for this too - albeit with a couple more fields. We'll cover howe we handle situations where navigation bars might require a large number of screens, how we customise the look and feel, and how we manage dashboards - a situation where multiple users may actually require different dashboard screens. Finally we'll show how we can build multiple sets of navigation bars, and assign these to users based on their role or any other factor. It's a powerful control that gives a simple means to implement a custom navigation bar for your users.
All my articles come with an example file (which is where about 90% of my time goes when preparing articles these days!) I highly encourage you to check it out as you'll find everything in there to help you get started building your own navigation controls.