Writing Our Second Scriptlet

By now, you should have grasped the idea of what Scriptlets are, and how to write them. It's time to design and code a sample which is a little more complex. What we have in mind is a Clock object that you can put in all your pages as a reminder of time passing. A digital clock is made up of three components: hours, minutes and seconds. We can render it using a simple text string that needs formatting each time it is updated, or we can use a small table that keeps the various logical parts of the time separate. A very simple HTML table can be defined like this:

<TABLE>
  <TR>
    <TD VALIGN="TOP" ALIGN="CENTER">0</TD>
    <TD VALIGN="TOP" ALIGN="CENTER">0</TD>
    <TD VALIGN="TOP" ALIGN="CENTER">0</TD>
  </TR>
</TABLE>

All the tags (TABLE, TR, TD) expose lots of attributes that set the formatting and layout of the table, but for our purposes they can all be ignored. However, a better approach would be to make them properties of the Clock Scriptlet. This sounds reasonable—especially for BORDER and CELLSPACING attributes.

A clock is a self-initialized component, so it doesn't need specific initialization code to work properly. If you open this Scriptlet in a browser you're able to use it straight away. Once more, this demonstrates that a Scriptlet is an HTML page that runs inside another HTML page:

The final 'extended' version of this page, which you'll meet later in the chapter, can be run or download from our Web site at http://rapid.wrox.co.uk/books/0707.

Coding the Clock Scriptlet

Our HTML clock component will exploit the setInterval and clearInterval methods of the window object. We just need to provide a function to stop and restart it, and some events. Here's the complete source code:

<HTML id=MyPage>
<TITLE>Clock Scriptlet</TITLE>

<script language="VBscript" for="window" event="onload">
    InitClock
</script>

<script language="VBscript">
 
Sub InitClock
  DoSetColor mBgColor, mFgColor 
  DoUpdateClock
  if InScriptlet then
    window.external.selectableContent = True
  end if
  if mEnabled then
    mTimer = window.setInterval("DoUpdateClock", 1000)
    mStartedAt = Time
  end if
End Sub
 
Sub DoSetColor(b, f)
  Set coll = document.all.tags("TABLE")  
  coll.item(0).style.backgroundColor = b
  coll.item(0).style.Color = f
  coll.item(0).style.fontFamily = "Tahoma"
End Sub
 
Sub DoUpdateClock
  t = Time
  mHour = Hour(t)
  mMins = Minute(t)
  mSecs = Second(t)  
  set coll = document.all.tags("TD")
  coll.item(0).innerHTML = Right("00"+CStr(mHour),2) + ":"
  coll.item(1).innerHTML = Right("00"+CStr(mMins),2) + ":"
  coll.item(2).innerHTML = Right("00"+CStr(mSecs),2) 
End Sub
 
Function DoFormatTime
  s = FormatDateTime(Time(), vbLongTime)
  DoFormatTime = s
End Function 
 
Function DoGetTime
  DoGetTime = Time
End Function 
 
Function DoAlarm
  if (InScriptlet And (Not window.external.frozen)) then
     window.external.raiseEvent "OnAlarm", window.document 
  end if 
End Function
</script>

<script language="JavaScript">
public_description = New CreateClock();  // declare the interface
var InScriptlet = (typeOf (window.external.version) == "string");

mBgColor = "gray";
mFgColor = "white";
mEnabled = 1;
mTimer = 0;
mStartedAt = 0;
mAlarm = 0;
 
function CreateClock() {
  this.put_Text = put_Text;
  this.get_Time = get_Time;
  this.get_StartedAt = get_StartedAt;
  this.put_ClockBgColor = put_ClockBgColor;
  this.put_FgColor = put_FgColor;
  this.get_FgColor = get_FgColor;
  this.put_BgColor = put_BgColor;
  this.get_BgColor = get_BgColor;
  this.Enable = Enable;
  this.Alarm = Alarm;
  this.event_OnStart = "";
  this.event_OnStop = "";
  this.event_OnAlarm = "";
}
 
function put_ClockBgColor(color) {
  document.bgColor = color;
  return 1;
}
 
function put_Text(sText) {
  document.all("Text1").innerHTML = sText;
  return 1;
}
 
function get_Time() {
  return DoFormatTime();
}
 
function get_StartedAt() {
  return mStartedAt;
}
 
function put_ForeColor( color ) {
  mFgColor = color;
  DoSetColor(mBgColor, mFgColor);
  return 1;
}

function get_ForeColor() {
  return mFgColor;
}

function put_BackColor(color) {
  mBgColor = color;
  DoSetColor(mBgColor, mFgColor);
  return 1;
}
function get_BackColor() {
  return mBgColor;
}

function Enable(b) {
  mEnabled = b; 
  if (b) {
    mTimer = window.setInterval("DoUpdateClock", 1000);
    if (InScriptlet) and (not window.external.frozen)) {
       window.external.raiseEvent("OnStart", 0);
       mStartedAt = DoGetTime();
    }
  }
  else {
    window.clearInterval(mTimer);
    Alarm(0);
    if (InScriptlet and (not window.external.frozen)) {
       window.external.raiseEvent("OnStop", 0);
    }
  }
  return 1;
}

function Alarm(secs) {
  if (secs) {
    mSnooze = window.setInterval("DoAlarm", secs); 
  }
  else {
    window.clearInterval(mAlarm);
  }
  return 1;
}
</script>

<BODY>
<TABLE>
<TR>
   <TD VALIGN="TOP" ALIGN="CENTER">0</TD>
   <TD VALIGN="TOP" ALIGN="CENTER">0</TD>
   <TD VALIGN="TOP" ALIGN="CENTER">0</TD>
</TR>
</TABLE>
</BODY>
</HTML>

The Clock Interface Definition

The public interface of the Scriptlet exposes the following properties, methods and events:

Name Description
FgColor Sets and gets the foreground color of the clock, i.e. color of the digits.
BgColor Sets and gets the background color of the clock area.
ClockBgColor Sets the background color of the Scriptlet area.
Time Returns the current time formatted accordingly to the user settings.
StartedAt Returns the last time when the clock Scriptlet was started.
Text Sets the text to be displayed below the clock.
Enable(state) A method that disables and enables the clock causing it to stop and go.
Alarm(secs) A method that sets up an alarm to raise an onAlarm event every given number of milliseconds.
onStop This event is fired each time the clock gets disabled.
onStart This event is fired each time the clock is started.
onAlarm This event signals that the given period is expired.

The Time property is read-only, and returns the current time. It makes use of the current user's format settings for the date and time with VBScript's FormatDateTime function. FgColor and BgColor define the color of the text and the clock background—by default, we have white text on a gray background. StartedAt has a secondary role, and its only purpose is to enrich the Scriptlet's interface. ClockBgColor is a write-only property assigned to the background color of the entire site area, which is usually darker than the clock itself.

Text is another write-only property that contains the text string we might want to associate with the clock. This text is the body of a SPAN tag with an ID of Text1. Since this is implemented via the element's innerHTML property, we can assign it text containing HTML tags as well, and be sure it will be correctly handled. This provides us with the opportunity to format the text, be it in italic or bold font, or to include images, links, etc. For instance:

Clock1.Text = "Current Time offered by <i>WROX Press</i>"

The final property, Enable, is at the core of the component. It accepts a Boolean value, and starts or stops the clock.

Updating the Time

All the updates of the clock are controlled by the setInterval method of the window object:

mTimer = window.setInterval("DoUpdateClock", 1000);

The setInterval method takes a string denoting the script code to be executed each time the given interval expires, and an interval period expressed in milliseconds. The code above causes the DoUpdateClock procedure to be called once every second. As you might guess, DoUpdateClock just refreshes the table representing the digital clock. First it obtains the current time in terms of hours, minutes and seconds. Then it sets the innerHTML properties of the three TD elements:

Sub DoUpdateClock
  t = Time
  mHour = Hour(t)
  mMins = Minute(t)
  mSecs = Second(t)
  set coll = document.all.tags("TD")
  coll.item(0).innerHTML = Right("00"+CStr(mHour),2) + ":"
  coll.item(1).innerHTML = Right("00"+CStr(mMins),2) + ":"
  coll.item(2).innerHTML = Right("00"+CStr(mSecs),2) 
End Sub

Stopping the Clock

The setInterval method returns a unique identifier for the timer is starts running, and we saved this in a variable mTimer. To stop the clock, we just pass this variable to the window object's clearInterval method:

window.clearInterval(mTimer);

The onStop and onStart events are fired after the clock has been stopped and restarted. The onStart event isn't raised when the Scriptlet is loading for the first time. As you have seen in our earlier discussions, the event declarations are not really required for the Scriptlet to work. The host page will always receive onScriptletEvent events, whatever the actual event the component raises with the raiseEvent method. However, we've included the event declarations simply to document the Scriptlet's interface.

Hosting the Clock Component

A sample page that hosts our Clock component might look like this:

Here's the code for the complete page, including the <OBJECT> tags that insert the Clock Scriptlet:

<html>
<title>Test page using Clock</title>
<b>Test page using Clock</b>

<SCRIPT LANGUAGE="VBScript" FOR="window" EVENT="onload">
  Clock1.BgColor = "lightblue"
  Clock1.FgColor = "blue"
  Clock1.Alarm 5000 
</SCRIPT>

<SCRIPT LANGUAGE="VBScript" FOR="Clock1" EVENT="onscriptletevent(n,o)">
  if n = "onStart" then
    MsgBox "Current time is " + Clock1.Time
  end if
  if n = "onStop" then
    MsgBox  "Started at " + CStr(Clock1.StartedAt)
  end if
  if n = "onAlarm" then
    CRLF = Chr(13) + Chr(10)
    sMsg = "Alarm " + Clock1.Time + CRLF + "Continue?"
    i = MsgBox(sMsg, vbYesNo, "Clock")
    if i = vbNo then Clock1.Alarm 0     
  end if
</SCRIPT>

<SCRIPT LANGUAGE="VBScript">
Sub Button1_Click()
  if Button1.Caption = "Stop" Then
    Clock1.Enable(0)
    Button1.Caption = "Start"
  else
    Clock1.Enable(1)
    Button1.Caption = "Stop"
  end if
End Sub
</SCRIPT>

<p>
<OBJECT ID="Clock1" WIDTH=100 HEIGHT=70 align="bottom" 
  type="text/x-scriptlet" DATA="clock.htm">
</OBJECT>
</p>

<OBJECT ID="Button1" WIDTH=96 HEIGHT=32
   CLASSID="CLSID:D7053240-CE69-11CD-A777-00DD01143C57">
   <PARAM NAME="Caption" VALUE="Stop">
</OBJECT>
</body>
</html>

During the onload event it assigns the clock colors. By clicking on the button you can stop or restart the clock.

Adding an Alarm to the Clock Container Page

Our clock page as it stands doesn't do very much, so we've added a function that simulates an alarm-clock. We can use several of the other methods and events that our Clock component has built into it. By assigning an interval in milliseconds using the Alarm method, like this:

Clock1.Alarm 5000 

we are notified of a specific onAlarm event after that period expires (in this case, 5 seconds). Our Clock component provides this custom event via the onScriptletEvent in the container page. The Alarm method works by setting an interval:

function Alarm(secs) {
  if (secs) 
    mAlarm = window.setInterval("DoAlarm", secs); 
  else 
    window.clearInterval(mAlarm);
  return 1;
}

To cancel the alarm, we simply pass zero to the Alarm method, which clears the interval timer. When that interval is up, the DoAlarm routine in our component is executed. This routine raises an event that can be detected in our page:

Function DoAlarm
  if (InScriptlet And (Not window.external.frozen)) then
     window.external.raiseEvent "onAlarm", window.document 
  end if 
End Function

Notice that our code checks both that we are actually running the component as a Scriptlet, and that the container page is ready to receive events, first.