1 ////////////////////////////////////////////////////////////////////////////////
3 // This file contains the JavaScript source for the FadingTooltip widget
4 // described in this IBM developerWorks article:
6 // Finite state machines in JavaScript, Part 1: Designing a widget
7 // http://www.ibm.com/developerworks/library/wa-finitemach1/
9 // (c) Copyright IBM Corporation 2006. All rights reserved.
11 // U.S. Government Users Restricted Rights - Use, duplication or disclosure
12 // restricted by GSA ADP Schedule Contract with IBM Corp.
14 // -- Edward Pring <pring@us.ibm.com>
16 ////////////////////////////////////////////////////////////////////////////////
19 // The constructor for the FadingTooltip object creates and initializes an object
20 // instance. The constructor"s arguments are:
22 // "htmlElement" is a required pointer to an HTML element. A tooltip will be
23 // displayed when the cursor pauses over this HTML element.
25 // "tooltipContent" is a required string containing the text and HTML tags to
26 // be displayed in the tooltip.
28 // "parameters" is an optional JSON object containing zero or more parameter
29 // values that determine how the tooltip will behave. The parameter names
30 // and default values are listed below.
32 // "this" points to the new FadingTooltip object instance
34 // The constructor throws an exception if any of these arguments are invalid. It
35 // implicitly returns a pointer to the new object instance.
37 function FadingTooltip(htmlElement, tooltipContent, parameters) {
39 // Do some basic validation of the constructor"s required arguments, and
40 // throw an exception if any are obviously invalid. There is obviously
41 // room for more rigorous validation here.
43 if (!htmlElement || typeof(htmlElement)!="object") throw "Sorry, "htmlElement" argument of FadingTooltip should be an HTML element";
44 if (!tooltipContent || typeof(tooltipContent)!="string") throw "Sorry, "tooltipContent" argument of FadingTooltip should be a string containing text and HTML tags";
46 // If the constructor"s optional argument is specified, make sure that
47 // all of its properties are defined in the object prototype, or throw
48 // an exception. Again, this could certainly be more rigorous.
50 if (parameters && typeof(parameters)!="object") { throw "Sorry, "parameters" argument of FadingTooltip should be a JSON object containing parameter values"; }
51 for (var parameter in parameters) {
52 if (typeof(this[parameter])=="undefined") throw "Sorry, "parameters{" + parameter + "} passed to FadingTooltip is not recognized";
55 // Save the constructor"s argument values in the new object instance.
57 this.htmlElement = htmlElement;
58 this.tooltipContent = tooltipContent;
59 for (parameter in parameters) this[parameter] = parameters[parameter];
62 // trace("creating FadingToolip for HTML element id=" + htmlElement.id);
63 // for (parameter in parameters) trace("... " + parameter + "=" + parameters[parameter]);
66 // Copy a pointer to the new object into the "self" variable. This variable
67 // will be referenced in the functions defined below that hook cursor events,
68 // so its value will be enclosed with those function definitions. This will
69 // enable the functions to locate this object later when they are called.
70 // Note that the constructor"s "htmlElement" argument is also referenced in
71 // some of the functions defined below, so its value will also be enclosed
76 // If the browser provides the W3C DOM Level 2 event model (for example,
77 // recent versions of Mozilla Firefox and Netscape Navigator), use it to
78 // hook mouse events for the HTML element. For each event type, provide
79 // an anonymous function as the mouse event handler. When the cursor
80 // moves over, within, or off the HTML element, the browser will call the
81 // corresponding mouse event handler: "this" will point at the HTML element,
82 // and the browser will pass an "event" object as an argument. The "event"
83 // object will specify the type of the event and the position of the cursor.
84 // The mouse event handlers will locate this FadingTooltip object via the
85 // "self" variable defined above and pass the "event" object to the finite
86 // state machine"s event handler (the "handleEvent" method defined below).
87 // When the finite state machine"s event handler executes, "this" will point
88 // at the FadingTooltip object, and the HTML element will be accessible via
89 // the "htmlElement" property of the object.
91 if (htmlElement.addEventListener) { // for FF and NS and Opera
92 //if (this.trace) trace("FadingTooltip hooking HTML element id=" + htmlElement.id + " using DOM Level 2 event model");
93 htmlElement.addEventListener("mouseover", function(event) { self.handleEvent(event); }, false);
94 htmlElement.addEventListener("mousemove", function(event) { self.handleEvent(event); }, false);
95 htmlElement.addEventListener("mouseout", function(event) { self.handleEvent(event); }, false);
98 // Otherwise, if the browser provides the Microsoft event model (recent versions
99 // of Internet Explorer only), use it to hook mouse events for the HTML element.
100 // For each event type, provide an anonymous function as the mouse event
101 // handler. When the cursor moves over, within, or off the HTML element,
102 // the browser will call the corresponding mouse event handler, but unlike
103 // DOM Level 2 event handlers defined above, "this" will point at the
104 // global window object, and the browser will not pass any arguments. An
105 // "event" object will be available in the global window object that will
106 // specify the type of the event and the position of the cursor, but there is
107 // no obvious way to locate the HTML element from the global window object.
108 // The mouse event handlers will locate this FadingTooltip object via the
109 // "self" variable defined above and pass the "event" object to the finite
110 // state machine"s event handler (the "handleEvent" method defined below).
111 // When the finite state machine"s event handler executes, "this" will point
112 // at the FadingTooltip object, and the HTML element will be accessible via
113 // the "htmlElement" property of the object.
115 else if (htmlElement.attachEvent) { // for MSIE
116 //if (this.trace) trace("FadingTooltip hooking HTML element id=" + htmlElement.id + " using Microsoft event model");
117 htmlElement.attachEvent("onmouseover", function() { self.handleEvent(window.event); } );
118 htmlElement.attachEvent("onmousemove", function() { self.handleEvent(window.event); } );
119 htmlElement.attachEvent("onmouseout", function() { self.handleEvent(window.event); } );
122 // If the browser does not provide either of the more modern event models, use
123 // the original W3C "DOM Level 0" event model to hook mouse events for the HTML
124 // element. This event model does not support multiple event handlers, so we
125 // will save a pointer to any previous handler in a local variable, and chain to
126 // it from our own handler after handling the event. For each event type, provide
127 // an anonymous function as the mouse event handler. When the cursor moves over,
128 // within, or off the HTML element, the browser will call the corresponding
129 // mouse event handler: like the DOM Level 2 event handlers defined above,
130 // "this" will point at the HTML element, but the browser may (Firefox and
131 // Netscape) or may not (Internet Explorer) pass an "event" object as an argument.
132 // The mouse event handlers will locate this FadingTooltip object via the
133 // "self" variable defined above and pass the "event" object to the finite
134 // state machine"s event handler (the "handleEvent" method defined below).
135 // When the finite state machine"s event handler executes, "this" will point
136 // at the FadingTooltip object, and the HTML element will be accessible via
137 // the "htmlElement" property of the object. Pointers to any previously
138 // hooked mouse event handlers are saved in local variables that will be
139 // enclosed with the function defintions, so that their values will be available
140 // when our functions are called. After our functions have called the
141 // "handleEvent" method, they will chain to previously hooked handlers, passing
142 // (Firefox and Netscape) or not passing (Internet Explorer) the "event" object
143 // as an argument, setting "this" to point at the HTML element.
145 else { // for older browsers
146 //if (this.trace) trace("FadingTooltip hooking HTML element id=" + htmlElement.id + " using DOM level 0 event model");
147 var previousOnmouseover = htmlElement.onmouseover;
148 htmlElement.onmouseover = function(event) {
149 self.handleEvent(event ? event : window.event);
150 if (previousOnmouseover) {
151 htmlElement.previousHandler = previousOnmouseover;
152 htmlElement.previousHandler(event ? event : window.event);
155 var previousOnmousemove = htmlElement.onmousemove;
156 htmlElement.onmousemove = function(event) {
157 self.handleEvent(event ? event : window.event);
158 if (previousOnmousemove) {
159 htmlElement.previousHandler = previousOnmousemove;
160 htmlElement.previousHandler(event ? event : window.event);
163 var previousOnmouseout = htmlElement.onmouseout;
164 htmlElement.onmouseout = function(event) {
165 self.handleEvent(event ? event : window.event);
166 if (previousOnmouseout) {
167 htmlElement.previousHandler = previousOnmouseout;
168 htmlElement.previousHandler(event ? event : window.event);
173 // Set the initial state of the finite state machine.
175 this.currentState = this.initialState;
178 // The prototype for the FadingTooltip object defines the properties of
179 // object instances, that is, the variables and methods of the object. This
180 // includes the optional parameters of the object constructor (and their
181 // default values), the object"s state variables, the table of
182 // action/transition functions, and a collection of private methods.
184 FadingTooltip.prototype = {
187 // of the object, otherwise the default values defined in the prototype will be
190 tooltipClass: null, // name of a CSS style for rendering the tooltip, or "null" for default style below
194 fadeRate: 24, // animation rate for fade-in and fade-out, in steps per second
195 pauseTime: 0.5, // how long the cursor must pause over HTML element before fade-in starts, in seconds
196 displayTime: 10, // how long the tooltip will be displayed (after fade-in finishes, before fade-out begins), in seconds
197 fadeinTime: 1, // how long fade-in animation will take, in seconds
198 fadeoutTime: 3, // how long fade-out animation will take, in seconds
200 // These are state variables used by the finite state machine"s
201 // action/transition functions (see the "actionTransitionTable" and
202 // utility functions defined below).
204 currentState: null, // current state of finite state machine (one of "actionTransitionFunctions" properties)
205 currentTimer: null, // returned by setTimeout, if a timer is currently running
206 currentTicker: null, // returned by setInterval, if a ticker is currently running
207 currentOpacity: 0, // current opacity of tooltip, between 0.0 and "tooltipOpacity"
209 lastCursorX: 0, // cursor x-position at most recent mouse event
210 lastCursorY: 0, // cursor y-position at most recent mouse event
211 trace: false, // trace execution points that may be helpful for debugging, if set to "true"
213 // The "handleEvent" method handles mouse and timer events as appropriate for
214 // the current state of the finite state machine. The required "event" argument
215 // is an object that has (at least) a "type" property whose value corresponds to
216 // one of the event types in the current state"s column of the
217 // "actionTransitionFunctions" table. For mouse events, it must also have
218 // "clientX" and "clientY" properties that specify the location of the cursor.
219 // This method will select the appropriate action/transition function from the
220 // table and call it, passing on the "event" argument. Note that the
221 // action/transition function is invoked via the "call" method of its Function
222 // object, which allows us to set the context for the function so that the
223 // built-in variable "this" will point at the FadingTooltip object. If we
224 // were to call the function directly from the "actionTransitionFunctions" table,
225 // the "this" variable would point into the table. The action/transition function
226 // returns a new state, which this method will store as current state of the finite
227 // state machine. This method does not return a value.
229 handleEvent: function(event) {
230 var actionTransitionFunction = this.actionTransitionFunctions[this.currentState][event.type];
231 if (!actionTransitionFunction) actionTransitionFunction = this.unexpectedEvent;
232 var nextState = actionTransitionFunction.call(this, event);
233 if (!nextState) nextState = this.currentState;
234 //if (this.trace) trace(""" + event.type + "" event caused transition from "" + this.currentState + "" state to "" + nextState + "" state");
235 if (!this.actionTransitionFunctions[nextState]) nextState = this.undefinedState(event, nextState);
236 this.currentState = nextState;
239 // The "unexpectedEvent" method is called by the "handleEvent" method when the
240 // "actionTransitionFunctions" table does not contain a function for the current
241 // event and state. The required "event" argument is an object, but only its
242 // "type" property is required. The method cancels any active timers, deletes
243 // the tooltip, if one has been created, and returns the finite state machine"s
244 // initial state. The unexpected event and state are shown in an "alert" dialog
245 // to the user, who will hopefully send a problem report to the author of this code.
247 unexpectedEvent: function(event) {
250 this.deleteTooltip();
251 alert("FadingTooltip handled unexpected event "" + event.type + "" in state "" + this.currentState + "" for id="" + this.htmlElement.id + "" running browser " + window.navigator.userAgent);
252 return this.initialState;
255 // The "undefinedState" method is called by the "handleEvent" method when the
256 // "actionTransitionFunctions" table does not contain a column for the next
257 // state returned by the selected function. The required "state" argument is
258 // the name of the undefined state. The method cancels any active timers, deletes
259 // the tooltip, if one has been created, and returns the finite state machine"s
260 // initial state. The undefind state is shown in an "alert" dialog to the user,
261 // who will hopefully send a problem report to the author of this code.
263 undefinedState: function(event, state) {
266 this.deleteTooltip();
267 alert("FadingTooltip transitioned to undefined state "" + state + "" from state "" + this.currentState + "" due to event "" + event.type + "" from HTML element id="" + this.htmlElement.id + "" running browser " + window.navigator.userAgent);
268 return this.initialState;
271 // The "initialState" constant specifies the initial state of the finite state
272 // machine, which must match one of the state names in the
273 // "actionTransitionFunctions" table below.
275 initialState: "Inactive",
277 // The "actionTransitionFunctions" table is a two-dimensional associative array
278 // of anonymous functions, or, if you prefer, an object containing more objects
279 // containing anonymous functions. The first dimension of the array (the outer
280 // object) is indexed by state names; the second dimension of the array (the
281 // inner objects) is indexed by event types. When a mouse or timer event hander
282 // calls the "handleEvent" method, it calls the appropriate function from the table,
283 // passing an "event" object as an argument, ensuring that "this" points at the
284 // FadingTooltip object. The selected function takes whatever actions are
285 // required for that event in the current state, and returns either the name of
286 // a new state, if a state transition is needed, or "null" if not. See the design
287 // documentation, in particular the state diagram and table, for details. Note that
288 // the array is sparse: state/event combinations that "should not occur" are
289 // empty. If an event does occur in a state that does not expect it, the
290 // "unexpectedEvent" method will be called.
292 actionTransitionFunctions: {
294 // The "Inactive" column of the "actionTransitionFunctions" table contains a
295 // function for each mouse and timer event that is expected in this state.
297 // When a "mouseover" event occurs in "Inactive" state, save the current
298 // location of the cursor, [re-]start the pause timer, and transition to
299 // "Pause" state. Note that this function is also executed for "mouseover"
300 // events in "Pause" state, and "mousemove" events in "Inactive" state,
301 // since all of the same actions, and the same transition, are appropriate
303 mouseover: function(event) {
305 this.saveCursorPosition(event.clientX, event.clientY);
306 this.startTimer(this.pauseTime*1000);
309 // When a "mousemove" event occurs in "Inactive" state, take the same
310 // actions, and make the same state transition, as for "mouseover"
311 // events in "Inactive" state. Note that this state/event situation
312 // was not anticipated in the initial design of the finite state machine;
313 // this function was added when it occurred unexpectedly during testing.
314 // With MSIE, this often happens as soon as the mouse moves over the HTML element,
315 // before any "mouseover" event occurs, presumably because of a bug in the
316 // browser. With FF and NN, this can also happen if the mouse remains over
317 // the HTML element after the tooltip has been displayed and faded out, and
318 // the mouse then moves within the HTML element.
319 mousemove: function(event) {
320 return this.doActionTransition("Inactive", "mouseover", event);
322 // When a "mouseout" event occurs in "Inactive" state, just ignore the event:
323 // take no action and make no state transition. Note that this state/event
324 // situation was not anticipated in the initial design; this function was added
325 // when it occurred unexpectedly during testing. With MSIE, this may happen
326 // when the mouse crosses the HTML element, without any "mouseover" event,
327 // presumably because of a bug in the browser. With FF and NN, this can also
328 // happen if the mouse remains over the HTML element after the tooltip has been
329 // displayed and faded out, and the mouse then moves off the HTML element.
330 mouseout: function(event) {
331 return this.currentState; // do nothing
333 }, // end of FadingTooltip.prototype.actionTransitionFunctions.Inactive
335 // The "Pause" column of the "actionTransitionFunctions" table contains a
336 // function for each mouse and timer event that is expected in this state.
338 // When a "mousemove" event occurs in "Pause" state, take the same
339 // actions, and make the same state transition, as for "mouseover"
340 // events in "Inactive" state.
341 mousemove: function(event) {
342 return this.doActionTransition("Inactive", "mouseover", event);
344 // When a "mouseout" event occurs in "Pause" state, just cancel the
345 // timer and return to "Inactive" state. Since tooltip has been created
346 // yet, there is nothing more to do.
347 mouseout: function(event) {
351 // When a "timeout" event occurs in "Pause" state, create the
352 // tooltip (with an initial opacity of zero). In the normal case,
353 // when the fade-in time is non-zero, start the animation ticker and
354 // transition to "FadeIn" state. But when the fade-in time is zero,
355 // skip the fade animation (to avoid dividing by zero when "timetick"
356 // events occur in "FadeIn" state), and transition directly to "Display"
357 // state (after increasing the tooltip opacity to its maximum value and
358 // setting the display timer).
359 timeout: function(event) {
361 this.createTooltip();
362 if (this.fadeinTime>0) {
363 this.startTicker(1000/this.fadeRate);
366 this.fadeTooltip(+this.tooltipOpacity);
367 this.startTimer(this.displayTime*1000);
371 }, // end of FadingTooltip.prototype.actionTransitionFunctions.Pause
373 // The "FadeIn" column of the "actionTransitionFunctions" table contains a
374 // function for each mouse and timer event that is expected in this state.
376 // When a "mousemove" event occurs in "FadeIn" state, take the same
377 // actions as for "mousemove" events in "Display" state. Note that no
378 // state transition occurs; the finite state machine remains in its
380 mousemove: function(event) {
381 return this.doActionTransition("Display", "mousemove", event);
383 // When a "mouseout" event occurs in "FadeIn" state, just transition
384 // to "FadeOut" state. Leave the animation ticker running; subsequent
385 // "timetick" events in "FadeOut" state will cause the fade animation to
386 // reverse direction at the current tooltip opacity.
387 mouseout: function(event) {
390 // When a "timetick" event occurs in "FadeIn" state, increase the
391 // opacity of the tooltip slightly (such that opacity increases from zero
392 // to the specified maximum in equal increments over the specified fade-in
393 // time at the specified animation rate). When tooltip opacity reaches the
394 // specified maximum, cancel the ticker, start the display timer, and
395 // transition to "Display" state.
396 timetick: function(event) {
397 this.fadeTooltip(+this.tooltipOpacity/(this.fadeinTime*this.fadeRate));
398 if (this.currentOpacity>=this.tooltipOpacity) {
400 this.startTimer(this.displayTime*1000);
403 return this.CurrentState;
405 }, // end of FadingTooltip.prototype.actionTransitionFunctions.FadeIn
407 // The "Display" column of the "actionTransitionFunctions" table contains a
408 // function for each mouse and timer event that is expected in this state.
410 // When a "mousemove" event occurs in "Display" state, move the tooltip
411 // to the current cursor location, and leave the finite state machine in
412 // its current state.
413 mousemove: function(event) {
414 this.moveTooltip(event.clientX, event.clientY);
415 return this.currentState;
417 // When a "mouseout" event occurs in "Display" state, take the same
418 // actions, and make the same state transitions, as for "timeout"
419 // events in "Display" state.
420 mouseout: function(event) {
421 return this.doActionTransition("Display", "timeout", event);
423 // When a "timeout" event occurs in "Display" state, in the normal case,
424 // (when the fade-out time is non-zero), start the animation ticker and
425 // transition to "FadeOut" state. But when the fade-out time is zero,
426 // skip the fade animation (to avoid dividing by zero when "timetick"
427 // events occur in "FadeOut" state), and transition directly to "Inactive"
428 // state (after deleting the tooltip).
429 timeout: function(event) {
431 if (this.fadeoutTime>0) {
432 this.startTicker(1000/this.fadeRate);
435 this.deleteTooltip();
439 }, // end of FadingTooltip.prototype.actionTransitionFunctions.Display
441 // The "FadeOut" column of the "actionTransitionFunctions" table contains a
442 // function for each mouse and timer event that is expected in this state.
444 // When a "mouseover" event occurs in "FadeOut" state, move the tooltip
445 // to the current cursor location, and transition back to "FadeIn" state.
446 // Leave the animation ticker running; subsequent "timetick" events in
447 // "FadeIn" state will cause the fade animation to reverse direction at
448 // the current tooltip opacity.
449 mouseover: function(event) {
450 this.moveTooltip(event.clientX, event.clientY);
453 // When a "mousemove" event occurs in "FadeOut" state, take the same
454 // actions as for "mousemove" events in "Display" state. Note that no
455 // state transition occurs; the finite state machine remains in the
457 mousemove: function(event) {
458 return this.doActionTransition("Display", "mousemove", event);
460 mouseout: function(event) {
461 return this.currentState; // do nothing
463 // When a "timetick" event occurs in "FadeOut" state, decrease the
464 // opacity of the tooltip slightly (such that opacity decreases from the
465 // specified maximum to zero in equal increments over the specified fade-out
466 // time at the specified animation rate). When tooltip opacity reaches zero,
467 // cancel the ticker, delete the tooltip, and transition to "Inactive" state.
468 timetick: function(event) {
469 this.fadeTooltip(-this.tooltipOpacity/(this.fadeoutTime*this.fadeRate));
470 if (this.currentOpacity<=0) {
472 this.deleteTooltip();
475 return this.currentState;
477 } // end of FadingTooltip.prototype.actionTransitionFunctions.FadeOut
478 }, // end of FadingTooltip.prototype.actionTransitionFunctions
480 // The "doActionTransition" method is used in the "actionTransitionFunctions"
481 // table when one function takes exactly the same actions as another function
482 // in the table. It selects another function from the table, using the required
483 // "anotherState" and "anotherEventType" arguments, and calls that function, passing
484 // on the required "event" argument, and then returning its return value. As with
485 // the "handleEvent" method, the function is called via the "call" method of its
486 // Function object, which allows us to set its context so that the build-in "this"
487 // variable will point to the FadingTooltip object while the function executes.
489 doActionTransition: function(anotherState, anotherEventType, event) {
490 return this.actionTransitionFunctions[anotherState][anotherEventType].call(this,event);
493 // The "startTimer" method starts a one-shot timer. The required "timeout"
494 // argument specifies the duration of the timer in milliseconds. The
495 // method defines an anonymous function for the timeout event handler.
496 // When the browser calls timer event handlers, "this" points at the
497 // global window object. Therefore, a pointer to the FadeTooltip object
498 // is copied to the "self" local variable and enclosed with the anonymous
499 // function definition so that the timeout event handler can locate the
500 // object when it is called. The browser does not pass any arguments to
501 // timer event handlers, so the timeout event handler creates a simple
502 // "timer event" object containing only a "type" property, and passes
503 // it to the "handleEvent" method (defined above). So, when the
504 // "handleEvent" method executes, "this" will point at the FadingTooltip
505 // object, and the "type" property of its "event" argument will identify
506 // it as a "timeout" event. The opaque reference to a timer object (returned
507 // by the browser when any timer is started) is saved as a state variable
508 // so that the timer can be cancelled prematurely, if necessary. This method
509 // does not return a value.
511 startTimer: function(timeout) {
513 this.currentTimer = setTimeout(function() { self.handleEvent( { type: "timeout" } ); }, timeout);
516 // The "cancelTimer" method cancels any one-shot timer that may be
517 // running (or recently expired) and then removes the opaque reference to
518 // to the timer object saved in the "startTimer" method (defined above).
519 // This method does not return a value.
521 cancelTimer: function() {
522 if (this.currentTimer) clearTimeout(this.currentTimer);
523 this.currentTimer = null;
526 // The "startTicker" method starts a repeating ticker. The required
527 // "interval" argument specifies the period of the ticker in milliseconds.
528 // The method defines an anonymous function for the ticker event handler.
529 // When the browser calls timer event handlers, "this" points at the
530 // global window object. Therefore, a pointer to the FadeTooltip object
531 // is copied to the "self" local variable and enclosed with the anonymous
532 // function definition so that the ticker event handler can locate the
533 // object when it is called. The browser does not pass any arguments to
534 // timer event handlers, so the ticker event handler creates a simple
535 // "timer event" object containing only a "type" property, and passes
536 // it to the "handleEvent" method (defined above). So, when the
537 // "handleEvent" method executes, "this" will point at the FadingTooltip
538 // object, and the "type" property of its "event" argument will identify
539 // it as a "timetick" event. The opaque reference to a timer object (returned
540 // by the browser when any timer is started) is saved as a state variable
541 // so that the ticker can be cancelled when it is no longer needed.
542 // This method does not return a value.
544 startTicker: function(interval) {
546 this.currentTicker = setInterval(function() { self.handleEvent( { type: "timetick" } ); }, interval);
549 // The "cancelTicker" method cancels any repeating ticker that may be
550 // running, and then removes the opaque reference to the timer object
551 // saved in the "startTicker" method (defined above). This method does
552 // not return a value.
554 cancelTicker: function() {
555 if (this.currentTicker) clearInterval(this.currentTicker);
556 this.currentTicker = null;
559 // The "saveCursorPosition" method is called when the cursor position
560 // changes while waiting for the cursor to pause over the HTML element.
561 // The required arguments "x" and "y" are the current cursor
562 // coordinates, which the method It saves the so that the tooltip
563 // can be positioned near it, after the cursor pauses, when the
564 // "Pause" state timer expires. This method does not return a value.
566 saveCursorPosition: function(x, y) {
567 this.lastCursorX = x;
568 this.lastCursorY = y;
572 // about to start. It creates a "floating" HTML Division element for
573 // the tooltip. The tooltip is styled with a named CSS style, if one
574 // is defined, or a default style if not. In either case, the initial
575 // opacity is set to zero for FF, NN, and MSIE. This method does not
578 createTooltip: function() {
580 // create an HTML Division element for the tooltip and load the
581 // tooltip"s text and HTML tags into it
582 this.tooltipDivision = document.createElement("div");
583 this.tooltipDivision.innerHTML = this.tooltipContent;
585 // if a named CSS style has been defined, apply it to the tooltip,
586 // otherwise apply some default styling
587 if (this.tooltipClass) {
588 this.tooltipDivision.className = this.tooltipClass;
590 this.tooltipDivision.style.minWidth = "25px";
591 this.tooltipDivision.style.maxWidth = "350px";
592 this.tooltipDivision.style.height = "auto";
593 this.tooltipDivision.style.border = "thin solid black";
594 this.tooltipDivision.style.padding = "5px";
595 this.tooltipDivision.style.backgroundColor = "yellow";
598 // make sure that the tooltip floats over the rest of the HTML
599 // elements on the page
600 this.tooltipDivision.style.position = "absolute";
601 this.tooltipDivision.style.zIndex = 101;
603 // position the tooltip near the last known cursor coordinates
604 this.tooltipDivision.style.left = this.lastCursorX + this.tooltipOffsetX;
605 this.tooltipDivision.style.top = this.lastCursorY + this.tooltipOffsetY;
607 // set the initial opacity of the tooltip to zero, using the proposed W3C
608 // CSS3 "style" property, and, if we are running MSIE, also create an
609 // "alpha" filter with an "opacity" property whose initial value is zero
610 this.currentOpacity = 0;
611 this.tooltipDivision.style.opacity = 0;
612 if (this.tooltipDivision.filters) this.tooltipDivision.style.filter = "alpha(opacity=0)"; // for MSIE only
614 // display the tooltip on the page
615 document.body.appendChild(this.tooltipDivision);
619 // tooltip. The required "opacityDelta" argument specifies the size
620 // of the increase (positive values) or decrease (negative values).
621 // The increase is limited to the specified maximum value; the decrease
622 // is limited to zero. This method does not return a value.
624 fadeTooltip: function(opacityDelta) {
626 // calculate the new opacity value as a decimal fraction, rounded
627 // to the nearest 0.000001 (that is, the nearest one-millionth),
628 // to avoid exponential representation of very small values, which
629 // are not recognized as valid values of the "opacity" style property
630 this.currentOpacity = Math.round((this.currentOpacity + opacityDelta)*1000000)/1000000;
632 // make sure the new opacity value is between 0.0 and the specified
633 // maximum tooltip opacity
634 if (this.currentOpacity<0) this.currentOpacity = 0;
635 if (this.currentOpacity>this.tooltipOpacity) this.currentOpacity = this.tooltipOpacity;
637 // change the "opacity" style property of the HTML Division element that
638 // contains the tooltip text, and, if we are running MSIE, find the "alpha"
639 // filter created in "createTooltip" (defined above) and change its "opacity"
640 // property to match, remembering that its range is 0 to 100, not 0 to 1
641 this.tooltipDivision.style.opacity = this.currentOpacity;
642 if (this.tooltipDivision.filters) this.tooltipDivision.filters.item("alpha").opacity = 100*this.currentOpacity; // for MSIE only
646 // changes while the tooltip is visible, whether it is fading in,
647 // fully displayed, or fading out. It moves the tooltip so that
648 // it follows the movement of the cursor. This method does not
651 moveTooltip: function(x, y) {
652 this.tooltipDivision.style.left = x + this.tooltipOffsetX;
653 this.tooltipDivision.style.top = y + this.tooltipOffsetY;
657 // completely. It deletes the HTML Division element. This method does
658 // not return a value.
660 deleteTooltip: function() {
661 if (this.tooltipDivision) document.body.removeChild(this.tooltipDivision);
662 this.tooltipDivision = null;
665 }; // end of FadingTooltip.prototype
668 // With MSIE and Opera, an extra "mouseover" event sometimes occurs in "Pause" state,
669 // after a previous "mouseover" event has made the finite state machine transition
670 // from "Inactive" state to "Pause" state. Presumably this is due to a bug in
671 // the browser. For MSIE and Opera only, add an extra anonymous function to the
672 // "actionTransitionTable" for this situation: when it occurs, take the same actions,
673 // and return the same state transition, as for a "mouseover" event in "Inactive" state.
675 if ( (window.navigator.userAgent).indexOf("MSIE")!=-1 || (window.navigator.userAgent).indexOf("Opera")!=-1 ) {
676 //if (this.trace) trace("Pause/mouseover hack added to state table");
677 FadingTooltip.prototype.actionTransitionFunctions.Pause.mouseover = function(event) {
678 return this.doActionTransition("Inactive", "mouseover", event);