1. The Bootstrap Package

The Bootstrap package is a collection of utility ACF functions used by the development environment to simplify the selection of files, directories, and more. It should be installed as one of the core packages in your application.

Most functions within this package have a FunctionID assigned, allowing you to use them directly in FileMaker calculations without the 'ACF_run' notation. Simply prefix them with 'ACFU_.' Here's a function reference, along with a listing of the package's source.

These functions are primarily intended for the development environment and are designed to serve as a standard ACF package in any application using the ACF-Plugin. They offer versatility and can be employed for various tasks related to operating system files.

  1. The Bootstrap Package
    1. bootstrap_AreWeLoaded ()
    2. LoadFile (filename)
    3. SaveFile (filename, content)
    4. SelectAndGetFile (startPath, Prompt)
    5. SelectFileOnly (startPath, Prompt)
    6. SelectFolder (Prompt)
    7. DirectoryExists (DirectoryPath)
    8. FileExists (FilePath)
    9. GetFilenameFromPath (path)
    10. GetDirectoriesFromPath (path)
    11. GetExtensionFromPath (path)
    12. SaveFileDialog (Prompt, proposed_folder, proposed_name)
    13. OpenOutputFile (path)
    14. WriteOutputMacFileUTF8 (FileNo, data)
    15. WriteOutputWinFileIso8859_1 (FileNo, data)
    16. WriteOutputWinFileUTF8 (FileNo, data)
    17. CloseFile (FileNo)
    18. NumFormat ( number, comma )
    19. BSBO_SaveDocumentDesktop (DocStore, SubPath, Title)
    20. GetPlatformString ( MacString, WinString )
    21. Save_Logg ( LoggText, LoggName )
    22. Append_Logg ( loggText , FilePath)
    23. AddMod10 ( Number )
    24. AddMod11 ( Number )
  2. Source code listing for the Bootstrap package

1.1. bootstrap_AreWeLoaded ()

This is a simple function to tell if we have loaded the bootstrap package. Also, see the Article about the startup script to see a full example of its use.

Example from startup script after loading the ACF compiled packages:

 # The bootstrap package should be in Preferences::ACF_Pack1, but if it's not...
If [ ACF_run ("bootstrap_AreWeLoaded") ≠ "Yes" ]
    # We lack the bootstrap package, compile it from Source....
    ...
End If

1.2. LoadFile (filename)

The LoadFile function opens a file and returns its content.

Example from a FileMaker script step:

Set Field [Preferences::SourceCode4; ACFU_LoadFile (Preferences::SourceFile4)]

1.3. SaveFile (filename, content)

The SaveFile function saves text to a disk file specified by the filename and its content.

Example:

If the source file does not exist, but you have its name and content, create the file and save the content to it.
Set Variable [$res; ACFU_SaveFile (Preferences::SourceFile4; Preferences::SourceCode4)]

1.4. SelectAndGetFile (startPath, Prompt)

The SelectAndGetFile function opens a file selection dialog, retrieves its content, and returns it. You can specify the starting path for the dialog and provide a prompt as the dialog heading. If the user selects a file, the global variable $$FileName is set to the selected file's path.

Here's an example of selecting the source file using a button and then setting the content and the path:

 # Select a file and get its content.
Set Field [Preferences::SourceCode4; ACFU_SelectAndGetFile (Preferences::SourcecodeFolder; "Select the source file")]
If [Preferences::SourceCode4 ≠ ""]
    # The user did not hit Cancel.
    Set Field [Preferences::SourceFile4; $$FileName]
End If

1.5. SelectFileOnly (startPath, Prompt)

The SelectFileOnly function works similarly to SelectAndGetFile but returns only the selected file's path without opening it.

Example:

Set Field [Preferences::SourceFile4; ACFU_SelectFileOnly (Preferences::SourcecodeFolder; "Select the sourceFile")]
 # If the SourceFile4 field is blank, the user did cancel the dialog.

1.6. SelectFolder (Prompt)

This function opens a directory selection dialog and returns the path to the selected directory.

Example:

Set Field [Preferences::SourcecodeFolder; ACFU_SelectFolder ("Where do you keep your ACF source files?")]

1.7. DirectoryExists (DirectoryPath)

The DirectoryExists function checks for the existence of a directory path and returns a boolean value (1 for true, 0 for false in FileMaker scripts).

Example:

If [Not (ACFU_DirectoryExists (Preferences::SourcecodeFolder))]
  # Take appropriate actions for a missing folder configuration.
  # (Select a new folder or present an error...)
End If

1.8. FileExists (FilePath)

This function checks for the existence of a disk file and returns true or false (1 or 0 in FileMaker scripts).

Example:

If [Not (ACFU_FileExists (Preferences::SourceFile4))]
   # Take appropriate actions for a non-existent source file.
   # Select the file or present an error, for example.
End If

1.9. GetFilenameFromPath (path)

The GetFilenameFromPath function returns only the last part of the filename from a full path.

Example:

 # Suppose our SourceFile4 contains: /Users/ole/MyACFFiles/bootstrap.acf
Set Variable [$SourceFileName; ACFU_GetFilenameFromPath(Preferences::SourceFile4)]
 # The $SourceFileName now contains bootstrap.acf

1.10. GetDirectoriesFromPath (path)

The GetDirectoriesFromPath function returns the directory path leading up to the filename of a full path, including the filename at the end.

Example:

 # Suppose our SourceFile4 contains: /Users/ole/MyACFFiles/bootstrap.acf
Set Variable [$SourceDirectory; ACFU_GetDirectoriesFromPath(Preferences::SourceFile4)]
 # The $SourceDirectory now contains /Users/ole/MyACFFiles/

1.11. GetExtensionFromPath (path)

The GetExtensionFromPath function returns the extension or file type (what appears after the final dot) of a filename or a full path.

Example:

 # Suppose our SourceFile4 contains: /Users/ole/MyACFFiles/bootstrap.acf
Set Variable [$FileExtension; ACFU_GetExtensionFromPath(Preferences::SourceFile4)]
 # The $FileExtension now contains: acf

1.12. SaveFileDialog (Prompt, proposed_folder, proposed_name)

The SaveFileDialog function presents a dialog to the user for saving a new file. You can specify a proposed folder and a proposed name for the file. If the file already exists, the user is alerted and can choose to overwrite it.

Example:

Set Field [Preferences::SourceFile4; ACFU_SaveFileDialog ("Save the file"; Preferences::SourcecodeFolder; "NewName.acf")]
If [Preferences::SourceFile4 ≠ ""]
    # A valid file has been selected; start writing to it...
End If

1.13. OpenOutputFile (path)

The OpenOutputFile function opens a file, keeping it open for subsequent write calls. It returns an open file number, which can be used for later writing operations.

For additional write functions and the close function, refer to the combined example below.

1.14. WriteOutputMacFileUTF8 (FileNo, data)

This function writes text to an open output file. For example, if you are exporting data, you can open the file with the OpenOutputFile function, write export content to it in a loop, and then close the file.

The file content will be in Mac UTF8 format.

1.15. WriteOutputWinFileIso8859_1 (FileNo, data)

This function is similar to the previous one, except it converts the data to ISO-8859-1 format for Windows systems and replaces carriage return codes with Windows Carriage-Return-Line-Feed codes. This is useful for exporting data from a Mac to a Windows system.

1.16. WriteOutputWinFileUTF8 (FileNo, data)

This function is also for writing data but produces it in UTF8 format for Windows.

1.17. CloseFile (FileNo)

The CloseFile function closes the open file after you have written the content to it.

Here's a combined example:

 # Present a File-Save dialog
Set Variable [$FileName; ACFU_SaveFileDialog ("Save the file"; ""; "NewName.txt")]
If [$FileName ≠ ""]
    # Open the selected file
    Set Variable [$FileNo; ACFU_OpenOutputFile($FileName)]

    # Loop through the records
    Go to record/request/page [First]
    loop


        # Generate and write content to the file
        Set Variable [$LineContent; ... some content for the file]
        Set Variable [$xx; ACFU_WriteOutputWinFileIso8859_1($FileNo; $LineContent & Char(13))]

        Go to record/Request/Page [Next; Exit after last: On]
    end loop
    # Close the file
    Set Variable [$xx; ACFU_CloseFile($FileNo)]
End If

1.18. NumFormat ( number, comma )

This function formats a number value to a text string with 1000 separators and two decimals. You specify the type of comma used in your application being a period or a regular comma. The function is used to merge fields in your layout to increase the readability of the numbers.

Example:

Set variable [$$SumInvoice; "$ " & ACFU_NumFOrmat(Invoice::InviceTotal; ".")] 
 # Ausme Invoice::InviceTotal contains 10055.35, The $$SSumInvoice now contains "$ 10 055.35"
 # <<$$SSumInvoice>> can now be placed on your layout as a merge-field. 

1.19. BSBO_SaveDocumentDesktop (DocStore, SubPath, Title)

This function is suited for a document portal in your application. The document record points to some documents in the operating system, a copy "document to desktop" button on the portal row is sometimes desirable. The user is on the way to a customer and needs some documents copied to his desktop or a folder on his customer visit.

If the user holds the "Alt-key" then a Save-File dialog appears where he can select the destination for the document.

The Parameters:

Example:

 # Example (Win): 
Set variable [$xx;
    ACFU_BSBO_SaveDocumentDesktop ( "\\192.168.1.20\vArchive\myDocArchive"; 
        "Customers/11213/Contract.docx"; "Customer 11213 Contract" ) ]
 # or (mac)
Set variable [$xx;
    ACFU_BSBO_SaveDocumentDesktop ( "vArchive:myDocArchive"; 
        "Customers/11213/Contract.docx"; "Customer 11213 Contract" ) ]

1.20. GetPlatformString ( MacString, WinString )

The GetPlatformString is used in a mixed environment where you need separate text strings for Mac versus Windows users.

Example:

Set variable [$DocStore; ACFU_GetPlatformString ( Preferences::MacArchive; Preferences::WinArchive ) ]
 # Then the $DocStore is from the field MacArchive for the Mac Users, and WinArchive for the Windows users.  

1.21. Save_Logg ( LoggText, LoggName )

The Save_Logg function is useful for providing logg files from functions in your application. It creates a folder in your Documents directory called ACFLoggFiles. It adds a timestamp to the name given and writes the LoggText to the file.

Example:

Set Variable [$LoggPath; ACFU_Save_Logg($$ProcessLogg; "ProcessLogg")]

1.22. Append_Logg ( loggText , FilePath)

The Append_Logg function is useful for appending some logg text that has already been created. The Save_Logg function above returns a path to the logg-file, and this can be applied to the FilePath parameter in this function.

Example:

Set Variable [$xx; ACFU_Append_Logg($$ProcessLogg2; $LoggPath)]

1.23. AddMod10 ( Number )

The AddMod10 generates a Modulo-10 Control-digit to a string of numbers supplied, using the Luhn algorithm.

1.24. AddMod11 ( Number )

The AddMod11 generates a Modulo-11 Control-digit to a string of numbers supplied, using the Luhn algorithm.

2. Source code listing for the Bootstrap package

package bootstrap "Functions to facilitate load and save source and binary files

- Require ACF_Plugin ver 1.6.0.3 as minimum.

";
/*

    Common use ACF functions. 
    If you duplicate some of the functions to change, Remember to change or remove the FunctionID that need to be a unique number, between 100 an 32767. 
    We have used 200-214 and 229-236 in this package. 
    The FunctionID statement makes the functions available as plugin-function directly in the FileMaker calculations using ACFU_FunctionName...
    You can use ACF_GetAllPrototypes to retrieve the prototypes for all the functions, for cut & paste into your calculations. 

*/

function bootstrap_AreWeLoaded ()
    return "Yes"; 
end

function LoadFile (string filename)
    FunctionID 200;
    string content; 
    int x; 
    x = open (filename, "r"); 
    content = read (x); 
    close ( x ) ; 
    return content; 
end

function SaveFile (string filename, string content )
    FunctionID 201;
    int x; 
    x = open (filename, "w"); 
    write (x, content); 
    close ( x ); 
    return 1; 
end

function SelectAndGetFile (string startPath, string Prompt)
    FunctionID 202;
    string cc = "";
    string filename = select_file (Prompt, startPath); 
    if (filename != "") then
         cc = LoadFile(filename); 
    end if
    $$FileName = filename;  
    return cc; 
end


function SelectFolder ( string prompt ) 
    FunctionID 203; 
    string folder = select_directory ( prompt ) ; 
    return folder; 
end

function DirectoryExists ( string DirectoryPath ) 
    FunctionID 204; 
    if ( isMac ) then
        DirectoryPath = substitute ( DirectoryPath, ":", "/");
        if ( ! directory_exists ( DirectoryPath ) ) then
            if ( left ( DirectoryPath, 1 ) != "/") then
                DirectoryPath = "/Volumes/" + DirectoryPath; 
            else
                return false; 
            end if
        else 
            return true; 
        end if
    else
        DirectoryPath = substitute ( DirectoryPath, "/", "\\");
    end if
    return directory_exists ( DirectoryPath ) ; 
end

function FileExists ( string FilePath ) 
    FunctionID 229; 
    if ( isMac ) then
        FilePath = substitute ( FilePath, ":", "/");
        if ( ! file_exists ( FilePath ) ) then
            if ( left ( FilePath, 1 ) != "/") then
                FilePath = "/Volumes/" + FilePath; 
            else
                return false; 
            end if
        else 
            return true; 
        end if
    else
        FilePath = substitute ( FilePath, "/", "\\");
    end if
    return file_exists ( FilePath ) ; 
end

function GetFilenameFromPath ( string path ) 
    FunctionID 205; 
    path = substitute ( path, ":", "/");
    path = substitute ( path, "\\", "/");
    return regex_replace("^(.+)/(.+\..+)$", path, "\2");
end

function GetDirectoriesFromPath ( string path ) 
    FunctionID 206; 
    path = substitute ( path, ":", "/");
    path = substitute ( path, "\\", "/");
    return regex_replace("^(.+)/(.+\..+)$", path, "\1");
end

function GetExtentionFromPath ( string path ) 
    FunctionID 207; 
    path = substitute ( path, ":", "/");
    path = substitute ( path, "\\", "/");
    return regex_replace("^(.+/)(.+\.)(.+)$", path, "\3");
end

function SelectFileOnly (string startPath, string Prompt)
    FunctionID 208;
    string cc = "";
    return select_file (Prompt, startPath); 
end

function SaveFileDialogue ( string prompt, string proposed_folder, string proposed_name ) 
    functionID 209; 
    string newfn = save_file_dialogue (prompt, proposed_folder, proposed_name) ; 
    return newfn; 
end

function OpenOutputFile ( string path ) 
    FunctionID 210; 
    int x = open ( path, "w" ); 
    return x; 
end

function WriteOutputMacFileUTF8 ( int FileNo, string data ) 
    FunctionID 211; 
    write ( FileNo , substitute ( data, "\r", "\n" )) ; 
    return "OK";
end

function WriteOutputWinFileIso8859_1 ( int FileNo, string data ) 
    FunctionID 212; 
    write ( FileNo , from_utf ( substitute ( data, "\r", "\r\n" ), "ISO-8859-1" )) ; 
    return "OK";
end 

function CloseFile ( int FileNo )
    FunctionID 213; 
    close ( FileNo ) ; 
    return "OK"; 
end

function WriteOutputWinFileUTF8 ( int FileNo, string data ) 
    FunctionID 214; 
    write ( FileNo , substitute ( data, "\r", "\r\n" )) ; 
    return "OK";
end

/*

    Formattering av nummer - med komma og 1000 gruppe-skille. 
    Her benyttes fast " " som 1000 gruppe skille 
    Eksempel fra FileMaker kalkulasjon: ACFU_NumFormat ( beløp, ",")
    Der beløp er 12345,2 => 12 345,20
    
*/
function NumFormat  ( float num, string comma ) 
    functionID 230; 
    string sNum; 
    if ( comma != ".") then
        sNum = substitute ( format ( "%.2f", num ), ".", comma ) ;  
    else
        sNum = format ( "%.2f", num ); 
    end if
    
    return regex_replace ( "\d{1,3}(?=(\d{3})+(?!\d))", sNum, "$& ") ;  
    
end

/*
Copy a document from a document store to user's desktop.
DocStore: the start path for the archive
ArchiveSubPath: The relative path starting from DocStore (including the filename). Designed to be a 
    common subpath to be used from both Mac and Windows, using :, / or \ as directory seps. 
Title: An optional title of the document to be used as the target filename (+ extension of the original file ) 
Example (Win): 
ACFU_BSBO_SaveDocumentDesktop ( "\\192.168.1.20\vArchive\myDocArchive"; "Customers/11213/Contract.docx"; "Customer 11213 Contract" ) 
or (mac)
ACFU_BSBO_SaveDocumentDesktop ( "vArchive:myDocArchive"; "Customers/11213/Contract.docx"; "Customer 11213 Contract" ) 

If the user holds the Alt key down while doing this, a File Save dialogue appears for the user to select an alternate name or location. 
Else, it will be copied to the user's desktop.  
*/

function BSBO_SaveDocumentDesktop (string DocStore, string ArchiveSubPath, string Title)
    functionID 231;
    if ( DocStore == "" ) then
        throw "DocStore for document not set"; 
    end if
    if ( ArchiveSubPath == "" ) then
        throw "ArchiveSubPath for document not set"; 
    end if
    
    if ( isMac ) then
        DocStore = "/Volumes/" + substitute ( DocStore, ":", "/");
    else
        DocStore = substitute ( DocStore, "\\", "/");
    end if
    if ( ! directory_exists ( DocStore ) ) then
        throw "Doc Store volume not available: " + DocStore;
    end if
    if ( right(DocStore, 1)  != "/") then
        DocStore += "/"; 
    end if
    
    ArchiveSubPath = substitute ( ArchiveSubPath, ":", "/"); 
    ArchiveSubPath = substitute ( ArchiveSubPath, "\\", "/"); 
    string SourceFile = DocStore + ArchiveSubPath; 
    string defname; 
    if ( Title == "" ) then
        defname = regex_replace("^(.+)/(.+\..+)$", SourceFile, "\2");
    else
        Title = substitute ( Title, ":", "" ) ; 
        Title = substitute ( Title, "/", "" ) ; 
        Title = substitute ( Title, "\\", "" ) ; 
        
        string ext = regex_replace("^(.+/)(.+\.)(.+)$", SourceFile, "\3");
        defname = Title + "." + ext; 
    end if
    
    string newfn; 
    bool abort = false; 
    if ( int ( @get(ActiveModifierKeys)@ ) == 8 ) then
        newfn = save_file_dialogue ("Where do you want to save the file?", desktop_directory(), defname) ; 
        if ( newfn == "" ) then
            abort = true; 
        end if
    else
         newfn = desktop_directory() + defname; 
    end if
    
    print format ( "Source:%s\nDestination:%s\n", SourceFile,newfn ) ; 
    if ( ! abort ) then
        string res = copy_file ( SourceFile, newfn ) ; 
        return res; 
    else 
        return "OK"; 
    end if
end

/* To Simplify the use of alternate strings on Mac and Windows
    Example using the above function from a FM Calculation to work on both platforms
    (Shown using hard coded strings, but normally one would use fields from preference table): 
    ACFU_BSBO_SaveDocumentDesktop ( ACFU_GetPlatformString ( "vArchive:myDocArchive"; "\\192.168.1.20\vArchive\myDocArchive"); "Customers/11213/Contract.docx"; "Customer 11213 Contract" ) 

*/
function GetPlatformString ( string MacString, string WinString ) 
    FunctionID 232; 
    if ( isWindows ) then
        return WinString; 
    else
        return MacString; 
    end if
end

/*

    To save content of log to a file in users document directory. 
    Folder name: ~/Documents/ACFLoggFiles/
    creates directory if it does not exists. 
    Returns Path to logg file, to be used in Append_Logg. 
    Parameters: 
        logg : The content to be written to the logg file. 
        name : The name part of the full fulename as: YYYYmmdd_hhmmss_<name>_logg.txt
    
*/
function Save_Logg ( string logg , string name)
    FunctionID 233; 
    string logdir = documents_directory()+"ACFLoggFiles";  
    string res; 
    if ( ! directory_exists ( logdir )) then
        res = create_directory (logdir ); 
    end if
    logg = substitute ( logg, "\r", "\n"); 
    string path = logdir+format("/%s_%s_logg.txt", string ( now()+3600, "%Y%m%d_%H%M%S"), name); 
    res = delete_file ( path ); 
    int x = open ( path, "w"); 
    if ( isWindows ) then 
        write ( x, substitute ( logg, "\n", "\r\n")); 
    else
        write ( x, logg); 
    end if
    close ( x ); 
    return path; 
end
/*

    Append logg entries to previously created logg with Save_Logg. 
    
*/
function Append_Logg ( string logg , string FilePath)
    FunctionID 234; 
    
    logg = substitute ( logg, "\r", "\n"); 
    int x = open ( FilePath, "wa"); 
    if ( isWindows ) then 
        write ( x, substitute ( logg, "\n", "\r\n")); 
    else
        write ( x, logg); 
    end if
    close ( x ); 
    return "OK"; 
end
/*

    Add Modulo 10 (Luhn Algorithm) control digit to a string of numbers
    
*/
function AddMod10 ( string number ) 
    FunctionID 235; 
    array int digits; 
    int l = length ( number ) ; 
    int i; 
// Check only digits. 
    if ( regex_match ( "\d+", number ) ) then
    
    // Build an array of integers. 
        for (i=1, l ) 
            digits[] = ascii ( substring ( number, i-1, 1 ) ) - 48 ; 
        end for
        
    // Back traverse every second and double it. If > 9, subtract 9. 
        for ( i = l, 1, -2 )
            digits[i]=digits[i]*2; 
            if (digits[i]>9) then
                digits[i] = digits[i]-9; 
            end if
        end for
        
    // Sum of the digits
        int sum; 
        for (i=1, l ) 
            sum += digits[i]; 
        end for
        
    // sum multiplied by 9, modulo 10 gives the digit. 
        int digx = mod ( sum*9 , 10); 
    
        return number + digx; 
    else
        return "ERROR: Expects only digits as parameter: " + number; 
    end if
end 
/*

    Add Modulo 11 (Luhn Algorithm) control digit to a string of numbers
    This  one might also add "-" at the end, according to the spesifications. 
    
*/
function AddMod11 ( string number ) 
    FunctionID 236; 
    array int digits; 
    int l = length ( number ) ; 
    int i, mult; 
// Check only digits. 
    if ( regex_match ( "\d+", number ) ) then
    
    // Build an array of integers. 
        for (i=1, l ) 
            digits[] = ascii ( substring ( number, i-1, 1 ) ) - 48 ; 
        end for
        
    // Back traverse every one and multiply 
        mult = 2; 
        int sum; 
        for ( i = l, 1, -1 )
            sum += digits[i]*mult; 
            mult ++; 
            if ( mult > 7) then
                mult = 2; 
            end if
        end for
        
        print sum; 
    // difference 11 and mod 11 gives the digit. 
        int digx = 11 - mod ( sum , 11);
        string ctrl; 
        if ( digx > 9) then
            ctrl = "-"; 
        else
            ctrl = string ( digx ); 
        end if
    
        return number + ctrl; 
    else
        return "ERROR: Expects only digits as parameter: " + number; 
    end if
end