Countdown Timers – Multi-Threading With Web Viewers

In FileMaker Pro 10, FileMaker introduced OnTimer scripts which allow us to trigger a script to be run after a specified period of time. OnTimer scripts are great for implementing delays, timed refreshes and a whole host of possibilities yet to be discovered. However, one drawback that I have run into is that when the script runs, the user’s activities can be interrupted. At the very least, you will notice mouse pointer flashes, and worse, depending on the nature of the script, it could cause the user to be exited from a field or position on the layout. So, while I have found OnTimer scripts to be great for delaying script execution, or periodic status checks, I have not found it to be as useful in cases where continuous, dynamic interface updates are required.

The multitasking capabilities in FileMaker Pro are limited in this way. Web viewers, however, run in different threads, and when fed with data from your FileMaker Pro database, they allow us to create rich, up-to-the-second, content, complete with animation, sounds, popup alerts, or whatever else we want to put in there. And, the best part: it has no noticeable affect on FileMaker’s performance!

This article covers a simple example of what can be done using Web Viewers, and it just scratches the surface of what can be done. For instance, I have also created an alarm clock as well as an interactive, internal instant messaging program with nothing but FileMaker Pro and web viewers: no plugins and no external internet access required. I’ll be discussing these in another article soon.

The Basics

Note: Feel free to skip right to The Countdown Timer Project section below if you are not interested in understanding the fundamentals of this process. You can make it work without a clear understanding, but if you wish to make enhancements or need to debug problems, you will want to read through this.

Data URI

When you set up a web viewer, you normally specify a URL (web address) indicating what web page the web viewer should display. With a data URI, we can specify the HTML (and JavaScript) we want to display in the web viewer directly in place of the URL itself. This is very powerful in that it allows us to easily build a web page to display using our FileMaker data. It is a great way to create custom HTML reports that are not so easily created within the structure of FileMaker Pro. I have also used it with Instant Web Publishing to achieve greater flexibility.

While the basic format of a URL is http://<address>, the basic format of the data URI is as follows:

data:[<MIME-type>][;charset=<encoding>][;base64],<data>

Don’t worry too much if that doesn’t make sense to you. You can copy and paste the code from the articles for these projects. All we will be doing is specifying a mime type (text/html), and including the HTML and JavaScript that we need. So our Data URI will look something like this:

data:text/html,<some_html_code>

If you are interested in general information about the data URI, check out the Wikipedia Data URI article.

Including FileMaker Pro Data

With the data URI, we can build dynamic HTML content within FileMaker Pro using the calculation engine. This allows us to easily include data from our FileMaker database within our HTML.

JavaScript

In these sample projects, we will be using JavaScript to create dynamic content. JavaScript is a powerful, object-oriented scripting language used extensively on nearly every website that you visit. If you are not familiar with JavaScript, don’t be too concerned. You can copy and paste the code below. Hopefully, you will get a sense of what the code is doing if you want to make some enhancements.

One of the key JavaScript functions we will be using is the setTimeout function. This function allows us to specify a function to be called after a given period of time. It is similar to FileMaker Pro’s, OnTimer Script, script step, but it does not tie up the FileMaker Pro scripting engine. We will be creating a JavaScript function to update the web viewer content, and that function will use the setTimeout function to call itself again in 1 second. So, the content will refresh continually.

The Countdown Timer Project

Step 1 – Create Timer Table

In this step, we create a simple table in our FileMaker Pro database that stores our timer parameters. There are a number of ways this could be set up depending on how you want to use the timer. For instance, for this example, we will set it up so there is one timer per user. In this way, if a user closes the database and opens it again (whether on the same workstation or a different workstation), their countdown timer will persist. Alternatively, the timer could be workstation based, function based or session based. You may also choose to allow a user to run multiple timers at one time.

For the user-based timer, our table will have the following fields:

__pk_TimerID – Number – Auto enter Serial. This is the primary key of the table. Though we don’t actually use this field for any relationships or other functionality, it is always good practice to include a primary key field in every table that you create. This way, if you ever need to isolate a record it in a relationship or script, you will have a way.

_fk_User – Text. In my database, this is the id of a record in the User table that corresponds to the currently logged in user. However, if such a table does not exist in your database, it could simply be the account name of the currently logged in user.

Label – Text. This field is optional and allows one to give the timer a label. For instance, if you want to remember to call someone in 1/2 hour, set the label to “Call Fred,” and when the timer goes off, it will flash, “Call Fred.” This is especially useful if you set it up to allow multiple simultaneous timers.

Status – Text. This field will be used to flag the timer as “Acknowledged” so it stops flashing and beeping. You could simply use a number field as a flag (with values 1 or 0), but I like using the text field to allow for future expansion.

Timestamp_End – Timestamp. This is the time that the timer will go off. This field will be set by a script that asks the user for a timer duration. It then simply adds the duration to the current time, and that will be the timestamp when the timer will go off.

Duration – Number. For data entry. This field stores the number of minutes before the timer should go off.

Step 2 – Relationships

Only one relationship is needed and there are two possible ways to handle it:

  • If you have a user table, such as I do in my database, create a relationship between the Timer table and the table occurrence of the User table that represents the currently logged-in user. The relationship will be based on this: Timer::_fk_user = User::UserID
  • If no user table and current user occurrence exists, simply create a global field in one of your tables (preferably a Globals table) that gets set upon log in to the current account user name. Then create a relationship between that table and the Timer table using your global account name field = Timer::_fk_User field.

In order to keep the scripting simple, select the checkbox to allow creation via this relationship:

Select allow relationships in this table via this relationship to simplify the scripting process.

Select “allow creation of records in this table via this relationship” to simplify the scripting process.

Step 3 – Web Viewer Setup

Create a web viewer sized 115×40 (you can do any size, but the HTML code below is optimized for this size.

The Timer.

For the web address, use the following code:

"data:text/html,<html>
<head>
<script type='text/javascript'>

/*Setting JavaScript Variables to value of FileMaker fields*/
var targetTimeTxt = '" & Timer::Timestamp_End & "';
var status = '" & Timer::Status & "';
var labelTxt = '" & Timer::Label & "';
/*End FileMaker Fields*/

var currentTime = new Date();
var targetTime = new Date(targetTimeTxt);
var remaining = Math.floor((targetTime - currentTime)/1000);

function setClock()
{
	var currentTime = new Date();
	var clock = document.getElementById('clock');
	var labelobj = document.getElementById('label1');
	var secondsRemaining=0;

	if(labelTxt=='')
	{
		labelobj.innerHTML='Timer';
	}
	else
	{
		labelobj.innerHTML=labelTxt;
	}

	if (targetTime>currentTime)
	{
		secondsRemaining=Math.floor((targetTime - currentTime)/1000);
	}

	var hours = Math.floor( secondsRemaining / 3600 );
	var minutes = Math.floor((secondsRemaining%3600) / 60 );
	if(minutes<10)minutes='0' + minutes;
	var seconds = secondsRemaining%60;
	if(seconds<10)seconds='0'+seconds;

	clock.innerHTML=hours + ':' + minutes + ':' + seconds;

	if(targetTimeTxt=='' || status=='Acknowledged' || ( secondsRemaining==0 && Math.floor(currentTime/1000)%2==0 ) )
	{
		document.body.style.backgroundColor='#FFFFFF';
		if ( targetTimeTxt=='' || status=='Acknowledged' )
		{
			clock.innerHTML='--:--:--';
		}
	}
	else if(secondsRemaining==0)
	{
		document.body.style.backgroundColor='#FFFF00';
		document.getElementById('sound1').Play();
	}

	setTimeout('setClock();',1000);
}

</script>
</head>
<body style='margin:4px;padding:0;font-size:14px;font-weight:bold;font-family: Arial, Helvetica, sans-serif;text-align:center;background-color:#FFFFFF;' onload='setClock();'>
<div id='label1' style='font-size:10px;font-weight:bold;'>
</div>
<div id='clock'>
</div>
<embed src='file:///System/Library/Sounds/Glass.aiff' autostart='false'  id='sound1'
enablejavascript='true' width='0' height='0'>
</body></html>"

It’s not necessary for you to understand all of this code in order for you to get this to work. However, near the top, there are a series of var statements. These are setting JavaScript variables which are later used in the JavaScript function. Many of these variables are being set with FileMaker Pro data. For instance,

var targetTimeTxt = '" & Timer::Timestamp_End & "';
var status = '" & Timer::Status & "';
var labelTxt = '" & Timer::Label & "';

These line set JavaScript variables to the value of these FileMaker fields. If your fields and/or table occurrence are named differently, you will need to adjust these lines accordingly.

Also, take note of the following HTML code:

<embed src='file:///System/Library/Sounds/Glass.aiff' autostart='false'  id='sound1'
enablejavascript='true' width='0' height='0'>

This specifies the sound that will be used when the timer goes off. The file URL after the scr= statement is the path on a Macintosh workstation to the Glass.aiff sound effect file. If you are using Windows, this path will have to be adjusted. If you are in a mixed environment, you may want to use a sound file that is located on the web.

Enhancement: You could paste this little web viewer to every layout in your solution. In this way, no matter what the user is doing, they will be able to see the timer and will be alerted when the timer goes off. You can adjust the font attributes in the body tag to make it fit.

Step 4 – Scripting

We will be creating one script that will fire when clicking on the timer web viewer. Depending on the state of the timer, it will perform one of three tasks:

  • When the timer is idle, it will prompt the user for a timer duration and optional label, then start the timer.
  • When the timer is running, it will ask the user if the timer should be stopped. If so, it stops and clears the timer.
  • When the timer is going off (the countdown has reached 0), it will flag the timer as acknowledged, thus causing the JavaScript function to stop flashing and dinging.

Here are the script steps:

If [ Timer::Status = “Acknowledged” or IsEmpty ( Timer::Timestamp_End ) ]
#If the timer is not running, prompt for timer settings and run timer.
Show Custom Dialog [
Title: “Set Timer”; Message: “Enter the number of minutes before the timer should go off:”;
Buttons: “OK”, “Cancel”;
Input #1: Timer::Duration, “Minutes”;
Input #2: Timer::Label, “Label (optional)”
]
If [ Get ( LastMessageChoice ) = 1 ]
Set Field [Timer::Timestamp_End; Get ( CurrentTimeStamp ) + Timer::Duration * 60 ]
Set Field [ Timer::Status; “Running” ]
End If
Else If [ Timer::Timestamp_End < Get ( CurrentTimeStamp ) ]
#If the timer is going off, set as “Acknowledged”.
Set Field [ Timer::Status; “Acknowledged” ]
Set Field [ Timer::Label; “” ]
Else
#Otherwise, the timer is running. Ask to stop it.
Show Custom Dialog [
Title: “Stop Timer”;
Message: “Would you like to stop the timer?”;
Buttons: “Yes”, “No”
]
If [ Get ( LastMessageChoice ) = 1 ]
Set Field [ Timer::Timestamp_End; Get ( CurrentTimeStamp ) ]
Set Field [ Timer::Status; “Acknowledged” ]
Set Field [ Timer::Label; “” ]
End If
End If
Commit Records/Requests [ No dialog ]

Finally, define a button for the web viewer that calls the script you just created.

That’s it. The timer should now work for you. If it doesn’t, take a look at the note on debugging below.

What Else Is Possible?

The possibilities are only limited by your imagination and HTML and JavaScript skills.

In the next article, I’ll be talking about a similar, but slightly more complex project to create an alarm clock.

After that, we’ll so something a little more fun, and perhaps more practical: an internal instant messaging system. This project is considerably more complex, but most of the complexity lies in FileMaker Pro development, rather than HTML and JavaScript. Arguably, the project could be accomplished exclusively with native FileMaker Pro tools, but I find it to be much more responsive, robust and dynamic when using Web Viewers.

Debugging

This technique can be challenging to debug, because if something it not working correctly, the only indication you generally receive is an empty web viewer.

Note: The debugging technique I describe here uses features specific to FileMaker Pro Advanced (specifically, the data viewer). If you do not have FileMaker Pro Advanced, you will have to create a calculated field to achieve the same result (see alternative steps in parentheses below).
  1. Copy the contents of the “web address” field from the web viewer.
  2. Open the data viewer, click the “Watch” tab and click the “+” button to add a new watch expression. (If not using FileMaker Pro Advanced, go to Manage Database and add a new calculated field.)
  3. Paste the code you copied from the web viewer into the calculation field.
  4. Click the Evaluate Now button in the lower left of the window. (If not using FileMaker Pro Advanced, add the calculated field to a layout.)
  5. Inspect the contents, especially the var expressions to see that the FileMaker data is being captured properly.
  6. If you were not able to see any problems, copy the result into your clipboard.
  7. Open your web browser (I prefer FireFox, because I like the error log).
  8. Paste the result into your browser’s location field (the place you normally type web addresses). Then press enter.
  9. Open your browser’s error log. In FireFox, go to Tools->Error Log. In Safari, go to Window->Activity. In Internet Explorer, double click the little error icon in the status bar at the bottom.

Hopefully, the errors reported by the browser will give you a clear enough indication of what went wrong. BTW: you may need to scroll all the way to the bottom of a long error log to see the most recent error.

Some other ideas:

Use the document.print JavaScript function to print messages to the web viewer or the document.alert function to bring up a dialog box. For instance, if you type the following in your JavaScript code:

document.print('hello world');

It will print hello world in the web viewer.

Or

document.alert('hello world');

This will bring up a dialog box displaying hello world.

You can also put variables (the terms defined in the var statements):

document.print(targetTimeTxt);

To concatenate strings, use the ‘+’ operator the same way you use the ‘&’ in FileMaker Pro calculations. For instance:

document.alert('This is the target time: ' + targetTimeTxt);

And of course, if you are still stuck or you have any questions or comments, feel free to post a comment below or contact me directly.

5 Comments