Finite state machines in JavaScript, Part 2: Implement a widget

Develop browser apps with JavaScript and finite state machines

Part 1 of this series illustrated how to use a finite state machine to methodically design complex behavior for a simple Web widget -- an animated tooltip that fades into and out of view. In this article, you learn to implement that behavior in JavaScript and take full advantage of its distinctive language features, including associative arrays and function closures. The resulting code is compact and concise, its logic is transparent, and its animation performs smoothly even on heavily loaded processors.

Part 3 will cover the practical issues of how to make the implementation work in all popular Web browsers.

Edward J Pring, Senior Software Engineer, IBM

Photo of Edward PringEdward Pring holds an M.S. degree in Computer Science from New York University and a B.S. degree in Mathematics from Stanford University. As part of IBM Research, he has contributed to a wide range of IBM products and technologies, including operating systems, publishing applications, terminal emulators for mainframes, virus protection for personal computers, network automation for the Digital Immune System, and visualization and performance analysis for Web Services. His patent portfolio spans all of these fields.



13 February 2007

Also available in Russian Japanese

Finite state machines are an organizing principle for those who design and implement complex behavior in event-driven programs, such as network adapters and compilers. Now, programmable Web browsers have opened a new event-driven environment to a new generation of applications. As browser-based applications, popularized by Ajax, become more complex, you can benefit from the discipline and structure that finite state machines offer.

Part 1 describes a tooltip widget for Web pages that has more elaborate behavior than the built-in implementation provided by popular Web browsers. This FadingTooltip widget fades into view after the cursor hesitates over an HTML element, and then fades from view after the tooltip has displayed for a while. The tooltip follows the cursor as it moves, even as it fades in and out, and the fading will reverse direction when the cursor moves away from the HTML element, or back over it. This behavior requires the FadingTooltip widget to respond to a variety of different events, and in some cases, the desired response to a particular event depends upon previous events.

Developers have used the finite state machine design pattern to organize event-driven programs like this. In Part 1, you applied applied its design discipline to produce a state table that defines the desired behavior, as shown in Figure 1.

Figure 1. State table for FadingTooltip widget
State table for FadingTooltip widget

The rows and columns of the state table are labeled with the names of the events that the widget will respond to, and the states the widget will wait in between events. Each cell in the table specifies the actions the widget will take when a particular event occurs in a particular state. Table cells can also specify the next state the widget will transition to, after the actions are taken, or specify that the widget willl remain in the same state. Empty table cells indicate events that should not occur in particular states. Also, in Part 1, you compiled a list of state variables the widget will need to remember between events so that it can execute related actions in different cells.

In Part 3, you'll test the implementation in popular browsers, and deal with the reality of should-not-occur situations.

Part 1 asserted that JavaScript is well-suited as an execution environment for finite state machines, and mentioned some of its capabilities that were relevant to the design phase. In this article, you will work through the details of translating your design into JavaScript, taking advantage of some elegant language features, and accommodating some inelegant details that make the implementation tricky.

Translating the design into JavaScript

Having completed the design of a finite state machine in Part 1, you are ready to implement the FadingTooltip widget in JavaScript. This is where the easy abstractions of the design phase give way to the harsh practicalities of real execution environments.

You'll consider only the most current versions of the most popular browsers: Netscape Navigator, Microsoft® Internet Explorer®, Opera, and Mozilla Firefox. Even this limited set of execution environments will provide headaches enough. You'll struggle with the details of actually hooking mouse and timer events from different browsers to a JavaScript program. An elegant JavaScript language feature called function closures will come to your rescue. You'll employ another elegant JavaScript language feature, associative arrays, to translate your state table directly into code. And, you'll see how to create and style a tooltip using an HTML division element, fill it with text and images, position it near the cursor, fade it into and out of view, and make it follow the cursor's movements.

But first, in the best spirit of object-oriented development, you need an object to contain everything else you'll implement, so we'll start with that.


An object to contain everything

The FadingTooltip widget will require a more sophisticated programming effort than is typical for the short snippets of JavaScript code that Web designers often cut and paste into HTML pages. Software engineers will be comfortable with the idea of grouping the variables and methods of the widget into an object, though the JavaScript object model might seem a bit peculiar to those schooled in Java™ and C++ programming. A JavaScript object is entirely adequate for your needs: it will let you group variables and methods into an object, and then create separate instances of the data for each tooltip. The object instances will share common code and run independently.

In JavaScript, an object constructor is just a function -- the function's name is the object's name. Your widget will need to know which HTML element to hook itself into and what content to display in its tooltip, so you'll specify these as arguments to the constructor and save them in the object. (You will also need a way to set parameters related to the behavior and appearance of the tooltip, so you will specify an argument for that too, and use it later in this article.) Variables are untyped, so the object constructor might begin with code shown in Listing 1.

Listing 1. JavaScript code for FadingTooltip object constructor
 function
                FadingTooltip(htmlElement, tooltipContent, parameters) { this.htmlElement =
                htmlElement; // save pointer to HTML element whose mouse events // are hooked to
                this object this.tooltipContent = tooltipContent; // save text and HTML tags for the
                tooltip's // HTML Division element ...

In JavaScript, you can add object properties, which can be variables or methods, to an object when it is created, or at any time thereafter, just by assigning a value to them, as the constructor does for the properties this.htmlElement and this.tooltipContent.

In JavaScript, an object prototype is a template for creating new instances of an object; it defines an object's initial properties and their initial values. You'll begin your object prototype with the state variables from Part 1 that the widget will need, as shown in Listing 2.

Listing 2. JavaScript code for FadingTooltip object prototype
                FadingTooltip.prototype = { currentState: null, // current state of finite state
                machine (one of the state // names in the table below) currentTimer: null, //
                returned by setTimeout, non-null if timer is running currentTicker: null, //
                returned by setInterval, non-null if ticker is running currentOpacity: 0.0, //
                current opacity of tooltip, between 0.0 and 1.0 tooltipDivision: null, // pointer to
                HTML division element when tooltip is visible lastCursorX: 0, // cursor x-position
                at most recent mouse event lastCursorY: 0, // cursor y-position at most recent mouse
                event ...

The object prototype is a good place to define nearly everything related to the finite state machine: the state table, its actions, and their parameters. You have one little loose end -- hooking cursor events -- to finish up in the object constructor, then the rest of this article is devoted to filling out the object prototype.


Hooking cursor events

As mentioned during the design phase in Part 1, browsers can pass events to JavaScript when the cursor moves onto, moves around within, and moves away from an HTML element. These events include helpful information, such as the event type and the current position of the cursor on the page. Browsers pass events by calling functions that were registered in advance. Unfortunately, the details of how these functions are registered and the arguments passed to them vary by browser. To ensure that oyur finite state machine hooks cursor events in all popular browsers, you'll need to implement three different event models. Fortunately, the code for each event model is quite compact. Unfortunately, the compactness of that code belies its complexity.

Mozilla Firefox, Opera, and recent versions of Netscape Navigator support a standardized event model proposed by the World Wide Web Consortium (W3C). This is your first choice because it's easy to register (and unregister) event functions, and chaining through multiple registered functions is handled by the browsers. If it is available, you can hook cursor events by calling the addEventListener method of the HTML element, passing an event type and a function to be called when that event occurs on that HTML element, as shown in Listing 3.

Listing 3. JavaScript code for hooking cursor events
 function
                FadingTooltip(htmlElement, tooltipContent, parameters) { ...
                htmlElement.fadingTooltip = this; if (htmlElement.addEventListener) { // for FF and
                NS and Opera htmlElement.addEventListener( 'mouseover', function(event) {
                this.fadingTooltip.handleEvent(event); }, false); htmlElement.addEventListener(
                'mousemove', function(event) { this.fadingTooltip.handleEvent(event); }, false);
                htmlElement.addEventListener( 'mouseout', function(event) {
                this.fadingTooltip.handleEvent(event); }, false); } ...

The second argument for the addEventListener calls are anonymous functions, which are functions that do not have names. This is your first opportunity to define functions within other functions in JavaScript, but it won't be the last, so get used to it now. You can use the function function anywhere in JavaScript code to define an anonymous function on-the-fly. It returns a pointer to the function, which can then be used like any other reference value. In yur FadingTooltip widget, you will pass function pointers to other functions as arguments, test them for null, assign them to variables, and declare them as object methods.

The anonymous functions passed to the addEventListener method don't appear to do much. When cursor events occur, the browser will call them, passing an event object to them that they will pass on to the handleEvent method of the FadingTooltip object. The browsers' event objects contain the event type, in addition to the cursor position, so one handleEvent method can handle all of the cursor events the widget must respond to.

Those simple anonymous functions also perform another important but subtle task. In the W3C event model, functions registered with the addEventListener method of an HTML element become methods of that element, so when browsers call them, the built-in this variable points at that HTML element. But the handleEvent method needs a pointer to the FadingTooltip object that contains your state variables. One way to accomplish this is to add a fadingTooltip property to the HTML element that points at the FadingTooltip object, and then follow it to invoke the handleEvent method of your object. This makes this point to the FadingTooltip object when the handleEvent method executes.

Hooking cursor events in Internet Explorer

Microsoft Internet Explorer does not currently support the proposed W3C standard event model, but does offer a similar event model of its own. The differences, as follows, are easy to accommodate:

  • Event types are slightly different
  • Registered functions do not become methods of the HTML element
  • Event objects are left in the global window object

If it is available, you can hook events by calling the attachEvent method of your HTML element, passing slightly different event types and functions, as shown in Listing 4.

This is your first opportunity to use function closures to enclose variables in function definitions, but it won't be the last, so get used to this, too.

Listing 4. JavaScript code for hooking cursor events in Internet Explorer
                function FadingTooltip(htmlElement, tooltipContent, parameters) { ... else if
                (htmlElement.attachEvent) { // for MSIE htmlElement.attachEvent( 'onmouseover',
                function() { htmlElement.fadingTooltip.handleEvent(window.event); } );
                htmlElement.attachEvent( 'onmousemove', function() {
                htmlElement.fadingTooltip.handleEvent(window.event); } ); htmlElement.attachEvent(
                'onmouseout', function() { htmlElement.fadingTooltip.handleEvent(window.event); } );
                } ...

The functions registered with the attachEvent method of an HTML element do not become methods of that element. When cursor events occur, the browser will call them, but the built-in this variable will point at the global window object, not the HTML element, so your functions cannot locate their FadingTooltip object from the pointer saved in the HTML element.

Fortunately, the anonymous function definitions are within the lexical scope of the htmlElement argument of the object constructor. Simply by using the htmlElement variable in the anonymous function definitions you can enclose it with those functions. This is called a function closure: when a function is defined within another function, and the inner function uses the local variables of the outer function, JavaScript saves those variables with the definition of the inner function. Later, after the outer function has returned, when the inner function is called, the outer function's local variables will still be available to it.

Here, JavaScript will retain the value of the htmlElement variable after the constructor returns so that it is still available to the anonymous functions when they are called by the browser. This allows them to locate their HTML elements and follow the pointers to their FadingTooltip object without help from the browser.

Since function closures are a feature of the JavaScript language, they work equally well in browsers that use the W3C event model. You could have used them to enclose the value of the constructor's htmlElement argument with the anonymous functions defined in the previous section, instead of using the built-in this variable.

Hooking cursor events in older browsers

For older browsers that do not support either the W3C or Internet Explorer event models, you will have to hook events using the original event model provided by early versions of Netscape Navigator. It is supported by all popular browsers and is widely used by Web designers for animating Web pages, but is the last choice for implementing more complex applications because it does not chain through multiple event handlers. To do this yourself, enclose pointers to previously registered event functions in the definitions of your own event functions, and then call them after calling your handleEvent method, as shown in Listing 5.

Listing 5. JavaScript code for hooking cursor events in older browsers
 function
                FadingTooltip(htmlElement, tooltipContent, parameters) { ... else { // for older
                browsers var self = this; var previousOnmouseover = htmlElement.onmouseover;
                htmlElement.onmouseover = function(event) { self.handleEvent(event ? event :
                window.event); if (previousOnmouseover) { htmlElement.previousHandler =
                previousOnmouseover; htmlElement.previousHandler(event ? event : window.event); } };
                    ... and similarly for 'onmousemove' and 'onmouseout' ... } }

Note, though, that this approach is incomplete. It allows the widget to register for the same events that other widgets have already registered to handle, and then chain to them, but it does not allow other event functions to unregister, since the chaining pointers are not accessible to them.

For variety, the code copied the constructor's this variable, which points to the FadingTooltip object, into a local variable named self, and then used the self pointer to locate the FadingTooltip object in the anonymous function definitions. This encloses a pointer to your FadingTooltip object in the anonymous function definitions so that they can locate it directly when any browser calls them, without depending upon the browser to provide a pointer to the HTML element, and without the need to save a pointer to the FadingTooltip object in the HTML element.

You could have enclosed a pointer to your FadingTooltip object in the anonymous functions you defined for the W3C and Microsoft event models as well. This would eliminate the need to save pointers to your objects in the HTML elements, and employ the same technique for locating the HTML element in all event models. The constructor in the source code is coded this way.

Now that you have hooked cursor events in all popular browsers, your object constructor is complete and you can return to the object prototype.


Setting timers and hooking timer events

You've completed the FadingTooltip constructor, and can continue filling out its prototype. In JavaScript, an object prototype can contain methods as well as variables; methods are simply variables that point at functions. You'll start with some general-purpose methods that start and cancel timers.

As you might recall from the design phase in Part 1, JavaScript provides two types of timers, one-shot timeouts and repeating timetick, and the finite state machine needs both. You can start the timers by calling the setTimeout or setInterval function, passing a time value (in milliseconds) and a function to be called when a timeout expires or a timetick occurs, respectively. They return opaque references that you can later pass to the clearTimeout or clearInterval function to cancel the timer.

Browsers will call the timer event functions that were passed as arguments to the setTimeout and setInterval function, once when the timeout value has elapsed, or repeatedly each time the timetick interval occurs, respectively, unless or until their timers are canceled. But these timeout and timetick functions do not become methods of any object. When the browser calls them, the this variable points at the global window object. Nor do browsers pass any information about the timer event to these functions.

After our struggles with cursor events, hooking timer events is easy. When you set a timer, you will copy the built-in this variable, which points at the FadingTooltip object containing our state variables, into a local variable named self that is within the lexical scope of the setTimeout and setInterval function calls. You will define anonymous functions that use the self variable and pass them to the setTimeout and setInterval functions as arguments. This encloses the self variable with the function definitions, so it is still available when the browser calls the functions, as shown in Listing 6.

Listing 6. JavaScript code for setting timers, and hooking timer events
                FadingTooltip.prototype = { ... startTimer: function(timeout) { var self = this;
                this.currentTimer = setTimeout( function() { self.handleEvent( { type: 'timeout' }
                ); }, timeout); }, startTicker: function(interval) { var self = this;
                this.currentTicker = setInterval( function() { self.handleEvent( { type: 'timetick'
                } ); }, interval); }, ...

Your timer event functions don't do much more than your cursor event functions. They create a trivial timer event object that contains only an event type -- either timeout or timetick -- and pass it to the same handleEvent method that handles cursor events.


Creating the action/transition table

In JavaScript, object prototypes can contain data structures such as arrays and other objects, as well as variables and methods. The elements of ordinary arrays are indexed by integers, whereas the elements of associative arrays are indexed by names instead of numbers. In JavaScript, associative arrays and objects are just different syntax for accessing the same data: object properties can be accessed as associative array elements, as shown in Listing 7.

Listing 7. JavaScript code for accessing object properties as associative array elements
 if ( htmlElement.fadingTooltip ==
                htmlElement["fadingTooltip"] ) ... // always true

You will take advantage of this by implementing your state table as a two-dimensional associative array of functions. You'll use state names and event names directly as the array's indexes. The non-empty cells of the array will point to anonymous functions that take actions for an event by calling utility methods (such as those that start and cancel timers), and then return the next state. The core of your handleEvent method will invoke these action/transition functions using array syntax, something like the code in Listing 8.

Listing 8. JavaScript code for calling anonymous functions stored in associative arrays
 var nextState =
                this.actionTransitionFunctions[this.currentState][event.type](event);

The handleEvent method will access the actionTransitionFunctions table as an associative array, using the current state and the event type as indexes, and select a function to call. It will pass the event object to that function as its argument. The function will take whatever actions are needed, and then return the name of the next state.

Because associative arrays are objects (and vice versa), you can define the actionTransitionFunctions table using object syntax, even though the handleEvent method will access it using array syntax. For example, in the initial state of Inactive, the only expected event is mouseover, so you can define a function to handle that situation, as shown in Listing 9.

Listing 9. JavaScript code for storing an anonymous function as an object property
 FadingTooltip.prototype = { ... initialState: 'Inactive',
                actionTransitionFunctions: { Inactive: { mouseover: function(event) {
                this.cancelTimer(); this.saveCursorPosition(event);
                this.startTimer(this.pauseTime*1000); return 'Pause'; } }, ...

The prototype for the FadingTooltip object contains the property actionTransitionFunctions, whose value is another object. It contains a property named Inactive, whose value is yet another object. It contains only one property, named mouseover, whose value is a function. When a mouseover event occurs in Inactive state, the handleEvent method will call this function. It expects an argument named event, takes three actions by calling three utility functions, and then returns Pause as the name of the next state. The actions include saving the cursor position, which browsers store in mouse event objects, and starting a timer, whose timeout value is a parameter named pauseTime (specified in seconds, which it converts to milliseconds, as required by the startTimer method).

Your widget will need to respond to three different events in the Pause state: the mousemove, mouseout, and timeout events. You will define a Pause object in the actionTransitionFunctions table that has a property for each of these event types, as shown in Figure 10.

Listing 10. JavaScript code for functions that respond to cursor events in Pause state
 FadingTooltip.prototype = { ... actionTransitionFunctions: { ...
                Pause: { mousemove: function(event) { return this.doActionTransition('Inactive',
                'mouseover', event); }, mouseout: function(event) { this.cancelTimer(); return
                'Inactive'; }, timeout: function(event) { this.cancelTimer(); this.createTooltip();
                this.startTicker(1000/this.fadeRate); return 'FadeIn'; } }, ...

When a mousemove event occurs in Pause state, the handleEvent method will call a function that simply calls the doActionTransition method, passing on its event argument, and returning whatever it returns. The doActionTransition method, as you might imagine, accesses the actionTransitionFunctions table using its first two arguments as array indexes, similarly to the handleEvent method, and passes its third argument to the function it finds there. When a mouseout event occurs, your code will call a function that cancels the timer started earlier in this section, and then transitions back to Inactive state.

Or, when a timeout event occurs, you will cancel any timer that might be running, create the tooltip with an initial opacity of zero, start a ticker, and transition to FadeIn state.

As yet another example of functions in the actionTransitionFunctions table, you'll define a function to handle timetick events in the FadeIn state, as shown in Listing 11.

Listing 11. JavaScript code for a function that responds to timer events in FadeIn state
 FadingTooltip.prototype = { ... actionTransitionFunctions: { ...
                FadeIn: { ... timetick: function(event) {
                this.fadeTooltip(+this.tooltipOpacity/(this.fadeinTime*this.fadeRate)); if
                (this.currentOpacity>=this.tooltipOpacity) { this.cancelTicker();
                this.startTimer(this.displayTime*1000); return 'Display'; } return
                this.CurrentState; } }, ....

Each time a timetick event occurs in FadeIn state, the handleEvent method will call a function that increases the opacity of the tooltip slightly. The duration of the fade in time (specified in seconds), the animation rate at which opacity is increased from zero (specified in steps per second), and the maximum opacity (specified as a float between 0.0 and 1.0), are all parameters. The function will return the current state, leaving the finite state machine in FadeIn state, until the tooltip's opacity reaches the maximum opacity parameter. Then it will cancel the ticker, start a timer to display the tooltip, and transition to Display State.

The remainder of the functions in the actionTransitionFunctions table are defined in a similar way. For details, please consult the complete source code, which is heavily commented, and compare it to Figure 1.


Implementing the event handler

We have referred so often, and with such portent, to the handleEvent method that its implementation might seem anti-climactic, as shown in Listing 12.

Listing 12. JavaScript code for event handler
 FadingTooltip.prototype = { ...
                handleEvent: function(event) { var actionTransitionFunction =
                this.actionTransitionFunctions[this.currentState][event.type]; if
                (!actionTransitionFunction) actionTransitionFunction = this.unexpectedEvent; var
                nextState = actionTransitionFunction.call(this, event); if
                (!this.actionTransitionFunctions[nextState]) nextState =
                this.undefinedState(nextState); this.currentState = nextState; }, ...

The actual implementation of accessing the actionTransitionFunctions table differs from the suggestion in a preceding section. The method does select a function to call from the actionTransitionFunctions table using the current state and event type as associative array indexes. However, the method copies a pointer to the selected function into a local variable, and then calls the function with the call method of the function object, rather than calling it directly. You can do this because function objects can be assigned to variables, just like any other value. You must do this because the built-in this variable needs to point at a FadingTooltip object while the function executes. If you actually called the function directly from the actionTransitionFunctions table using array indexes, as suggested earlier, the this variable would point into the table. The call method of the function sets the this variable to its first argument, and then calls the function, passing the remainder of its arguments.

Remember that the actionTransitionFunctions table is sparse; you defined functions for the events you expect in each state, and left all other cells empty. The handleEvent method will handle any unexpected events by calling the unexpectedEvent method. Or, should an action/transition function return some value that is not a valid state, it will call the undefinedState method. These methods will cancel any running timers, delete the tooltip if one has been created, and return the finite state machine to its initial state. One method is shown in Listing 13; the other is nearly identical.

Listing 13. JavaScript code for unexpected event handler
 FadingTooltip.prototype =
                { ... unexpectedEvent: function(event) { this.cancelTimer(); this.cancelTicker();
                this.deleteTooltip(); alert('FadingTooltip received unexpected event ' + event.type
                + ' in state ' + this.currentState); return this.initialState; }, ...

These methods will display an alert dialog describing the error, in the hope that some helpful user will send a problem description to the code's author.


Displaying the tooltip, finally

With nothing left to implement but the tooltip itself, you can't postpone that any longer.

You want the tooltip to appear near the cursor when the timeout event occurs in Pause state, but browsers do not pass the cursor position to timer events. Fortunately, browsers do pass the cursor position to cursor events, so you will save it in state variables by calling this saveCursorPosition method when cursor events occur, as shown in Listing 14.

Listing 14. JavaScript code for saving cursor position
 FadingTooltip.prototype = {
                ... saveCursorPosition: function(event) { this.lastCursorX = event.clientX;
                this.lastCursorY = event.clientY; }, ...

The tooltip is an HTML division element containing whatever text, images, and markup are passed to the constructor in its tooltipContent argument. The createTooltip method is shown in Listing 15.

Listing 15. JavaScript code for creating a tooltip
 FadingTooltip.prototype = { ...
                createTooltip: function() { this.tooltipDivision = document.createElement('div');
                this.tooltipDivision.innerHTML = this.tooltipContent; if (this.tooltipClass) {
                this.tooltipDivision.className = this.tooltipClass; } else {
                this.tooltipDivision.style.minWidth = '25px'; this.tooltipDivision.style.maxWidth =
                '350px'; this.tooltipDivision.style.height = 'auto';
                this.tooltipDivision.style.border = 'thin solid black';
                this.tooltipDivision.style.padding = '5px';
                this.tooltipDivision.style.backgroundColor = 'yellow'; }
                this.tooltipDivision.style.position = 'absolute'; this.tooltipDivision.style.zIndex
                = 101; this.tooltipDivision.style.left = this.lastCursorX + this.tooltipOffsetX;
                this.tooltipDivision.style.top = this.lastCursorY + this.tooltipOffsetY;
                this.currentOpacity = this.tooltipDivision.style.opacity = 0;
                document.body.appendChild(this.tooltipDivision); }, ...

If a CSS class name is specified as a parameter, you will apply it to your HTML division element's appearance. Otherwise, you'll apply some basic styling by default. But a few aspects of the tooltip's behavior depend upon its appearance, such as its position and opacity, so you will override anything related to these properties that might be specified in a stylesheet. The HTML division element will be positioned on the page with absolute coordinates, starting near the last saved cursor position, above any other layered elements. Its initial opacity will be zero, which is fully transparent.

Each time a timetick event occurs in FadeIn or FadeOut state, the fadeTooltip method will be called to increase or decrease the opacity of the tooltip slightly, while ensuring that it remains within a range between zero and the maximum opacity parameter, as shown in Listing 16.

Listing 16. JavaScript code for fading a tooltip
 FadingTooltip.prototype = { ...
                fadeTooltip: function(opacityDelta) { this.currentOpacity += opacityDelta; if
                (this.currentOpacity<0) this.currentOpacity = 0; if
                (this.currentOpacity>this.tooltipOpacity) this.currentOpacity =
                this.tooltipOpacity; this.tooltipDivision.style.opacity = this.currentOpacity; },
                ...

The action/transition functions also need utility methods that move and delete the tooltip. Their implementations are straightforward, and fully described in the source file commentary.

As mentioned occasionally in this part of the article, you need to define parameters before the implementation can be considered complete. They are properties of the object prototype, but unlike the state variables, they have default values as shown in Listing 17.

Listing 17. JavaScript code for defining parameters in the object prototype
                FadingTooltip.prototype = { ... tooltipClass: null, // name of a CSS style to apply
                to the tooltip, or // 'null' for default style tooltipOpacity: 0.8, // maximum
                opacity of tooltip, between 0.0 and 1.0 // (after fade-in, before fade-out)
                tooltipOffsetX: 10, // horizontal offset from cursor to upper-left // corner of
                tooltip tooltipOffsetY: 10, // vertical offset from cursor to upper-left // corner
                of tooltip fadeRate: 24, // animation rate for fade-in and fade-out, in // steps per
                second pauseTime: 0.5, // how long the cursor must pause over HTML // element before
                fade-in starts, in seconds displayTime: 10, // how long to display tooltip (after
                fade-in, // before fade-out), in seconds fadeinTime: 1, // how long fade-in
                animation will take, in seconds fadeoutTime: 3, // how long fade-out animation will
                take, in seconds ... };

The optional parameters argument of the object constructor is an object coded in JavaScript Object Notation (sometimes called JSON) that can override the default values for any of these properties, as shown in Listing 18.

Listing 18. JavaScript code for initializing parameters in the object constructor
                function FadingTooltip(htmlElement, tooltipContent, parameters) { ... for (parameter
                in parameters) { if (typeof(this[parameter])!='undefined') this[parameter] =
                parameters[parameter]; } ... };

The constructor examines each property in its parameters argument; for each one, if the property exists in the prototype, then its value overrides the parameter's default value. Remember that the prototype is an object, so it is also an associative array. Here again, you used object notation to define the parameters, and array notation to access them.

And with that, the implementation of the FadingTooltip is complete. You can download the actual source code for the constructor and prototype.


A few words about performance

Before moving on to test the implementation, a few words about performance are in order.

Browsers execute JavaScript programs synchronously. When an event that is hooked occurs, browsers call its event handler and wait for it to return before continuing on to the next event. If more events occur before the event handler returns, browsers enqueue them until the event handler does return, and then process the queued events in order, one at a time, synchronously. If an event handler takes too long, it might delay the browser's own responses to events that have not been hooked. Users might think the program is sluggish, or that the browser is malfunctioning.

It is always important to keep event handlers as short as possible, but this is especially important in programs likethis, which simulate animation with closely spaced timer events. If a timetick event handler takes longer than the ticker interval, timetick events will pile up on top of each other in the browser's event queue, saturating the processor and making the browser unresponsive.

For example, with an default animation rate of 24 steps per second, a timetick event handler has at most 40 milliseconds to do everything it needs to before returning to the browser, assuming that it has the processor all to itself. On modern workstations, this is enough time to do a lot of processing. Your goal, though, is not to do as much as you can in the time available, but to use as little processor time as possible. If your implementation's processor usage is negligible, its animations will run smoothly and our program will be responsive even when the processor is heavily loaded with other activity.

Avoid the temptation to set your animation rate at 60 or 85 steps per second, thinking, perhaps, that matching your monitor's refresh rate will produce smoother animation. This reduces the time between timetick events to about 12 milliseconds. If your timetick event handler takes longer than this, or there is contention for the processor, your animations might become jerky, or the browser might become unresponsive.


Ready to test

With the implementation complete, you are ready to test your code on some real browsers. That is the subject of Part 3 in this series. Remember, though, that development is an iterative process; you might need to return to the design or implementation phase ...

Resources

Learn

  • Ajax: A New Approach to Web Applications: Read Jesse James Garrett's article that coined the term Ajax.
  • The book JavaScript: The Definitive Guide (David Flanagan, published repeatedly by O'Reilly Media between 1996 and 2006): Find exhaustive information on how JavaScript works in browsers.
  • The Standard ECMA-262: ECMAScript Language Specification (Ecma International, 1999) : Peruse the authoritative definition of the JavaScript language implemented by popular browsers.
  • Document Object Model (DOM) Level 2 Events Specification (W3C, 2000): Refer to this spec for the authoritative definition of the DOM Level 2 event model.
  • The Gecko DOM Reference (Mozilla): Get the authoritative definition of the object interfaces, including events, implemented by the Firefox browser.
  • HTML and DHTML Reference (Microsoft): Refer to the authoritative definition of the object interfaces, including events, implemented by the Internet Explorer browser.
  • Chapters 21 "Protocol Representation with Finite State Models" by Andre A. S. Danthine, and 25 "Executable Representation and Validation of SNA" by Gary D. Schultz, et. al. in Computer Network Architectures and Protocols (edited by Paul E. Green, Jr., Plenum Press, 1982): Read historic examples of finite state machines applied to computer network protocols.
  • Chapter 3.5 "Finite Automata" in Compilers: Principles, Techniques, ad Tools (Alfred V. Aho et. al., Addison-Welsley, 1986): Read a description of how finite state machines are applied to computer language compilers.
  • Chapter 5 "Behavioral Patterns" in Design Patterns: Elements of Reusable Object-Oriented Software (Erich Gamma et. al., Addison-Welsley, 1995): Read about the State pattern for a discussion of implementing finite state machines.
  • Chapter 13.25 "TCP State Machine" in Internetworking with TCP/IP (Douglas E. Comer, Simon and Schuster Company, 1995): Read to understand the finite state machine that underlies the Internet.
  • Chapter 15 "State Machines" in the Unified Modeling Language 2.0 Superstructure Specification (Object Management Group, 2004): Read for a complete graph representation of finite state machines.
  • State Chart XML (SCXML): State Machine Notation for Control Abstraction (RJ Auburn et. al., W3C): Read a proposed XML representation of finite state machines.
  • developerWorks Web development zone: Expand your site development skills with articles and tutorials that specialize in Web technologies.
  • The technology bookstore: Browse for books on these and other technical topics.
  • developerWorks technical events and webcasts: Stay current with jam-packed technical sessions that shorten your learning curve, and improve the quality and results of your most difficult software projects.

Get products and technologies

Discuss

More downloads

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Select information in your profile (name, country/region, and company) is displayed to the public and will accompany any content you post. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Web development on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development
ArticleID=195560
ArticleTitle=Finite state machines in JavaScript, Part 2: Implement a widget
publish-date=02132007