06. Reactor Development Tutorial
- 1 06. Reactor Development Tutorial
- 1.1 Section A: BlackBox Management
- 1.2 FR Toolbox and Talking With FileMaker
- 1.2.1 1. Introduction to FRToolBox and Asynchronicity
- 1.2.2 2. Using parameters
- 1.2.3 3. Pulling records from your found set
- 1.2.4 4. Pulling a single record
- 1.2.5 5. Updating a record
- 1.2.6 6. CreatePlanet
- 1.2.7 7. Deleting a record
- 1.2.8 8. Polling For Changes
- 1.2.9 9. Running a script
- 1.2.10 10. Pull Container Images
- 1.2.11 11. Using The Starting Template
- 1.2.12 12. Debugging
06. Reactor Development Tutorial
The following provides everything you should know to create your own BlackBoxes (or modify others) in Reactor.
The first section provides an introduction to management of a BlackBox in Reactor.
The second section is a more advanced guide to the FR Toolbox functions, used to communicate with your FileMaker application.
This entire tutorial is around building several functions for a single BlackBox. If you would prefer to see the functions in action, you can download the fully-completed BlackBox here:
https://www.dropbox.com/s/6fn6eo85h66ly56/TutorialBB%20%281%29.rbb?dl=0
You can import this into the Reactor application by adding a new BlackBox, and choosing to import it.
However the best way to learn Reactor development is to progress through this tutorial step-by-step – but you’re more than welcome to use the completed BlackBox to access individual snippets of code or to help figure out any issues you might be having with your own BlackBoxes.
Section A: BlackBox Management
BlackBox Files
If you have not done so before, now would be the ideal time to go through the in-app tutorial for Reactor. You can do this from the Support screen in Reactor.
When you create or select a BlackBox to manage, you start off on the ‘source files’ tab.
This is what appears when clicking on one of the demo BlackBoxes, it comprises the main file for the BlackBox, and any supporting files it requires. If you start a new BlackBox, this list will start off empty. Go ahead and create a new BlackBox called TutorialBB
.
By the time you get to the end of this tutorial, your TutorialBB will be chocker-full of functions that communicate with FileMaker via Reactor. The BlackBox is a portable file you can install on any computers you wish to invoke these functions.
You will be invited to start uploading files, or create an HTML template after creating your first function - see the following section.
Any files you add will now appear in your list of files. Accompanying it will be two buttons
Clicking the edit button will open the document in your editor of choice, or Moogie will ask you what it should be if he doesn’t know yet. We recommend BBEdit for Mac or Notepad++ for Windows - but it’s entirely up to you.
These are the default applications when editing your source files. You’re also welcome to change this, by clicking it under ‘your editors’. The first option is for your text-editor, and the second option is for your image editor.
Editing the file will then open it in this editor.
The document is now “officially” open. That means when you compile the BlackBox, it will pull the file back into Reactor — so you will need to save any changes you make in the editor itself first!
You can do this manually by clicking ‘upload’. If you no longer wish to pull any changes back into Reactor, click ‘close’ - though this won’t close the file in your text editor, you’ll need to do that yourself. And you can re-open the file at any point. Ie, if you’re done making your changes and want to pull these changes in - click Upload. You can then click close.
BlackBoxes Functions and Parameters
Click the ‘functions’ tab and you can see a list of functions for the BlackBox. Most of the time, there will be a single function for each BlackBox, but if you wish to create a suite of BlackBox utilities for a single BlackBox, you can create a function for each,
If you are creating a new BlackBox, you will need to create a function to start with.
To do this, click the ‘+’ button at the top of the function list, and fill in the details.
Call our new function for our new BlackBox called TestFunction
.
Now that we have our function, go ahead and add a new parameter. Give it a name called MyParameter
, and let Moogie know if it’s optional, if it’s hidden and/or if whether changes to the parameter should trigger a refresh of the BlackBox. Also ensure you set the ‘data type’ to Text.
Now we need to give our BlackBox something to do. Let’s create a template file. Please see our more advanced guide on what a BlackBox file does, but in its purest sense, it creates an HTML document for your function that starts you off with the boilerplate code needed - ready to customise.
So click the ‘html template…’ button to create one,
It automatically gives it the same same of the function you’re generating it for. Now let’s go ahead and build it.
We have set only a single text-based parameter for the function, so the template builder automatically pulls that in to the JavaScript block, and stores it as a variable.
As of this point, we have now provided all the details to build it into a BlackBox, click across to the Preview tab.
Compiling and Previewing
Click ‘Compile’ and if you switch back to the Functions tab you’ll now see a file below the BlackBox details. The BlackBox file itself - which you can now take and deploy anywhere using the following function:
BlackBoxAdd( <<bb_containerfield>> )
Now let’s preview our BlackBox.
You can now view your BlackBox do…nothing. That’s because all you’ve created is a blank page. Now it’s time to turn to some JavaScript and actually get your BlackBoxes to do stuff.
FR Toolbox and Talking With FileMaker
1. Introduction to FRToolBox and Asynchronicity
Anytime you create a BlackBox there is a single file that will always be included: FRToolBox.js
This is a JavaScript library that you can use for talking back to FileMaker.
Essentially, it’s used for pulling through the data you need to work with, and for pushing it back. Additionally, it can also be used for running scripts in FileMaker. When building an HTML document from a template, you don’t need to worry about importing this library yourself - it’s automatically included and imported.
It’s also important to note that all the FRToolBox functions you call are asynchronous. To those that are more versed in FileMaker Development than Web Development, it’s the same as Perform Script On Server
with ‘Wait for completion’ turned off.
What you need to keep in mind is that when you call a function, you’re simply telling FRToolBox to do something, then moving on with your life. The next line of code doesn’t necessarily know about how it went, or get passed what comes back.
However FRToolBox has built in callback functions, so there are ways to do stuff with the FileMaker data that comes back.
2. Using parameters
Using our testing BlackBox from the first section, let’s try doing something with the parameter. A parameter can be anything, usually it’s a field name used to pull/push data, or something else like a string of text, number, date, etc.
Edit the index.html
document we created before, and where you see:
<!–– This is the main HTML for the BlackBox ––>
Replace this with
<?Reactor $MyParameter Reactor?>
Everything between the <?Reactor Reactor?>
tags will be interpreted by Reactor. The $
tells Reactor we’re referring to a parameter, and MyParameter
is our parameter name.
We put it here because it’s smack bang inside the <body>
element of the HTML. The main content for any BlackBox will go here.
Save the document and recompile. We still see nothing because you haven’t given the parameter a value yet. You can do this on the preview tab by entering it alongside the parameter name:
Now look across to the right, and you should see it update in real time!
Our BlackBox now does something!
3. Pulling records from your found set
Most typically, one or more of your parameters will refer to a FileMaker field so you can query data.
Go ahead and add a new parameter, but this time the ‘data type’ should be Field Name
The first thing you want to do when querying data in Reactor is pulling records from your found set.
To get started, add the following code inside the <script>
block before the HTML content.
FMLink = 'FM.link';
MyField = '<?Reactor bbdev_Field( $MyField ) Reactor?>';
FMTableOccurrenceName = '<?Reactor bbdev_TO( $MyField ) Reactor?>';
FunctionWhereClause = '<?Reactor bbdev_Relationship( bbdev_TO( $MyField ) ) Reactor?>';
var RecordsObj = new Object;
The first line is simply a constant used to refer to a path to your FileMaker application.
The second line grabs the field name from the parameter.
The third line uses your parameter to find out what the table occurrence is we’re dealing with.
The fourth line looks at the relationship to the table occurrence from your current context.
The fifth line gives us somewhere to put the data when it comes back.
Then below this, write a function for pulling the data from FileMaker:
function GetData() {
QueryData = FRTB.find(
FMTableOccurrenceName + '::' + MyField
).where(FunctionWhereClause).send(InitData);
}
This calls the FRTB.find
FRToolBox function. It uses the table occurrence name and the field name to know what to pull in.
It sets the where
clause, so that only data that relates to the table occurrence per your current context comes through. In other words, the relationship in FileMaker is honoured.
InitData
is the name of another function, this is called when the data comes back. We can’t simply parse the data in the next line of code due to the asynchronous nature of FRToolBox.
Next, let’s go ahead and create the InitData
function:
function InitData( response ) {
AllData = Array();
for ( var i=0; i < response.data.length; i++) {
Record = new Object;
Record.MyField = response.data[i][FMTableOccurrenceName + '::' + MyField];
AllData.push(Record);
}
RecordsObj = AllData;
}
The response
parameter to this function is what comes back when FRToolBox queries the data.
AllData
is our array to contain the records that come back.
The for loop
traverses through the data that comes back. For each, it creates an object called Record
, and sets its MyField
property what comes back as the value contained in the MyField
field for the current record. It then pushes this record onto the array of records.
But because we defined the AllData
object within this function, we can’t access it outside of the function, so we pass it over to the object we did define outside of this function: RecordsObj
.
After we’ve defined the functions, we need to run them at some point. So after the InitData
function, let’s call the function to pull in the data (which in turns calls the function to parse the data.
GetData();
Great, we have told Reactor to pull the data from FileMaker, and then to convert it into a form that JavaScript can work with. But at this point, all it is, is a JavaScript object. It doesn’t display anywhere.
So add the following line to the bottom of your for loop
to add the value of the field for each record to the HTML:
document.write(Record.MyField + "<br>");
The <br>
adds a line break at the end of each, so they’ll be readable.
The contents of your <script>
block should now look like so:
FMLink = 'FM.link';
MyField = '<?Reactor bbdev_Field( $MyField ) Reactor?>';
FMTableOccurrenceName = '<?Reactor bbdev_TO( $MyField ) Reactor?>';
FunctionWhereClause = '<?Reactor bbdev_Relationship( bbdev_TO( $MyField ) ) Reactor?>';
var RecordsObj = new Object;
function GetData() {
QueryData = FRTB.find(
FMTableOccurrenceName + '::' + MyField
).where(FunctionWhereClause).send(InitData);
}
function InitData( response ) {
AllData = Array();
for ( var i=0; i < response.data.length; i++) {
Record = new Object;
Record.MyField = response.data[i][FMTableOccurrenceName + '::' + MyField];
AllData.push(Record);
document.write(Record.MyField + "<br>");
}
RecordsObj = AllData;
}
GetData();
FMLink = 'FM.link';
MyField = '<?Reactor bbdev_Field( $MyField ) Reactor?>';
FMTableOccurrenceName = '<?Reactor bbdev_TO( $MyField ) Reactor?>';
FunctionWhereClause = '<?Reactor bbdev_Relationship( bbdev_TO( $MyField ) ) Reactor?>';
var RecordsObj = new Object;
function GetData() {
QueryData = FRTB.find(
FMTableOccurrenceName + '::' + MyField
).where(FunctionWhereClause).send(InitData);
}
function InitData( response ) {
AllData = Array();
for ( var i=0; i < response.data.length; i++) {
Record = new Object;
Record.MyField = response.data[i][FMTableOccurrenceName + '::' + MyField];
AllData.push(Record);
document.write(Record.MyField + "<br>");
}
RecordsObj = AllData;
}
GetData();
Now, to give it a test, let’s use the Reactor Schedules
table:
Reactor is now pulling through the values of the Schedules::Description
fields for the records that are included in the related set from our current context.
4. Pulling a single record
We can pull records from a found set, but what if you want to pull a single record?
Well, you could either do it by pulling a found set, but restricting the relationship to point to a single record (in the relationship graph).
Or you could do it from within the JavaScript.
In this tutorial we’ll be querying the Planets table. In this table we have a record for each planet, each record has a planet name, and a planet description. So let’s say our function just wants to know the description for Neptune.
To do this, we’ll create a new function called PlanetDescription
and add three parameters:
PlanetNameField: the name of the field whose value we know
PlanetDescriptionField: the name of the field whose value we want to know
QueryValue: the value we’re checking against
To get started, create a new function called PlanetDescription
and give it the following parameters:
This function will perform a different action from what we’ve done previously, so let’s add a new source document for it. Create a new html template for this function.
First clear out everything that’s inside the <script>
block. For more detail on what this pre-generated code is, check out the section on generating a starting template.
Clear out the code we’ve entered into the <script>
block, and enter this:
FMLink = 'FM.link';
QueryValue = '<?Reactor $QueryValue Reactor?>';
FMTableOccurrenceName = '<?Reactor bbdev_TO( $PlanetNameField ) Reactor?>';
PlanetNameField = '<?Reactor bbdev_Field( $PlanetNameField ) Reactor?>';
PlanetDescriptionField = '<?Reactor bbdev_Field( $PlanetDescriptionField ) Reactor?>';
//This is what is used to retrieve data from FileMaker
function GetData() {
QueryData = FRTB.find(
FMTableOccurrenceName + '::' + PlanetDescriptionField
).where(PlanetNameField + "='" + QueryValue + "'").send(function(response){
ResultValue = response.data[0][FMTableOccurrenceName + '::' + PlanetDescriptionField];
document.write(QueryValue + " is known as " + ResultValue + "<br>");
});
}
GetData();
There are some key differences in this code:
We’re passing in a query value, so we know what planet we’re looking for (Neptune)
We’re passing in the PlanetName field, so we know where to find that value
We’re passing in the PlanetDescription field, so we know what field value to pass back
The
where
clause doesn’t use the relationship from any table occurrences, but a simple match onPlanetName
=QueryValue
We’re only expecting a single value back (at most), so we don’t need to loop through the response, we can just look at the first value
We’re not passing over to another function to process the response, we’re instead defining an anonymous function inside the
find
. That keeps it all within the one block of code for simplicityWe write to the HTML what planet we’re looking for, and what its description is
Pop back to the Preview tab and switch to our new function. Now we just need to provide some values as parameters for the preview:
And you should now see the BlackBox doing its job to the right:
If you try changing the QueryValue
to a different planet, the output will change accordingly:
5. Updating a record
For this section we’ll be creating two new functions:
PlanetList: List the planet names with descriptions
SetPlanetDescription: Update the description for a specific planet
For PlanetList
we’ll have two parameters: PlanetNameField
and PlanetDescriptionField
Create a new template document, and once again, clear out all the code that’s automatically generated for you in the <script>
block.
Replace it with:
FMLink = 'FM.link';
PlanetNameField = '<?Reactor bbdev_Field( $PlanetNameField ) Reactor?>';
PlanetDescriptionField = '<?Reactor bbdev_Field( $PlanetDescriptionField ) Reactor?>';
FMTableOccurrenceName = '<?Reactor bbdev_TO( $PlanetNameField ) Reactor?>';
FunctionWhereClause = '<?Reactor bbdev_Relationship( bbdev_TO( $PlanetNameField ) ) Reactor?>';
function GetData() {
QueryData = FRTB.find(
FMTableOccurrenceName + '::' + PlanetNameField,
FMTableOccurrenceName + '::' + PlanetDescriptionField
).where(FunctionWhereClause).send(function(response){
for ( var i=0; i < response.data.length; i++) {
planetname = response.data[i][FMTableOccurrenceName + '::' + PlanetNameField];
planetdescription = response.data[i][FMTableOccurrenceName + '::' + PlanetDescriptionField];
document.write(planetname + ": " + planetdescription + "<br>");
}
});
}
GetData();
If you don’t know what this code does, refer to the above section on Pulling records from your found set, we’re just using a different table with different parameters. It’s also somewhat compacted for simplicity.
Pop over to the Preview tab, switch to this function and set our preview parameters.
Ok, that’s the easy bit.
Now we want to create our function which will update the description of a specific planet.
Create a new template document, with the following in the <script>
block:
FMLink = 'FM.link';
PlanetName = '<?Reactor $PlanetName Reactor?>';
NewDescription = '<?Reactor $NewDescription Reactor?>';
PlanetNameField = '<?Reactor bbdev_Field( $PlanetNameField ) Reactor?>';
PlanetDescriptionField = '<?Reactor bbdev_Field( $PlanetDescriptionField ) Reactor?>';
FMTableOccurrenceName = '<?Reactor bbdev_TO( $PlanetNameField ) Reactor?>';
FunctionWhereClause = '<?Reactor bbdev_Relationship( bbdev_TO( $PlanetNameField ) ) Reactor?>';
function UpdateRecord() {
FRTB.update(
[FMTableOccurrenceName + '::' + PlanetDescriptionField, NewDescription]
).filter(
FMTableOccurrenceName + '::' + PlanetNameField + '=' + PlanetName
).send(function() {
document.write("Done!");
});
}
The first part of this code should be pretty familiar by now. The second block is a function that updates the planetname
record, so its description is set to newdescription
for the record that corresponds with the supplied PlanetName
.
When it’s done it outputs “Done!” to the HTML, and therefore the web-viewer.
But how do we call it? You could either pop into the JavaScript console and run UpdateRecord()
. Or build it into the BlackBox.
The HTML of any template document includes a <div>
block to get you started, replace it with this:
<div onclick=UpdateRecord() style="width:116px;height:20px;background-color:#529028;color:white;padding:10px;font-family:Helvetica,arial;text-align:center;cursor:pointer;">Update Record
</div>
That styles the div to look like a button, and instructs it to run our JavaScript function when the button is clicked.
Now give it a whirl.
When you click ‘Update Record’, it will report back telling you it’s done. Switch to the PlanetList
function, and check to see if Earth’s description has been changed:
Success!
We have created two new functions. One that lists all planets with their descriptions, and another one that allows you to change the description of any planet.
Go ahead and change it back to “The Blue Planet”.
Creating a new record
For this section let’s create a new function that creates a new record.
6. CreatePlanet
It will have two parameters: PlanetNameField
and PlanetDescriptionField
However, now we’re going to try add a couple of text fields to the HTML that will provide these values.
Create a new template document, and once again, clear out all the code that’s automatically generated for you in the <script>
block.
Our next step will be to provide the HTML elements. Pop the following between the <body>
tags in the HTML:
<myform>
<div class=myform>
<label>Planet Name:</label><input id='name' type="text"><br><br>
<label>Planet Description:</label><input id='description' type="text"><br><br>
<button type="button" onclick="addPlanet();">Add Planet!</button>
</div>
</myform>
This should be fairly obvious, but it’s a basic form. It’s two text fields, with accompanying labels, and a button.
Set our new function to use our new HTML document, compile your changes, and give it a preview:
Doesn’t look great does it? Let’s clean it up with some basic CSS. Pop the following between the <style>
tags in your HTML document:
.myform {
width: 300px;
clear: both;
}
label {
padding-bottom:5px;
}
.myform input {
clear: both;
width: 100%;
height:30px;
font-size:14px;
}
button {
background-color:green;
color:white;
border:none;
padding:10px;
width: 200px;
font-size:14px;
cursor:pointer;
}
Save, recompile and preview:
There, looks a bit more like an actual form now.
So we have the structure, look-and-feel of our HTML document, but it doesn’t do anything. We need to tell our button what to do when we click it. Update the <button>
tag with this:
<button type="button" onclick="addPlanet();">Add Planet!</button>
This tells the HTML to run the JavaScript function addPlanet()
when the button is clicked.
Now we need to write the actual function, add this inside the <script> block.
function addPlanet() {
FMLink = 'FM.link';
PlanetNameField = '<?Reactor bbdev_Field( $PlanetNameField ) Reactor?>';
PlanetDescriptionField = '<?Reactor bbdev_Field( $PlanetDescriptionField ) Reactor?>';
FMTableOccurrenceName = '<?Reactor bbdev_TO( $PlanetNameField ) Reactor?>';
PlanetNameField_Full = FMTableOccurrenceName + "::" + PlanetNameField;
PlanetDescriptionField_Full = FMTableOccurrenceName + "::" + PlanetDescriptionField;
newPlanetName = document.getElementById('name').value;
newPlanetDescription = document.getElementById('description').value;
if (newPlanetName=="" || newPlanetDescription=="") {
alert ("You need to provide a name and description");
return
}
var Data = new Object;
Data[PlanetNameField_Full] = newPlanetName;
Data[PlanetDescriptionField_Full] = newPlanetDescription;
FRTB.create(Data).send({'onsuccess': function() {
alert ("Planet added!");
}});
}
This grabs our script parameters, the values we’ve entered into the text fields, and sends an instruction to Reactor to create a new Planet record with the provided values.
If either of the fields doesn’t contain a value, we throw up an alert to the user and go no further.
We send the values to Reactor by packaging them into a data object.
Recompile and preview our function to see it in action:
Success!
Now pop back to our PlanetList function, and confirm our new planet has appeared:
And there it is!
7. Deleting a record
For this section let’s create a new function that deletes a record. As with the last section, we’ll build in an HTML interface - this time to identify the planet we wish to delete.
So build a new function:
DeletePlanet
It will take two parameters:
PlanetIDField
PlanetNameField
The first field will be used to identify a record to be deleted. The other field will be used for the list of planets.
Create a new HTML template file, and replace the content inside of the <body>
tag of the HTML with the following:
<select id="planetlist"></select><br>
<button type="button" onclick="DeletePlanet();">Delete Planet!</button>
This is pretty simple, it’s just two elements. The first is a <select>
element. To the uninitiated, it’s the equivalent of a dropdown field in FileMaker. The second is a button to perform our action, which is a function called DeletePlanet()
.
Also add the following CSS between the <style>
tags so our button looks a bit nicer.
button {
background-color:green;
color:white;
border:none;
padding:10px;
width: 200px;
font-size:14px;
cursor:pointer;
margin-top:10px;
}
Now pop the following within the <script>
tags:
FMLink = 'FM.link';
PlanetIDField = '<?Reactor bbdev_Field( $PlanetIDField ) Reactor?>';
PlanetNameField = '<?Reactor bbdev_Field( $PlanetNameField ) Reactor?>';
FMTableOccurrenceName = '<?Reactor bbdev_TO( $PlanetIDField ) Reactor?>';
FunctionWhereClause = '<?Reactor bbdev_Relationship( bbdev_TO( $PlanetIDField ) ) Reactor?>';
function GetData() {
QueryData = FRTB.find(
FMTableOccurrenceName + '::' + PlanetNameField,
FMTableOccurrenceName + '::' + PlanetIDField
).where(FunctionWhereClause).send(function(response){
for ( var i=0; i < response.data.length; i++) {
planetname = response.data[i][FMTableOccurrenceName + '::' + PlanetNameField];
planetid = response.data[i][FMTableOccurrenceName + '::' + PlanetIDField];
x = document.getElementById("planetlist")
var option = document.createElement("option");
option.text = planetname;
option.id = planetid;
x.add(option);
}
});
}
function DeletePlanet() {
planetid = document.getElementById("planetlist").options[document.getElementById("planetlist").selectedIndex].id;
planetname = document.getElementById("planetlist").options[document.getElementById("planetlist").selectedIndex].value;
if (planetid=="" || planetname=="") {
alert ("You need to specify a planet to delete");
return
}
FRTB.remove(FMTableOccurrenceName).where(PlanetIDField + "='" + planetid + "'").send(function() {
var i;
for(i = document.getElementById("planetlist").options.length - 1 ; i >= 0 ; i--) {
document.getElementById("planetlist").remove(i);
}
GetData();
alert(planetname + " has been deleted");
});
}
GetData();
This JavaScript has two functions.
The GetData()
function is similar to that from our ListPlanets
function, with one key difference, instead of writing the planet data to the HTML as plain-text, we’re adding it to our <select>
drop-down element. Each record is a single <option>
element. The visible value
is the planet name, and we also store the record ID as a hidden id
parameter.
The DeletePlanet
function is run when the button is clicked from our HTML. First it identifies what planet we’ve selected from the list. If we don’t have a record ID or a planet name, it doesn’t let us go any further. On the other hand, if we do, it sends an instruction to Reactor to delete the Planet record with an ID matching that of the planet we’ve selected. After the record is deleted, it clears our dropdown and reloads it.
So having provided some HTML content to handle our content, some CSS to style our HTML, and some JavaScript to pull data in, and delete a record when the button is clicked, set our new HTML file as the main source file for our new function, compile/recompile the BlackBox and preview.
You should see a list of planets appear when clicking the dropdown.
So try selecting the planet we created in the previous section and clicking ‘Delete Planet’.
Before celebrating, let’s make sure it has been deleted:
It’s not in our list, but to be absolutely sure, let’s go to our PlanetList
function and get a 2nd opinion:
Success! Our testing planet is no more.
8. Polling For Changes
Sometimes you’ll want to show data changes in real time. For this section, we’ll utilise Reactor’s polling functionality.
We’ll create a table of planet data, which you will see changing before your eyes as the data changes.
Create a new function called LiveTable
with the following parameters:
Create a new template file.
First we need to define a table in the HTML, so now we need to replace the HTML block with this:
<table id="planettable" style="width:100%"></table>
Before going any further, a bit of an explanation of how Reactor polling works.
When you pull in any data to Reactor using field names, Reactor can utilise a modification timestamp auto-enter field to keep checking for changes after the initial query.
It must be called zModID
, and exist on any tables you are querying which you wish to poll:
There are three types of changes that are picked up by polling:
New records
Deleted records
Updated records
Ok, with that out of the way, on with the code. Replace the <script>
block with this:
FMLink = 'FM.link';
// This is where variables are set from the given parameters
var RecordsObj = new Object;
FMTableOccurrenceName = '<?Reactor bbdev_TO( $PlanetNameField ) Reactor?>';
FunctionWhereClause = '<?Reactor bbdev_Relationship( bbdev_TO( $PlanetNameField ) ) Reactor?>';
PlanetNameField = '<?Reactor bbdev_Field( $PlanetNameField ) Reactor?>';
PlanetDescriptionField = '<?Reactor bbdev_Field( $PlanetDescriptionField ) Reactor?>';
//This is what is used to retrieve data from FileMaker
function GetData() {
QueryData = FRTB.find(
FMTableOccurrenceName + '::' + PlanetNameField,
FMTableOccurrenceName + '::' + PlanetDescriptionField
).where(FunctionWhereClause).poll(PollChanges).send(function(response) {
var table = document.getElementById("planettable");
for ( var i=0; i < response.data.length; i++) {
planetname = response.data[i][FMTableOccurrenceName + '::' + PlanetNameField];
planetdescription = response.data[i][FMTableOccurrenceName + '::' + PlanetDescriptionField];
rowid = response.data[i][FMTableOccurrenceName + '::rowid'];
var row = table.insertRow(i);
row.id = rowid;
var cell1 = row.insertCell(0);
var cell2 = row.insertCell(1);
cell1.innerHTML = planetname;
cell2.innerHTML = planetdescription;
}
});
}
function PollChanges(results) {
// Deleted
for( var i=0; i<results.remove.length; i++ ){
for( var j=0; j<document.getElementById("planettable").rows.length; j++ ){
if (document.getElementById("planettable").rows[j].id == results.remove[i]) {
document.getElementById("planettable").deleteRow(j);
}
}
}
// Changed
for( var i=0; i<results.update.length; i++ ){
QueryData = FRTB.find(
FMTableOccurrenceName + '::' + PlanetNameField,
FMTableOccurrenceName + '::' + PlanetDescriptionField
).where("rowid="+results.update[i]).send(function(response) {
planetname = response.data[i][FMTableOccurrenceName + '::' + PlanetNameField];
planetdescription = response.data[i][FMTableOccurrenceName + '::' + PlanetDescriptionField];
for( var j=0; j<document.getElementById("planettable").rows.length; j++ ){
if (document.getElementById("planettable").rows[j].id == results.update[i]) {
document.getElementById("planettable").rows[j].cells[0].innerHTML = planetname;
document.getElementById("planettable").rows[j].cells[1].innerHTML = planetdescription;
}
}
});
}
// New
for( var i=0; i<results.create.length; i++ ){
var table = document.getElementById("planettable");
var row = table.insertRow(document.getElementById("planettable").rows.length);
row.id = results.create[i];
var cell1 = row.insertCell(0);
var cell2 = row.insertCell(1);
QueryData = FRTB.find(
FMTableOccurrenceName + '::' + PlanetNameField,
FMTableOccurrenceName + '::' + PlanetDescriptionField
).where(FunctionWhereClause).poll(PollChanges).send();
}
}
The GetData()
function does the usual, it queries the Planet records, with one notable difference:
).where(FunctionWhereClause).poll(PollChanges).send(function(response) {
Attaching .poll(PollChanges)
to the query tells Reactor to run the PollChanges()
function when polling.
The PollChanges
function has three separate blocks. Each block is a different loop, each loops through a different subset of the polling data; one for new records, one for updated records, and one for deleted records.
When a new record is created, we simply add a new row to the table for the new record.
When an updated record is discovered, we re-query that record, and update its cells of the table.
When a record has been deleted, we remove it from the table.
So save our document, recompile and preview.
Now open up a new window, and start adding new planet records, deleting them, and changing them. You’ll see your web-viewer reflecting this in real-time!
9. Running a script
For this section we’ll call a FileMaker script from within the web-viewer using Reactor.
First let’s create a new FileMaker script. Call this script ‘Show Dialog’, with the following script steps:
It’s a very simple script, which takes a parameter and displays it in a Custom Dialog.
Now, let’s create a new BlackBox function. This function will show a list of planets, with the ability to click a planet, which will run your FileMaker script.
Create a function called ChoosePlanet
. Your function should have the following parameters:
Create a new HTML template file, and replace the <body>
block with the following:
Please select a planet:
<br/><br/>
<ul id='planetlist'></ul>
This sets the basic structure of our web-viewer. It’s a header instructing the user to select a planet, with a list element (an un-ordered list to be specific).
And next, replace the <script>
block with the following:
FMLink = 'FM.link';
PlanetNameField = '<?Reactor bbdev_Field( $PlanetNameField ) Reactor?>';
PlanetDescriptionField = '<?Reactor bbdev_Field( $PlanetDescriptionField ) Reactor?>';
ScriptName = '<?Reactor $ScriptName Reactor?>';
FMTableOccurrenceName = '<?Reactor bbdev_TO( $PlanetNameField ) Reactor?>';
FunctionWhereClause = '<?Reactor bbdev_Relationship( bbdev_TO( $PlanetNameField ) ) Reactor?>';
function GetData() {
QueryData = FRTB.find(
FMTableOccurrenceName + '::' + PlanetNameField,
FMTableOccurrenceName + '::' + PlanetDescriptionField
).where(FunctionWhereClause).send(function(response){
for ( var i=0; i < response.data.length; i++) {
planetname = response.data[i][FMTableOccurrenceName + '::' + PlanetNameField];
planetdescription = response.data[i][FMTableOccurrenceName + '::' + PlanetDescriptionField];
var ul = document.getElementById("planetlist");
var li = document.createElement("li");
li.appendChild(document.createTextNode(planetname));
li.setAttribute("description", planetdescription);
li.setAttribute("onclick", "RunScript('" + planetname + "','" + planetdescription + "');");
ul.appendChild(li);
}
});
}
function RunScript(planetname,planetdescription) {
FRTB.script(ScriptName,planetname).send();;
}
GetData();
The GetData()
function should be pretty clear by now, it retrieves the planet names and descriptions (based on the field names passed through as parameters). It then creates a new <li>
element for each planet record, and adds it to our <ul>
list element. It takes the name of the planet as the value displayed for each list item, and it stores the description as an attribute. It also instructs the HTML to run a JavaScript function called RunScript()
when a planet is clicked, passing through the planet name and description.
If you cast your eyes below, you’ll see the RunScript()
function, this takes a name and description and tells Reactor to run the script that’s passed through as the ScriptName
parameter.
Also, add the following CSS between the <style>
tags to give our HTML a look and feel:
ul {
padding:0;
width:200px;
}
li {
list-style-type:none;
border:#539129 solid;
margin-bottom:5px;
padding:5px;
color:#0d260d;
}
li:hover {
background-color:#539129;
color:white;
cursor:pointer;
}
Save the document, and check out our new function. Set the parameters as so:
Now compile the BlackBox, and see it in action:
Success! It will send through the planet name you clicked to a FileMaker script.
So what have we done? We have:
Pulled through a data set of planets
Created a UI that lists the planets and allows the user to select one
Sends the clicked planet back to FileMaker Pro to process
So we have sent through a single parameter - the planet name. What if we want to send through multiple parameters? The best way would be to send it as JSON.
To do this, we create a new JavaScript object that contains the planet name and description, convert the object to JSON text, and send through that JSON to our script.
Replace the contents of our RunScript()` function with the following:
Planet = new Object();
Planet.name = planetname;
Planet.description = planetdescription;
Planet_JSON = JSON.stringify(Planet);
FRTB.script(ScriptName,Planet_JSON).send();;
And update our FileMaker script to pull the planet name and description from the JSON, and change our custom dialog so the title is the planet name, and the main content is the planet description:
Save our HTML document, our FileMaker script, recompile and give it another go:
Success! We have now successfully passed through multiple parameters to a FileMaker script from within a Reactor function.
10. Pull Container Images
When creating a Reactor function, you can also send through a container field as a parameter. Reactor is able to accept this container field and pull in the contents as images.
Let’s create a new function called ViewPlanet, with a single parameter:
(Be sure to set the data type as Container)
Go ahead and create new template HTML file and replace the <body>
block with:
<div id='Images'>
<?reactor bbdev_LoadImages( $PlanetImageField ; $PlanetImageField ; $com.reactorize.env.loadedfilepath ) reactor?>
</div id='Images'>
This tells Reactor to pull the container images in, based on the PlanetImageField
parameter, within the PlanetImages
<div>
element. The first parameter is the field we’re pulling the images from, and second parameter sets the filename of each image.
Save, compile, set the following parameter and give it a test:
Great, it works - we can pull through images from a found set of records.
It simply places the images inside the <div>
tags as <img>
elements:
<img src="mercury.png.png" />
<img src="venus.png.png" />
<img src="earth.png.png" />
<img src="mars.png.png" />
<img src="jupiter.png.png" />
<img src="saturn.png.png" />
<img src="uranus.png.png" />
<img src="neptune.png.png" />
<img src="Pluto-transparent.png.png" />
However, we’re likely to want a bit of control over how the images display. Let’s say we want to choose from a list of planets, and display the image of the planet we’re chosen. The first thing is we need to pull the images in, and store them in the HTML structure.
So we’ll add a 2nd parameter for the function that pulls in the planet name, this will form the filename of the image, this way we’ll be able to access an individual image.
And we’ll need to change our HTML to assign each image with the planet name as the filename. We’ll also want to change our <div>
to be invisible, so while it stores the images, it won’t display them until we wish to.
<div id='Images' style='display:none'>
<?reactor bbdev_LoadImages( $PlanetImageField ; $PlanetNameField ; $com.reactorize.env.loadedfilepath ) reactor?>
</div id='Images'>
When we compile and preview our updated function, we’ll now see an empty web-viewer.
So let’s update our HTML to include the structure to pull in our planet names into a dropdown list, and display the selected planets image. Replace the contents of the <body>
block with this:
<select id="planetlist" onchange="ChangeImage();"></select><br/><br/>
<img id="planetimage"></img>
<div id='Images' style='display:none'>
<?reactor bbdev_LoadImages( $PlanetImageField ; $PlanetNameField ; $com.reactorize.env.loadedfilepath ) reactor?>
</div id='Images'>
This shows a dropdown for the list of planets, an <img>
placeholder for when a planet is selected, and the Images <div>
tells Reactor to pull in the images from the PlanetImageField
paramater, but doesn’t display them yet.
Now we need to build in the logic to fill our planet dropdown, and populate our <img>
placeholder when a planet is selected from that list. Replace the contents of the <script>
block with this:
FMLink = 'FM.link';
PlanetNameField = '<?Reactor bbdev_Field( $PlanetNameField ) Reactor?>';
FMTableOccurrenceName = '<?Reactor bbdev_TO( $PlanetNameField ) Reactor?>';
FunctionWhereClause = '<?Reactor bbdev_Relationship( bbdev_TO( $PlanetNameField ) ) Reactor?>';
function GetData() {
QueryData = FRTB.find(
FMTableOccurrenceName + '::' + PlanetNameField
).where(FunctionWhereClause).send(function(response){
for ( var i=0; i < response.data.length; i++) {
planetname = response.data[i][FMTableOccurrenceName + '::' + PlanetNameField];
x = document.getElementById("planetlist")
var option = document.createElement("option");
option.text = planetname;
x.add(option);
}
ChangeImage();
});
}
function ChangeImage() {
planetname = document.getElementById("planetlist").options[document.getElementById("planetlist").selectedIndex].value;
document.getElementById("planetimage").src = planetname + ".png";
}
GetData();
The GetData()
function is pretty self-explanatory, it pulls in the planet names and uses them to populate the planet list dropdown.
The ChangeImage()
function looks at the currently selected planet name, and populates the <img>
placeholder with the corresponding planet image. This function is called when the planet list <select>
element is changed to a different selection. It’s also called when the planet names are initially pulled in to set it to the first planet in the list.
Go ahead and recompile our BlackBox and give it a preview. Change the PlanetImageField
to the full-sized image field:
You should see the image switch when you change the planet in the dropdown list.
11. Using The Starting Template
In this tutorial we have been writing our code from scratch. When we generate a HTML template for a function, it gets us started with not only the basic HTML structure, but also includes code for the following:
Imports all JavaScript and CSS documents listed in on the
Source Files
tab for the BlackBox (including theFRToolbox
library needed for any Reactor functions)Sets any text-based parameters as variables
Pulls in any container images and stores them in an invisible
<div>
Performs the following for any field-based parameters:
Stores the field name, table occurrence and relationship predicates as variables
Writes a function for retrieving record values, and storing as an array of objects
Writes a function for creating a new record from JavaScript object
Writes a function for updating a particular record from JavaScript object
The template code won’t be perfect, and in many cases you may wish to simply clear much of it (as we have in this tutorial), but it will help to get you started, and save a lot of time.
12. Debugging
Like all development, things will go wrong when building your BlackBoxes. It’d be very unlikely if you got to the end of this tutorial without that happening.
Reactor has a debugging mode to assist you when this happens.
It is accessible with the following function: BlackBoxDebugging( "Yes" )
To enable debugging mode, you can either pop it in the web-viewer:
Or run it as part of a script:
After you’ve enabled debugging mode, you will see a small icon in the bottom-right of any web-viewers that use Reactor functions - such as the Preview tab:
If you don’t see it, maybe try switching to layout mode and then back to browse mode.
If you click it, you’re presented with the following tabs:
The Console
tab gives you access to a JavaScript console. If your javaScript contains any errors it will list them here, and you’ll be able to run JavaScript in real-time. This might help you to debug variables, try running functions, etc:
The HTML
tab allows you to review the HTML structure that renders in the web-viewer, this can help debug errors around where we’re inserting things in <?reactor
reactor?>
tags, such as script parameters and container field images. You can even inspect the CSS for each HTML element.
The CSS
tab allows you to view the CSS for the HTML, across as many CSS files as your HTML uses. You can even change the CSS properties in real time. This can help you to tweak the look and feel and see your changes instantly. Though it won’t save your changes - you’ll need to actually make the changes back in your CSS/HTML document.
The Requests
tab allows you to see the communications between your JavaScript and Reactor. This will help you to debug queries, it lists all Reactor communications, whether they succeeded/failed, the request, and the response:
There is also a Browser
button, this will simply open your BlackBox function in your default browser, in a new tab. This allows you to conduct some more advanced debugging using your default browsers development tools:
You can disable debugging mode at any time using the function BlackBoxDebugging ( "No" )
: