Feb 14, 2011

Priorizando CSS sobre JavaScript - Refactorización de un ejemplo

En un mundo dominado ampliamente por las aplicaciones web no importa cuán "hard-core" seamos (o creamos ser) los desarrolladores de software, pero es muy probable que todos, de una forma u otra, hayamos tenido que trabajar con HTML, CSS, y JavaScript. El problema fundamental es que muchas veces menospreciamos estas tecnologías y a los que las utilizan, lo que provoca que nuestro propio rendimiento en ellas deje mucho qué desear. He conversado con muchos que consideran que el trabajo en la interfaz de aplicaciones web es para "diseñadores" y "programadores de poca monta", y créanme que personalmente estoy en total desacuerdo con ellos.

Esta apatía de muchos, provoca que no siempre prestemos especial atención a la calidad de nuestra interfaz de usuario. Muchas veces nuestro brillante "back-end" queda opacado con una código horrible en la interfaz. Funcional pero horroroso. Y mi opinión es que "funcional" no lo es todo en el desarrollo de software: hay que lograr que las cosas funcionen, y funcionen bien.

Cuando se empieza a aprender a hablar un lenguaje nuevo porque no nos queda otro remedio (y yo soy especialista en ello desde que vivo en los Estados Unidos), siempre tendemos a arrastrar construcciones de nuestra lengua natal al nuevo idioma y tratamos de hacer traducciones literales. Por desgracia esto no siempre funciona, por lo que terminamos diciendo disparates o logrando confundir a nuestros oyentes. Lo mismo sucede cuando utilizamos nuevas tecnologías: cómo muchas veces no conocemos todas sus características, terminamos con un código sub-óptimo y muchas veces extremadamente frágil a los cambios que tarde o temprano llegarán. Si ha esto unimos nuestro "desprecio" por lo que consideramos "trabajo para frustrados", jamás aprenderemos a hacerlo bien y seremos nostros los "programadores mediocres" incapaces de lograr que algo tan simple como una interfaz de usuario funcione correctamente.

Veamos un ejemplo bien simple que tuve la oportunidad de rescatar antes de tirarlo a la basura, luego de haberle explicado a un "hard-core" cómo hacerlo correctamente. El ejemplo pueden copiarlo en un fichero HTML y cargarlo en un browser para que vean su comportamiento. Obviamente, con propósitos de poner el ejemplo, aquí está solo un fragmento completamente funcional del código original:
<head>
    <script type="text/javascript"         
      src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js">
    </script>
</head>
<body>
  <style type="text/css">
      #button {
          width: 100px;
          height: 50px;
          border: 1px solid #000000;
          background-color: #0000ff;
      }
  </style>

  <script type="text/javascript"> 
      $(document).ready(function() {
          $('#button').click(function() {
            $(this).attr("pressed", $(this).attr("pressed") == "true" 
              ? "false" 
              : "true");
                
            $(this).css("background-color", $(this).attr("pressed") == "true"
              ? "#ff0000" 
              : "#0000ff");
          });
   
          $('#button').mouseenter(function() {
            if ($(this).attr("pressed") == "false" 
              || $(this).attr("pressed") == undefined) {
                $(this).css("background-color", "#00ff00");
            }
          });
   
          $('#button').mouseleave(function() {
            if ($(this).attr("pressed") == "false" 
              || $(this).attr("pressed") == undefined) {
              $(this).css("background-color", "#0000ff");
            }
          });
      });
  </script>
 
  <div id="button">
  </div>
</body>
El ejemplo muestra un DIV llamado "button", el cual cambia de color cada vez que movemos el puntero del mouse sobre él. Además, cuando hacemos click en él, el DIV cambiará su color a rojo y no volverá a cambiar el color con el movimiento del mouse hasta tanto no hagamos click en él por segunda vez. Nuestro DIV está básicamente simulando el comportamiento de "presionado" en un botón. El código es muy simple: se utilizan dos eventos ("mouseenter" y "mouseleave") para detectar el movimiento del mouse, y en ambos se cambia el color siempre y cuando el botón no se encuentre presionado (para esto se usa un atributo en el tag DIV). Del mismo modo, en el evento "click", se cambia el valor del atributo "pressed" y se fija el color del DIV en rojo.

A pesar que el código funciona perfectamente, tiene un pequeño problema, y es que fue desarrollado por un programador cuya especialidad obviamente no es en las interfaces de usuario. El código ha sido escrito como cualquier persona lo haría en C#, o Java, o cualquier otro lenguaje de alto nivel, pero en el caso de JavaScript y CSS, la solución mostrada está muy lejos de ser la adecuada. Vayamos paso a paso arreglando el código hasta lograr algo más consistente.

Veamos primeramente el código requerido para lograr que el color del DIV cambie cuando movemos el mouse sobre él:
$('#button').mouseenter(function() {
    if ($(this).attr("pressed") == "false" 
        || $(this).attr("pressed") == undefined) {
        $(this).css("background-color", "#00ff00");
    }
});

$('#button').mouseleave(function() {
    if ($(this).attr("pressed") == "false" 
        || $(this).attr("pressed") == undefined) {
        $(this).css("background-color", "#0000ff");
    }
});
Ambos eventos hacen lo mismo: en caso de que el atributo "pressed" del DIV no exista o tenga valor "false", el color de fondo será modificado. En el caso del "mouseenter", el color será verde (#00ff00), y en caso del "mouseleave", el color se restaurará al color azul original (#0000ff). Nada complicado aquí, sin embargo, cualquiera que haya leído un poco sobre CSS se dará cuenta que ambos eventos son completamente innecesarios ya que lo mismo puede lograrse sin una sola línea en JavaScript. Vamos a borrar ambos eventos y adicionemos lo siguiente en la sección de los estilos:
#button:hover {
    background-color: #00ff00;
}
En el código anterior ":hover" es denominado una "pseudo-class" que es utilizada para especificar una regla que será aplicada cuando el mouse se encuentre sobre el DIV. Si ejecutamos el código con los cambios veremos que nuestro DIV sigue cambiando de color con el movimiento del mouse, e incluso cuando hacemos click en él cambia a color rojo, sin embargo, luego que está en rojo, los cambios producto al movimiento del mouse dejan de funcionar. ¿Por qué? Pues porque en el evento "click" estamos cambiando el color "manualmente" (con manualmente me refiero a cambios hechos con JavaScript y no con CSS) a través del método "css" de JQuery. Este método aplica las reglas de estilos en el atributo "style" de los controles HTML, el cual tendrá precedencia a cualquier otra regla que definamos en nuestro CSS. El siguiente es el código HTML generado después que cambiamos nuestro DIV a color rojo:
<div id="button" style="background-color: red;">
</div>
¿Cómo corregir el problema? Pues simplemente eliminando la línea que cambia el color rojo en JavaScript y añadiendo una nueva regla en nuestro CSS. Para hacer esto, aprovecharemos el attributo "pressed" que estamos utilizando para indicar el estado de nuestro DIV. Adicionemos las siguientes líneas en nuestra sección de estilos:
#button[pressed=true] {
    background-color: #ff0000;
}
En este caso, "[pressed=true]" es un selector disponible a partir de la versión 2 de CSS. Básicamente nuestra regla puede leerse de la siguiente forma: "Para un elemento con ID = 'button' y que tenga un atributo llamado 'pressed' con valor 'true', se utilizará el rojo (#ff0000) como color de fondo". Después de esto, solamente nos queda eliminar la línea en JavaScript que modifica el color del DIV, dejando nuestro evento "click" de la siguiente forma:
$('#button').click(function() {
    $(this).attr("pressed", $(this).attr("pressed") == "true" 
        ? "false" 
        : "true");
});
Si refrescamos nuestro browser nuevamente, veremos que ahora el ejemplo funciona perfectamente y hemos reemplazado una buena cantidad de código JavaScript con reglas de estilos. El código ahora es mucho más flexible, claro, y mejor programado de forma general. Veamos cómo quedó todo al final:
<head>
    <script type="text/javascript"         
      src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js">
    </script>
</head>
<body>
  <style type="text/css">
      #button {
          width: 100px;
          height: 50px;
          border: 1px solid #000000;
          background-color: #0000ff;
      }

      #button:hover {
          background-color: #00ff00;
      }

      #button[pressed=true] {
          background-color: #ff0000;
      }
  </style>

  <script type="text/javascript"> 
      $(document).ready(function() {
          $('#button').click(function() {
            $(this).attr("pressed", $(this).attr("pressed") == "true" 
              ? "false" 
              : "true");
          });
      });
  </script>
 
  <div id="button">
  </div>
</body>
Yo para nada soy un experto en JavaScript + CSS pero por fortuna últimamente he podido aprender unos cuantos trucos que han sido de mucha utilidad. Una de las reglas más mencionadas por los que sí son expertos en este tema es la de siempre priorizar las reglas de estilos sobre el código en JavaScript. Todo lo que podamos hacer utilizando CSS será siempre bienvenido. Una muy buena fuente detallando las posibilidades del CSS es el documento del W3C sobre los selectores en CSS3 (como siempre, casi ninguno de los nuevos selectores funcionan en Internet Explorer 8). Les recomiendo a todos los que están relacionados con este mundo que no dejen de leerlo.

1 comment:

  1. yudielgc2/14/2011

    Good practice: siempre priorizar las reglas de estilos sobre el código en JavaScript.

    ReplyDelete

Note: Only a member of this blog may post a comment.