Tooltip

Se ritieni utile questo articolo, considera la possibilità di effettuare una donazione (il cui importo è a tua completa discrezione) tramite PayPal. Grazie.

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:

Tooltip - Class Diagram

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;\">&nbsp;</div>\
            <table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\"" + ttbstyle + ">\
                <tr>\
                    <td class=\"tooltip_tl\">&nbsp;</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\">&nbsp;</td>\
                    <td class=\"tooltip_b\">&nbsp;</td>\
                    <td class=\"tooltip_br\">&nbsp;</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:

  1. Viene rimossa l'eventuale istanza precedente e viene creato dinamicamente il "contenitore" (DIV) del nostro tooltip

  2. Al contenitore viene associata la classe (CSS) corrispondente allo stile impostato

  3. Viene generato il codice HTML per la renderizzazione del contenuto del tooltip

  4. Lo script determina le posizioni e le dimensioni degli elementi principali per calcolare il posizionamento del tooltip

  5. Se non è stata specificata una posizione viene automaticamente determinata la posizione ottimale del tooltip rispetto all'elemento cui deve essere ancorato

  6. 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.