17 de abril de 2010

jQuery Tooltip qTip Optimización

Veremos, mediante un ejemplo, lo sencillo que es agregar Tooltip con jQuery, usando el plugin qTip. Además comentaré un ejemplo de rendimiento real que se me presentó y solventé usando algunas técnicas jQuery junto con este control (qTip).

Este post contará de 2 partes:

  1. Uso e implementación simple del plugin jQuery qTip.
  2. Optimización de páginas que usen Tooltip qTip.

 

jQuery qTip, Ejemplo Sencillo

El uso de los Tooltip es muy frecuente y muy vistoso, sobre todo cuando puedes insertar código HTML en el Tooltip, y este es el caso que hoy nos ocupa, hablaremos del plugin jQuery qTip. En esta primera parte veremos un ejemplo bien simple, no profundizaremos demasiado porque el sitio oficial de este plugin está bastante completo y documentado.

Para nuestro ejemplo tendremos que tener los .js de jQuery y también el del plugin qTip, ambos podrás descargártelos desde los sitios oficiales.

Pero veamos el código del ejemplo:

<HTML xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>jQuery Tooltip Simple</title>
 
  <script src="../Scripts/jquery-1.3.2.min.js" type="text/javascript"></script>
  <script src="../Scripts/jquery.qtip-1.0.0.js" type="text/javascript"></script>
 
  <script type="text/javascript">
    $(document).ready(function() {
      $('p').qtip({
        content: 'El texto a mostrar <b>jQuery Tooltip</b>'
       });
     });
   </script>  
</head>
<body>
  <p style="width:110px;">Mostrar Tooltip</p>
</body>
</HTML>

hasta aquí el uso simple y estándar de este plugin jQuery. Veamos en la segunda parte el problema que se me presentó en un proyecto real.

 

jQuery Tooltip, Ejemplo Optimización

Recientemente estaba trabajando en un proyecto donde mostrábamos distintos Tooltip usando este plugin jQuery. Ver imagen:

jQuery Tooltip 

Al pasar por encima del cuadro resaltado en amarillo, aparecía el Tooltip relacionado (HTML marcado en azul), y todo estaba bien, pero, y siempre hay un pero:

Problema:

El problema estaba en que el HTML correspondiente al Tooltip lo generábamos dinámicamente del lado del servidor, y al mostrar unos 200 resultados aproximadamente, pues nada, que la página se recargaba bastante con información que en la mayoría de los casos no se usaba.

Solución:

Para solucionarlo lo que hicimos fue una mezcla de varias cosas:

  1. En lugar de construir todo el HTML en el servidor, almacenamos solo los datos necesario para poder construir el HTML en el navegador (javascript).
  2. Construir el HTML por javascript en el navegador, solo cuando se necesita (solo si se pasa por encima (mouseover)).
  3. Asociar el HTML generado con el plugin jQuery Tooltip qTip.

Por simplicidad, solo mostraré el código que puede resultar interesante y no el ejemplo integro.

1. Generando en el el servidor los datos en JSON:

litEscalas.Attributes.Add("jsonData", DevToolTipJsonEscalas(itinerario));

Aquí adicionamos un atributo "jsonData"al literal, este atributo contendrá los datos necesarios en formato JSON, veamos el código de la función DevToolTipJsonEscalas:

private string DevToolTipJsonEscalas(Itinerario itinerario)
{
  StringBuilder sb = new StringBuilder("[");
  . . . .
  . . . .
 
  sb.Append("{ ");
  sb.Append("'Var1': '1', 'Var2': 'Val2'" . . . .
  sb.Append(" }");
  . . . .
  . . . .
 
  sb.Append("]");
  return sb.ToString();
}

 

Nota: Esta función no está completa, lo importante que debes saber es que el string devuelto debe estar en formato JSON ( [{}, {}, {}] )

 

2. Construir el HTML por javascript en el navegador:

En la página web tendríamos una función que dado el JSON recién construido sea capaz de formar el HTML que necesitamos para mostrar en el Tooltip:

//Construir la tabla del Tooltip Itinerario
function BuildToolTipItinery(jsonData) {
  var arrData = eval('(' + jsonData + ')');
 
  if (arrData == undefined) return 'Nada...';
 
  var table = "<div>";
  table += "<table width='100%'>";
 
  for (var i = 0; i < arrData.length; i++) {
    var isHead = parseInt(arrData[i].IsHead);
    if (isHead == 1) {
      table += "<tr><td colspan='4'>"
               + arrData[i].HeadTest
               + "</td></tr>";
    }
    else {
      table += "<tr>" +
                  '<td>' + arrData[i].Date1 + '</td>' +
                  '<td>' + arrData[i].Time1 + '</td>' +
                  '<td>' + arrData[i].AirPort1 + '</td>' +
                  '<td>Terminal: ' + arrData[i].Terminal1 + '</td>' +
                '</tr>';
      table += "<tr>" +
                  '<td>' + arrData[i].Date2 + '</td>' +
                  '<td>' + arrData[i].Time2 + '</td>' +
                  '<td>' + arrData[i].AirPort2 + '</td>' +
                  '<td>Terminal: ' + arrData[i].Terminal2 + '</td>' +
                '</tr>';
    }
  }
 
  table += "</table></div>";
  return table;
}

Nota: Esta función javascript si la dejaré tal como estaba para no perder tiempo, el lector deberá tener en cuenta cuales han sido las propiedades de su caso en particular.

 

3. Asociar el HTML generado con el plugin jQuery Tooltip qTip:

Por último hacemos la asociación:

$(document).ready(function() {
    $('.ShowToolTipItinerario').one('mouseover', function(event) {
      event.preventDefault();
      $this = $(this);
      $this.attr('title', BuildToolTipItinery($this.attr('jsonData')));
      var optionTT = {
        position: {
          corner: { target: 'topLeft'
                    tooltip: 'bottomRight'} },
          style: { padding: 5, width: 450}
        };    
      $this.qtip(optionTT);
     
      $this.trigger('mouseover');
    });
});

Aquí debemos tener algunas cosas en cuenta:

  1. Los literales a los que asociaremos los Tooltip tendrán como clase '.ShowToolTipItinerario'.
  2. Nótese que usamos el método jQuery one(), que solo se lanzara una única vez.
  3. Posteriormente asociamos el elemento al que se le ha pasado el mouse por encima ('mouseover'), con el plugin qTip (Además hemos personalizado algunos comportamientos del plugin qTip).
  4. Y por ultimo lanzamos o forzamos el evento 'mouseover', de lo contrario tendríamos un efecto extraño debido a que la primera vez que pasaras por encima del literal no se mostraría Tooltip alguno.

Y eso fue todo, así pudimos solucionar nuestro problema, y la página que inicialmente pesaba unos 1500 KB (y si digo bien 1500 KB) la rebajamos a 385 KB.

 

Artículos relacionados:

7 comentarios:

  1. Hola, estoy usando qTip para mostrar Tooltip, pero quiero retrasar la aparición del tooltip ¿Que puedo hacer?

    ResponderEliminar
  2. Hola, como puedo retrasar la aparición del tooltip?

    ResponderEliminar
  3. Hacerlo es muy sencillo solo tienes que hacer uso de la opción que ofrece qTip: show: { delay: 1500 }

    Ejemplo completo de delay qTip (Retraso del Tooltip):

    var optionTTCenter = {
    hide: { fixed: true },
    position: {
    target: $('#frmAereoBuscador'),
    corner: { tooltip: 'center' },
    adjust: { screen: true, resize: true }
    },
    style: { padding: 5, width: 450 },
    show: { delay: 1500 }
    };

    $('.ShowToolTipCenter').qtip(optionTTCenter);


    salu2 y suerte...

    ResponderEliminar
  4. Muy bueno pero... y como bien dices siempre hay un pero ;)

    1. ¿Por qué haces esto?
    $this.attr('title', BuildToolTipItinery($this.attr('jsonData')));

    ¿No es posible asignar el valor directamente al atributo title?

    litEscalas.Attributes.Add("title", DevToolTipJsonEscalas(itinerario));

    En mi modesta opinión sería mejor que usaras la libreria json2.js para parsear el string a json que usar eval, este ultimo los valores null los evalua como 'null' no así la librería que tiene otras muchas ventajas, quedando de esta manera:
    var arrData = JSON.parse(jsonData)
    Por otra parte podrías evitarte tener que conformar el objeto json si en lugar de usar el método private DevToolTipJsonEscalas usuaras lo que te da Visual Studio, algo como esto:
    System.Web.Script.Serialization.JavaScriptSerializer().Serialize(itinerario), reducirías significativamente el tiempo y trabajo

    ResponderEliminar
  5. Hola Anónimo:

    Estoy de acuerdo contigo en casi todo lo que me comentas:

    Sobre la librería json2.js, la suelo usar y es muy practica, no la incluí en el artículo para no complicar con otras cosas al lector, o sea para no tener que explicarle sobre esta librería, pero llevas razón, es mucho mejor su uso, además de que te evitas algunos otros problemas de seguridad; de hecho en mis proyectos suelo usarla, aunque últimamente ya no me ha hecho tanta falta, porque a partir de la versión jQuery 1.4 ya se transforman automáticamente los string con formato JSON en objetos Javascript (al menos en los distintos métodos Ajax .ajax(), .load(), etc. …).

    De igual forma en el método C# DevToolTipJsonEscalas construyo la cadena con formato JSON manualmente, para que el lector entienda mejor el formato de dicha cadena, pero igual es un error mio y debía haber usado algún mecanismo proporcionado por .Net, sobre esto tengo un artículo que recomiendo se lean (http://www.esasp.net/2010/06/c-serializar-json-datacontract.html).

    Ahora bien en lo que si no estoy de acuerdo es en el uso de:
    litEscalas.Attributes.Add("title", DevToolTipJsonEscalas(itinerario));
    y es que precisamente en esto consiste la optimización del jqTip, en NO PASAR desde el servidor el HTML que debe mostrar el ToolTip, sino que solo pasaremos el JSON necesario para construir ese HTML en el cliente y además construirlo solo si se solicita, solo si se demanda. Y es por esto que solo paso la cadena JSON y que tengo una función javascript que dada la cadena JSON construye y devuelve el HTML necesario.

    Bueno y lo dejo por aquí, que me he extendido demasiado, de todas formas solo comentarte que me encanta que la gente deje su opinión, y la tuya creo que ha sido muy acertada y ha hecho que se agudice y profundice mas en el tema, muchas gracias….

    Salu2,
    Derbis

    ResponderEliminar
  6. Hola Derbis, primero que nada, muy bueno el artículo de la serializacion por contratos, que recomiendas que leyeramos, ahora en cuanto a lo de usar el atributo title en lugar de crear uno nuevo, creo que no me explique bien, lo te preguntaba era por que creabas un nuevo atributo para enviar el objeto json para conformar el tooltip y no lo hacias en el mismo atributo title, quería saber si esto provocaba algún conficto o algún efecto visual inesperado. Pero tu respuesta me ha dado una muy buena idea, y es no enviar esa info desde el servidor, hacerlo con el método $.ajax cuando se lance el mouseover por primera vez.
    También se me ocurre que se podría probar enviar un objeto serializado en xml y quedaría ya conformada la tabla html necesaria o usando linqToXml para crear el tooltip. Se que es más compacto json que xml pero la ventaja que le veo en este caso al xml es que no necesitaría un método js para conformar la tabla y la otra, y mayor ventaja, que le veo es en el mantenimiento, ya que de esta manera solo tendría que modificar el código del servidor en caso que necesite agregar o eliminar campos.
    Gracias por todo ;-)

    ResponderEliminar
  7. hola, muy buen post. Estaba tratando de implementarlo pero no puedo agregar un atributo al Literal, VS.net no me da la opción

    de ante mano gracias.

    ResponderEliminar