Finite state machines in JavaScript, Part 3: Test the widget

Develop browser apps with JavaScript and finite state machines

In this series you learn to use how a finite state machine to methodically design complex behavior for a simple Web widget -- an animated tooltip that fades into and out of view. The resulting code is compact and concise, its logic is transparent, and its animation performs smoothly even on heavily loaded processors. In this article, learn how to deal with practical issues to make the implementation work in all popular Web browsers, and wrap things up.

Part 1 showed how to use a finite state machine to methodically design complex behavior for a simple Web widget. Part 2 described how to implement that behavior in JavaScript, and take full advantage of its distinctive language features, including associative arrays and function closures.

Share:

Edward J Pring (pring@us.ibm.com), 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 March 2007

Also available in Russian

For years, those who design and implement complex behavior in event-driven programs have used finite state machines as an organizing principle. 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, designers and implementers can benefit from the discipline and structure that finite state machines offer.

Part 1 of this series described a tooltip widget for Web pages with more elaborate behavior than the built-in implementation provided by popular Web browsers. This behavior requires the FadingTooltip widget to respond to a variety of different events. Sometimes the desired response to an event depends upon previous events. You used the finite state machine pattern to design this behavior. The resulting state graph and table representations indicate the actions to take in response to each event in all situations. You also compiled a list of variables to remember between events to take related actions.

Part 2 translated your design into JavaScript, taking full advantage of function closures and associative arrays. The implementation accommodates the quirks of popular browsers without sacrificing efficiency or elegance. You implemented code to: hook cursor events for all three browser event models, start and cancel both types of timers, and hook their timer events. You implemented your state table as a common handler for all events, plus a function array for all actions and transitions. The tooltip is a fully parameterized HTML Division element that moves and fades in response to cursor and timer events, as specified in the state table.

In this final article you'll test the implementation in some popular browsers. You need to construct a simple test page that creates some FadingTooltip widgets and binds them to HTML elements. For comparison, your test page will also illustrate some built-in tooltips. Immediately you will encounter some should-not-occur conditions, and so will have the opportunity to see how gracefully the design adapts. This article concludes with some observations about performance, and ideas for further development of finite state machines for browser-based applications.

Running applications in browsers

Ideally, you should test applications in all of their likely execution environments. For JavaScript applications, this is a daunting task given the variety of browsers available and the number of versions in widespread use. Because the FadingTooltip widget is only a technology demonstration, and won't be used beyond this series, I only tested with the current versions of four popular browsers:

See Resources to download these browsers.

  • Netscape Navigator 8.1
  • Microsoft® Internet Explorer® 6.0
  • Opera 9.0
  • Mozilla Firefox 1.5

This widget was only tested with the simple harness described in the following section. Any production application would warrant more comprehensive testing.


A simple test harness

One straightforward way to test your implementation is with some code embedded in an HTML Web page. The code must create FadingTooltip objects with the constructor, and bind them to HTML elements. A simple way to do this is with a function, defined in the HTML head element of a Web page, that uses the id attributes of HTML elements, as shown in Listing 1.

Listing 1. JavaScript code for creating FadingTooltip widgets
<head>
    ...
    <script src='FadingTooltip.js' content='text/javascript'></script>
    <script content='text/javascript'>
        function createFadingTooltip(id, content, parameters) {
            new FadingTooltip(document.getElementById(id), content, parameters);
        }
    </script>
    ...
</head>

The arguments for the createFadingTooltip function are an HTML element identifier, the content of the tooltip, and an optional set of parameters. The function simply converts the element identifier into a pointer and then calls the constructor, passing the remaining arguments unchanged. The pointers to the objects returned by the constructor are discarded, since the constructor encloses pointers to the objects with the event functions it defines, as described in Hooking Cursor Events in Part 2.

Next, you need some HTML elements with id attributes, defined in the HTML body element of a Web page, as shown in Listing 2.

Listing 2. HTML code for some sample HTML elements
<body>
    ...
    <p>These elements have tooltips defined with the FadingTooltip widget:
    <div id='tests' class='TestStyle'>
        Here are some <span id='TestLabel'>more elaborate tooltips</span>: 
        <input type='text' id='TestInput' size=25>
        <input type='button' id='TestButton' value='Press this button'>
    </div>
    ...

Finally, you need some code that calls the createFadingTooltip function with appropriate arguments for each HTML element, as in Listing 3.

Listing 3. JavaScript code to bind FadingTooltip widgets to HTML elements
<body>
    ...
    <script content='text/javascript'>
        createFadingTooltip('TestLabel', 
                         'Move your cursor a bit to the right, please ...');
        createFadingTooltip('TestInput', 
                         'Type the following, <i>please</i>:' +
                         '<ul compact style='margin-top: 0; margin-bottom: 0'>' +
                         '<li>your bank account number' + 
                         '<li>your PIN number' +
                         '</ul>' +
                         '<i>Thank you</i> in advance.', 
                         { fadeinTime: 3,
                           fadeoutTime: 3 } );
        createFadingTooltip('TestButton', 
                         '<img src='smiley.gif' align='absmiddle'>' +
                          '<big>Go ahead.</big> ' +
                          'Press it. ' +
                          '<small>What's the harm? <small>Trust me.</small></small>', 
                          { tooltipOpacity: 1, 
                            tooltipClass: 'AnotherTestStyle',
                            pauseTime: 2,
                            fadeinTime: 0.5, 
                            displayTime: 0, 
                            fadeoutTime: 10,
                            trace: true } );
    /script>
    ...

The first tooltip, for the HTML element identified as TestLabel, is created with the simplest of arguments: the content is text only, and the parameter argument is omitted, so default values will be used for all parameters. The second tooltip, for the TestInput HTML element, formats the content with some markup, and specifies the fade in and fade out times (in seconds). The third tooltip, for the TestButton HTML element, includes an image in the formatted content, and specifies more parameters, including a Cascading Stylesheet (CSS) class to style the tooltip.

Mozilla Firefox is the newest and trendiest browser available, and adheres more closely to open standards than some others, so we will start with it. (If you use Microsoft Internet Explorer, you might want to skip ahead now and read the next two sections, about should not occur and fading doesn't work, and then return here.)

Try the test harness now. It contains some HTML elements with built-in tooltips, and the FadingTooltip examples described above, so you can compare and contrast them. Note the more elaborate behavior of the FadingTooltips. They fade into and out of view, rather than popping in and out, and the fading reverses direction as the cursor moves over and away from the HTML elements. They follow the cursor's movement, and do not disappear when keyboard events occur. They have a more elaborate appearance: FadingTooltips are styled, their text is formatted, and they can contain images.

Decide for yourself whether these differences are improvements, and whether they are worth the time and effort you invested in developing your own tooltip widget. If so, you might want to copy the source files for the implementation and test harness onto your disk drive so you can alter the parameters, styles, or code (see Download). You don't need a Web server of your own for this; just use the file://... URL to load your modified test harness into your browser.


When should-not-occur events occur

Microsoft Internet Explorer is by far the most widely used browser, so you have to test the implementation with it as well, and it doesn't take long to find a problem. An alert, like shown in Figure 1, appears immediately when the cursor passes over any of the HTML elements with FadingTooltip widgets.

Figure 1. An unexpected mousemove event in Internet Explorer
An unexpected

Refer to the state table from Completing the state table in Part 1, reproduced below in Figure 2. Recall that we did not expect mousemove events in Inactive state, so we left this cell empty in the implementation of the actionTransitionFunctions table in Creating the action/transition table in Part 2.

Figure 2. Initial state table for FadingTooltip widget
Initial state table for FadingTooltip widget

Intuitively, a mouseover event should precede a mousemove event, causing a transition to Pause state, which does expect mousemove events (see Figure 5 in Part 1). Evidently, Firefox works as your intuition expects, but Internet Explorer does not. Perhaps there is a bug in a finite state machine within Internet Explorer that generates cursor events, or perhaps that browser does not keep track of its state internally. In any case, you'll have to accommodate this situation in your own finite state machine.

Fortunately, the discipline required to apply the finite state machine pattern pays off now by making changes like this easy. Consider what behavior is needed should a mousemove event occur in Inactive state: the actions and transition are the same as for a mouseover event in Inactive state. Referring back to your implementation of the actionTransitionFunctionstable, remember that the doActionTransition method allows any function in the table to duplicate the actions and transitions of another function. Using that method, you can accomodate this unexpected event by adding the function highlighted in bold in Listing 4 to the actionTransitionFunctions table.

Listing 4. JavaScript code to handle an unexpected mousemove event in Inactive state
FadingTooltip.prototype = { 
    ...
    actionTransitionFunctions: { 
    ...
    Inactive: {
          mousemove: function(event) {return this.doActionTransition('Inactive', 'mouseover', event);},
       ...

Further testing with Internet Explorer will soon reveal another similar unexpected situation, as shown in Figure 3.

Figure 3. An unexpected mouseout event in Internet Explorer
An unexpected

Again, intuition led you to expect that mouseover events would precede mouseout events, so the mouseout events would not occur in Inactive state. Fortunately, this unexpected event is also easy to accommodate. No actions or transition are needed in this situation; you just want the finite state machine to ignore mouseout events in Inactive state, as shown in Listing 5.

Listing 5. JavaScript code to handle an unexpected mouseout event in Inactive state
FadingTooltip.prototype = { 
    ...
    actionTransitionFunctions: { 
    ...
       Inactive: {mouseout: function(event) {return this.currentState; // do nothing},
      ...

In the design phase, you did not anticipate that mousemove or mouseout events would occur in Inactive state. But before criticizing Microsoft for the mouseover events in their browser, let's imagine what would happen with any browser in this situation: the cursor moves over an HTML element and pauses there long enough for the FadingTooltip widget to fade into view, display for a while, and then fade from view. This would cycle your finite state machine through each of its states, returning to Inactive state with the cursor still over the HTML element. When the cursor then moves, any browser will generate a mousemove or mouseout event in Inactive state. This does in fact occur in other browsers, including Firefox. Independent of any possible bugs in Internet Explorer, there is a design defect in our finite state machine, as shown in Figure 4.

Figure 4. An unexpected mousemove event in Firefox
An unexpected

Fortunately, the changes for Inactive state that you have already implemented for Internet Explorer handle this situation correctly, so no additional changes are needed to cover thr design defect.

Unfortunately, continued testing after implementing these changes reveals that one more should-not-occur situation occurs with Internet Explorer: an unexpected mouseover event in Pause state.

Since mouseover events in Pause state need the same actions and transition as mouseover events in Inactive state, you can handle this situation by calling the doActionTransaction method. However, there is no legitimate sequence of events (which I can think of) that can lead other browsers into this situation, so let's implement this design change only for Internet Explorer, as in Listing 6.

Listing 6. JavaScript code to handle an unexpected event only in Internet Explorer

Click to see code listing

Listing 6. JavaScript code to handle an unexpected event only in Internet Explorer

FadingTooltip.prototype = { 
    ...
    actionTransitionFunctions: { 
        ...
    },
    ...
};
if ( (window.navigator.userAgent).indexOf('MSIE')!=-1 ) {FadingTooltip.prototype.actionTransitionFunctions["Pause"]["mouseover"] = function(event) { return this.doActionTransition('Inactive', 'mouseover', event);};}

If the browser is some version of Internet Explorer, modify the actionTransitionFunctions table of the FadingTooltips prototype, after it has been defined but before it is used, to handle mouseover events in Pause state the same way as mouseover events in in Inactive state. And remember that in JavaScript, associative arrays and objects are equivalent, so either notation can be used to modify the table.


When fading tooltips don't fade

Unfortunately, after all of the alerts about unexpected events have been eliminated by the design changes in the previous section, continued testing with Internet Explorer reveals another unrelated problem. Tooltips defined with the FadingTooltip widget pop into and out of view, rather than fading in and out. Tragically, Internet Explorer does not support the proposed standard CSS opacity style (see Resources).

Internet Explorer does support a non-standard style named filter that has a similar capability (see Resources). To employ it, you need to modify the createTooltip method by inserting the lines highlighted in bold in Listing 7.

Listing 7. Additional JavaScript code to create tooltip in Internet Explorer

Click to see code listing

Listing 7. Additional JavaScript code to create tooltip in Internet Explorer

FadingTooltip.prototype = { 
    ...
    createTooltip: function() {  
    this.tooltipDivision = document.createElement('div');
        ...   
        this.currentOpacity = this.tooltipDivision.style.opacity = 0;
       if (this.tooltipDivision.filters) { // for MSIE onlythis.tooltipDivision.style.filter = 'alpha(opacity=0)';}
        ...
    },	
    ...

A corresponding insertion into the fadeTooltip method is also needed, as shown in Listing 8.

Listing 8. Additional JavaScript code to fade tooltip in Internet Explorer

Click to see code listing

Listing 8. Additional JavaScript code to fade tooltip in Internet Explorer

FadingTooltip.prototype = { 
    ...
    fadeTooltip: function(opacityDelta) { 
        ...
        this.tooltipDivision.style.opacity = this.currentOpacity;
       if (this.tooltipDivision.filters) { // for MSIE onlythis.tooltipDivision.filters.item('alpha').opacity = 100*this.currentOpacity;}
    },	
    ...

On a lighter note, Opera might have limited brand-name recognition as a browser, but is well regarded in technical circles. Unfortunately, Opera was comparatively late in supporting the CSS opacity style, so FadingTooltip widgets will also pop in and out of view, rather than fading in and out, with versions of Opera prior to release 9. Unlike Internet Explorer, earlier versions of Opera have no alternative syntax for transparency; the only solution is to upgrade to the current version.


A few more words about performance

With your FadingTooltip widget now working smoothly in popular browsers, it's time to find out how close you came to the performance goal of negligible processor usage. An easy way to do this on Windows is to watch the Performance panel of the Windows Task Manager while running the widget's animations.

On most workstations, usually several programs always run quietly in the background, even when you've not started any applications. Some of these programs drive the small icons near the clock on the Windows taskbar; others have no visible manifestation at all. The Performance panel of the Windows Task Manager, however, shows their activity, as shown in Figure 5.

Figure 5. Windows Task Manager showing normal background activity
Windows Task Manager showing normal background activity

Before running the FadingTooltip widget, you can eliminate some of this background activity so that it doesn't obscure the widget's processor usage. You can close most programs that display icons in the Windows taskbar from their context menus. You can stop other background programs from the Windows Control Panel for Services.

On a quiescent system with a 1.1GHz Intel Pentium-III processor, the processor usage of the FadingTooltip widget does indeed seem to be negligible, as shown in Figure 6.

Figure 6. Windows Task Manager showing widget animation activity
Windows Task Manager showing widget animation activity

Because the widget's animation places little demand on the processor, we can be confident that it will run smoothly even when the processor is heavily loaded with other activity.


Don't forget to update that design documentation

Now that you are finished testing your implementation, you need to update the documentation to reflect the design and implementation changes you made. The unexpected events you encountered correspond to empty cells in the state table, so you need to update those cells (highlighted in blue) with the actions you took to accommodate those situations, as shown in Figure 7.

Figure 7. State table for FadingTooltip widget, as tested
State table for FadingTooltip widget, as tested

As shown in Figure 8, the matching updates to the state graph are equally simple.

Figure 8. State graph for FadingTooltip widget, as tested
State graph for FadingTooltip widget, as tested

The lines of code you inserted into the createTooltip and fadeTooltip methods to accommodate Internet Explorer's way of styling opacity don't really rise to the level of a design change. You will document this in the source commentary (see Download).


Further development

The purpose of this series is to demonstrate how to apply the finite state machine design pattern to browser-based applications, and to show how to exploit two distinctive features of the JavaScript language to produce an elegant, efficient program. Consequently, you developed the FadingTooltip widget as a single self-contained JavaScript object. The code is tight, but it is not very flexible. Before I close this discussion, I want to consider some possible directions that further development might take.

Other visual widgets could benefit from appearance and behavior similar to the FadingTooltip widget. For example, you might display syntax error messages for input field typing mistakes, suggestions for filling in complex forms, or guidance through unfamiliar workflows in styled tooltip-like boxes that fade into view when events dictate, move with the cursor, and eventually fade from view when events warrant. One possible next development step might involve refactoring the FadingTooltip code to move the basic machinery of the finite state machine into a separate object. This might include the event hooks, the event handler, the error methods, and the timer methods. Then you could implement a family of visual widgets, including the FadingTooltip widget, as separate objects that inherit the data and methods of the basic object and add the state variables, transition tables, and action methods needed for each type of widget.

Tooltip-like widgets and other visual widgets might benefit from additional animation effects similar to fading. For example, tooltips might slide into view from an edge of the window, or expand as though zooming in from a vanishing point, or unfold into view from an icon like origami in reverse, or shimmy like Jell-O as the cursor moves them. Any of these animation effects might be as effective with context menus, dialog boxes, and input prompts as they would be with tooltip-like widgets. Another possible next development step might involve building a framework for visual widgets that further separates the finite state machines for each type of animation, and the behavior of each type of widget, into separate objects so that any combination can be bound to a particular HTML element.

Of course, we have not considered several other types of events in these articles, such as those arising from the keyboard or the network. For example, the individual characters typed into a text input field might trigger actions that validate values as they are entered, or display the remaining valid values in a selection list, based on a formal grammar represented as a finite state machine. Network requests that fail or time out might be handled by retrying alternate services represented as protocol graphs. Another possible next development step might involve:

  • Extending a framework for finite state machines to include hooks for keyboard and network events
  • Graph or table representations for formal language grammars and network protocols
  • Actions to validate text values and manage network sessions

Other applications running on the same processor can originate events and expose actions that are of interest to programs running in browsers. For example, a voice-over-IP telephony application might generate ring-in, ring-out, connect, and disconnect events, and expose call, hold, and hang-up actions. Yet another possible next development step might involve extending a framework to include methods to hook events in other applications and take actions in them. This might require some Java™ plug-ins to make events and actions in other processes accessible to finite state machines running in browsers. And that would be a very satisfying place to end, since JavaScript began as a scripting language for Java plug-ins.

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.
  • W3C Cascading Style Sheets Under Construction: Follow the CSS3 development and find a rough schedule for CSS WG (Cascading Style Sheets Working Group, formerly "CSS & FP WG") activities.
  • MSDN Alpha Filter: Adjust the opacity of the content of an object.
  • 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.
  • 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.
  • 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. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. 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, Java technology
ArticleID=201523
ArticleTitle=Finite state machines in JavaScript, Part 3: Test the widget
publish-date=03132007