Tooltip
I messaggi rivolti all'utente delle nostre applicazioni Web hanno quasi sempre il triste aspetto di finestre grigie, non inserite nel contesto applicativo e caratterizzate da icone allarmanti. Infatti i feedback all'utente vengono solitamente mostrati come messaggi di testo presentati in un alert box (window.alert). In questo articolo vedremo come realizzare una libreria che ci permetta di contestualizzare i nostri messaggi presentandoli strettamente associati all'elemento a cui fanno riferimento, renderli immediati grazie ad icone diversificate ed aumentarne le potenzialità, non solo consentendo la formattazione del messaggio, ma permettendo anche l'inserimento di elementi interattivi. Il tutto utilizzando un'interfaccia grafica standard, consolidata e attraente: i tooltip in stile Windows XP (per intenderci: quei fumetti che appaiono associati alle icone poste nella barra di Windows, vicino all'orologio, come ad esempio le notifiche degli aggiornamenti automatici di Windows Update)
Esempio pratico
La via più semplice per capire meglio lo scopo che ci siamo preposti è quella di visualizzare un esempio pratico. Cliccate sul tasto posto di seguito per mostrare un tooltip dimostrativo:
Implementazione
Il diagramma seguente mostra l'interfaccia della classe Tooltip:

Dove:
proprietà id: è l'ID del tooltip nel DOM della pagina
proprietà message: è il testo (comprensivo di eventuale HTML) mostrato nel tooltip
proprietà parentElement: è l'elemento della pagina a cui il tooltip verrà ancorato
proprietà title: è il titolo mostrato nel tooltip
proprietà width: è la larghezza (in pixel) che avrà il tooltip
proprietà position: indica dove il tooltip sarà ancorato al proprio parentElement (sopra, sotto, a destra, a sinistra oppure nella posizione automaticamente ritenuta ideale)
proprietà style: consente di specificare lo stile del tooltip (nell'implementazione seguente viene mostrata una diversa icona a fianco del titolo)
metodo hide: nasconde il tooltip
metodo show: visualizza il tooltip
costruttore Tooltip: crea un'istanza dell'oggetto
Implementiamo il costruttore e le proprietà:
Tooltip = function(id, title, message, style, parentelement, width, position)
{
this.id = id;
this.title = title;
this.message = message;
this.style = style;
this.parentElement = (parentelement.tagName) ? parentelement : document.getElementById(parentelement);
this.width = (width + "" == "undefined" || width == null || width + "" == "") ? null : width;
this.position = (position + "" == "undefined" || position == null || position + "" == "") ? null : position;
}
Fin qui il codice è auto-esplicativo: il costruttore valorizza tutte le proprietà della nostra classe e, qualora uno o più parametri fossero stati omessi, imposta dei valori di default. Si noti che parentElement può essere sia oggetto che l'ID di un oggetto; in questo secondo caso sarà l'inizializzazione della proprietà a recuperare l'istanza dell'oggetto nel DOM della pagina.
Aggiungiamo ora alla classe tooltip l'implementazione del metodo più complesso: show()
this.show = function()
{
removeObject(this.id);
// create element
var d = document.createElement("div");
d.id = this.id;
d.className = "tooltip";
d.style.position = "absolute";
if(this.width != null)
d.style.width = this.width + "px";
d.style.visibility = "hidden";
// set style
var ttbstyle = "";
switch(this.style)
{
case Tooltip.Critical:
ttbstyle = " class=\"critical\"";
break;
case Tooltip.Question:
ttbstyle = " class=\"question\"";
break;
case Tooltip.Exclamation:
ttbstyle = " class=\"exclamation\"";
break;
case Tooltip.Information:
ttbstyle = " class=\"information\"";
break;
}
// build html
var html =
"<div id=\"" + this.id + "_arrow\" class=\"arrow\" style=\"display:none;\"> </div>\
<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\"" + ttbstyle + ">\
<tr>\
<td class=\"tooltip_tl\"> </td>\
<td class=\"tooltip_t\">" + this.title + "</td>\
<td class=\"tooltip_tr\" onclick=\"hideTooltip('" + this.id + "');\" title=\"Chiudi\"><span>chiudi</span></td>\
</tr>\
<tr>\
<td class=\"content\" colspan=\"3\">" + this.message + "</td>\
</tr>\
<tr>\
<td class=\"tooltip_bl\"> </td>\
<td class=\"tooltip_b\"> </td>\
<td class=\"tooltip_br\"> </td>\
</tr>\
</table>";
d.innerHTML = html;
document.body.appendChild(d);
// get objects
var arrow = document.getElementById(this.id + "_arrow");
// get sizes and positions
var size = getElementSize(d);
var windowsize = getElementSize(document.body);
var parentelementposition = getElementPosition(this.parentElement);
var parentelementsize = getElementSize(this.parentElement);
arrow.style.display = "block";
var arrowsize = getElementSize(arrow);
// set position
if(this.position == null || this.position == Tooltip.PositionAuto) // find best positions
{
// @right?
if((windowsize.width - parentelementposition.left - parentelementsize.width - size.width - arrowsize.width) > 0 && (parentelementposition.top + parentelementsize.height / 2 - size.height / 2) > 0)
this.position = Tooltip.PositionRight;
// @left?
else if((parentelementposition.left - size.width - arrowsize.width) > 0 && (parentelementposition.top + parentelementsize.height / 2 - size.height / 2) > 0)
this.position = Tooltip.PositionLeft;
// @top?
else if((parentelementposition.left + parentelementsize.width / 2 - size.width / 2) > 0 && (parentelementposition.top - size.height - arrowsize.height) > 0)
this.position = Tooltip.PositionTop;
// @bottom (default)
else
this.position = Tooltip.PositionBottom;
}
switch(this.position)
{
case Tooltip.PositionRight:
d.style.left = parentelementposition.left + parentelementsize.width + arrowsize.width + "px";
d.style.top = parentelementposition.top + parentelementsize.height / 2 - size.height / 2 + "px";
arrow.className = "arrow_right";
arrowsize = getElementSize(arrow);
arrow.style.top = size.height / 2 - arrowsize.height / 2 + "px";
arrow.style.left = -arrowsize.width + "px";
break;
case Tooltip.PositionLeft:
d.style.left = parentelementposition.left - size.width - arrowsize.width + "px";
d.style.top = parentelementposition.top + parentelementsize.height / 2 - size.height / 2 + "px";
arrow.className = "arrow_left";
arrowsize = getElementSize(arrow);
arrow.style.top = size.height / 2 - arrowsize.height / 2 + "px";
arrow.style.left = size.width + "px";
break;
case Tooltip.PositionTop:
d.style.left = parentelementposition.left + parentelementsize.width / 2 - size.width / 2 + "px";
d.style.top = parentelementposition.top - size.height - arrowsize.height + "px";
arrow.className = "arrow_top";
arrowsize = getElementSize(arrow);
arrow.style.top = size.height + "px";
arrow.style.left = size.width / 2 - arrowsize.width / 2 + "px";
break;
case Tooltip.PositionBottom:
d.style.left = parentelementposition.left + parentelementsize.width / 2 - size.width / 2 + "px";
d.style.top = parentelementposition.top + parentelementsize.height + arrowsize.height + "px";
arrow.className = "arrow_bottom";
arrowsize = getElementSize(arrow);
arrow.style.top = -arrowsize.height + "px";
arrow.style.left = size.width / 2 - arrowsize.width / 2 + "px";
break;
}
// show tooltip
d.style.visibility = "visible";
}
I commenti presenti contrassegnano i punti del codice più significativi, in particolare:
Viene rimossa l'eventuale istanza precedente e viene creato dinamicamente il "contenitore" (DIV) del nostro tooltip
Al contenitore viene associata la classe (CSS) corrispondente allo stile impostato
Viene generato il codice HTML per la renderizzazione del contenuto del tooltip
Lo script determina le posizioni e le dimensioni degli elementi principali per calcolare il posizionamento del tooltip
Se non è stata specificata una posizione viene automaticamente determinata la posizione ottimale del tooltip rispetto all'elemento cui deve essere ancorato
Viene posizionato il contenitore del tooltip e la freccia di ancoraggio
Il metodo per nascondere il tooltip:
this.hide = function() { hideTooltip(this.id); }
Definiamo le costanti statiche relative al posizionamento e allo stile del tooltip:
// tooltip - icon style
Tooltip.Critical = 16;
Tooltip.Question = 32;
Tooltip.Exclamation = 48;
Tooltip.Information = 64;
// tooltip - position style
Tooltip.PositionAuto = 0;
Tooltip.PositionTop = 1;
Tooltip.PositionRight = 2;
Tooltip.PositionBottom = 3;
Tooltip.PositionLeft = 4;
Il codice della classe Tooltip si appoggia ad alcune funzioni di utilità:
// hideTooltip: hide a tooltip
function hideTooltip(id)
{
removeObject(id);
}
// getElementPosition: return left and top
function getElementPosition(obj)
{
var l = obj.offsetLeft - obj.scrollLeft;
var t = obj.offsetTop - obj.scrollTop;
var e = obj.offsetParent;
while(e != null)
{
l += e.offsetLeft - e.scrollLeft;
t += e.offsetTop - e.scrollTop;
e = e.offsetParent;
}
return {left : l, top : t};
}
// getElementSize: return width and height
function getElementSize(obj)
{
var w = obj.offsetWidth;
var h = obj.offsetHeight;
return {width : w, height : h};
}
// removeObject: remove a child node
function removeObject(id)
{
var obj = document.getElementById(id);
if(obj != null)
document.body.removeChild(obj);
}
Queste funzioni sono un estratto della libreria presentata nell'articolo JavaScript Base Library, a cui si rimanda per maggiori approfondimenti ed eventuali aggiornamenti.
Layout grafico
Lo stile grafico del nostro Tooltip è determinato dal CSS applicato. Di seguito il codice dello Style Sheet utilizzato per visualizzare i tooltip in stile Windows XP:
.tooltip { text-align: left; margin: 0px; padding: 0px; }
.tooltip .arrow,
.tooltip .arrow_top { margin-top: -3px; position: absolute; width: 18px; height: 18px; background: url(images/tooltip_arrow_top.gif) 0px 0px no-repeat; }
.tooltip .arrow_right { margin-left: 1px; position: absolute; width: 18px; height: 18px; background: url(images/tooltip_arrow_right.gif) 0px 0px no-repeat; }
.tooltip .arrow_bottom { margin-top: 1px; position: absolute; width: 18px; height: 18px; background: url(images/tooltip_arrow_bottom.gif) 0px 0px no-repeat; }
.tooltip .arrow_left { margin-left: -1px; position: absolute; width: 18px; height: 18px; background: url(images/tooltip_arrow_left.gif) 0px 0px no-repeat; }
.tooltip .tooltip_tl { font-size: 1px; width: 5px; background: url(images/tooltip_tl.gif) 0px 0px no-repeat; }
.tooltip .tooltip_t { font-family: Tahoma, Verdana, Arial, sans-serif, Helvetica, Geneva; font-weight: bold; border-top: 1px solid #000; padding: 0px 10px 0px 20px; }
.tooltip table.information .tooltip_t { background: #fefee1 url(images/tooltip_information.gif) 0px 5px no-repeat; }
.tooltip table.question .tooltip_t { background: #fefee1 url(images/tooltip_question.gif) 0px 5px no-repeat; }
.tooltip table.exclamation .tooltip_t { background: #fefee1 url(images/tooltip_exclamation.gif) 0px 5px no-repeat; }
.tooltip table.critical .tooltip_t { background: #fefee1 url(images/tooltip_critical.gif) 0px 5px no-repeat; }
.tooltip .tooltip_tr { cursor: pointer; width: 30px; height: 30px; background: url(images/tooltip_tr.gif) top right no-repeat; }
.tooltip .tooltip_tr span { display: none; }
.tooltip .content { padding: 0px 7px 0px 7px; font-family: Tahoma, Verdana, Arial, sans-serif, Helvetica, Geneva; font-weight: normal; vertical-align: top; border-left: 1px solid #000; border-right: 1px solid #000; background: #fefee1; }
.tooltip .tooltip_bl { font-size: 1px; width: 5px; background: url(images/tooltip_bl.gif) left bottom no-repeat; }
.tooltip .tooltip_b { font-size: 10px; border-bottom: 1px solid #000; background: #fefee1; }
.tooltip .tooltip_br { font-size: 1px; width: 5px; background: url(images/tooltip_br.gif) right bottom no-repeat; }
Possiamo ottenere una maggiore personalizzazione del risultato modificando il codice HTML generato nel metodo show() della classe Tooltip
Nell'esempio allegato troverete il codice JavaScript completo, un esempio di utilizzo, il CSS e le relative immagini.