Newsflash

 
Home arrow Buzzword-noncompliant arrow Tooltip boxes test 1
Tooltip boxes test 1 PDF Print
Written by Administrator   
Tuesday, 09 May 2006

In this article I explain step-by-step the script I wrote for popping up tooltip boxes over parts of the text. I use this script in some of my articles on Gimp. My tool tip boxes contain reminders on how to perform certain actions in Gimp. Of course, it is already possible to create tip boxes by putting the tip box text in the title attribute of an element over which the tip box should pop up. But if you want to customize tip boxes with specific font type, size and color, or even add images to them, you need to write a script for that.
In this article I try to explain the script from the perspective of "why" rather than "what".
Note: I realize very well this script does not take into account all possible browsers, and it will not work with some older browsers. I tested it with more or less recent versions of Firefox, Internet Explorer and Opera, and it worked. I don't have access to every possible version of every browser on every operating system in the world, and even if I had, it would be unbearably tedious to figure out ways around browser incompatibilities. It should be, as they say, an exercise for the reader. :-)

1. How do we make something happen when the user moves the mouse cursor over a particular segment of the text?


This question implies a sub-question: how does the browser know that a particular segment of the text is "special" and something needs to happen when a mouse cursor is moved over it? What separates this segment from the rest of the text?
We put that portion of the text in a span element. For example:
When you <span class="whatIsThis">tile</span> this image, you'll get seamless tiles.

We put the word "tile" in a span element because we want a tooltip to pop up over the word "tile". (The tooltip will contain text "Filters->Map->Tile", which is the menu option that creates tiles out of an image. In other words, this tooltip reminds the user how to tile an image.)
Why does this span element have a class? A class, as we know, allows us to assign various properties to this element, such as font face, size, color, alignment, what-have-you. The word "tile" needs those attributes (for example, a different color) to appear "special", to stand out from the rest of the text. There is another reason why it could benefit from having a class; more about it later.

1.1. How do we make something happen when the mouse is moved over an element?


By associating an event handler with this span element. An event handler is a function that is executed when a certain event is fired. In our case, we need to handle two types of events: mouseover and mouseout. mouseover, as the name implies, fires when the user moves the mouse over an element, and mouseout, when the user moves the mouse away from the element.

1.2. How do we associate an event handler with an element?


By registering the event handler to the element, for example, with this snippet of Javascript code (though there are other, obsolete as well as advanced, ways to do it.)

mySpan.onmouseover = ShowTipBox;
mySpan.onmouseout = HideTipBox;


In this snippet, mySpan is a variable that represents a <span> element; ShowTipBox is a function that causes a tip box pop up, and HideTipBox makes it disappear. Notice that there are no brackets after event handler function names! (This article on Quirksmode has a paragraph that explains why, better than I could, so I won't even bother paraphrasing it.)
We probably have more than one <span> element on our page, and we need to assign event handlers to all of them. For that, we write this Javascript function (adapted from the Quirksmode article referenced above:
function assignEventHandlers()
{
  var spans = document.getElementsByTagName('span');
  var iSpanIndex = 0;
  for (var i=0; i < spans.length; i++) {
    if (spans[i].className == 'whatIsThis') { 
      spans[i].explNumber = iSpanIndex;
      spans[i].onmouseover = MakeVisible;
      spans[i].onmouseout = MakeInvisible; 
      iSpanIndex++;
    }
  }
}


This function will need to be called after the document has been loaded, because at that time the <span> elements will have been initialized. To make that happen, I put this code snippet right before the closing </body> tag:

<script type="text/javascript" language="JavaScript1.2">
onload = assignEventHandlers();
</script>


1.2.1. A closer look at the assignEventHandlers() function

The first line calls a DOM function getElementsByTagName which, true to its name, gets elements with the given tag name (span), and puts them into an array. Then the for loop iterates through this array. For each <span> element we first check if its class attribute is "whatIsThis". This brings us to the second reason why it's good for each <span> element to have a class attribute. If your page is served by a content management system (CMS), there is a chance that the HTML code generated by this CMS has additional <span> elements. At least that's the case with Joomla (a CMS I'm using). So we need to make sure that we process only those <span> elements that were created by us. An easy way to do it is to assign them their own class.

But how do we make the right tip box pop up over each <span> element? To associate each <span> element with its corresponding tooltip box, we assign it a property explNumber. explNumber is not a CSS property, it is something we have created. With Javascript we can create all kinds of element properties; there is no reason to limit ourselves only by properties defined by CSS.

In our case, explNumber indicates the order in which this <span> element appears on the page. It is a zero-based index. It is also, as we will see later, the index of its corresponding tooltip. In this example we are assuming that each <span> element has a unique explanation; that is, there are no two <span> elements with the same explanation. Hence, the index of each tooltip is the same as the index of its <span> element. The 0th <span> element is associated with the 0th tip box, 1st with the 1st, etc. However, it is not necessarily the equal to the loop variable i, since, as we mentioned before, if this article is served by a content management system, the system-generated code may contain span elements of its own, and we need to differentiate "our" <span> elements from "alien" ones (which we do by checking its class name, as discussed above.) That's why we keep track of a counter iSpanIndex.

(A case where different <span> elements have the same explanation is just as easy to implement, and is discussed at the end of this article.)

2. What kind of beast -- or what kind of HTML element -- is a tooltip box, anyway?



A tool tip box can be created with a <div> element (and some other elements, but I guess <div> may be one of the more popular choices). The interestingness of the <div> tag is that it lets you create page elements that overlap other elements on the page. You can also assign them a style property called visibility. A tooltip box is nothing else than a <div> element that is made visible sometimes -- when the user moves the mouse over a certain area of the page -- and is invisible the rest of the time.

Of course, it needs to pop up in the right place, such as immediately below or above the word with which it is associated. You can make it pop up in the right place by setting the X and Y coordinates of its upper left corner. An element's top left corner coordinates are, too, a style property, and can be programmatically assigned with Javascript.

Here is an example of a function that creates a <div> element. (This function was inspired by www.scottandrew.com article on DOM windows.
function CreateTipBox(w, h, col, text){
  var elem = document.createElement("div");
  elem.style.position = "absolute";
  elem.style.width = w + "px";
  elem.style.height = h + "px";
  elem.style.backgroundColor = col;
  elem.style.visibility = "hidden";
  
  elem.innerHTML = text;
  return elem;
}


This function takes the following arguments:
  • w: width of the tooltip box;
  • h: height of the tooltip box;
  • col: color of the tooltip box;
  • text: text the tooltip box will contain.


The code is pretty much self-evident. The first line of the function creates an element with <div> tag. The rest of the lines assign width, height, and color to the corresponding style properties of this element. Its visibility is initially set to "hidden", because we want the tooltip box to show up only when the mouse is moved over a certain part of the text, and remain hidden the rest of the time.

So, CreateTipBox is a function that creates a <div> element. To create a whole bunch of these elements we will invoke this function in a for loop. But first we need to decide the dimensions of tool tip boxes.

The size of a tooltip box depends, of course, on the amount of text on it, as well as on the font size of that text. I derived a very crude heuristic to estimate the width and height of a tip box. In the font I use for tooltips in my article, one character is about 8 px wide. To calculate the width of the tooltip if the explanation fit in one line, I multiply 8 by the number of characters in the explanation. Of course, a long explanation should be broken up in several lines, so I decide how many lines I want to have. In this font size 4 lines fit in about 75 px height. Based on that, I estimate the height of the tipbox. It can be adjusted more accurately by trial and error later.

Once we have determined width and height, in pixels, of each tip box, we put them in their corresponding arrays. Let's say there are 6 different explanations, hence, there will be 6 different toolboxes.

boxHeights = ["25", "25", "75", "75", "75", "75"];
boxWidths = ["130", "60", "120", "120", "180", "125"];
explanations = [
"Filters->Map->Tile",
"Ctrl+D",
"In The Gimp toolbox, a tool designated by letter T: <img src=\"images/stories/GimpArticles/textTool.jpg\">",
"An icon shaped like a cross with arrows on ends: <img src=\"images/stories/GimpArticles/moveTool.jpg\">",
"If rulers are not visible, you can turn them on by going to View->Show Rulers",
"Shift+Ctrl+F brings up the dialog box of the last used filter"];


Having declared these arrays of tooltip properties at the beginning of our <script> section, we can now programmatically create an array of tooltips, and append each one to the document body:
numTooltips = 6;
var tooltips = new Array(numTooltips);

for (var i=0; i < numTooltips; i++) {
  tooltips[i] =	
    new CreateTipBox(boxWidths[i], boxHeights[i], "#ffeedd", explanations[i];
  document.body.appendChild(tooltips[i]);
}

So far we have written a function that creates tipbox <div> elements, and a function that takes <span> elements and assigns mouse event handlers to them. Now we need to write those event handler functions.

3. Mouse event handling functions ShowTipBox and HideTipBox

The function ShowTipBox must do this:

  • find out the X and Y coordinates of the mouse cursor when the mouse is moved over a <span> element;
  • set those coordinates to be the left (X) and top (Y) coordinates of the appropriate tooltip <div> element;
  • make the appropriate tooltip visible.

function ShowTipBox(event) { 
    if (!event) var event = window.event;
    var posx = 0;
    var posy = 0;
    var isOpera = (navigator.userAgent.indexOf('Opera') != -1);
    var isIE = (!isOpera && navigator.userAgent.indexOf('MSIE') != -1)
    if (event.pageX || event.pageY) {
      posx = event.pageX;
      posy = event.pageY;
    }
    else if (event.clientX || event.clientY) {
      posx = event.clientX;
      posy = event.clientY;
      if (isIE) {
	posx += document.body.scrollLeft;
	posy += document.body.scrollTop;
      }
    }
    tooltips[this.explNumber].style.left = posx + "px";
    tooltips[this.explNumber].style.top = posy + "px";
    tooltips[this.explNumber].style.visibility = "visible";
}

The first line,

if (!event) var event = window.event;

is necessary to make this work in Internet Explorer, where event is a property of window. Firefox and Netscape, on the other hand, pass the event object to event handlers. For a much more detailed discussion of how different browsers access events see the "Event Accessing" article on the wonderful Quirksmode site.

The biggest part of this function is dedicated to finding out mouse cursor's coordinates when the mouse is moved over a <span> element. Unfortunately, there are major browser incompatibilities in this area, so mouse cursor coordinates can only be reliably determined by checking what browser the client is using. The mouse position-determining code in this function is adapted from "Mission Impossible - mouse position" article by Peter-Paul Koch, the author of Quirksmode. In this article he explains very well why the browser checking is necessary and why there is no better way to do it, and what all those lines of code mean.

The last 3 lines is where the explNumber property, assigned to the <span> element earlier, becomes useful. The right tooltip for this <span> element is the one whose index in the tooltips array is the explNumber property of this <span> element: tooltips[this.explNumber].

After determining mouse cursor's x and y coordinates, we assign them respectively to the style.left and style.top properties of tooltips[this.explNumber] element. We also set the style.visibility property to visible.

The only thing function HideTipBox must do is set style.visibility property of appropriate <div> element to hidden. And the second line of this function does just that. However, Firefox has a... strangeness that took me weeks to figure out how to work around. And I still don't know why Firefox does that. My workaround was an accidental discovery, not an application of logic.

function HideTipBox() { 
    tooltips[this.explNumber].innerHTML = "<p class=\"sansserif\">" 
      + explanations[this.explNumber] + '</p>';
    tooltips[this.explNumber].style.visibility = "hidden";
}

The workaround is in the first line. Why is it necessary? Why would we need to re-assign the same content to the innerHTML property of the tooltip element that it already has? After all, its innerHTML was set when it was created with the CreateTipBox function, and it could not have disappeared. And in fact, in two other browsers I tested this with -- IE and Opera -- this is not necessary. But in Firefox, if this line of code is missing, something strange happens to the tipbox element. It flickers. You move the mouse cursor on the corresponding <span> element, the tooltips box appears, but it does not stay put. It flickers on and off many times a second. That's ugly, and it makes the text in it very hard to read. For some reason, if the HideTipBox function re-assigns the tooltip's innerHTML element, the tip box stays put while the mouse is over the <span> element! Where is the logic in that? When the mouse is over the <span> element, mouseout event should not even be firing, and HideTipBox should not be invoked! So why does it make a difference if a certain line of code is present in it or not?

The complete page, including Javascript and HTML code

So now we put all this code together in a script. Here is what the code of the finished web page, including both the JavaScript and the HTML, looks like. The content of this sample page includes snippets from an actual article on Gimp that I wrote. These snippets are taken out of context and are not supposed to make sense. :-) The actual article and the demonstration of tip boxes in action can be found here.


<html>
<head><title>Creating wavy buttons with Gimp</title>
   <STYLE TYPE="text/css">
    span.whatIsThis {color: #0000ff}
    p.sansserif {font-family: sans-serif; font-size: 80%; margin-left: 10px; 
                 margin-top: 5px}
    img.insideTipBox {float: right}
   </STYLE>
</head>
<body>

<script type="text/javascript" language="JavaScript1.2">

explanations = [
"Filters->Map->Tile", 
"Ctrl+D", 
"In The Gimp toolbox, a tool designated by letter T <img class=\"insideTipBox\"
  src=\"images/stories/GimpArticles/textTool.jpg\">", 
"An icon in a shape of a cross with arrows on ends <img class=\"insideTipBox\" 
  src=\"images/stories/GimpArticles/moveTool.jpg\">", 
"If rulers are not visible, you can turn them on by going to View->Show Rulers", 
"Shift+Ctrl+F brings up the dialog box of the last used filter"
];

boxHeights = [ "25", "25", "100", "120",  "75",  "75"];
boxWidths  = ["130", "60", "100", "120", "180", "125"];

function CreateTipBox(w, h, col, text){
  var elem = document.createElement("div");
  elem.style.position = "absolute";
  elem.style.width = w + "px";
  elem.style.height = h + "px";
  elem.style.backgroundColor = col;
  elem.style.visibility = "hidden";
  
  elem.innerHTML = text;
  return elem;
}


function ShowTipBox(event) { 
    if (!event) var event = window.event;
    var posx = 0;
    var posy = 0;
    var isOpera = (navigator.userAgent.indexOf('Opera') != -1);
    var isIE = (!isOpera && navigator.userAgent.indexOf('MSIE') != -1)
    if (event.pageX || event.pageY) {
      posx = event.pageX;
      posy = event.pageY;
    }
    else if (event.clientX || event.clientY) {
      posx = event.clientX;
      posy = event.clientY;
      if (isIE) {
	posx += document.body.scrollLeft;
	posy += document.body.scrollTop;
      }
    }
    tooltips[this.explNumber].style.left = posx + "px";
    tooltips[this.explNumber].style.top = posy + "px";
    tooltips[this.explNumber].style.visibility = "visible";
}

function HideTipBox() { 
    tooltips[this.explNumber].innerHTML = "<p class=\"sansserif\">" 
     + explanations[this.explNumber] + '</p>';
    tooltips[this.explNumber].style.visibility = "hidden";
}

function assignEventHandlers()
{
  var spans = document.getElementsByTagName('span');
  for (var i=0; i < spans.length; i++) {
    if (spans[i].className == 'whatIsThis') { 
      spans[i].explNumber = i;
      spans[i].onmouseover = ShowTipBox;
      spans[i].onmouseout = HideTipBox; 
    }
  }
}

numTooltips = 6;
var tooltips = new Array(numTooltips);

for (var i=0; i < numTooltips; i++) {
  tooltips[i] =	new CreateTipBox(boxWidths[i], boxHeights[i], "#ffeedd", 
                                 explanations[i];
  document.body.appendChild(tooltips[i]);
}

</script>

<p>When you <span class="whatIsThis">tile</span> this image, 
you'll get seamless tiles.</p>

<p>I started by <span class="whatIsThis">duplicating</span> the 
background image.</p>

<p>Select <span class="whatIsThis">Text tool</span> in The Gimp 
main dialog box. The lower section of the main dialog box will display Text 
Options: font, size, color, indent, etc. </p>

<p>With the <span class="whatIsThis">Move tool</span>, drag the 
text to where you want it to be in the image.</p> 

<p>A horizontal guide can be created by pressing the left mouse button down 
on the <span class="whatIsThis">horizontal ruler</span> at the top of 
the image, and dragging the mouse to where you want to put the guide.</p>

<p>I now duplicate the original image, <span class="whatIsThis">bring 
up the Waves filter again</span>, and, keeping amplitude the same, I change 
the Phase to 90.</p>

<script type="text/javascript" language="JavaScript1.2">
  onload = assignEventHandlers();
</script>

</body>

</html>

A modification: some tooltip boxes may be associated with more than one element

What if some explanations need to pop up over more than one text segment? What if there are several <span> elements that use the same tip box? Then the script can be easily modified like this.

Let's say, 3 different <span> elements (out of 6) use a tipbox whose index is 1. Then we will create an array explNumbers, containing information about which <span> element uses which explanation. For example:

explNumbers = ["0", "1", "2", "1", "1", "3"];

This shows that the 0th <span> element uses the 0th explanation; 1st <span> element uses the 1st explanation, 2nd <span> element uses the 2nd explanation, 3rd and 4th <span> elements use the 1st explanation again; and the 5th <span> element uses the 3rd explanation.

(Since there are now only 4 different explanations, boxHeights and boxWidths arrays will have only 4 elements each.)

In the assignEventHandlers() function, line

spans[i].explNumber = iSpanIndex;

needs to be changed to

spans[i].explNumber = explNumbers[iSpanIndex];

As discussed above, the variable iSpanIndex will indicate the order number of <span> elements in the article, counting only "our" <span> elements -- the ones whose class name is 'whatIsThis'. (Remember that if our article is embedded in a page generated by a content management system, the HTML code of the page may contain other <span> elements, and we need to tell ours apart.)

In this modified case, explNumbers[iSpanIndex] will tell the code which explanation needs to be associated with the <span> element whose index among "our" <span> elements is iSpanIndex.

Last Updated ( Saturday, 07 July 2007 )
 
< Prev   Next >

(C) 2008 Elze's CMS experiment
Joomla! is Free Software released under the GNU/GPL License.