reptile7's JavaScript blog
Wednesday, March 05, 2014
 
Retooling the Tool Tip, Part 3
Blog Entry #312

In today's post we'll clean up the Java Goodies "Tip Box" script so that it works with Safari, Opera, and Firefox, as well as with Internet Explorer. We've duked it out with the script's Microsoft-implemented proprietary features over the last couple of entries; we'll hold onto some of these features but many of them will be sent packing in the discussion below.

Event listener registration

HTML 4 indirectly associates onmouseover and onmouseout with the document object in that it green-lights their use as attributes with the body element, which is a stand-in for the document object; HTML5 makes the association more explicit. Modern browsers are in sync with the current HTML onmouseover/onmouseout specification and therefore we can leave the document.onmouseover = toolTip; and document.onmouseout = unTip; registrations just as they are.

The event object

It would be a good idea to create a cross-browser event object for getting the mouseover/mouseout event target and for horizontally positioning the tool tip; this is conveniently accomplished via the ?: conditional operator, for example:

function toolTip(e) { var e = e ? e : window.event; ... }

Giving credit where credit is due, I learned this little trick from Apple's "Supporting Three Event Models at Once" tutorial.

Getting the event target

The srcElement property is supported by IE, Safari, and Opera, but not by Firefox. The Netscape-implemented target property, srcElement's standard equivalent, has cross-browser support although its IE support began only with IE 9. We can again use the ?: operator to make everybody happy:

var src = e ? e.target : e.srcElement;

Have you got a tip?

The src.tip if gate has an exact DOM equivalent:

if (src.hasAttribute("tip")) { ... }
// Better: if (src.getAttribute("tip")) { ... }


The hasAttribute(name) method of the Core DOM's Element interface returns true when an attribute with a given name is specified on [the calling] element or has a default value, false otherwise, quoting the W3C. Frustratingly, Dottoro reports that IE's hasAttribute( ) support does not predate IE 8. Fortunately, we can also use the Element interface's getAttribute(name) method, which was implemented by Microsoft for IE 4, to test for the presence of the tip attribute; getAttribute(name) returns the value of the calling element's name attribute, and as long as that value is not an empty string it will convert to true in a boolean context.

The src bottom

The var y = src.offsetTop + src.offsetHeight; determination can be left alone. CSS's top and height won't get you to the bottom of the Dignified's Domain link but offsetTop and offsetHeight will; moreover, the latter properties have cross-browser support and are themselves on track to be standardized.

Displaying the tool tip

Two entries ago I intimated that we shouldn't use the insertAdjacentHTML( ) method to render the tool tip because it's not supported by Mozilla's browsers; had I done a bit more homework, however, I would have learned that Firefox at least does support it (I haven't had Camino or Netscape 9 on my hard disk since I replaced my hard drive last fall so I can't vouch for those browsers). My bad.

But there's actually a more fundamental reason why we shouldn't use insertAdjacentHTML( ) for this operation. Like its appendChild( ) and insertBefore( ) DOM counterparts, insertAdjacentHTML( ) is meant to insert content into the normal flow of a document - Dottoro's insertAdjacentHTML( ) demo (which works very nicely with Firefox) does a good job of illustrating this - and we're not doing that: we're popping up a div that is removed from the normal flow by virtue of its absolute positioning. More appropriately we can
(a) code the div normally (in the document body HTML and not scriptically),
(b) initially set its CSS display to none, and then
(c) switch the display to block at runtime:

#zTip { display: none; position: absolute; ... }
...
document.getElementById("zTip").style.display = "block";
...
<div id="zTip"></div>


The src element's tip value can be loaded into the div via the innerHTML property (vide infra).

(Before moving on, here's another insertAdjacentHTML( ) problem:
With successive mouseovers
(1-2) Safari and Opera overwrite the zTip object but
(3-4) Firefox doesn't, and adds more <div id="zTip"></div>s to the end of the document body - those divs just sit there because Firefox 'moves' the zeroed-out first-created zTip object toward the src element - it is now clear to me that this also occurs with IE 5.1.7 and IE 4.5 on my iMac.)

Referencing the zTip div

Per the preceding code we should use the getElementById( ) method, and not the zTip id value, to reference the tool-tip div (getElementById( ) isn't supported by IE 4.x, but I would certainly like to think that no one out there is using IE 4.x in this day and age).

var tooltipDiv = document.getElementById("zTip");
tooltipDiv.innerHTML = src.getAttribute("tip"); ...


We can preface the statements that set the div's top/left offsets (and content, and width if desired) with tooltipDiv in lieu of the with construct; the statements'll be a bit longer, but that's a small price to pay to forestall any confusing bugs and compatibility issues, eh?

top/left offsets

The posTop/posLeft properties are supported by IE and Safari; contra Dottoro, Opera has dropped support for them, whereas Firefox never supported them in the first place. Naturally, we ought to exchange posTop/posLeft for their standard top/left counterparts.

For left-ing the zTip div, the window.event.x expression, which is supported by IE and Safari and Opera but not by Firefox, can be safely replaced by a cross-browser and standard e.clientX expression as long as the page width doesn't exceed the viewport width (and let's hope that's the case). If we have to do any horizontal scrolling to get to the src element, however, then we'll have to augment the e.clientX measurement with a document.body.scrollLeft term.

(The state-of-the-art way to get the mouseover x-coordinate relative to the left edge of the document content area is via the pageX property, which unfortunately is not supported by IE 5-8. For IE 5-8 we should be able to go back to the x property, but I am unable to confirm this; in practice, I find that e.x and e.clientX invariably give the same return with IE 5.1.7, Safari, and Opera.)

In sum, to bring everyone and everything on board - and remembering that top/left values have a string data type and require a unit identifier - the positioning code should be formulated as:

tooltipDiv.style.top = y + 14 + "px";
tooltipDiv.style.left = e.clientX + document.body.scrollLeft + 10 + "px";


The div width, revisited

An absolutely positioned div with a non-auto left value and auto width and right values should have a shrink-to-fit width (in most cases this will be the "preferred width", although it could be either the "available width" or the "preferred minimum width" if the div is close to the right edge of the viewport), and that's what I see for the zTip div with Safari, Opera, and Firefox, but it's not what I see with IE 4.5, which gives the div a width of 100% in the absence of a specific width setting, and I suspect that's why Ryan put the style.width = src.tip.length * 1.5; assignment in the with block. A shrink-to-fit width is in fact what we want, however, and if modern browsers are willing to set that width for us, then who are we to stand in their way?

Losing the tool tip

I'm sure you can rewrite the unTip( ) function at this point but I'll give you my code anyway:

function unTip(e) { var src = e ? e.target : e.srcElement; if (src.getAttribute("tip")) document.getElementById("zTip").style.display = "none"; }

With Safari and Opera title-induced tool tips disappear on their own (without mousing out from the tooltipped element) after about 10 seconds, whereas the corresponding Firefox tool tips hang around indefinitely. You can make the style.display = "block" zTip div disappear in 10 seconds by adding a window.setTimeout(function ( ) { unTip(e) }, 10000); command to the end of the if (src.getAttribute("tip")) { ... } block in the toolTip( ) function.

Let's see it...

We conclude with a demo incorporating the code presented in this entry (I've tweaked the zTip div's stylings a bit).

Move your mouse cursor over the link below to display a custom tool tip.

Blogger.com


Comments: Post a Comment

<< Home

Powered by Blogger

Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)