MBS Plugin Advent calendar 2024

candy cane Monkeybread Monkey as an elf candy cane
Door 1 - The Database

Hello and welcome to this year's MBS FileMaker Advent calendar. This year our logo monkey has turned into a Christmas elf and has to take care of the coordinated distribution of presents at Christmas. What could be more obvious for our monkey than to get an overview with a FileMaker database. Over the next 24 days, we'll show you how to pimp a simple database with the help of the MBS FileMaker Plugin. All you need is to have fun with FileMaker. We'll start by creating the database and work our way through step by step. Even though the advent calendar is suitable for beginners, advanced users and professionals can certainly use some of the ideas from the advent calendar for their own purposes.

So let's start with our first door and thus with the basis for this calendar. Our FileMaker database. Although we provide you with this database, it can be useful to deal with the topic of how such a database can be set up. Initially, we only have a few tables, which we will expand over the course of the Advent calendar. Initially, our database consists of 3 tables. The table with the name of the giftee is our main table in which we find the information about the giftee, such as the last name, first name, date of birth and address. In addition, each recipient has their own identification number that can be uniquely assigned to them. This means that the number exists exactly once for each recipient. In this example, we call it the Primary Key in the table.

We would also like to include the telephone numbers and e-mail addresses of the recipients. But there is a problem, because a recipient can have different phone numbers or e-mail addresses and we would like to use this possibility to include them in our database. For this reason, we still need the Telephone and Mail tables in which we can record this data.

I will demonstrate this using the telephone table. However, it works in the same way for the e-mail addresses. The Giftee and Phone table are linked with a relationship in the relationship graph. We use the primary key of the giftee table and theForeign key field in the Telephone table. So if a telephone number belongs to a specific giftee, we take the primary key of a person and enter it in the telephone table as Foreign key.

We do the same for our mail addresses.

This completes our database schema for now and we would now like to create a layout. We want to see our giftee data. To do this, we first add the last name, first name, address and birthday fields to our layout and position them as we want.

Now, of course, we would also like to see our telephone numbers and e-mail addresses on the layout. We use portals for this. We drag a portal onto our layout and enter the corresponding tables from which the information comes from. At the moment we can see everything we want to see.

Of course we would like to make the whole thing a bit more user-friendly. Since we are going to create different layouts, we want to create our own customized theme. To do this, we first change something in our layout, such as the color of the top navigation in green. We now see that a red arrow lights up at the top next to the style. This means that FileMaker wants to point out that this is not the original style of the selected theme. Here we now have the option of either overwriting the original style or creating a new style. I would always recommend that you create a new style so that you always have the opportunity to return to the other style.

The style is currently only visible on this single layout, which we would now like to change - we can currently see the red arrow next to the theme. Again, we have the option of overwriting the theme or creating a new one. Again, the advice to create a new one. After creating a new design, we can change it to our liking. We keep our colors in green and red and choose a nice font for the field naming and the info texts.

Of course, the logo with our elf monkey should not be missing. To do this, we simply drag our logo into the application and can then use the mouse to set the size and position it accordingly. Here's a little tip: If you press the Option key at the same time as scaling with the mouse, the proportions will be retained.
Now our database template is ready.

I hope you enjoyed the first little door and we'll see you again tomorrow.

Download Advent24-Door 1.fmp12

Watch the video on YouTube

candy cane Monkeybread Monkey as an elf candy cane
Door 2 - Install the MBS FileMaker Plugin

Welcome to the second door. Today we want to install the MBS plugin together, which we need for the other doors. Even if you do not have a license for our plugin yet, you can simply download it for testing.
Click here to go to the website and download it.

Here we have the choice between two archives. On the one hand a dmg archive for Mac and iOS users and a zip archive for Windows and Linux users. However, both archives have the same content. So click on the archive that suits you and download it.

Let's take a look at the downloaded archive. We have a folder for each operating system. Then a folder Extras ( this is not of interest to us in this advent calendar) and a folder Examples. In the Examples folder you will find FileMaker databases in which the various functionalities of the MBS plugin are shown. This examples folder has grown over many years and contains over 600 different examples that you can study.

The folders with the names of the individual operating systems are important for the installation of the plugin. If we open such a folder, we can see that the plugin file is in there. If we open the folder for Windows, there are even two files inside.

One file is intended for 32 bit systems and the other for 64 bit systems. If you are unsure which system you are using, copy both files and the plugin will automatically fetch the one it needs.

To install, open FileMaker and look for the Preference... dialog. Under Windows you will find it under Edit > Preferences... and on Mac under FileMaker Pro > Settings... now go to the Plugins tab and open the plugin folder by clicking on the appropriate button.

We then place the file(s) matching the operating system in this folder. Now we just have to close FileMaker completely once (e.g. via Command-Q on macOS). Now you can open FileMaker again and the plugin is ready for use.

To see if we can really use it now, we create a new script in the FileMaker script area. We want to display the current plugin version in a dialog box. First of all, we create a variable in which we store the version. We set the value of the variable with a plugin function. Each plugin function call is started with an MBS, followed by the function name and possibly parameters in brackets. In our case, the function name is version and we have no other parameters. This sets our value and we can output the information in a dialog.

Set Variable [ $version ; Value: MBS("Version") ] Show Custom Dialog [ "MBS Plugin Version" ; $Version ]

The plugin is running Congratulations!

If you are a Mac user, you may have already noticed one of the free goodies from the MBS plugin. Because when you enter the letters of the function name, possible names that have the same beginning as our input are displayed. This is the auto-completion for MBS function names. There are more of these cool goodies that we try to support developers with. Have a look at the goodies on our website.Unfortunately, for technical reasons, we can only offer the vast majority of developer goodies for Mac. But we are trying to do what we can to extend the range for Windows as well.

If you have any questions about installing the plugin, please take a look at our installation videos.
MBS Plugin Installation on Windows
MBS Plugin Installation on macOS

Now everything is prepared so that we can start expanding our database with cool features tomorrow.

candy cane Monkeybread Monkey as an elf candy cane
Door 3 - Drag & Drop

Welcome to our third door in this advent calendar. Today we would like to start expanding our database. We want to have the possibility to store additional documents for the giftees. For this purpose, we will create a table with the name Files. This table should store the name of the file, the type and the file itself as a container value. In addition, we also need a field with the Foreigen Key here, because we want to establish a relationship so that the files can also be assigned to the giftee.

Now we have to think about how we want to bring these files into our database. We want to use drag and drop to do this. So we build an area where we can drag the desired file and the type and name are then automatically read out and the file and the remaining information are then stored in a record in the new table. This data record should be related to the giftee that is currently displayed.

We can create drag and drop areas with the MBS FileMaker Plugin. To avoid wasting useful space for a drag and drop field on our layout, we would like to place this drag and drop field as an overlay, i.e. a freely movable area on our layout that can be activated and deactivated by clicking a button. To do this, we first need to create an overlay and then assign drag and drop functionality to it.

So let's start with the overlay first. To do this, we have the functions from the Overlay section. First, we create a new overlay with the MBS function Overlay.Create. This function returns a reference. We also need to specify this reference in the other functions so that we know exactly which overlay in our project the functions should be applied to. Next, we want to determine the size and starting position of our overlay. To do this, we use the Overlay.SetFrame function and, as already mentioned, specify the overlay reference, the position with X and Y coordinates and the height and width of the overlay in pixels. So we want the top left corner of our overlay to be 30 pixels away from the right and top edge. In addition, our overlay should be square and 200 x 200 pixels in size. Our function call looks like this:

Set Variable [ $r ; Value: MBS("Overlay.SetFrame"; $overlay; 30; 30; 200; 200) ]

Now we want to create a background image for the overlay so that we can see it immediately. To do this, we use the Overlay.SetImage function. Here, too, we first need the reference and then the background image as a container. Of course, we can now place the image in a container on our layout, but we don't want to do that. For this reason, we create a table that is not related to the other tables and contains all the information we need to organize our database in a single data record. In our example, I will call this table Data. This table initially only gets the container field for the overlay from us and in the resulting layout we create the first data record and drag our desired background image into the container.

But now we have no access to this table from the Giftee table. The solution to this problem is, to write a script that stores the data from the Data table in global variables. Global variables are assigned a double $$ sign and are visible to the user in our entire FileMaker database. When we close FileMaker, these are released again. So we create a script WriteData. In order to have access to the data, we first go to the Data layout with the Go to Layout command. There we call up the first data record, which contains our data. Now we write the image stored there to the global variable $$Overlay_Image. Now we go back to the Giftee layout with Go to Layout.
This is what the complete script looks like:

Go to Layout [ “Data” (Data) ; Animation: None ]
Go to Record/Request/Page [ First ]
Set Variable [ $$Overlay_Image ; Value: Data::Image_Overlay ]
Go to Layout [ “giftee” (Giftee) ; Animation: None ]

This script for writing the data must be called once. It makes sense to do this automatically once when opening. Script triggers are available that automatically call a script when a certain action occurs in FileMaker. Here we would like to use the script trigger OnFirstWindowOpen. This calls a script as soon as the first window of our solution has been opened, i.e. we have started it. The settings for this script trigger can be found under File > File Options under the Script Trigger tab. Here you can select it and pass the script. As we have already opened FileMaker and therefore also the first window, we let the script run once.

Now, after this short excursion, we come back to our Overlay.SetImage function. We first specify the reference of our overlay in this function and then specify the background as the container value (our global variable). So that the overlay is now also displayed, we set the visibility to 1 in the Overlay.SetVisible function.

Now we can start our script once and see if it works. Our overlay is displayed, but cannot yet be moved on the screen and we are beginning to wonder how we can get it out of there again. Because even closing FileMaker has no effect. So we have to find another solution. And for that we need another script. On the one hand, we can simply hide the overlay by setting the visibility in the already known function Overlay.SetVisible to 0, but then our overlay is still present in the working memory and could also be displayed again by setting this function to 1 again, but if we want to remove it completely, we call one of the release functions. There are two available for this. One is the Overlay.Release function with which we can remove a specific overlay by specifying the reference and the other is Overlay.ReleaseAll with which all overlays are removed. In this example, we use the Overlay.ReleaseAll function.

Set Variable [ $r ; Value: MBS("Overlay.ReleaseAll") ]

Now we want to change our overlay script so that we can also move the overlay. To do this, we call the function Overlay.SetMovableByWindowBackground, pass the reference and set the value to 1. Now the overlay can also be moved. At the moment, it is not yet useful to us because it cannot yet receive files. For this we now need the functions for drag and drop from the plugin. We want to create a drop area now. To make the overlay our drop area, we use the DragDrop.AttachToOverlay function and enter the reference number of our overlay here. This function returns the reference number of the drag and drop area. We save this in a global variable (double $$ sign), as we need this reference in another script and local variables (single $ sign) are now visible in the script in which they were used for the first time. We now set up a script which is to be called when a file is dragged onto the drag and drop area. We assign this script to the area with DragDrop.SetDragActionHandler. In the parameters we pass the reference of the drag and drop area, as well as the FM file in which the script is defined (in our case the same file) and the name of the script we want to call. Our entire script overlay now looks like this:

Set Variable [ $overlay ; Value: MBS("Overlay.Create") ]
Set Variable [ $r ; Value: MBS("Overlay.SetFrame"; $overlay; 30; 30; 200; 200) ]
Set Variable [ $r ; Value: MBS("Overlay.SetImage"; $overlay; $$Overlay_Image) ]
Set Variable [ $r ; Value: MBS("Overlay.SetVisible"; $overlay; 1) ]
Set Variable [ $r ; Value: MBS("Overlay.SetMovableByWindowBackground"; $overlay; 1) ]
Set Variable [ $$DragDrop ; Value: MBS("DragDrop.AttachToOverlay"; $overlay) ]
Set Variable [ $r ; Value: MBS("DragDrop.SetDragActionHandler"; $$DragDrop; Get(FileName); "DragDrop") ]

Now we turn to the DragDrop script that is called when one or more files have been dragged onto the area. First we determine how many files arrive via the drag and drop area, here we also pass the reference of the DragDrop area which is in a global variable.

1 Like

Nice to know:
A loop occurs quite often in programming. Here, a part of the program is executed repeatedly until a certain condition is met. In our case, we increment a variable. Once this has reached a certain value, we exit the loop again.

Then we create a loop that runs through the individual files. First we use DragDrop.GetPath to get the path to the file we are currently looking at. With Files.FileExists we check whether this file really exists. If this file exists, we use the functions Files.FileName, Files.FileExtension and Files.ReadFile to read the name, type and the file itself using the path. With Files.ReadFile, in addition to the path, we also specify the mode in which the file is read. For example, we can first decode a file Base64 in order to be able to read it. In this case, we simply specify auto so that the file is processed automatically. In the next step, we check whether there was an error with this function, as otherwise there would be no valid file in the container that we could add to the database. As the called function is an MBS function, we can query this with MBS("IsError"). This function returns true if there was an error in the last MBS call. Otherwise it returns false. If no error has occurred, we first save the primary key of our currently displayed Giftee in a variable. This is necessary because we want to open a new window in the next step and transfer the value from our original window to the new one. The new window is named "NewFile" and displays the layout files. We now create a new record in this window with the correct layout. Here we first transfer the saved Primary Key as Foreigen Key so that the data record in the Files table is related to the data record in Giftee. Now we also set the just determined values of name, type and the file itself in the record. After we have set everything, we want to close the window again. In the Close script step, you have several options for determining the window to be closed. Here I advise you to always close the window using the name of the window so that the wrong window is not closed by mistake, e.g. because there was an error in the script and the window that is closed is not the active window. This is what our script looks like now:

Set Variable [ $Count ; Value: MBS("DragDrop.GetPathCount"; $$DragDrop) ]
Set Variable [ $i ; Value: 0 ]
Loop [ Flush: Always ]
	Set Variable [ $FilePath ; Value: MBS("DragDrop.GetPath"; $$DragDrop; $i) ]
	If [ MBS("Files.FileExists"; $FilePath) ]
		Set Variable [ $Name ; Value: MBS("Files.FileName"; $FilePath) ]
		Set Variable [ $Typ ; Value: MBS("Files.FileExtension"; $FilePath) ]
		Set Variable [ $File ; Value: MBS("Files.ReadFile"; $FilePath; "auto") ]
		If [ MBS("IsError") ≠ True ]
			Set Variable [ $PK ; Value: Giftee::PrimaryKey ]
			New Window [ Style: Document ; Name: "NewFile" ; Using layout: “Files” (Files) ]
			New Record/Request
			Set Field [ Files::ForeignKey ; $PK ]
			Set Field [ Files::File_Name ; $Name ]
			Set Field [ Files::File_Typ ; $Typ ]
			Set Field [ Files::File ; $File ]
			Close Window [ Name: "NewFile" ; Current file ]
		End If
	End If
	Set Variable [ $i ; Value: $i+1 ]
	Exit Loop If [ $i ≥ $Count ]
End Loop

If you now start the script overlay, the overlay is displayed in FileMaker and we can drag files onto the overlay. I have added a portal to my layout as shown in door one which displays the data of files. Above the portal I added a button that executes the script overlay and a button that hides the overlay again.

If the files do not appear after you have dragged them onto the overlay, please do not panic right away. It may be that everything is OK in your programming, but you are still missing a relevant setting. The plugin calls a script as soon as we drag a file onto the overlay. However, we must first allow the plugin to do this in FileMaker. For this reason, we go to the security settings File>Manage>Security and select a profile here. We click on the gray pencil next to the profile name in the bar to edit the privacy sets.

In this list we select the Privilage Set fmplugin and activate it.

If you now exit the dialog and show the layout again, the files will be added to the database.

That's it for today and I hope to see you again tomorrow for the next door.

candy cane Monkeybread Monkey as an elf candy cane
Door 4 - Add GTC with DynaPDF

Today I would like to show you a tool that you can use in FileMaker with the MBS FileMaker Plugin: DynaPDF. With DynaPDF you have the possibility to create, edit, merge, analyze or sign PDF documents in FileMaker. DynaPDF offers you many possibilities around the topic of PDF.

Our monkey also wants to use one of these functionalities today. Because today we want to build the possibility to attach user terms and conditions to a PDF document. The user terms and conditions always remain the same and can be attached to any outgoing document, e.g. an invoice. I will show you how this works today in this door.

First we have to initialize DynaPDF, which means we have to tell FileMaker where the DynaPDF function library is located. The files are automatically delivered with the download of the plugin and can be found in Examples > DynaPDF. The files have the extension dll and dylib. Which of the files you need depends on your operating system. If you want to use DynaPDF on a Mac, you will need the path to the dynapdf.dylib file. If we are working with Windows, it also depends on whether we are working with a 32 or 64 bit version. If you are using FileMaker 19 or higher, this decision is easy, as only a 64 system can be used here. In any case, the library has the extension .dll. If you are not sure, just put both dll in the same folder and the plugin will grab the file it needs. The next piece of information we need is the DynaPDF license key. If you want to use DynaPDF in your solutions, you need an appropriate DynaPDF license and an MBS FileMaker Plugin license.

There are four different DynaPDF licenses. Which license you need depends mainly on which DynaPDF functions you use. If you need more information, please visit our website to find out which license is right for you. However, we do not necessarily need a license to try it out, but need to live with the watermark. After we have determined the path to the libraries and the license key, if available, we can now call the DynaPDF.Initialize function. In the parameters we first enter the path to the library and then the license key. If we do not yet have a license key, we pass an empty string here.

Nice to know:
If you want to test whether a specific license is sufficient for your solution, you can also enter the name of the license instead of the license key. If the license is not sufficient, an error is displayed.

In our examples, we always use the InitDynaPDF script for initialization. This searches for the library in the same folder where our database is located. You are welcome to copy this script for your projects. DynaPDF must be initialized before the first call of another DynaPDF function. It can be practical if we have a function that checks whether this has already been done. We can use the DynaPDF.IsInitialized function for this.

After this has happened, we can start our work. We created the portal yesterday and can now save files for a customer there. In this portal we would now like to have a button for each entry which is a PDF that we can use to attach the terms and conditions that are stored in a global variable $$GTC. These should then be transferred to our portal as a new entry. The name of the file is then: original name with GTC.

First we extend our Script Data. This is because we want to store the PDF file with our terms and conditions in our data table and then write it to the $$GTC variable. We therefore add this line to the script:

Set Variable [ $$GTC ; Value: Data::GTC ]

Now we want to create the script that will later be called behind a button in the portal. We have already seen that we have to make sure that DynaPDF has been initialized before using it for the first time.

We want to use DynaPDF here, so we first ask whether it is already initialized and if this is not the case, then we run the InitDynaPDF script that we have copied into our project.

Now we need to create a working environment for the PDF. To do this, we call the DynaPDF.New function. This then gives us a reference number with which we can continue working. In order to be able to import the file in the portal into our working environment, we first need to open it. As it is located in a container, we use the DynaPDF.OpenPDFFromContainer function and specify the reference and the container in which the file is located. If you want to work with files that are stored in a location on your computer's disk, use DynaPDF.OpenPDFFromFile. After the file has been opened, we now need to import the document into the working environment. We use the DynaPDF.ImportPDFFile function for this. As this is the first document in the working environment, we only need to specify the reference in the parameters. If required, you can also specify where the document should be inserted. We can also see this in the second document. If required, you can also specify scaling factors for x and y. The function returns how many pages have been imported into the working environment. We save this information because we need it for the second import. Now we do almost the same with the second file. First we open it again with DynaPDF.OpenPDFFromContainer from the variable that contains the container value and then we import it with DynaPDF.ImportPDFFile. Here we specify the reference as before, but now we need the page number where we want to insert the first page of this document. So we take the page number of the first document and add 1 to it to get this page. We want to save the PDF from our working environment. To do this, we use the DynaPDF.Save function. We enter our reference in the parameters, as well as the new file name, which we have to put together before we can save it.

To do this, we first retrieve the old file name from the field. As this still has a .pdf extension, we have to trim this extension. We first determine the length of the file name and subtract 4 characters from it. Then we pass the file name to the Left function, which outputs the length-4 characters of the file name. So we get the file name without the ending. We then append "with GTC.pdf" to this string so that we have the desired file name.

Now we want to write the new file to our database. To do this, we store the giftee's primary key in a variable and open a new window using the Files layout. Here we create a new entry and write the appropriate information in the fields. After we have closed the window again and released the DynaPDF reference in memory with DynaPDF.ReleaseAll, we give the portal a refresh so that the new file is also displayed directly.

Here you can find the script for today's implementation:

If [ MBS("DynaPDF.IsInitialized") = False ]
	Perform Script [ Specified: From list ; "InitDynaPDF" ; Parameter:    ]
End If
Set Variable [ $pdf ; Value: MBS("DynaPDF.New") ]
Set Variable [ $r ; Value: MBS("DynaPDF.OpenPDFFromContainer"; $pdf; Files::File) ]
Set Variable [ $r ; Value: MBS("DynaPDF.ImportPDFFile"; $pdf) ]
Set Variable [ $pages ; Value: $r ]
Set Variable [ $r ; Value: MBS("DynaPDF.OpenPDFFromContainer"; $pdf; $$GTC) ]
Set Variable [ $r ; Value: MBS("DynaPDF.ImportPDFFile"; $pdf; $pages+1) ]
Set Variable [ $Length ; Value: Length ( Files::File_Name )-4 ]
Set Variable [ $FileName ; Value: Left ( Files::File_Name ;$Length ) & " with GTC.pdf" ]
Set Variable [ $NewFile ; Value: MBS("DynaPDF.Save"; $pdf; $FileName;) ]
Set Variable [ $PK ; Value: Giftee::PrimaryKey ]
New Window [ Style: Document ; Name: "NewFile" ; Using layout: "Files" (Files) ]
New Record/Request
Set Field [ Files::File_Typ ; "pdf" ]
Set Field [ Files::File_Name ; $FileName ]
Set Field [ Files::File ; $NewFile ]
Set Field [ Files::ForeignKey ; $PK ]
Close Window [ Name: "NewFile" ; Current file ]
Set Variable [ $r ; Value: MBS("DynaPDF.ReleaseAll") ]
Refresh Portal [ Object Name: "Files" ]

We have reached our goal for today and will read again tomorrow.

1 Like
candy cane Monkeybread Monkey as an elf candy cane
Door 5 - Apple Contacts

Today is day 5 of our calendar and our monkey has a few calls to make on his iPhone. He just doesn't want to keep typing in the number and searching for the customer. It would be much easier if he could simply save the specific contacts in Apple Contacts with the touch of a button. That's no problem, because as a Mac user you can read and write data in Apple Contacts. We use our Contacts functions for this.

But before we can start, we first need to be able to assign phone numbers and email addresses to our contacts. Until now, this was very complicated because we had to leave the giftee layout and enter the data in the respective layouts of the tables. We would now like to change this. To do this, we go to our relationship diagram and double-click on the equal sign between our phone and giftee relationship. Then the Edit Relationship dialog opens and we can make settings. Here we can decide whether we can delete or create data records via this relationship. To do this, we can make a choice in the checkboxes at the bottom. We tick Allow creation of records in this table via this relationship and Delete related records in this table when a record is deleted in the other table for the Telphone table. This allows us to create records in our portal and if a gifte is deleted, the associated telephone numbers are also deleted. We now do the same for the mail addresses.

Now we add a button to our layout with which we export the data to Apple Contacts.

A contact is identified in the Apple phone book by an ID. We get this ID back when we create the contact for the first time. We save it in the database so that we can later change email addresses and telephone numbers in the contact. We add this ContactID as a field to the Giftee table. The field must not be on the layout so that we can read and write it, so we do not add it to the layout.

Now we come to the appropriate script. We first want to distinguish whether we are creating a new contact or whether we are updating a contact that has already been added by FileMaker. We can see whether a contact has already been created by the fact that the ContactID field is empty for a contact that does not exist yet.

If this ContactID field is empty, we create a new contact using the MBS function CNContactStore.NewContact. This function then returns the ContactID, which we store directly in our database. Now we want to enter the values for the contact. The CNContact.SetValue function is available for this purpose. Here we first enter the reference again in the parameters and then the name of the value that we want to change and the value that we want to set. In our case, for example, the names are givenName, familyName and birthday. The value for birthday is not so easy to pass because we have to pass it as JSON. A JSON is a text with a special data structure. In this text, for example, we have entries that are assigned via key-value pairs. So here we have an array that we can imagine as a list with the keys day, month and year, to each of which the appropriate values are assigned. You may find more information about JSON in another door ;-)

# Birthday
Set Variable [ $day ; Value: Day ( Giftee::Birthday ) ]
Set Variable [ $month ; Value: Month ( Giftee::Birthday ) ]
Set Variable [ $year ; Value: Year ( Giftee::Birthday ) ]
Set Variable [ $BDJSON ; Value: JSONSetElement ( "{}" ; 
	["day" ; $day ; JSONNumber]; 
	["month" ; $month ; JSONNumber]; 
	["year" ; $year ; JSONNumber] ) ]
Set Variable [ $JSONBDAYCon ; Value: MBS("CNContact.Value"; $CNID; "birthday") ]

The address is an easier thing to do. In the Apple phone book we can have not just one address, but several, e.g. to the office or home. Since we want to know exactly where the gifts are to be delivered, we limit ourselves to the home address. To add the address there is the function CNContact.AddPostalAddress. In the parameters of this function, we first enter the reference and the label for the address. This is followed by the street, city, state, zip code and country. We could also specify ISOCountryCode, subLocality or subAdministrativeArea. But we will leave these out now.

Set Variable [ $r ; Value: MBS("CNContact.AddPostalAddress"; $CNID; 
	"Home"; Giftee::Street; Giftee::City; Giftee::State; Giftee::Postcode; Giftee::Country) ]

All we need to do is add the telephone numbers and email addresses. We add the individual email addresses and telephone numbers in a similar way to the address, by calling certain functions. Here we use CNContact.AddPhoneNumber and CNContact.AddEmailAddress. In both functions, we first enter the ID, then the label name and finally the number or email address. We determine these values by looping through the two portals. First we go to the portal, we want to run through by specifying the object name of the portal. We gave this object the name in layout mode in the settings. In this Portal we go to the first data record and start the loop. After we have written the data to the address book, we go to the next entry in the portal within the loop. Here we see that for the telephone portal.

Go to Object [ Object Name: "Telephoneportal" ]
Go to Portal Row [ Select: Off ; First ]
Loop [ Flush: Always ]
	Set Variable [ $Number ; Value: Telephone::Number ]
	Set Variable [ $TypTel ; Value: Telephone::Type ]
	Set Variable [ $r ; Value: MBS("CNContact.AddPhoneNumber"; $CNID; $Number; $TypTel) ]
	Go to Portal Row [ Select: Off ; Next ; Exit after last: On ]
End Loop

Now we have to transfer this new contact to the address book using CNContactStore.AddContact, specifying the reference.

Let's deal with the case in the Else part that the contact already exists, i.e. our ContactID field is not empty. In this case, we save the reference number in a variable so that we can continue working with it. Now problems can occur when retrieving the contact, as there may be an incorrect value in our database. We can counteract this by using CNContact.JSON to get the contact as JSON. If this could not be found in the address book, we get an error back and can record this with MBS(“isError”). In the event of an error, we display a dialog. But if the contact exists in the phone book, it should be updated. To do this, we compare the old values with the new ones. We get the corresponding value with CNContact.Value, enter the ID and the name of the value and compare it with the value from the database in an If. If the values are not the same, the value is set again with CNContact.SetValue. With Birthday, we also get a JSON as a response with this function. To compare this, we also build a JSON from the date of birth in the database and compare the two JSON contents with each other using the MBS function JSON.EqualContent. If they do not match, we have already assembled our JSON that we can set as a value and can pass it.

# Birthday
Set Variable [ $day ; Value: Day ( Giftee::Birthday ) ]
Set Variable [ $month ; Value: Month ( Giftee::Birthday ) ]
Set Variable [ $year ; Value: Year ( Giftee::Birthday ) ]
Set Variable [ $BDJSON ; Value: JSONSetElement ( "{}" ; 
	["day" ; $day ; JSONNumber];
	["month" ; $month ; JSONNumber]; 
	["year" ; $year ; JSONNumber] ) ]
Set Variable [ $JSONBDAYCon ; Value: MBS("CNContact.Value"; $CNID; "birthday") ]
If [ MBS("JSON.EqualContent"; $BDJSON; $JSONBDAYCon) ≠ 1 ]
	Set Variable [ $r ; Value: MBS("CNContact.SetValue"; $CNID; "birthday"; $BDJSON) ]
End If

As the address, telephone numbers and email addresses are difficult to check for existence and changes, we take a radical approach here. We delete the address, all email addresses and telephone numbers from the contact and add them again as previously seen. To delete, we simply use CNContact.SetValue to pass an empty array to the corresponding value name, i.e. an open and a closed square bracket.

# Address
Set Variable [ $r ; Value: MBS("CNContact.SetValue"; $CNID; "postalAddresses"; "[]") ]
Set Variable [ $r ; Value: MBS("CNContact.AddPostalAddress"; $CNID; "Home"; 
	 Giftee::Street; Giftee::City; Giftee::State; Giftee::Postcode; Giftee::Country) ]

After we have made all the necessary changes, we need to apply these changes to the contacts. To do this, we use the CNContactStore.UpdateContact function, specifying the reference to save the changes.

Now we have done this and can transfer our customer data to the address book at the touch of a button.

Here is the complete script from today:

# Contacts in file Advent24

If [ Giftee::ContactID = "" ]
	# We need a new Contact
	Set Variable [ $CNID ; Value: MBS( "CNContactStore.NewContact" ) ]
	Set Field [ Giftee::ContactID ; $CNID ]
	Set Variable [ $r ; Value: MBS("CNContact.SetValue"; $CNID; "givenName"; Giftee::first_name) ]
	Set Variable [ $r ; Value: MBS("CNContact.SetValue"; $CNID; "familyName"; Giftee::last_name) ]
	Set Variable [ $day ; Value: Day ( Giftee::Birthday ) ]
	Set Variable [ $month ; Value: Month ( Giftee::Birthday ) ]
	Set Variable [ $year ; Value: Year ( Giftee::Birthday ) ]
	Set Variable [ $BDJSON ; Value: JSONSetElement ( "{}" ; 
		["day" ; $day ; JSONNumber]; 
		["month" ; $month ; JSONNumber];
		["year" ; $year ; JSONNumber] ) ]
	Set Variable [ $r ; Value: MBS("CNContact.SetValue"; $CNID; "birthday"; $BDJSON) ]
	Set Variable [ $r ; Value: MBS("CNContact.AddPostalAddress"; $CNID; "Home"; 
	 	 Giftee::Street; Giftee::City; Giftee::State; Giftee::Postcode; Giftee::Country) ]
	Go to Object [ Object Name: "Telephoneportal" ]
	Go to Portal Row [ Select: Off ; First ]
	Loop [ Flush: Always ]
		Set Variable [ $Number ; Value: Telephone::Number ]
		Set Variable [ $TypTel ; Value: Telephone::Type ]
		Set Variable [ $r ; Value: MBS("CNContact.AddPhoneNumber"; $CNID; $Number; $TypTel) ]
		Go to Portal Row [ Select: Off ; Next ; Exit after last: On ]
	End Loop
	# 
	Go to Object [ Object Name: "EmaiPortal" ]
	Go to Portal Row [ Select: Off ; First ]
	Loop [ Flush: Always ]
		Set Variable [ $Address ; Value: Email::Emailaddress ]
		Set Variable [ $TypMail ; Value: Email::Typ ]
		Set Variable [ $r ; Value: MBS("CNContact.AddEmailAddress"; $CNID; $Address; $TypMail) ]
		Go to Portal Row [ Select: Off ; Next ; Exit after last: On ]
	End Loop
	Set Variable [ $r ; Value: MBS("CNContactStore.AddContact"; $CNID) ]
	# 
Else
	# We have an ID in the FMDatabase 
	Set Variable [ $CNID ; Value: Giftee::ContactID ]
	Set Variable [ $Contact ; Value: MBS( "CNContact.JSON"; $CNID ; 1 ) ]
	If [ MBS("IsError") = 0 ]
		# Contact exists
		If [ MBS("CNContact.Value"; $CNID;"familyName") ≠ Giftee::last_name ]
			Set Variable [ $r ; Value: MBS("CNContact.SetValue"; $CNID; "familyName"; Giftee::last_name) ]
		End If
		# 
		If [ MBS("CNContact.Value"; $CNID;"givenName") ≠ Giftee::first_name ]
			Set Variable [ $r ; Value: MBS("CNContact.SetValue"; $CNID; "givenName"; Giftee::first_name) ]
		End If
		# Birthday
		Set Variable [ $day ; Value: Day ( Giftee::Birthday ) ]
		Set Variable [ $month ; Value: Month ( Giftee::Birthday ) ]
		Set Variable [ $year ; Value: Year ( Giftee::Birthday ) ]
		Set Variable [ $BDJSON ; Value: JSONSetElement ( "{}" ; 
			["day" ; $day ; JSONNumber]; 
			["month" ; $month ; JSONNumber]; 
			["year" ; $year ; JSONNumber] ) ]
		Set Variable [ $JSONBDAYCon ; Value: MBS("CNContact.Value"; $CNID; "birthday") ]
		If [ MBS("JSON.EqualContent"; $BDJSON; $JSONBDAYCon) ≠ 1 ]
			Set Variable [ $r ; Value: MBS("CNContact.SetValue"; $CNID; "birthday"; $BDJSON) ]
		End If
		# 
		# Address
		Set Variable [ $r ; Value: MBS("CNContact.SetValue"; $CNID; "postalAddresses"; "[]") ]
		Set Variable [ $r ; Value: MBS("CNContact.AddPostalAddress"; $CNID; 
			"Home"; Giftee::Street; Giftee::City; Giftee::State; Giftee::Postcode; Giftee::Country) ]
		# 
		# JSON of the FM Adress vergelichen mit Knoten JSON wenn nicht geich dann mit JSON anlegen
		Set Variable [ $r ; Value: MBS("CNContact.SetValue"; $CNID; "phoneNumbers"; "[]") ]
		Go to Object [ Object Name: "Telephoneportal" ]
		Go to Portal Row [ Select: Off ; First ]
		Loop [ Flush: Always ]
			Set Variable [ $Number ; Value: Telephone::Number ]
			Set Variable [ $TypTel ; Value: Telephone::Type ]
			Set Variable [ $r ; Value: MBS("CNContact.AddPhoneNumber"; $CNID; $Number; $TypTel) ]
			Go to Portal Row [ Select: Off ; Next ; Exit after last: On ]
		End Loop
		# 
		Set Variable [ $r ; Value: MBS("CNContact.SetValue"; $CNID; "emailAddresses"; "[]") ]
		Go to Object [ Object Name: "EmaiPortal" ]
		Go to Portal Row [ Select: Off ; First ]
		Loop [ Flush: Always ]
			Set Variable [ $Address ; Value: Email::Emailaddress ]
			Set Variable [ $TypMail ; Value: Email::Typ ]
			Set Variable [ $r ; Value: MBS("CNContact.AddEmailAddress"; $CNID; $Address; $TypMail) ]
			Go to Portal Row [ Select: Off ; Next ; Exit after last: On ]
		End Loop
		# Save
		Set Variable [ $r ; Value: MBS("CNContactStore.UpdateContact"; $CNID) ]
		# 
	Else
		# We have an ID but contact could not be found
		Show Custom Dialog [ "Error" ; "Contact could not be found" ]
	End If
End If

I look forward to meeting you again tomorrow. Until then, have a peaceful time.

1 Like
candy cane Monkeybread Monkey as an elf candy cane
Door 6 - Sending Out Wishlist Forms

Today it is time for us to take care of the wish lists of the giftees. We want to prepare a form that we send by e-mail to the giftees, who fill out this form. In a later door we can then read this information from our returned document.

To do this, we first create the template we want to send. To do this, we first need a simple PDF file on which we can position our fields. We have the option of either building this form with DynaPDF, or we can use a pre-designed PDF in which we create fields. I chose this method now. The document was created with Pages and then output as a PDF via the print settings. We want to write several labels in this file. The labels are not only important for the labeling, so that the customer knows which data is to be entered there, but also for positioning the form fields. We want to position these behind the labels.

Now we have everything we need to create our template.

Since we are working with DynaPDF, we first have to initialize it again, as we already explained in door 4.

If [ MBS("DynaPDF.IsInitialized")=0 ]
	Perform Script [ Specified: From list ; "InitDynaPDF" ; Parameter:    ]
End If

We place our form in the same folder as the database. This allows us to determine the path to the form with a small workaround. We first determine the path to the current database with Get(FilePath) to determine the path to the current database. However, this is a FileMaker path that looks a little different to a native path. With the MBS function Path.FileMakerPathToNativePath we can convert a FileMaker path to a native path. Now we use the MBS function Path.RemoveLastPathComponent to remove the last component of this path, i.e. the file name of the FM database, from the path, so that we get the folder in which the two items are located. Now we append the name of the form file. To do this, we use Path.AddPathComponent and specify the folder path and the file name. We also want to specify the path where we will save our template file. Again, we use the folder path and append the new file name WishList.pdf.

Set Variable [ $PathFM ; Value: MBS("Path.FileMakerPathToNativePath"; Get(FilePath)) ]
Set Variable [ $Folder ; Value: MBS("Path.RemoveLastPathComponent"; $PathFM) ]
Set Variable [ $FormPath ; Value: MBS("Path.AddPathComponent"; $Folder;"WishListForm.pdf") ]
Set Variable [ $TemplatePath ; Value: MBS("Path.AddPathComponent"; $Folder;"WishList.pdf") ]

After we have determined the paths for loading the form file and for saving the template file, we would like to load the form file into a PDF working environment so that we can modify it.

To do this, we first create a new working environment with DynaPDF.New, open the form file for import with DynaPDF.OpenPDFFromFile, specifying the reference and path, and import the form file into our working environment with DynaPDF.ImportPDFFile. So that we can now also set the fields on the page, i.e. change the page, we have to make the page editable. To do this, we call the DynaPDF.EditPage function, specifying the reference and the page to be edited, in our case the first one.

Set Variable [ $pdf ; Value: MBS("DynaPDF.New") ]
Set Variable [ $r ; Value: MBS("DynaPDF.OpenPDFFromFile"; $pdf; $FormPath) ]
Set Variable [ $r ; Value: MBS("DynaPDF.ImportPDFFile"; $pdf) ]
Set Variable [ $r ; Value: MBS("DynaPDF.EditPage"; $pdf; 1) ]

Before we set the fields, we first want to briefly configure their appearance. We can choose the background color and the color of the border. Our fields should be light green. We set the border color to white so that it does not disturb us. The color values are set as RGB values with DynaPDF.RGB.

Set Variable [ $r ; Value: MBS("DynaPDF.SetFieldBackColor"; $pdf; MBS("DynaPDF.RGB"; 214; 247; 204)) ]
Set Variable [ $r ; Value: MBS("DynaPDF.SetFieldBorderColor"; $pdf; MBS("DynaPDF.RGB"; 255; 255; 255)) ]

Now it's time for our fields. To create these fields, we use a self-written function that creates a field and positions it appropriately. This custom function is called CreatTextfield and has two parameters. The first parameter is the name of the field to be created and the second is the name of the label behind which the field is to be positioned. We will get to know this function in more detail in a moment.

Set Variable [ $r ; Value: CreateTextfield ( "one" ; "1." ) ]
Set Variable [ $r ; Value: CreateTextfield ( "two" ; "2." ) ]
Set Variable [ $r ; Value: CreateTextfield ( "three" ; "3." ) ]
Set Variable [ $r ; Value: CreateTextfield ( "four" ; "4." ) ]
Set Variable [ $r ; Value: CreateTextfield ( "five" ; "5." ) ]
Set Variable [ $r ; Value: CreateTextfield ( "six" ; "6." ) ]
Set Variable [ $r ; Value: CreateTextfield ( "seven" ; "7." ) ]
Set Variable [ $r ; Value: CreateTextfield ( "eight" ; "8." ) ]
Set Variable [ $r ; Value: CreateTextfield ( "nine" ; "9." ) ]
Set Variable [ $r ; Value: CreateTextfield ( "ten" ; "10." ) ]

After we have created our fields for the Christmas wishes, we would now like to create a field that is not visible, but is extremely important for our later import. We want to create an ID field that contains the primary key of our giftee. In this way, we can ensure that our document can be assigned to exactly this one giftee without any misunderstanding.

First we create the field with DynaPDF.CreateTextField in our PDF environment with the name ID. Since the text field has no parent field, we enter a -1 here. With the 0 in the parameters we indicate that the field has a single line, because we deactivate multiline. The 200 in the parameters describes the maximum length of the text of 200 characters. The last 4 parameters are for position and size. We position the field outside the visible PDF area, with a negative y value. With a width of 300 pixels and a height of 20 pixels, it offers enough space for our primary key.

Since the field should be invisible and read only, we set the corresponding properties in DynaPDF.SetFieldFlags.

Set Variable [ $IDField ; Value: MBS("DynaPDF.CreateTextField"; $pdf;"ID"; -1; 0; 200; 120; -50; 300; 20) ]
Set Variable [ $r ; Value: MBS("DynaPDF.SetFieldFlags"; $pdf; $IDField; "Hidden ReadOnly Invisible NoView"; 1) ]

Now we have done everything on our page and we can close the page again with DynaPDF.EndPage and write the template to the desired path with DynaPDF.OpenOutputFile. We can then release the environment again.

Set Variable [ $r ; Value: MBS("DynaPDF.EndPage"; $pdf) ]
Set Variable [ $r ; Value: MBS("DynaPDF.OpenOutputFile"; $pdf; $TemplatePath) ]
Set Variable [ $r ; Value: MBS("DynaPDF.Release"; $pdf) ]

We have already talked about the custom function CreateTextfield and want to take a closer look at this function now. This function has two input parameters FieldName and SearchText. A field is created with DynaPDF.CreateTextField as we have just seen. Before we can take this step, however, we must first determine the position of the field. With DynaPDF.FindText we first search for a specific text on the page. In our case, the name of the label. We receive the position of the label text as a return using eight coordinates that describe the corner points of a rectangle that completely covers the text we are looking for (x1 y1 x2 y2 x3 y3 x4 y4). Since we always want to have the same distance to the side edges of the fields, we only have to look for a y value of the upper edge and a y value of the lower edge when determining the missing data. We therefore use MiddleWords to determine the 1st y and the 3rd y value. Since our numbers are still in text format, but we want to calculate with them, we convert them into numbers with Math.TextToNumber. We can now call our already familiar DynaPDF.CreateTextField function. Let's take a look at the last 4 parameters that make up the positioning. The 120 is a fixed value and describes the distance of the field to the left side edge (x). Now it's the turn of the upper side edge. As we also want to have a little space above and below our text, we subtract 3 pixels from the y value. This results in the y value for the top edge. The value 300 is a fixed value and describes the width of the field. This is always the same in this example. Our last value is the height of the field. We calculate this height by subtracting the lower y value from the upper y value. The difference describes the height of the search result. As we have just mentioned that we want to leave a little space at the top and bottom, we add 5 to the height here.

Let ( 
	[ 
	Search = MBS( "DynaPDF.FindText"; $pdf; SearchText; 0 );
	posText1 = MiddleWords ( Search ; 2 ; 1 );
	posNum1 = MBS( "Math.TextToNumber"; posText1 );
	posText2 = MiddleWords ( Search ; 6 ; 1 );
	posNum2 = MBS( "Math.TextToNumber"; posText2 );
	Field = MBS( "DynaPDF.CreateTextField"; $pdf; FieldName; -1; 0; 200; 120; posNum1-3; 300; posNum2-posNum1+5 )
	]; 

	Field)

Export for the individual giftees

Now it's time for us to prepare the documents to be sent to our customers. We have already described that we need to install a protection so that we can assign the correct wish list to each giftee and fill the invisible ID field with the primary key of the giftee.

To do this, we now load our template with the already known functions into a new work environment and set the value with DynaPDF.SetTextFieldValue, specifying the reference, the name of the field to be set and the primary key.

As we do not want to save the PDF in a file this time but in a container, we use the DynaPDF.Save function to save the PDF file. Finally, we release the working environment.

If [ MBS("DynaPDF.IsInitialized")=0 ]
	Perform Script [ Specified: From list ; "InitDynaPDF" ; Parameter:    ]
End If

Set Variable [ $PathFM ; Value: MBS("Path.FileMakerPathToNativePath"; Get(FilePath)) ]
Set Variable [ $Folder ; Value: MBS("Path.RemoveLastPathComponent"; $PathFM) ]
Set Variable [ $TemplatePath ; Value: MBS("Path.AddPathComponent"; $Folder;"WishList.pdf") ]
Set Variable [ $pdf ; Value: MBS("DynaPDF.New") ]
Set Variable [ $r ; Value: MBS("DynaPDF.OpenPDFFromFile"; $pdf; $TemplatePath) ]
Set Variable [ $r ; Value: MBS("DynaPDF.ImportPDFFile"; $pdf) ]
Set Variable [ $r ; Value: MBS("DynaPDF.SetTextFieldValue"; $pdf; "ID"; Giftee::PrimaryKey) ]
Set Variable [ $File ; Value: MBS("DynaPDF.Save"; $pdf; "Wishlist_" & Giftee::first_name & "_" & Giftee::last_name & ".pdf") ]
Set Field [ Giftee::WishlistOut ; $File ]

Set Variable [ $r ; Value: MBS("DynaPDF.ReleaseAll") ]

We can now create the individual ones and send them out to our recipients to fill in diligently.

That's it for today with our door and we'll see you again tomorrow.

candy cane Monkeybread Monkey as an elf candy cane
Door 7 - Open Files With Roundabout Route Via Temporary Files

Welcome to day 7 of our advent calendar. Today we would like to add a function to our document portal from day 3. So far we can add documents to this portal via drag and drop, what we are missing now is the possibility to open them from FileMaker.

To do this, we use the MBS function Files.LaunchFile. We can pass a path to this function and the file or folder located at this path is then launched with the standard program provided for this purpose.

But we realize now that we have a container value and no path, how do we do this now? You could perhaps come up with the idea of writing the path to a field when reading it in. This procedure becomes problematic, if we move the original file to a new location and the path is no longer correct. So we have to take a different way. We save the file in the temporary folder and display it from there.

To do this, we first create the path where we want to save the file. We determine the temporary folder with the Folders.UserTemporary function. In the Folders area, we also have further functions for other special folders such as Desktop, Applications or Documents.

Once we have the path to this folder, we still need to append the file name with the correct file extension. The file name is made up of the primary key and the file name. The file name already contains the file extension. We now put together this determined file name with the Path.AddPathComponent function, specifying our temporary path in the parameters.

Now that we have the path, we can write the file to this path with Files.WriteFile. In the parameters, we first specify the container value of the file to be written and then the composite path. Next, we use isError to check whether there was a problem writing this file. If there was none, we now open the file with Files.LaunchFile. Again, we can make an error query so that the user is not confused that no file is displayed if we cannot open the file for some reason.

Here is the script for today:

Set Variable [ $Temp ; Value: MBS("Folders.UserTemporary") ]
Set Variable [ $Name ; Value: Files::PrimaryKey & Files::File_Name ]
Set Variable [ $Path ; Value: MBS("Path.AddPathComponent"; $Temp; $Name) ]
Set Variable [ $r ; Value: MBS("Files.WriteFile"; Files::File; $Path) ]
If [ MBS("IsError")=0 ]
	Set Variable [ $r ; Value: MBS("Files.LaunchFile"; $Path) ]
	If [ MBS("IsError") ]
		Show Custom Dialog [ "Error" ; "File can not be opened" ]
	End If
Else
	Show Custom Dialog [ "Error" ; "File can not be written to Temp" ]
End If

Now we can add the button to the portal and pass the script to the button.

That's it for today's door and I hope to see you again tomorrow.

candy cane Monkeybread Monkey as an elf candy cane
Day 8 - Weather data with JSON

Today is day 8 of our advent calendar and winter is getting a bit chilly for our monkey. To avoid any surprises at Christmas, he wants to be able to call up the current temperature and weather conditions for each customer in FileMaker.

We would like to retrieve the weather data from a weather database. The weather data can be found on the website openweathermap.org. A few years ago we already made a video about how we implement this with FileMaker nativ functions. Today I would like to show you how this works with the JSON functions of MBS. These have the advantage that they can work very quickly, even with large amounts of data.

But let's start with the question: What is JSON?

JSON stands for JAVA Script Object Notation and originally comes from web programming with JavaScript. JSON is a data format that is used to exchange data between two applications. The data can be very complex, it is particularly suitable for multidimensional arrays. In FileMaker, we can imagine the concept of arrays as having a list of which the elements can also consist of lists. What seems complicated to us now can be realized very well and quickly in JSON. So we can store large amounts of data with JSON, we can make queries, i.e. process them, and they are well suited for transferring between two applications. We will also see this later in our example. Despite the complexity, data in JSON remains very clear and readable for humans with the right formatting. The data format is also very flexible, so that data can be added and deleted without essentially changing the basic structure. A JSON text consists of objects and arrays. We will now take a closer look at how such objects and arrays are structured. The smallest possible JSON is an empty array that can be described by two square brackets: or an empty object. This consists of two round brackets: { }

Let's fill this object with life. We fill an object with elements. An element consists of a key and a value. These two are separated by a colon. An element can look like this:

{name:“Toni“}

The key indicates which information is involved. The value can be of different types. On the one hand, we have a text. This is then framed with quotation marks. Then we have the numbers. We do not need quotation marks for numbers. There are also boolean values that can be true or false. Then we have the null object which is effectively a placeholder, it means neither 0 as a number nor null as text, but simply that we have not assigned a value to the key. In addition, an element can also contain an object again, allowing information to be nested in multiple dimensions. Here, for example, we have the Person element, which in turn contains information about the person, such as their name. Last but not least, we have arrays. Roughly speaking, the array is a list of things. For example, if I want to give a person hobbies, I can write them in such an array. The individual entries are separated by a comma and the list is enclosed in square brackets. With arrays, we can even put different types in the list. For example, I can add strings and numbers to the same array. An array can also contain JSON objects or an array itself.

The individual data types with examples:

String
{ "name" : "Stefanie"}
Numbers
{ "age" : 29}
Boolean (true or false)
{ "PetOwner" : false}
Null 
{ "name" : null }
Object
{ "person" : { "name" : "Stefanie"} }
Array
{ "Pets" : [ "dog", "cat", "mice" ] }

A single piece of information in an object is not yet fun and not yet very useful. For this reason, we now want to have several elements in one object. We realize this by separating them with a comma.

{ 
"LastName":  "Smith" ,
 "FirstName":  "Toni" 
}

We have just said that an element can also receive an object as a value. This is useful, for example, if the person we are describing has a pet that also has its own properties. We can see that in this example:

{
"Name":  "Smith",
"FirstName": "Toni",
"Hobbies":  ["programming", "reading", "taking photos"],
"Dog":  { "name":"Benni", 
                 "age":  6,
                "LikeTreats": true
    }

But what if we now have two persons and want to merge them into one JSON. In this case, we can use the array and treat the two people as list entries in this array:

{
person: 
[
{
"Name":  "Smith",
"FirstName": "Toni",
"Hobbies":  ["programming", "reading", "taking photos"],
"Dog":  { "name":"Benni", 
                 "age":  6,
                "LikeTreats": true
    },

{
"Name":  "Doe",
"FirstName": "Jane",
"Hobbies":  ["dancing", "karate", "writing"],
"Dog":  { "name":"Nala", 
                 "age":  2,
                "LikeTreats": true
    }
]
}

Now we have seen what a JSON can look like. Now we want to be able to read it out and here we have FileMaker's own functions, which are already very sufficient for small applications with little data and infrequent retrieval. But if, for example, you now have data records such as our person document with even more information and instead of 2 with 20,000 person data records, then such a query can take a certain amount of time. In this context, MBS offers you JSON functions that work somewhat more efficiently and I will introduce them to you today as part of our example.

Now let's move on to our example. We want to read the weather data from an API. API stands for Application Programming Interface and therefore describes an interface between two applications. Data is therefore exchanged. And like every API, the Openweather Map API has its own special features. Let's take a look at the API together. To work with the API, we need an API key. We get this when we log in from the site. It is free for the data we want to query today. So that we can look at the documentation of the API, we go to API and there under Current Weather Data to API doc.

Here we now have various options for determining the weather for a specific region. We use the option to determine the weather according to the city name, because we already have this in our data set and do not have to determine the longitude and latitude first. We get the desired JSON if we adapt a web address accordingly:
https://api.openweathermap.org/data/2.5/weather?q={city name},{state code},{country code}&appid={API key}

Let's adapt this, so that we can view such a JSON in the browser. We replace the terms and the curly brackets with values so that our web address looks like this, for example, if our API key were 123:
https://api.openweathermap.org/data/2.5/weather?q=**Miami**,**FL**,**US**&appid=**123**

Then we get a JSON that looks like this after formatting:

{
	"coord":	{
		"lon":	-80.1937,
		"lat":	25.7743
	},
	"weather":	[
		{
			"id":	802,
			"main":	"Clouds",
			"description":	"scattered clouds",
			"icon":	"03d"
		}
	],
	"base":	"stations",
	"main":	{
		"temp":	301.3,
		"feels_like":	305.79,
		"temp_min":	299.82,
		"temp_max":	302.05,
		"pressure":	1014,
		"humidity":	81,
		"sea_level":	1014,
		"grnd_level":	1014
	},
	"visibility":	10000,
	"wind":	{
		"speed":	0.45,
		"deg":	295,
		"gust":	2.68
	},
	"clouds":	{
		"all":	44
	},
	"dt":	1731336923,
	"sys":	{
		"type":	2,
		"id":	2009435,
		"country":	"US",
		"sunrise":	1731324976,
		"sunset":	1731364416
	},
	"timezone":	-18000,
	"id":	4164138,
	"name":	"Miami",
	"cod":	200
}

We only need this part of the information for our purposes. We want to read out this part in our script right now.

…
"weather":	[
		{
			"id":	802,
			"main":	"Clouds",
			"description":	"scattered clouds",
			"icon":	"03d"
		}
	],
	"base":	"stations",
	"main":	{
		"temp":	301.3,
		"feels_like":	305.79,
		"temp_min":	299.82,
		"temp_max":	302.05,
		"pressure":	1014,
		"humidity":	81,
		"sea_level":	1014,
		"grnd_level":	1014
	},
…

So let's start with our script: To be able to read information from a website in FileMaker, we use Insert from URL and switch off the dialog. We specify the variable $JSON as the target. This is where our JSON ends up with which we can continue working. Under Specify URL, we then enter the composite URL that we have determined. This then looks like this with the inserted fields:

"https://api.openweathermap.org/data/2.5/weather?q=" & Giftee::City & "," & 
	Giftee::State & "," & Giftee::Country & "&appid=" & $$APIKey

As you need your own API key, I have replaced the key with a global variable that you set in another script.

Now we can start reading the JSON. As described earlier, the JSON is structured in layers. We now have to make our way through this JSON. To do this, we go through the individual levels. First we want to read out the temperature. To do this, we go to the key main and then to temp. We can now structure this path in different ways. Either we separate the levels with a line break or we use dots as in most conventions. We now use dots here so that we stay very close to the notation for the FM custom function in connection with JSON. This means that our path now looks like this:
main.para

Now we can also determine the other paths. We find these under weather and now we have an array here. For an array, we need to say which number of elements we would like to have. We would like to have the first element from the array. But now we don't write a 1, but a 0. This is because in computer science, counting often doesn't start at one, but at 0. This is also the case with JSON. We now also have various options for writing the array. First of all, like with FM's own JSON functions, when we address an array we can put a square bracket around the 0: [0]. But because we use MBS functions in combination with JSON, we can simply omit the brackets. In the array we then get the value of description or icon. So now our three paths look like this:
main.temp
weather.0.description
weather.0.icon

We can now pass each of these three paths to the MBS function JSON.GetPathItem to get the value. In the parameters, we first specify the JSON to query from, then the path and finally we specify whether we want to get the result as JSON or as a value. This means if, for example, we were to select JSON for our description, which comes back as a string (0), we would get back a text with quotation marks. However, as we want the texts without quotation marks, we enter a 1 here. With our temperature, which is a number, this question is more difficult to answer. This is because the MBS FileMaker Plugin converts the values into the regional format. As I have set the regional format for Germany, the decimal point is a comma. But I don't want that now because I would like to have the number with a point in the database to avoid confusion. For this reason, I have the JSON returned here, as the decimal separator in JSON is always a point.

A brief note on JSON: JSON is case sensitive, so pay attention to the exact spelling of the keys.

We get a text back as an icon, but this does not say much about our weather. You can find tables for these codes on the internet and we need to translate them into emojis.

We now describe this table with a combination of if and elseif. It looks like this:

# Icon in file Advent24

Set Variable [ $iconCode ; Value: Get(ScriptParameter) ]
If [ $iconCode="01d"or $iconCode="01n" ]
	Set Variable [ $icon ; Value: "☀️" ]
Else If [ $iconCode="02d" or $iconCode="02n" ]
	Set Variable [ $icon ; Value: "🌤" ]
Else If [ $iconCode="03d" or $iconCode="03n" ]
	Set Variable [ $icon ; Value: "🌥" ]
Else If [ $iconCode="04d" or $iconCode="04n" ]
	Set Variable [ $icon ; Value: "☁️" ]
Else If [ $iconCode="09d" or $iconCode="09n" ]
	Set Variable [ $icon ; Value: "🌦" ]
Else If [ $iconCode="10d" or $iconCode="10n" ]
	Set Variable [ $icon ; Value: "🌧" ]
Else If [ $iconCode="11d" or $iconCode="11n" ]
	Set Variable [ $icon ; Value: "⛈️" ]
Else If [ $iconCode="13d" or $iconCode="13n" ]
	Set Variable [ $icon ; Value: "🌨" ]
Else If [ $iconCode="50d" or $iconCode="50n" ]
	Set Variable [ $icon ; Value: "🌫" ]
Else
	Set Variable [ $icon ; Value: "" ]
End If
Exit Script [ Text Result: $icon ]

As you can see, we have outsourced this as a single script so that the main weather script remains clearer. We now only have to call this in the weather script and then set the fields.

# Weather in file Advent24

Insert from URL [ Select ; With dialog: Off ; Target: $JSON ; 
	"https://api.openweathermap.org/data/2.5/weather?q=" & 
	 Giftee::City & "," & Giftee::State & "," & Giftee::Country & "&appid=" & $$APIKey ]
Set Variable [ $Temp ; Value: MBS("JSON.GetPathItem"; $JSON; "main.temp"; 0) ]
Set Variable [ $description ; Value: MBS("JSON.GetPathItem"; $JSON;"weather.0.description"; 1) ]
Set Variable [ $icon ; Value: MBS("JSON.GetPathItem"; $JSON;"weather.0.icon"; 1) ]
Perform Script [ Specified: From list ; “Icon” ; Parameter: $icon ]
Set Variable [ $icon ; Value: Get(ScriptResult) ]
Set Field [ Giftee::Weather_temperature ; $Temp ]
Set Field [ Giftee::Weather_weather ; $description ]
Set Field [ Giftee::Weather_icon ; $icon ]

Our monkey can now check the weather for every present. I hope you enjoyed it again and that we'll see you again tomorrow.

1 Like
candy cane Monkeybread Monkey as an elf candy cane
Day 9 - Archives

Our monkey likes to have things together in a compact form. For this reason, it also likes archives and would like to have the option of creating an archive from the children's information and the individual files in the portal. We want to implement this now.

We can create various archives with the MBS plugin. The following formats are available: 7zip, ar, arbsd, argnu, arsvr4, bsdtar, cd9660, cpio, gnutar, iso, iso9660, mtree, mtree-classic, newc, odc, oldtar, pax, paxr, posix, raw, rpax, shar, shardump, ustar, v7tar, v7, warc, xar und zip.

Probably the best known is the zip format. We also want to use this today. To do this, we first create a new archive with Archive.Create. In the parameters, we first specify zip as the format and then have to decide on a filter. Depending on the individual formats, there are various filters that can be specified. For the zip format, you can choose between "store" or "deflate", whereby "deflate" provides loss-free data compression on the files and "store" does not apply any compression. Optionally, we can also specify the desired storage location for the zip file. If this is on the hard disk, we simply enter the path here, otherwise we leave this parameter empty. The other two parameters are further options, such as encryption and a password for the archive. However, we do not need these today.

We want to compress our zip archive with deflate and save it to the desktop. This means we create a path. To do this, we use Path.AddPathComponent again and this time the Folders.UserDesktop function to determine the path to the desktop.

Set Variable [ $Path ; Value: MBS("Path.AddPathComponent"; MBS("Folders.UserDesktop"); 
	Giftee::first_name & "_" & Giftee::last_name & ".zip") ]
Set Variable [ $Archive ; Value: MBS("Archive.Create"; "zip"; "deflate"; $Path) ]

As long as the archive is open, we can add files. We now want to do this with our portal files. To do this, we first go to our portal with Go to Object and enter the name of our portal here. Then we go through the portal entry by entry in a loop. To do this, we first get the file into a variable and then write this file into the archive using Archive.AddContainer. In the parameters of this function, we specify the file first and then the file name that the file should have in the archive.

Go to Object [ Object Name: "Files" ]
Go to Portal Row [ Select: Off ; First ]
Loop [ Flush: Always ]
	Set Variable [ $Data ; Value: Files::File ]
	Set Variable [ $r ; Value: MBS("Archive.AddContainer"; $Data; Files::File_Name) ]
	Go to Portal Row [ Select: Off ; Next ; Exit after last: On ]
End Loop

If you want to write a file from the disk to an archive, use the Archive.AddFile function and first enter one or a list of file names that are to be transferred from a specific folder to the archive. Then enter the path to this folder.

Now we not only want to transfer the files to a recipient, but also their personal data. We write these in a text file. To do this, we first put together the text that we want to have in this file and run through the two portals for telephone and e-mail in the same way as shown above with the files portal.

Set Variable [ $text ; Value: "ID: " & Giftee::PrimaryKey & 
	"¶Name: " & Giftee::first_name & " " & Giftee::last_name & 
	"¶Birthday: " & Giftee::Birthday & 
	"¶Address:¶" & Giftee::Street & "¶" & Giftee::Postcode & " " & 
	Giftee::City & "¶" & Giftee::State & " " & Giftee::Country & "¶¶" ]
Set Variable [ $Text ; Value: $text & "¶¶Telephone:" ]
Go to Object [ Object Name: "Telephoneportal" ]
Go to Portal Row [ Select: Off ; First ]
Loop [ Flush: Always ]
	Set Variable [ $PhoneTyp ; Value: Telephone::Type ]
	Set Variable [ $PhoneNumber ; Value: Telephone::Number ]
	If [ $PhoneNumber ≠ "" ]
		Set Variable [ $Text ; Value: $text & "¶"& $PhoneTyp & ": " & $PhoneNumber ]
	End If
	Go to Portal Row [ Select: Off ; Next ; Exit after last: On ]
End Loop
# 
Set Variable [ $Text ; Value: $text & "¶¶Email:" ]
Go to Object [ Object Name: "EmaiPortal" ]
Go to Portal Row [ Select: Off ; First ]
Loop [ Flush: Always ]
	Set Variable [ $MailTyp ; Value: Email::Typ ]
	Set Variable [ $MailAddress ; Value: Email::Emailaddress ]
	If [ $MailAddress ≠ "" ]
		Set Variable [ $Text ; Value: $text & "¶"& $MailTyp & ": " & $MailAddress ]
	End If
	Go to Portal Row [ Select: Off ; Next ; Exit after last: On ]
End Loop

We then write this composite text to the archive as a text file using Archive.AddText, specifying the text, the desired encoding and the file name.

Set Variable [ $r ; Value: MBS("Archive.AddText"; $Text; "UTF-8"; 
	Giftee::first_name & "_" & Giftee::last_name & "_Data.txt") ]

Because we now have all the data we want in our archive, we close it again with Archive.Close. If we had decided at the beginning that we wanted to have the archive returned as a container value instead of saving it in a file, we could pass the name of the file as a parameter here.

Set Variable [ $r ; Value: MBS("Archive.Close") ]

Now we can try out our script and voila it creates an archive with the desired files.

I hope to see you again tomorrow for the 10th door.

candy cane Monkeybread Monkey as an elf candy cane
Day 10 - Reading The Wish List

Welcome to Door 10. A few days have passed since we sent out our wish list forms and the first wish lists are slowly rolling in. It's time for us to incorporate readout into our solution. For this reason, we first create a container in which we can place the file to read it out and create a new script. Since we also need DynaPDF here, we must first initialize it. Then we load the file from the container into a newly created working environment as we have already seen in the previous doors.

If [ MBS("DynaPDF.IsInitialized")=0 ]
	Perform Script [ Specified: From list ; “InitDynaPDF” ; Parameter:    ]
End If
# 
Set Variable [ $pdf ; Value: MBS("DynaPDF.New") ]
Set Variable [ $r ; Value: MBS("DynaPDF.OpenPDFFromContainer"; $pdf; Giftee::WishlistIn) ]
Set Variable [ $r ; Value: MBS("DynaPDF.ImportPDFFile"; $pdf) ]

Now it's time to read the fields. First, we read the ID field to compare it with our primary key in the database so that we can make sure that the document we have received also belongs to the giftee and has not been imported into the wrong data record. We use the DynaPDF.GetField function to query a field. We first pass the reference and the name of the field to the function as parameters. Then what information we want from the field. In this case, we are only interested in the value, but text color or field flags could also be queried, for example. If this function returns an error or the ID does not correspond to the primary key, we return an error message to the user and exit the script.

Set Variable [ $ID ; Value: MBS("DynaPDF.GetField"; $pdf; "ID" ; "Value") ]
If [ MBS("IsError") or $ID ≠ Giftee::PrimaryKey ]
	Show Custom Dialog [ "Wrong File"; 
		"This file does not correspond to the original template" ]
	Set Variable [ $r ; Value: MBS("DynaPDF.ReleaseAll") ]
	Exit Script [ Text Result: "Wrong File" ]

But if the ID corresponds to the primary key, then we query the values of the individual fields and write them to variables first. We then assemble the variables into a list.

Else
	Set Variable [ $one ; Value: MBS("DynaPDF.GetField"; $pdf; "one"; "Value") ]
	Set Variable [ $two ; Value: MBS("DynaPDF.GetField"; $pdf; "two"; "Value") ]
	Set Variable [ $three ; Value: MBS("DynaPDF.GetField"; $pdf; "three"; "Value") ]
	Set Variable [ $four ; Value: MBS("DynaPDF.GetField"; $pdf; "four"; "Value") ]
	Set Variable [ $five ; Value: MBS("DynaPDF.GetField"; $pdf; "five"; "Value") ]
	Set Variable [ $six ; Value: MBS("DynaPDF.GetField"; $pdf; "six"; "Value") ]
	Set Variable [ $seven ; Value: MBS("DynaPDF.GetField"; $pdf; "seven"; "Value") ]
	Set Variable [ $eight ; Value: MBS("DynaPDF.GetField"; $pdf; "eight"; "Value") ]
	Set Variable [ $nine ; Value: MBS("DynaPDF.GetField"; $pdf; "nine"; "Value") ]
	Set Variable [ $ten ; Value: MBS("DynaPDF.GetField"; $pdf; "ten"; "Value") ]
	Set Field [ Giftee::Wishes ; $one & "¶" & $two& "¶" & $three& "¶" & 
		$four& "¶" & $five& "¶" & $six& "¶" & $seven& "¶" & 
		$eight& "¶" & $nine& "¶" & $ten ]
	Set Variable [ $r ; Value: MBS("DynaPDF.ReleaseAll") ]
End If

Now, of course, we also want to display our data. On the one hand, we could display a simple custom dialog, but we also have the option of displaying a list on which we can even select things, e.g. what the recipient really gets from our Christmas Elf under the tree. The ListDialog is a dialog that can be called centrally in our solution. This means that we could, for example, make all the settings in one script and then display them in another script. However, this also means that the dialog may still contain data from a previous use in the solution. For this reason, we first call ListDialog.Reset to reset all settings so that we can set them according to our wishes without having legacy data. We can use ListDialog.SetWindowTitle to set the title of the window and ListDialog.SetPrompt to set the text in the dialog. We can also display checkboxes in front of our list elements. This allows us to make a more precise selection straight away. We show the checkboxes by specifying the value 1 in ListDialog.SetShowCheckboxes. We actually have a list with the values we want to have in the list. We can pass this list to the ListDialog.AddItemsToList function. This adds our entries to the list. If we want to add a single value to the list, we can also use the function ListDialog.AddItemToList. Now everything is set up the way we want it and we can display the list dialog with ListDialog.ShowDialog.

Set Variable [ $r ; Value: MBS("ListDialog.Reset") ]
Set Variable [ $r ; Value: MBS("ListDialog.SetWindowTitle"; "Wish List") ]
Set Variable [ $r ; Value: MBS("ListDialog.SetPrompt"; 
	"Wishes of " & Giftee::first_name & " " & Giftee::last_name) ]
Set Variable [ $r ; Value: MBS("ListDialog.SetShowCheckboxes"; 1 ) ]
Set Variable [ $r ; Value: MBS("ListDialog.AddItemsToList"; Giftee::Wishes ) ]
Set Variable [ $r ; Value: MBS("ListDialog.ShowDialog") ]

Now we can view and select the entries within the dialog. After the dialog has been closed, we can query the list of items that have been touched. For this we use the function ListDialog.GetCheckedTitles. We then save this list in a field.

Set Variable [ $List ; Value: MBS( "ListDialog.GetCheckedTitles" ) ]
Set Field [ Giftee::Presents ; $List ]

We have now read in our wish list. We'll meet again tomorrow and continue to prepare for the delivery of the presents.

1 Like
candy cane Monkeybread Monkey as an elf candy cane
Day 11 - Know Your Way

Today we are already on day 11 of our advent calendar. Our little monkey would like to take a look at the region where the recipients live. The best way to do this is to take a look at the map. MBS has the MapView functions for Mac and iOS to display maps from Apple Maps.

To do this, we mark the position where we want to display our map with a rectangle. We name this rectangle Map. Now we want to create our map. We can either use the MapView.CreateWithSize function to place the map with a specific size at specific coordinates on the screen or we can use the MapView.CreateWithControl function and place our map on a control that we have previously placed on your layout. We want to place our map on our rectangle with the name Map. So we use the second option. In the function, we first specify the window in which we want to create this map. As we want to place the button in the same window in which we want to display the map and the window must be the frontmost one, we can enter a 0 for the window index, followed by the name of the control on which we want to place the map. Optionally, we can now enter x and y values by an amount that we would like to move the map in relation to the control. We don't want to do this, so we will leave it out.

Set Variable [ $$Mapview ; Value: MBS("MapView.CreateWithControl"; 0; "Map") ]

Now that we have a map, we want to display the customer's address on the map. To do this, we use the MapView.ShowAddressfunction to set the focus of our map on the address. In this function, we first enter our Mapview reference and then our address, which we have put together from the street, city, state and country, separated by commas. But we don't just want to display the address, we also want to mark it with a picture of our monkey. We already have this image in the global variable $$Overlay_Image. To mark the point of the address on the map, we use the function MapView.AddPoint. We pass this function a JSON with the information about this point. We have already seen how a JSON is structured on day 8. Now we create new information in an empty object with the functions JSON.AddStringToObject and JSON.AddNumberToObject. In these two functions, we first specify the JSON object to which we want to add the information and then the key and the value. In our case, we want to specify the address, the title of the annotation, the image of the annotation and the size of this image in the JSON. For our title, we take the first and last name. The title is displayed next to the icon when we move the mouse over it. The part of the script then looks like this:

Set Variable [ $r ; Value: MBS("MapView.ShowAddress"; $$Mapview; $address) ]

Set Variable [ $PointJSON ; Value: "{}" ]
Set Variable [ $PointJSON ; Value: MBS("JSON.AddStringToObject"; $PointJSON; "address" ; $address ) ]
Set Variable [ $PointJSON ; Value: MBS("JSON.AddStringToObject"; $PointJSON; "title" ; Giftee::first_name & " " & Giftee::last_name ) ]
Set Variable [ $PointJSON ; Value: MBS("JSON.AddStringToObject"; $PointJSON; "image" ; Base64Encode ( $$Overlay_Image ) ) ]
Set Variable [ $PointJSON ; Value: MBS("JSON.AddNumberToObject"; $PointJSON; "imageWidth"; 40) ]
Set Variable [ $PointJSON ; Value: MBS("JSON.AddNumberToObject"; $PointJSON; "imageHeight"; 40) ]

Set Variable [ $r ; Value: MBS( "MapView.AddPoint"; $$Mapview; $PointJSON ) ]

Of course, we would also like to have the option of hiding this card again. As already described in other doors for references, we have the option here of releasing a single reference or all at the same time. Again, we choose MapView.ReleaseAll because we don't need to know the exact reference. We also place it at the beginning of the script that creates a map, before the Create function, so that any legacy data is removed.

Now we can make settings for the display of the map. This means we can show and hide certain things on the map. We can show and hide traffic, buildings, our own location and points of interest on the map.The functions MapView.SetShowsTraffic, MapView.SetShowsBuildings, MapView.SetShowsUserLocation and MapView.SetShowsPointsOfInterest are available for this purpose.But controls can also be displayed. For example, we can display the compass, the scale and the zoom buttons. For this we use the functions MapView.SetShowsCompass, MapView.SetShowsScale and MapView.SetShowsZoomControls. To be able to make these settings in a space-saving way, we use a popup button. On this popup we place for each of these settings the name and to the right and left of the name two buttons that turn this option on and off. For each of these options, we also create a global field in our database in the Data table. We can define whether a field is global in the field settings. This field has the same values for each data record and can be seen from anywhere in the solution. The values in the fields are specific to the user and session and have no influence on other users.

We also create a script for each setting. First we get the script parameter that we pass directly with the button settings and save it in the $param variable. Then we call the function to set the property and enter the reference of the card and the read value $param. Then we set the global field that matches the property with the value in $param. This is what the script for points of interest looks like, for example:

Set Variable [ $param ; Value: Get(ScriptParameter) ]
Set Variable [ $r ; Value: MBS("MapView.SetShowsPointsOfInterest"; $$Mapview; $param) ]
Set Field [ Data::POI ; $param ]

These scripts are then passed to the buttons next to the respective properties, with the difference that the On buttons in the dialog of the button are given a 1 as a parameter to activate the properties and the Off buttons a 0 to deactivate the properties.

So that we know what is currently switched on and what is switched off, we now have to color the buttons using conditional formatting. To do this, we take advantage of the fact that we set the global fields when making the settings. Once these are set, we look at the conditional formatting and ask whether they are 1 for the On buttons and 0 for the Off buttons. This is what the query looks like for the On button for Points of Interest: Data::POI=1

If this condition is correct, the button should turn light blue.

We repeat this with each button with the corresponding field and value.

We have now also integrated these functions into our solution. I hope you have fun with it. See you tomorrow :-)

1 Like
candy cane Monkeybread Monkey as an elf candy cane
Day 12 - Sending Mails

We have already put together an archive in one of the previous doors. We would now like to be able to send this archive by email with the click of a button. To do this, we create a button in the mail portal.

When the button is pressed, a dialog is displayed. In this dialog we then set our text and our subject. In addition, we would like to have three buttons here with which we can either Cancel, Send the mail without archive attachment or Send with archive.

To create the dialog we use the functions from the dialog area. With these functions we can freely design our dialog. We can add buttons and fields and even display an icon to give the dialog a nicer look.

The dialog exists in the entire project, which means we can set the settings once and then call it in other scripts. It is important to note that if we want to create a new dialog, we must first reset the dialog settings. To do this, we use the function Dialog.Reset. Now we can set up the dialog as we need it. We want a field for the subject and one for the content of the mail. To add text fields, we use the Dialog.AddField function. As the subject is a standard field with a standard height, we do not need to specify anything here except the label. The content is different. We can make some specifications in the parameters. As we have just seen with the subject, we specify the field label, but we can also set a predefined text in the field or display a placeholder text in an empty field. The hiding of the text by dots, as is usual with passwords, can also be set here in the parameters. What interests us with our contents field is the height of the field. If this is higher than 20, it is a text view, which means that it is possible to fill this field with several lines of text. Please note that fields in the dialog are only available on macOS.

We now need the buttons with which we can decide how to proceed. To do this, we use the Dialog.SetButton function and specify the index of the button we want to set. A dialog can have up to 10 buttons. The index starts at 0 and is followed by the button name. To make the dialog look nice, we want to set our monkey as an icon. To do this, we use Dialog.SetIcon and specify our global variable with the image. The dialog is then displayed with Dialog.Run.

Set Variable [ $r ; Value: MBS("Dialog.Reset") ]
Set Variable [ $r ; Value: MBS("Dialog.AddField"; "Subject") ]
Set Variable [ $r ; Value: MBS("Dialog.AddField"; "Contents"; ""; ""; 0; 200) ]
Set Variable [ $r ; Value: MBS("Dialog.SetButton"; 0; "Send with zip") ]
Set Variable [ $r ; Value: MBS("Dialog.SetButton"; 1; "Send without zip") ]
Set Variable [ $r ; Value: MBS("Dialog.SetButton"; 2; "Cancel") ]
Set Variable [ $r ; Value: MBS("Dialog.SetIcon"; $$Overlay_Image) ]
Set Variable [ $r ; Value: MBS("Dialog.Run") ]

As long as the dialog is open, our script stops. Now we have functions for reading our fields and can find out which button was selected. With Dialog.GetFieldText, specifying the index, we read out the two text fields. The function Dialog.GetButtonPressed gives us the index of the button that has been pressed. We can now react differently depending on the button that has been pressed. If our index of the button is 2, the Cancel button has been selected. In this case, we exit the current script. If the index is 0 or 1, we create our email. To do this, we create an email reference in the workspace. We use the function “SendMail.CreateEmail” which returns the reference. Now we can set the subject and the content. Here we need the mail reference and the value we want to set as parameters. Then we define who the email is to be sent to. To do this, we use the SendMail.AddRecipient function and first enter the mail ID in the parameters and then the type of recipient. This is because we can not only simply create a recipient, but also someone in blind copy, for example. Here you have the choice of TO, BCC, CC or ReplyTo. We then enter the email address in another parameter. If we wish, we can also enter the name of the recipient. Now we have to decide whether we want to attach the archive. If the button with the index 0 was pressed, the archive should be assembled in a separate script. We have already seen how to create this archive from the files in a previous door. Only this time we want to have a conatiner value instead of creating the archive on the disk. We leave out the path in Archive.Create and set a file name in Archive.Close as an additional parameter. As our archive is not text and we can only return a text parameter to a calling script, we save the archive in a global variable. We have named this $$SendArchive. We set an OK as the return from the script. Back in our calling script we can then query the script result and if this is OK, we attach the archive with SendMail.AddAttachmentContainer, specifying the mail reference and the file in the global variable.

Set Variable [ $Subject ; Value: MBS( "Dialog.GetFieldText"; 0 ) ]
Set Variable [ $Contents ; Value: MBS( "Dialog.GetFieldText"; 1 ) ]
Set Variable [ $Button ; Value: MBS("Dialog.GetButtonPressed") ]
If [ $Button=2 ]
	Exit Script [ Text Result: "Cancel" ]
Else If [ $Button=0 or $Button=1 ]
	#Create Email
	Set Variable [ $EMail ; Value: MBS("SendMail.CreateEmail") ]
	Set Variable [ $r ; Value: MBS("SendMail.SetSubject"; $EMail; $Subject) ]
	Set Variable [ $r ; Value: MBS("SendMail.SetPlainText"; $EMail; $Contents) ]
	# Can be TO, BCC, CC or ReplyTo
	Set Variable [ $r ; Value: MBS("SendMail.AddRecipient"; $EMail; "TO"; Email::Emailaddress; Giftee::first_name & " " & Giftee::last_name) ]
	If [ $Button=0 ]
		# With Zip 
		Perform Script [ Specified: From list ; “ConatinerArchive” ; Parameter:    ]
		If [ Get(ScriptResult)="OK" ]
			Set Variable [ $r ; Value: MBS("SendMail.AddAttachmentContainer"; $EMail; $$SendArchive) ]
		Else
			Show Custom Dialog [ "Error" ; "Archive not created" ]
			Exit Script [ Text Result: "Error" ]
		End If
	End If

Next, we can specify a sender with SendMail.SetFrom, again specifying the reference, the sender mail address and our name. So that we can also send the mail correctly, we still have to specify the mail account via which we send the mail, so we set the password, the server and the user name with SendMail.SetSMTPPassword, SendMail.SetSMTPServer and SendMail.SetSMTPUserName, specifying the mail reference and the corresponding value.

# Set From 
Set Variable [ $r ; Value: MBS("SendMail.SetFrom"; $EMail; $Mailadress; "Monkey the Christmas Elf") ]
# Password
Set Variable [ $r ; Value: MBS("SendMail.SetSMTPPassword"; $EMail; $$MailPassword) ]
# Server
Set Variable [ $r ; Value: MBS("SendMail.SetSMTPServer"; $EMail; $$MailServer) ]
# User
Set Variable [ $r ; Value: MBS("SendMail.SetSMTPUserName"; $EMail; $$MailUserName) ]

At this point, we need to create a new CURL session. To do this, we use CURL.New.This function provides us with another reference, which we then pass to the SendMail.PrepareCURL function together with the MailID reference. This function then sets the settings that we have already made for the mail in the CURL. We then use CURL.Perform to send the mail. If we get an OK back from this function, everything went well during sending. If there was a problem with the connection, a text with an error number is returned instead of the OK. We have to be careful here. If there was an error during sending, we cannot catch it with the MBS("IsError") function, because there was no error in the function but in the connection. For this reason, we have to check whether the function has returned OK. If not, we want to display a dialog.

Set Variable [ $curl ; Value: MBS("CURL.New") ]
Set Variable [ $r ; Value: MBS("SendMail.PrepareCURL"; $EMail; $Curl) ]
Set Variable [ $r ; Value: MBS("CURL.Perform"; $curl) ]
	If [ $r ≠ "OK" ]
		Show Custom Dialog [ "Error" ; "E-Mail can't send!:¶" & $r ]
	End If

We are now finished and can send our mails.

1 Like
candy cane Monkeybread Monkey as an elf candy cane
Day 13 - Share Files With Apple Dialog

As today is Friday the 13th and we don't want to push our luck too far, we're only adding one little thing today, but it's very effective. Because under Mac we can also use the typical sharing dialog in FileMaker.

Today we want to add this option for the files in the portal. To do this, we create an additional button in the portal. Then we call the following script step with it:

Set Variable [ $r ; Value: MBS( "SharingService.ShareItems"; ""; Files::File ) ]

This function shows us the dialog. In the first parameter, we can specify which service is to be used for sharing.

com.apple.messages.ShareExtension Messages
com.apple.Notes.SharingExtension Notes
com.apple.reminders.sharingextension Reminders
com.apple.share.AirDrop.send AirDrop
com.apple.share.Mail.compose Mail
com.apple.share.System.add-to-iphoto Add to Photos

If you specify one of these service identifiers in the parameter, the file is shared via this single option. If you want to give the user the choice of which of the options they want to use, simply leave this parameter empty. We have also chosen this option.

In the additional parameter, we then specify the file that is to be shared. Please note that not all splitting methods can be used for all file types.

The mail action allows you to pass formatted text to Mail and include attachements. Great to send emails with christmas greetings.

I hope you enjoyed today's little door and we will see each other again tomorrow.

candy cane Monkeybread Monkey as an elf candy cane
Day 14 - XML

When rebuilding the weather station, you have probably already seen that you can get the data not only as JSON, but also as XML.

XML stands for eXtensible Markup Language and is a markup language that was developed to store and transport data in a structured and easily readable form. It was standardized by the World Wide Web Consortium (W3C) and first published in 1998. Similar to JSON, XML is often used to exchange data between interfaces. Like JSON, XML is also structured in layers, but the way it is written is more reminiscent of HTML. This is because in XML we also work with tags.

We still remember our JSON example that looked like this:

{
  "person": [
    {
      "Name": "Smith",
      "FirstName": "Toni",
      "Hobbies": ["programming", "reading", "taking photos"],
      "Dog": {
        "name": "Benni",
        "age": 6,
        "LikeTreats": true
      }
    },
    {
      "Name": "Doe",
      "FirstName": "Jane",
      "Hobbies": ["dancing", "karate", "writing"],
      "Dog": {
        "name": "Nala",
        "age": 2,
        "LikeTreats": true
      }
    }
  ]
}

If we now wanted to represent this JSON as XML, it would look like this, for example:

<persons> 
	<person> 
		<Name>Smith</Name> 
		<FirstName>Toni</FirstName> 
		<Hobbies>
			<Hobby>programming</Hobby> 
			<Hobby>reading</Hobby> 
			<Hobby>taking photos</Hobby> 
		</Hobbies> 
		<Dog>
			<name>Benni</name> 
			<age>6</age> 
			<LikeTreats>true</LikeTreats>
		 </Dog> 
	</person> 
	<person>
		<Name>Doe</Name> 
		<FirstName>Jane</FirstName> 
		<Hobbies> 
			<Hobby>dancing</Hobby> 
			<Hobby>karate</Hobby>
			<Hobby>writing</Hobby> 
		</Hobbies> 
		<Dog> 
			<name>Nala</name> 
			<age>2</age> 
			<LikeTreats>true</LikeTreats>
		</Dog> 
	</person> 
</persons>

We have a root tag in this case Persons. This is the first level that encloses the data. Then we can specify the individual persons. In JSON, the persons have an array. In XML, the array is replaced by repeating a tag several times. So that we have a valid JSON, every tag that is opened must also be closed again. The situation is similar for nested objects such as the dog. Here, too, we have an extra hierarchy for the dog, which is then nested again.

Another difference to JSON is that XML can have attributes. In XML, you can store information both as the content of an element and as attributes in a tag. Example:

<Person name="Smith" age="30">
    <Hobby>reading</Hobby>
</Person>

Here, the tag has two attributes: name and age.

To summarize, arrays need to be represented in XML by repetition, and objects by nested tags. There are also attributes that we can use. Otherwise, the main idea between JSON and XML remains the same: data is represented in a structured way, just with a different syntax. JSON is a lot more compact and often easier to read for the developer, but many APIs use XML instead of JSON. XML is also used for e-invoices, for example, and is embedded in the PDF for the ZUGFeRD standard, enabling invoices to be processed automatically. XML remains essential, especially where strict structuring and validation of data is required.

The MBS FileMaker Plugin also offers you functions with which you can create, read and change XML. Since we are already a little familiar with our weather API, today we want to retrieve the same data with XML.

The address we enter in the browser is almost the same as for JSON, except that we add a mode=xml to specify that we want to retrieve the data as XML. This is what the retrieval on the web now can looks like in our script:

Insert from URL [ Select ; With dialog: Off ; Target: $XML ; 
	"https://api.openweathermap.org/data/2.5/weather?q=" & 
	Giftee::City & "," & Giftee::State & "," & Giftee::Country & "
	&appid=" & $$APIKey & "&mode=xml" ]

The XML that comes out for Miami can then look like this, for example:

<?xml version="1.0" encoding="UTF-8"?>
<current>
	<city id="4164138" name=„Miami">
		<coord lon="-80.1937" lat=„25.7743"></coord>
		<country>US</country>
		<timezone>-18000</timezone>
		<sun rise="2024-11-28T11:48:46" set=„2024-11-28T22:29:29“></sun>
	</city>
	<temperature value="295.59" min="294.15" max="296.47" unit=„kelvin"></temperature>
	<feels_like value="296.19" unit=„kelvin"></feels_like>
	<humidity value="88" unit=„%"></humidity>
	<pressure value="1016" unit=„hPa"></pressure>
	<wind>
		<speed value="1.54" unit="m/s" name=„Calm"></speed>
		<gusts></gusts>
		<direction value="120" code="ESE" name=„East-southeast"></direction>
	</wind>
	<clouds value="20" name="few clouds“></clouds>
	<visibility value=„10000"></visibility>
	<precipitation mode=„no"></precipitation>
	<weather number="801" value="few clouds" icon=„02n“></weather>
	<lastupdate value=„2024-11-29T02:38:49"></lastupdate>
</current>

Here we can see that attributes are used extensively. We need the attribute value from the temperature part and the weather part. From the weather part, we also need the attributes value and icon.

Again, we have to make our way through the XML structure and this looks similar to the JSON. We separate the individual levels with dots here too. If we then want to read an attribute, we enter # to make it clear that we want an attribute, followed by the name of the attribute. In this way, we can access the information using the following paths:

Key Path
Temperature current.temperature#value
Weather description current.weather#value
Weather icon current.weather#icon

We now pass these paths to the XML.GetPathValue function. This reads out the value by specifying the XML and the path. In our case, it looks like this.

Set Variable [ $Temp ; Value: MBS("XML.GetPathValue"; $XML; "current.temperature#value") ]
Set Variable [ $description ; Value: MBS("XML.GetPathValue"; $XML; "current.weather#value") ]
Set Variable [ $icon ; Value: MBS("XML.GetPathValue"; $XML; "current.weather#icon") ]

We have already written the script icon for converting the icon code into an emojie and use it again here. We then set the values in the appropriate fields.

Perform Script [ Specified: From list ; "Icon" ; Parameter: $icon ]
Set Variable [ $icon ; Value: Get(ScriptResult) ]
Set Field [ Giftee::Weather_temperature ; $Temp ]
Set Field [ Giftee::Weather_weather ; $description ]
Set Field [ Giftee::Weather_icon ; $icon ]

I hope you enjoyed this little door and that we will read each other again tomorrow.

candy cane Monkeybread Monkey as an elf candy cane
Day 15 - vCard QR

Today we want to create QR codes that contain the contact details of one of our giftee. If we scan this contact with our cell phone, it can be transferred directly to the phone's address book. To do this, we first have to write a so-called vcf file. The ending vcf stands for Virtual Contact File. We can see how such a file is structured if we export a contact from the Apple calendar and open it in a text editor. It may look like this:

BEGIN:VCARD
VERSION:3.0
PRODID:-//Apple Inc.//macOS 13.4.1//EN
N:Mustermann;Max;;;
FN:Max Mustermann
ORG:Mustermann AG;
EMAIL;type=INTERNET;type=WORK;type=pref:xyz@abc.de
TEL;type=CELL;type=VOICE;type=pref:012346789
TEL;type=IPHONE;type=CELL;type=VOICE:1011121314
TEL;type=HOME;type=FAX:15161718
ADR;type=HOME;type=pref:;;Musterstraße 3;Musterstadt;;1234;Germany
NOTE:Conference 
item1.URL;type=pref:www.xyz.com
item1.X-ABLabel:_$!<HomePage>!$_
BDAY:2000-04-01
END:VCARD 

We have different key and value pairs here that are separated from each other with a colon.

There is information that must be entered in a VCard and there is information that is optional. One piece of information that you must always include in your QR code are the tags that enclose our content. This type of text always starts with BEGIN:VCARD and ends with END:VCARD.

In addition, we need the version we are using. There are several versions in which the data structure is slightly different and which have different support. Today we use version 3.0 as this is also used by Apple as the export version. For this reason, the next line in our data is already fixed: VERSION:3.0.

Now it's time for the actual data. Here we start with the name, first we have the key N in which we first enter the last name and then the first name separated by a semicolon. If the person has other first names, these can be entered separated by another semicolon. Any titles, such as a doctorate, can also be entered with a semicolon. We also need the key FN. This is the representation of the name as we would write it. For example, we can write the Dr. title of a person in front of it. For Dr. Manuel Müller, for example, these two lines look like this:

N:Miller;Michael;Dr.
FN:Dr. Michael Miller

This would already fulfill the minimum amount of data that a vCard must have. But we would still like to include the rest of the information we have about our client in the vCard.

Now we come to a part that can be repeated several times in a vCard, the telephone number. The key to the phone number is TEL. Here we can specify additional types. These can be e.g. text, voice, fax, cell, video or pager. If we want to assign several of these types to a number, we can either specify a comma-separated list or write several times e.g. Type="voice";Type="fax". In this example we see a pref as the type for the first telephone number we have entered. This pref means that it is the preferred telephone number.

TEL;type=CELL;type=VOICE;type=pref:012346789

In our example, we have a cell phone number that is used and preferred for making calls. The telephone number is then placed after a colon.

The whole thing works in a similar way for email addresses. Here the key is EMAIL.

[type or paste code here](mailto:EMAIL;type=INTERNET;type=WORK;type=pref:xyz@abc.de)

Now we come to a somewhat more complicated but very important part, the address. Here, too, we can enter a type, as we have just seen with the telephone number. The address itself consists of various components that are separated by semicolons. The order of these components is very important and must be respected. If we omit a component, we leave the area between the separating semicolons empty. First we can specify a mailbox. This is followed by a possible address extension separated by a semicolon, then the street, city, region, zip code and finally the country. This can then look like this, for example:

ADR;type=HOME;type=pref:;;Musterstraße 3;Musterstadt;NRW;1234;Germany

We do not need the post box and the address extension in the example, but the values would be entered between the semicolons if required.

Another interesting value to enter in a QR code is the date of birth. This has the key BDAY. The value is composed as follows YYYYMMDD. If you do not know the YEAR, it is replaced with two -: - -MMDD

In our example, the person was born on April 1, 2000: BDAY:20000401 Here, the specification differs from the Apple way.

Now that we know what this information should look like, we can put it together in our script and then write it in a QR code.

We first compose the content of the QR code by connecting the fixed text modules with the variable values.

# Start VCard
Set Variable [ $Content ; Value: "BEGIN:VCARD" & Char(13) & "VERSION:3.0" & Char(13) ]
# Name
Set Variable [ $Content ; Value: $Content  & "N:" & Giftee::last_name & ";" & Giftee::first_name & ";;;" & Char(13) ]
Set Variable [ $Content ; Value: $Content & "FN:" & Giftee::first_name & " " & Giftee::last_name & Char(13) ]

For the portals, we first go to the portal with Go to Object and run through the individual entries with a loop. With Go to Portal Row we move forward through the individual entries. We check whether the most important information in the portals here, the email address and the telephone number, are not empty, otherwise we do not need to create this entry. In the telephone part, you can also see that we make the distinction whether it is the first telephone number in the portal, because we then give it the label pref to mark it as a preferred telephone number. We make this distinction as to whether it is the first non-empty entry with a variable that we set to 1 before starting the loop and as soon as we have run through the branch of the condition that gives us a telephone number as the preferred telephone number, we set the value of the variable to 0. As a result, this path is no longer run through the next time and we only ever have one telephone number with the label pref.

# EMail
Go to Object [ Object Name: "EmailPortal" ]
Go to Portal Row [ Select: On ; First ]
Loop [ Flush: Always ]
	If [ Email::Emailaddress ≠ "" ]
		Set Variable [ $Content ; Value: $Content & "EMAIL;type=" & Upper ( Email::Typ ) & ":" & Email::Emailaddress & Char(13) ]
	End If
	Go to Portal Row [ Select: On ; Next ; Exit after last: On ]
End Loop
# 
# Telephone
Go to Object [ Object Name: "Telephoneportal" ]
Go to Portal Row [ Select: On ; First ]
Set Variable [ $L1 ; Value: 1 ]
Loop [ Flush: Always ]
	If [ Telephone::Number ≠ "" ]
		If [ $L1=1 ]
			Set Variable [ $Content ; Value: $Content  & "TEL;type=" & Telephone::Type & ",pref:" & Telephone::Number & Char(13) ]
			Set Variable [ $L1 ; Value: $L1=0 ]
		Else
			Set Variable [ $Content ; Value: $Content  & "TEL;type=" & Telephone::Type & ":" & Telephone::Number & Char(13) ]
		End If
	End If
	Go to Portal Row [ Select: On ; Next ; Exit after last: On ]
End Loop

We have to include a lot of fields for the address, but this is simple text as usual.

# Address
If [ Giftee::Street ≠ "" or Giftee::City ≠ "" or Giftee::Postcode ≠ "" or Giftee::Country ≠ "" or Giftee::State ≠ "" ]
	Set Variable [ $Content ; Value: $Content  &  "ADR;type=HOME;type=pref:;;" & Giftee::Street & ";" & Giftee::City & ";" &  "" & ";" & Giftee::Postcode & ";" & Giftee::Country & Char(13) ]
End If

We have to include a lot of fields for the address, but this is simple text as usual. The birthday is more interesting. First we check whether the birthday is entered, as it is one of the optional values and can also be omitted. The year that we get back from the Year function is always 4 digits, unless it is necessarily a historical birthday before the year 1000. We can therefore adopt this. For the month, however, the months January to September are single digits when we call the Month function. The date format that we have in the vCard always requires two digits. For this reason, we query the length of the text that the Month function returns from the birth date and if this is not 2, we add a 0 to the front. The month then has the required two digits again. We do the same for the days in the date. Last but not least, we put the date together in the form YYYYMMDD and then append it to the content. Now only the END tag is missing, which we also append.

# Birthday
If [ Giftee::Birthday ≠ "" ]
	Set Variable [ $Year ; Value: Year ( Giftee::Birthday ) ]
	# 
	If [ Length ( Month ( Giftee::Birthday ) ) ≠ 2 ]
		Set Variable [ $Month ; Value: "0" & Month ( Giftee::Birthday ) ]
	Else
		Set Variable [ $Month ; Value: Month ( Giftee::Birthday ) ]
	End If
	# 
	If [ Length ( Day ( Giftee::Birthday ) ) ≠ 2 ]
		Set Variable [ $Day ; Value: "0" & Day ( Giftee::Birthday ) ]
	Else
		Set Variable [ $Day ; Value: Day ( Giftee::Birthday ) ]
	End If
	Set Variable [ $BDay ; Value: $Year & $Month & $Day ]
	Set Variable [ $Content ; Value: $Content & "BDAY:" & $BDay & Char(13) ]
End If
# End VCard
Set Variable [ $Content ; Value: $Content & "END:VCARD" ]

We have built the content of the QR code and we can now generate the QR code. We want to use the Barcode.GenerateJSON function to do this. The function name already contains the word JSON. We have to pass this function a JSON that contains the settings for the desired barcode. We have already explained how the JSON functions work in more detail in one of the previous doors. So let's take a look at the information we need to set in this JSON. First of all, we have to specify which barcode we want to generate exactly, because in addition to the QR code, the MBS FileMaker Plugin can generate more than 80 other different barcode types. So we enter the value QRCode in the Symbology key. Then, of course, we want to transfer the QR code content that we have created, so we enter this under the key Text. Next comes the Scale factor. The scale factor should have at least the value 4 when printing a barcode with a high resolution. The barcodes in the plugin can have barcode-specific options, which can have different meanings for the individual barcodes. Option1 is such a value that can set something completely different for different barcodes. In the case of the QR code, it defines the ECC level. The ECC level specifies how often the information within the QR code is repeated. This has the advantage that a QR code can still be read even if it has been damaged or, for example, a small logo has been positioned in the middle. ECC 4 is the highest level we can set.

Since all settings have been made we can generate the QR code in the Barcode.GenerateJSON function by specifying our JSON. We get a GraphicsMagick reference as the return value. GraphicsMagick is another topic from the plugin that deals with graphical functions.

Set Variable [ $QR_JSON ; Value: MBS( "JSON.CreateObject" ) ]
Set Variable [ $QR_JSON ; Value: MBS( "JSON.AddStringToObject"; $QR_JSON; "Symbology"; "QRCode") ]
Set Variable [ $QR_JSON ; Value: MBS( "JSON.AddStringToObject"; $QR_JSON; "Text"; $Content) ]
Set Variable [ $QR_JSON ; Value: MBS( "JSON.AddNumberToObject"; $QR_JSON; "Scale"; 4) ]
Set Variable [ $QR_JSON ; Value: MBS( "JSON.AddNumberToObject"; $QR_JSON; "Option1"; 4) ]
Set Variable [ $QRRef ; Value: MBS( "Barcode.GenerateJSON"; $QR_JSON ) ]

So that we can save the QR code in a file on the disk, we assemble the path. The file should be stored on the desktop under the name QR_lastName_ firstName.png. We then use GMImage.WriteToFile to write this GMImage reference as an image file to the correct path. If you want to save the QR code in a container, you can instead use the GMImage.WriteToContainer function. As GMImage is also a reference, we release it in memory. From a previous door we already know the Files.LaunchFile function, which opens a file for us directly with the standard program provided to do this.

Set Variable [ $Path ; Value: MBS("Path.AddPathComponent"; MBS("Folders.UserDesktop"); "QR_" & Giftee::last_name & "_" & Giftee::first_name & ".png") ]

Set Variable [ $r ; Value: MBS("GMImage.WriteToFile"; $QRRef; $Path) ]

Set Variable [ $r ; Value: MBS( "GMImage.FreeAll" ) ]
Set Variable [ $r ; Value: MBS("Files.LaunchFile"; $Path) ]

We are now finished with our script and can generate the barcode.

I hope you enjoyed today's door and we'll see again tomorrow.