A scripted button bar FileMaker calendar date picker
This method for a FileMaker calendar date picker uses button bars and scripting only. No table of date records, no relationships, no portals, no custom functions and no web viewer are required. It is context agnostic, and handles leap years without additional consideration. Purely button bars, script and global variable list, and 2 global number fields. The method builds a list of day numbers via a conventional FileMaker script loop, and tests faster than using a Virtual List. It is rapid under Web Direct
This page describes the FileMaker button bar configuration and scripting required to build a single month date picker button bar matrix. A variation as a 3 month calendar date picker button bar matrix and scripting can also be built and is detailed in a separate page.
The month structure is 6 button bars, each of 7 segments, forming a conventional 6 week calendar 'month' matrix, with leading and trailing month days. Two globals are used for month number and year. On changing the global month value (+/- year), the script then builds a list of 42 date day numbers:
- calculate the day number of the first of the month date for the specified month and year, using FileMaker GetAsNumber(date) function syntax which returns the number of days since 1/1/0001; e.g. 1/1/2018 = 736695.
- calculate the day number of the first Monday to display, of the week that the first of month date falls in
- build a list of 42 day numbers, starting from the first Monday day number, using a simple standard FileMaker script loop sequence, and set the list into a global variable.
A calculation in each button bar segment parses the day number value, respective to the segment sequence, from the $$_variable list of 42 day number values, and calculates the relevant day number component of the date to display. The weeks start on Monday as per British convention. Days of the preceding month and following month that may occur in the first, fifth and sixth weeks are greyed using conditional formatting defined on each of the relevant button bar segments.
The method can be readily expanded to a 3 month matrix of button bar dates, and scripting is modified to handle the 3 months spanning consecutive years.
The method is reasonably fast. Testing on LAN executes on the order of 6 msec (laptop client) to 30-40msec (iPads of c. 2013, 2012 vintage) for a single month , and 65 msec for the 3 month matrix using the FileMaker script loop to establish the day number list . Via FileMaker WebDirect the same c. 4 msec performance is obtained for laptop and iPads tested A virtual list modification to the same script by comparison takes substantially longer to execute.
Globals for month and year
Two number fields are defined in a utility table; g_month and g_year. A dual segment button bar is added to the layout to step up or down through the months and the assigned change month script rolls the year up for December to January and down for January to December; simple and conventional.
The calendar month button bar matrix
The Calendar month matrix uses 6 horizontal button bars, each of 7 button bar segments. There are 2 components that need to be defined for each segment to display the date day number; the actual Day of the date, and the Conditional Format to grey out prior and next month days. Adding a third component; the Action to run a script or open a popover for each segment is straightforward.
Object names are defined for the six Button bars: bb_w1, bb_w2, bb_w3, bb_w4, bb_w5, bb_w6. The last steps of the script are to refresh objects using the object names. Refresh Object is quicker than a Refresh Window step, 5 vs 30msec (local or hosted to client), and provide a smoother redraw in Web Direct.
a). The calendar button bar segment - calendar day calculation
The script creates a global variable list of day numbers ($$_list). Each button bar segment from 1 to 42 parses the respective button bar sequence value (N) from the list. FileMaker Day numbers for January 2018 illustrated.
Global variable day number list scripted output
736695
736696
736697
736698
736699
736700
736701
736702
736703
736704
736705
736706
736707
736708
736709
736710
736711
736712
736713
736714
736715
736716
736717
736718
736719
736720
736721
736722
736723
736724
736725
736726
736727
736728
736729
736730
736731
736732
736733
736734
736735
736736
736737
Calendar month button bar segment date calculation example
Let([ N = 1 ;
LST = $$_list;
];
Day( GetAsDate( GetAsNumber(GetValue (LST; N) )))
)
b). The calendar button bar segment - Conditional Format
A conditional format is defined for each button bar segment in the first, fifth and sixth rows of button bars, with a paler grey text CF result. This shows days from prior and following months where applicable.
The conditional format calculation is written with the first row to include N=x, as this makes it easier to check the sequence of values for the button segments, without having to burrow down and open the actual FileMaker - Specify calculation sub-window. The calculation is the same for all segments except for the N value. The calculation could probably be rewritten to evaluate the segment object name; but less efficient than a hard coded number. Same strategy applies to the segment date calculation.
Calendar button bar segment Conditional Format calculation
Let([ N = 1 ;
LST = $$_list;
M = Cal::g_month
];
Month( GetAsDate( GetAsNumber(GetValue (LST; N) ))) ≠ M
)
c). The calendar week starts MONDAY
The real world thinks of the working week as starting Monday. That's why the weekEND is both Saturday and Sunday. So does this calendar display. The script calculates an offset from Monday to the first of the month; this can be easily modified for the week to start on Sunday. The weekend Saturday and Sunday columns are slightly differentiated by placing a grey fill rectangle object behind the button bars. The Button Bars Segments themselves are styled to have no fill colour.
The scripting - build a list of 42 day numbers for the Calendar month
GetAsNumber(date) is used as per FileMaker convention to return the date as the number of days since 1/1/0001. Date is used consistent with the sensible order convention of DD/MM/YYYY.
The month up/down buttons are attached to the edit_month script, and this in turn calls the ss_date_list script as a subscript. For the sake of completeness, the edit_month_up_down script is described.
a). Script sequence logic
- (dev) start timer
- set month +/- year; up/down
- do subscript to build list
- calculate fdom date
- calculate fdom daynum
- calculate fdom dow
- calculate daynum of first day of the first week of the matrix to display
- build daynum list x42 via script loop
- refresh object - for each button bar object
- (dev) end timer
- (dev) update stats
b). Step the calendar month up/down
The two down/up buttons have simple script parameters; month|down, month|up. There is a trap for empty month or year values added into the script.
The change month script needs to capture the global month and year values before changing them, for the subsequent evaluate year change.
A refresh object last step for each of the six button bars is mandatory for the button bars to update. While this is not strictly necessary on a local file, it is mandatory when served.
Script step | Defn. | |
---|---|---|
Set Variable $_fn | getSP_n ( 1 ) ] | month |
Set Variable $_q | getSP_n ( 2 ) ] | up |
Set Variable $_month_ref | Cal::g_month | 1 |
Set Variable $_year_ref | Cal::g_year | 2018 |
// trap for empty month or year IF IsEmpty($_month_ref) or IsEmpty($_year_ref) set field Cal::g_month = Month( Get ( CurrentDate )) set field Cal::g_year = Year( Get ( CurrentDate )) set variable $_fn = "" set variable $_q = "" ELSE |
||
IF | $_q = "down" | |
Set Field Cal::g_month | Let([ M = $_month_ref ]; Case( M ≥ 2; M-1 ; M = 1; 12 )) |
|
// conditionally reset the year - when jan to dec If [ $_month_ref = 1 ] Set Field Cal::g_year |
$_year_ref -1 | |
Else If | $_q = "up" | |
Set Field Cal::g_month | Let([ M = M = $_month_ref ]; Case( M ≤ 11; M + 1 ; M = 12; 1 )) |
2 |
// conditionally reset the year - when dec to jan If [ $_month_ref = 12 ] Set Field Cal::g_year |
$_year_ref + 1 | |
END IF END IF |
||
Perform subscript: ss_date_list | ||
Refresh button bar objects: bb_w1, bb_w2, bb_w3, bb_w4, bb_w5, bb_w6 |
c). Subscript - build list of Calendar month day numbers to display
Script step | example result |
---|---|
Set Variable [ $_month; Value:Cal::g_month ] | 2 |
Set Variable [ $_year; Value:Cal::g_year ] | 2018 |
Set Variable [ $_fom_date; Value:GetAsDate( "1/" & $_month & "/" & $_year) ] | 1/2/2018 |
Set Variable [ $_fom_daynum; Value:GetAsNumber(GetAsDate( $_fom_date )) ] | 736726 |
Set Variable [ $_fom_dow; Value:DayOfWeek( $_fom_date) ] // requires text input; so wrapping $var in GetAsDate fails |
5 |
Set Variable [ $_start_daynum; Value:Let([
DOW = $_fom_dow ]; $_fom_daynum - Case( DOW = 1; 6; DOW = 2; 0; DOW = 3; 1; DOW = 4; 2; DOW = 5; 3; DOW = 6; 4; DOW = 7; 5; ) )] |
736723 |
Script loop sequence to build list | example result |
---|---|
build day number list via loop - no date table required
Set Variable [ $_n; Value:1 ] Set Variable [ $_exit; Value:$_day_count ] Set Variable [ $_list; Value:$_start_daynum ] Loop Set Variable [ $_day_n; Value:$_start_daynum + $_n ] Set Variable [ $_list; Value:Let([ LST = $_list ;VAL = $_day_n ];LST & ¶ & VAL )] Set Variable [ $_n; Value:$_n + 1 ] Exit Loop If [ $_n > $_exit ] End Loop Set Variable [ $$_list; Value:$_list ] |
736723 736724 736725 736726 736727 736728 736729 736730 736731 736732 736733 736734 736735 736736 736737 736738 736739 736740 736741 736742 736743 736744 736745 736746 736747 736748 736749 736750 736751 736752 736753 736754 736755 736756 736757 736758 736759 736760 736761 736762 736763 736764 |
Comparison executeSQL to build list
For comparison testing only, the loop sequence is disabled and an executeSQL step enabled. This virtual list approach does require a table of dates and daynumbers, and uses two custom functions. None of which are required for the normal calendar script loop method.
Comparison testing showed eSQL/VL approach to slower than the simple loop list build, and performance progressively degraded with more date records, in the requisite Days table.
Script step to build list | example result |
---|---|
Let([ // retrieve these values RSLT = sqlFN( Days::day_num ) ; // from table TBL = sqlTN( Days::id) ; //where field: fa = sqlFN( Days::day_num) ; // where fm values for placeholders are: va = $_start_daynum ; ~query = " SELECT ~result FROM ~table WHERE ~fa >= ? ORDER BY ~sort ASC FETCH FIRST 42 ROWS ONLY " ; ~sqlQuery = Substitute( ~query; ["~result" ; RSLT ] ; ["~table" ; TBL ] ; ["~fa" ; fa ] ; ["~sort" ; fa ] ) ]; ExecuteSQL ( ~sqlQuery ; "" ; "" ; va ) ) |
736723 736724 736725 736726 736727 736728 736729 736730 736731 736732 736733 736734 736735 736736 736737 736738 736739 736740 736741 736742 736743 736744 736745 736746 736747 736748 736749 736750 736751 736752 736753 736754 736755 736756 736757 736758 736759 736760 736761 736762 736763 736764 |
Transportability
Once an initial calendar is built, it is readily transportable into other files:
- create table called Cal (or temporarily rename the existing utility table)
- create global number fields in table Cal: g_month and g_year
- import scripts: edit_change_month and ss_date_list
- copy and paste in calendar button bar matrix, step month button bar, and month and year global fields
- all of which takes about 2 minutes.
- then update the theme for the added styles attached to the objects:
- bb_cal_month
- bb_w1, bb_w2, bb_w3, bb_w4, bb_w5, bb_w6
- day labels
- fld_cal_month and fld_cal_year
- cal_panel
- cal_panel_weekend
Speed Testing - FileMaker button bar calendar date picker
Using a script loop approach to build the day number list is faster than using a executeSQL Virtual List approach to parse a list from a table of date records. Both methods are quick on LAN testing.
Web Direct using the loop script method to establish the day list is both quick, and offers equivalent performance across laptop and iPad devices tested.
Speed of execution of Virtual List executeSQL method is directly dependent on how many records there are in the date table, more is slower. It is universally slower by orders of magnitude than the script loop approach to build the day number list in all tests. This might be improved by running the eSQL step as a Perform Script on Server construct.
Refresh of the button bar objects, needs to be done via refresh Objects rather than a refresh window, and is mandatory when served.
a). local file on client
method | device | msec av | msec sd | n |
---|---|---|---|---|
FM script loop | MBP | 6 4 |
0.7 1 |
100 101 |
FM script loop | iPad mini 2 | 21 | 8.4 | 135 |
FM script loop | iPad 3 | 40 | 6.9 | 100 |
Virtual list eSQL | MBP | 18 | 5.8 | 100 |
Virtual list eSQL | iPad mini 2 | 72 | 20.7 | 100 |
Virtual list eSQL | iPad 3 | 144 | 65.5 | 100 |
b). Server hosted file - access via FMPA and FMGo
method | client | av msec | sd msec | n |
---|---|---|---|---|
FM script loop | MBP | 6 | 1.0 | 91 |
FM script loop | iPad mini 2 | 31 | 5.1 | 100 |
FM script loop | iPad 3 | 40 | 4.3 | 100 |
Virtual list eSQL | MBP | 71 85 |
39.2 29.8 |
100 101 |
Virtual list eSQL | iPad mini 2 | 185 313 |
60.1 122.5 |
100 101 |
Virtual list eSQL | iPad 3 | 271 1253 |
56.3 232 |
60 51 |
c). Server hosted file - access via Web Direct
method | device | msec av | msec sd | n |
---|---|---|---|---|
FM script loop | MBP Safari 11.0 | 5 4 |
2 0.7 |
100 101 |
FM script loop | iPad mini 2 Safari 11.0 | 4 4 |
1.2 0.7 |
100 101 |
FM script loop | iPad 3 | 4 4 |
0.7 0.8 |
100 101 |
Virtual list eSQL | MBP | 13 | 8.5 | 101 |
Virtual list eSQL | iPad mini 2 | 71 | 13.1 | 97 |
Virtual list eSQL | iPad 3 | 1010 | 315 | 101 |
c).Virtual list tests
The prior tests were carried out on a file with 753 day records in the Days table. Tests were repeated over a few days. To alleviate the tedium of button pressing, a simple test script was constructed to run the month up and down script, for 25 up or 25 down iterations. The stats sequence was removed to a separate script for manual execution. Except for baseline test locally on MBP, all other tests conducted on the file hosted on server.
One interesting quirk in manual testing, is that month changes for current year 2018 run in 35 sd 6.5 msec, in 2019 in 25 msec sd 5.9, and 2017 45 msec sd 7.2. It was noticing this curious pattern during manually stepping through months, that lead to scripting the month change iterations as 25 up or down starting from the same month/year each repetition of 25 month changes. in order to further standardise testing.
i). Virtual list tests - FMPA and FMGo clients
device | client | day records | msec av | msec sd | n |
---|---|---|---|---|---|
MBP | local | 1800 1857 1826 |
29 29 25 |
13.7 12.7 7.7 |
51 101 101 |
MBP | fmpa | 761 761 761 |
26 27 25 |
16 20.3 18.8 |
97 97 101 |
ipad mini 2 | fmgo | 761 761 |
57 50 |
31.1 20.6 |
97 101 |
ipad 3 | fmgo | 761 761 |
199 180 |
112 102 |
97 101 |
MBP | fmpa | 1800 1857 1826 |
49 49 53 |
31.5 17 31 |
101 101 101 |
ipad mini 2 | fmgo | 1800 1857 |
49 136 |
31.5 57 |
101 101 |
ipad 3 | fmgo | 1800 1857 |
481 600 |
257 245 |
101 101 |
MBP | fmpa | 4657 | 95 90 |
28 19.2 |
51 97 |
ipad mini 2 | fmgo | 4657 | 327 308 |
107 62 |
101 97 |
ipad 3 | fmgo | 4657 | 1072 1021 |
235 147 |
101 97 |
ii). Virtual list tests - Web Direct
device | client | day records | msec av | msec sd | n |
---|---|---|---|---|---|
MBP | webdirect | 761 | 12 | 5.9 | 101 |
ipad mini 2 | webdirect | 761 | 11 | 5.6 | 101 |
ipad 3 | webdirect | 761 | 11 | 5.6 | 101 |
MBP | webdirect | 1857 | 31 | 13.5 | 101 |
ipad mini 2 | webdirect | 1857 | 34 | 12.2 | 101 |
ipad 3 | webdirect | 1857 | 34 | 11.2 | 101 |
MBP | webdirect | 4657 | 84 70 |
64 12.9 |
51 97 |
ipad mini 2 | webdirect | 4657 | 74 70 |
20 12.9 |
51 97 |
ipad 3 | webdirect | 4657 | 74 70 |
15 12.9 |
101 97 |
d). testing hardware
- MBP - Macbook Pro 2012, 2.3GHz 16GB RAM,251GB SSD, OSX.12.6, FMPA 16.0.3
- SRV - Mac Mini late 2014 2.6GHz, 16GB RAM, 1TB HDD; OSX.13.2; FMS 16.0.2
- Ipad mini 2- 2013 64GB, iOS 11.2.2, FM Go 16.0.3
- Ipad 3 - 2012 1.0 GHz, 1 GB RAM, iOS 9.3.5, FM Go 15.0.4