Scriptia Javascript y buenas prácticas en español Tue, 09 Mar 2010 00:36:14 +0000 http://wordpress.org/?v=2.6.3 en Keypress, apilado de eventos, control de valores y setTimeout /articulos/2009/12/keypress-apilado-de-eventos-control-de-valores-y-settimeout.html /articulos/2009/12/keypress-apilado-de-eventos-control-de-valores-y-settimeout.html#comments Tue, 01 Dec 2009 13:50:34 +0000 choan /?p=187
  • Referencia de eventos DOM en la Wikipedia Eventos DOM --estándares y propietarios-- correctamente descritos en DOM events...
  • Eventos en jQuery Presentamos un repaso en profundidad a los métodos de jQuery...
  • onbeforeunload: tiende una mano al usuario ¿Alguna vez has cerrado una ventana cuando tenías un formulario...
  • Related posts brought to you by Yet Another Related Posts Plugin.]]>
    Algo que me han preguntado hoy:

    Choan, cariño mío, guapetón de mi alma… tengo dos campos en un formulario y quiero que uno refleje los cambios en el otro… y controlando keypress no tengo el valor actualizado

    Pues no, no lo tienes. Solución: controlar la pila de ejecución. El truco: usar un timeout para que el método que actualiza los valores se ejecute después de acabar con la gestión de eventos. Algo así:

    // en el manejador del evento keypress
    setTimeout(updateValue, 0); // <-- la clave está en el cero

    Ahora bien, lo de recoger el valor actualizado basándonos en keypress no es suficiente, porque hay mil maneras de rellenar el campo sin pulsar una tecla (copipegar por menús, lectores de códigos de barras, yo-que-sés…)

    Así que mi consejo es defenderse controlando también el evento blur. Lo pongo en términos de jQuery, suponiendo que el campo que controlamos lleva el identificador source y el de destino es conocido como destination.

    (function($) {
    
      var init_copy_values = function() {
        $('#source').bind('keypress blur', function(e) {
          if (e.type == 'keypress') setTimeout(updateValue, 0);
          else updateValue();
        });
      },
      updateValue = function() {
        $('#destination').val($('#source').val());
      };
    
      $(init_copy_values);
    
    }(jQuery));

    Pues nada, ya está, un post sin insultar a nadie.

    ]]>
    /articulos/2009/12/keypress-apilado-de-eventos-control-de-valores-y-settimeout.html/feed
    Vilaweb y el desarrollador tonto del haba /articulos/2009/11/vilaweb-y-el-desarrollador-tonto-del-haba.html /articulos/2009/11/vilaweb-y-el-desarrollador-tonto-del-haba.html#comments Tue, 24 Nov 2009 11:52:18 +0000 choan /?p=184 Me cuentan por ahí que hoy se ha lanzado la nueva versión de Vilaweb. El culmen de las buenas prácticas en marcado HTML (sí, es sarcasmo).

    Atención al siguiente fragmento, extraído del tal sitio el 24 de noviembre de 2009:

    <div class="zona-imatge">
      <img alt="Detingut el batlle de Polop per la mort del seu predecessor&lt;br/&gt;" src="/media/portada/81/1259046000.jpg" width="299" height="254"/>
      <div class="params" style="display: none">
        <div class="w">299</div>
        <div class="h">254</div>
        <div class="file">/media/portada/81/1259046000.jpg</div>
      </div>
    </div>

    Mi comentario (versión breve): imbécil.

    Mi comentario (versión extendida): eres tan tonto que cada vez que te documentas, en lugar de mejorar empeoras. ¿Qué demonios hacen esos «parámetros» metiendo ruido en el HTML? ¿Se te ha ocurrido, por ventura, aplicar un tipo de «no intrusividad» parida por tu genial cerebro?

    Supongo que estarás pensando: ya te vale, Choan, despotricas y no dices nada. Así es, en efecto. Si sabes lo que está mal (fatal, punible con muerte) en ese pedazo de código no hace falta que te cuente nada. Y si no lo sabes, sigue en tu santa ignorancia, busca una multinacional en la que triunfar como un programador mediocre y haz que tu madre se sienta orgullosa de ti. Cretino.

    (Hay otras perlas, pero la que más ganas me ha dado de ponerme faltón ha sido esta. Y sí, el diseño también me parece una mierda.)

    ]]>
    /articulos/2009/11/vilaweb-y-el-desarrollador-tonto-del-haba.html/feed
    Número de días en un mes /articulos/2009/03/numero-de-dias-en-un-mes.html /articulos/2009/03/numero-de-dias-en-un-mes.html#comments Thu, 05 Mar 2009 17:09:07 +0000 choan /?p=166 Ver una vaca encima de un cobertizo no es nada comparado con las cosas que uno se encuentra en los scripts que corren por estos mundos de dios.

    Uno de los campos en los que cualquier mindundi es capaz de aplicar al máximo su desconocimiento de javascript es el del cálculo de días en un mes. Utilizar una docena de condicionales es de flojos.

    La máxima: cuando tengas que echar cuentas, sobre cualquier cosa, utiliza las herramientas del lenguaje. No hagas cuentas de la vieja.

    El código. Función que recibe un mes (en notación numérica humana) y un año (opcional, si no se recibe tomamos el año actual) y retorna el número de días en el mes. Ningún condicional fue dañado durante el experimento.

    function daysInMonth(humanMonth, year) {
      return new Date(year || new Date().getFullYear(), humanMonth, 0).getDate();
    }
    daysInMonth(2, 2009); // 28
    daysInMonth(2, 2008); // 29

    Las claves:

    • ECMAScript maneja internamente las fechas como datos numéricos (véase Tiempo Unix)
    • en ECMAScript, los meses se numeran del 0 al 11 y los días de 1 a N. Nosotros alimentamos la función con un mes humano. Así que la fecha que se crea (d) corresponderá al día 0 del mes siguiente al que buscamos. El algoritmo de creación de fechas es tolerante (básicamente suma lo que le demos y obtiene un entero, así que el día 0 de un mes es siempre el último día del mes anterior).

    En fin, que todo esto va de que la máquina es más lista que tú y tus nudillos. Y que todos los blogueros juntos, incluido el menda.

    Adendum

    Podemos usar una estratagema similar para saber si el año es bisiesto:

    function isLeapYear(year) {
      return new Date(year, 0, 366).getFullYear() == year;
    }
    ]]>
    /articulos/2009/03/numero-de-dias-en-un-mes.html/feed
    Trabajando con jQuery: timeline arrastrable, del espaguetismo al plugin /articulos/2008/10/jquery-del-espagueti-al-plugin.html /articulos/2008/10/jquery-del-espagueti-al-plugin.html#comments Mon, 20 Oct 2008 18:44:20 +0000 choan /articulos/2008/10/jquery-del-espagueti-al-plugin.html
  • Carrusel Continua la lluvia de plugins para jQuery. Esta vez nos...
  • Un patrón de desarrollo de plugins para jQuery Mike Alsup, autor de jQuery form plugin y otras delicias,...
  • Eventos en jQuery 1.2 Días ha que escribí una nota sobre los eventos en...
  • Related posts brought to you by Yet Another Related Posts Plugin.]]>
    En el capítulo de hoy veremos cómo reconvertir un código espagueti en un bonito plugin reutilizable.

    Objetivo del widget: permitir desplazar horizontalmente mediante arrastre (pincho, arrastro, suelto) un bloque de contenido. Si tienes prisa por ver en qué acaba la cosa, ve directamente a la demo.

    Elementos: un contenedor (con overflow: auto) y su contenido. Para nuestro ejemplo:

    <div id="#timeline">
      <ul>
        <li>...</li>
        <li>...</li>
      </ul>
    </div>

    Con unos estilos tal que:

    #timeline {
     width: 600px;
     height: 400px;
     overflow: auto;
    }
    
    #timeline ul {
     width: 1000px;
     margin: 0;
     padding: 0;
    }
    
    #timeline li {
     display: block;
     width: 200px;
     padding: 10px;
     margin: 0;
     float: left;
    }

    Sobre estas piedras edificaremos nuestra iglesia. Veamos paso a paso el código necesario para hacer que la cosa se mueva. Pondremos en marcha la maquinaria cuando pinchemos sobre el contenedor:

    $('#timeline')
      .bind('mousedown', function(e) {
        // aquí vendrá nuestro código
      });

    A fin de evitar el uso de variables globales, utilizarmos data, un método de jQuery que nos permite mantener relaciones entre valores y elementos DOM. Vamos a mantener los siguientes datos: posición actual del scroll, elemento al que afecta y posición del puntero en el momento del mousedown.

    $(document)
      .data('timeline', {
        element: this,
        scroll: this.scrollLeft,
        x : e.clientX
      });

    Observa que estamos conservando los datos en document. En cada momento existirá un máximo de un elemento en desplazamiento.

    Bien, ha empezado la acción… pero todavía no estamos siguiendo los movimientos del puntero. Asignemos un par de manejadores para los eventos mousemove y mouseup de document.

    ¿Por qué para document y no para el contenedor con el que estamos tratando? Aunque el puntero salga del contenedor durante el arrastre, queremos seguir desplazando el contenido. Y si saliera y no controláramos mouseup a nivel de documento, nuestro script nunca abandonaría el modo “estamos arrastrando cositas”.

    Como somos chulos y modernos, aprovecharemos los eventos.con-espacio-de-nombres que jQuery gentilmente pone a nuestra disposición. Para mousemove, recuperamos los datos que hemos almacenado en mousedown y modificamos la propiedad scrollLeft del contenedor sumándole la diferencia entre la posición actual del ratón y la almacenada. En mouseup, limpiamos los datos almacenados y eliminamos los manejadores de eventos del espacio timeline asignados a document.

    jQuery(document)
      .bind('mousemove.timeline', function(e) {
        var data = jQuery(this).data('timeline');
        data.element.scrollLeft = data.scroll + data.x - e.clientX;
      })
      .bind('mouseup.timeline', function(e) {
        jQuery(this)
          .removeData('timeline')
          .unbind('.timeline');
      });

    Para rematar, cancelaremos la acción por defecto de mousedown para no seleccionar texto mientras arrastramos:

    e.preventDefault();

    Poniéndolo todo junto y dentro de una llamada a ready, tenemos la cosita funcionando:

    jQuery(document).ready(function() {
    
      jQuery('#timeline')
        .bind('mousedown', function(e) {
    
          jQuery(document)
            .data('timeline', {
              element: this,
              scroll: this.scrollLeft,
              x : e.clientX
            })
            .bind('mousemove.timeline', function(e) {
              var data = jQuery(this).data('timeline');
              data.element.scrollLeft = data.scroll + data.x - e.clientX;
            })
            .bind('mouseup.timeline', function(e) {
              jQuery(this)
                .removeData('timeline')
                .unbind('.timeline');
            });
    
          e.preventDefault();
    
        });
    });

    ¡Genial! Con esto ya tenemos el comportamiento deseado, pero… no parece muy reutilizable. Pluguinifiquemos.

    Construyendo un plugin

    Cuando invocamos la función jQuery (o su alias habitual, $), recuperamos un objeto. Dicho objeto tiene un prototipo. Para añadir métodos al objeto devuelto por jQuery, los añadimos a su prototipo. Así que algo tal que:

    jQuery.prototype.miMetodo = function() {
    
    };

    Ya nos serviría para ir tirando. Pero hay un idioma más jotacueriesco para decir lo mismo. De paso añadamos un return this a nuestro método para permitir el encadenamiento de llamadas:

    jQuery.extend(jQuery.fn, {
      miMetodo : function() {
        return this;
      }
    });

    Con esto ya podemos darnos el gustazo de escribir:

    jQuery('#miCosa')
      .miMetodo()
      .etcetera();

    Pero escribir jQuery (que es lo que debemos hacer si queremos asegurarnos de que nuestro plugin es compatible con otras bibliotecas) una y otra vez es escribir mucho. Vamos a meter todo el código de nuestro plugin dentro de una función anónima que nos permita tener un alias seguro para jQuery.

    (function($) {
    
    })(jQuery);

    Y vamos a rellenarlo poquito a poco.

    (function($) {
      $.extend($.fn, {
        timeline : function() {
          this
            .bind('mousedown', handleMouseDown);
          return this;
        }
      });
    
      function handleMouseDown(e) {
        // bla bla
      }
    
    })(jQuery);

    Fíjate: extraemos el código de manejo del evento mousedown. ¿Por qué? No necesitamos ningún dato local y por tanto no necesitamos del cierre funcional para acceder a dicho dato (ni generar la función al vuelo para cada widget). Haremos lo mismo para los manejadores de mousemove y mouseup:

    (function($) {
      $.extend($.fn, {
        timeline : function() {
          this
            .bind('mousedown', handleMouseDown);
          return this;
        }
      });
    
      function handleMouseDown(e) {
        $(document)
          .data('timeline', {
            element: this,
            scroll: this.scrollLeft,
            x : e.clientX
          })
          .bind('mousemove.timeline', handleMouseMove)
          .bind('mouseup.timeline', handleMouseUp);
        e.preventDefault();
      }
    
      function handleMouseMove(e) {
        var data = $(this).data('timeline');
        data.element.scrollLeft = data.scroll + data.x - e.clientX;
      }
    
      function handleMouseUp(e) {
        $(this)
          .removeData('timeline')
          .unbind('.timeline');
      }
    
    })(jQuery);

    Con esto nuestro código espagueti ya parece algo más ordenadito. Pero un plugin sin opciones sabe a poco. Vamos a considerar la posibilidad de configurar la proporción de desplazamiento/arrastre y de decidir si ocultamos o no la barra de scroll nativa. Por partes:

    $.extend($.fn, {
      timeline : function(opts) {
        var config = $.extend({
          speed: 1.5,
          accesible: true
        }, opts || {});
    
        this
          .bind('mousedown', config, handleMouseDown);
    
        if (!config.accesible) {
          this.css('overflow', 'hidden');
        }
    
        return this;
      }
    });

    Ahora el método timeline acepta un argumento que corresponde a un objeto de configuración. Utilizamos jQuery.extend para aplicar los valores sobre una configuración por defecto. Y al asignar el manejador para mousedown lo pasamos como argumento. Si esto resulta nuevo para ti, lo que te falta saber es que con una asignación de este tipo, el manejador recibirá estos datos extra en la propiedad data del objeto que representa el evento. Modifiquemos el manejador para transmitir la configuración:

    function handleMouseDown(e) {
      $(document)
        .data('timeline', {
          element: this,
          scroll: this.scrollLeft,
          x : e.clientX,
          config : e.data
        })
        .bind('mousemove.timeline', handleMouseMove)
        .bind('mouseup.timeline', handleMouseUp);
      e.preventDefault();
    }

    Utilicemos la configuración en handleMouseMove:

    function handleMouseMove(e) {
      var data = $(this).data('timeline');
      data.element.scrollLeft = data.scroll + (data.x - e.clientX) * data.config.speed;
    }

    Y listo. Ya tenemos un plugin bastante decente. Para ponerlo en marcha:

    jQuery(document)
      .ready(function() {
        $('el.selector-de-dios')
          .timeline({ speed: 2, accesible: false })
      });

    Bola extra 1: configuración por defecto

    Si nuestro usuario siempre va a preferir la opción no accesible del plugin, no por ser idiota (u obedecer órdenes) le vamos a obligar a pasar la configuración una y otra vez. Apañemos:

    $.extend($.fn, {
      timeline : function(opts) {
        var config = $.extend({}, $.fn.timeline.defaults, opts || {});
        // ...
        return this;
      }
    });
    
    $.fn.timeline.defaults = {
      speed: 1.5,
      accesible: true
    };

    De esta manera permitimos al usuario configurar las opciones de manera global manipulando jQuery.fn.timeline.defaults a su antojo.

    Bola extra 2: mousewheel

    El plugin mousewheel permite controlar los movimientos de la ruedita del ratón. Aprovechémoslo:

    $.extend($.fn, {
        timeline : function(opts) {
          // ...
          if ($.fn.mousewheel) {
            this
              .mousewheel(function(e, delta) {
                this.scrollLeft -= delta * config.mousewheelSpeed;
                e.preventDefault();
              });
          }
          return this;
        }
      });

    Puedes probar la combinación de todos estos elementos en la demo correspondiente.

    Coda

    Choan no inventa nada nuevo, lo tratado aquí viene a ser una combinación de mis conocimientos, gustos y experiencia y lo expuesto en estos dos artículos:

    Ten presente que la arquitectura propuesta para este plugin no siempre es la más adecuada —ya hablaremos de ello— y que el plugin de ejemplo no está publicado ni oficial ni extra oficialmente y nadie te dará soporte.

    ]]>
    /articulos/2008/10/jquery-del-espagueti-al-plugin.html/feed
    jShoulda y JsUnitTest, pruebas unitarias en JavaScript al estilo Shoulda /al-margen/2008/10/jshoulda-y-jsunit-test-unit-testing-javascript.html /al-margen/2008/10/jshoulda-y-jsunit-test-unit-testing-javascript.html#comments Sat, 11 Oct 2008 16:57:45 +0000 choan /al-margen/2008/10/jshoulda-y-jsunit-test-unit-testing-javascript.html
  • Pruebas unitarias con QUnit Las pruebas unitarias (unit testing) son necesarias y convenientes, ya...
  • Related posts brought to you by Yet Another Related Posts Plugin.]]>
    Hablábamos el otro día de QUnit, hoy hablaremos de JsUnitTest –otro sistema de testing– y de jShoulda, una capa de abstracción que un servidor ha moldeado con sus propias manos y a imagen y semejanza de Shoulda.

    Principiemos hablando de JsUnitTest, un port sin dependencias del sistema de testing desarrollado para probar prototype y script.aculo.us.

    ¿Qué necesitamos?

    • jsunittest.js, la biblioteca de testing;
    • un documento HTML que la referencie.

    Algo como esto bastará:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
    <head>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
      <title>Minimal JsUnitTest</title>
      <script src="/js/jsunittest.js" type="text/javascript"></script>
      <link rel="stylesheet" href="/css/unittest.css" type="text/css" media="screen"/>
    </head>
    
    <body>
      <h1>Minimal JsUnitTest</h1>
    
      <div id="testlog">
        Los resultados de las pruebas se muestran, por defecto
        en el elemento con id=testlog
      </div>
    
    <script type="text/javascript">
      // aquí irá el código de nuestras pruebas,
      // también podemos hacer referencia a un script externo
    </script>
    
    </body>
    </html>

    Cómo escribir tests con JsUnitTest

    Para definir y ejecutar las pruebas, creamos una instancia de Test.Unit.Runner:

    new Test.Unit.Runner({});

    En el objeto de configuración que pasamos como argumento, definimos como propiedades las pruebas a realizar:

    new Test.Unit.Runner({
      testNombreDelTest : function() {
        this.assert(true);
      },
      testOtroTest : function() {
        this.assert(true);
      }
    });

    Los nombres de las propiedades correspondientes a pruebas deben comenzar por “test”. Si deseamos ejecutar funciones de preparación y limpieza antes y después de cada una de las pruebas, utilizamos las propiedades setup y teardown para definirlas:

    new Test.Unit.Runner({
      setup : function() {
        this.foo = 1;
      },
      testNombreDelTest : function() {
        this.assert(true);
      },
      testFoo : function() {
        this.assertEqual(1, this.foo);
      }
    });

    Observa: tanto las funciones de preparación como las de prueba se ejecutan en el contexto de una instancia de Test.Unit.Testcase. Las posibles aserciones son métodos de dicha instancia (por eso usamos this). También podemos utilizar this para mantener variables que vayamos a utilizar en nuestras pruebas.

    La mayor parte de los métodos asertivos aceptan tres parámetros: el valor esperado, el valor real y (opcionalmente) un mensaje que el sistema nos mostrará en caso de error.

    He aquí una lista deliberadamente incompleta de los métodos de aserción:

    • assert
    • assertEqual
    • assertNotEqual
    • assertEnumEqual
    • assertEnumNotEqual
    • assertHashEqual
    • assertHashNotEqual
    • assertIdentical
    • assertNotIdentical
    • assertNull
    • assertNotNull
    • assertUndefined
    • assertNotUndefined

    Comprueban lo que su nombre parece indicar. Un detalle importante: en JavaScript, dos arrays con el mismo contenido (o dos objetos-usados-como-hashes) no pueden compararse mediante igualdad. Por tanto, si quieres comparar dos arrays necesitarás utilizar assertEnumEqual. Y para comparar dos objetos, assertHashEqual.

    Automatización

    drnic, el humano detrás de JsUnitTest, es también padre de newjs, un gem (si no sabes qué es un gem, ignora esta sección hasta que lo averigües) pensada para facilitar el desarrollo de bibliotecas JavaScript. Sus virtudes:

    • incluye JsUnitTest;
    • simplifica la creación de nuevos tests;
    • automatiza la ejecución de los tests.

    Sin entrar en detalles. O lo ves, o no lo ves:

    $ newjs milib
          create
          create  config
          create  lib
          create  src
          create  script
          create  tasks
          create  test/assets
          create  test/unit
          create  test/functional
          create  test/assets/unittest.css
          create  test/assets/jsunittest.js
          # etcetera
      dependency  install_rubigen_scripts
          exists    script
          create    script/generate
          create    script/destroy
    $ cd milib
    $ script/generate unit_test basic
          exists  test/unit
          create  test/unit/basic_test.html
    $ rake test_units
    (in /Users/choan/Current/tirame/milib)
    
    Started tests in Safari
    .
    Finished in 2 seconds.
    1 tests, 1 assertions, 0 failures, 0 errors
    
    Started tests in Firefox
    .
    Finished in 1 seconds.
    1 tests, 1 assertions, 0 failures, 0 errors
    
    Skipping Internet Explorer, not supported on this OS
    
    Skipping Konqueror, not supported on this OS
    
    Started tests in Opera
    .
    Finished in 3 seconds.
    1 tests, 1 assertions, 0 failures, 0 errors

    Sí, amigo mío, mediante la ejecución de una tarea rake hemos ejecutado las pruebas en cada uno de los navegadores disponibles en nuestro sistema y hemos recogido los resultados. Algo que Chuck Norris no es capaz de hacer.

    jShoulda

    Pero vayamos con jShoulda, que yo he venido aquí a hablar de mi libro.

    jShoulda es una capa de abstracción para JsUnitTest que permite escribir nuestros tests al más puro estilo Shoulda. Algo así:

    context('A context', {},
      should('run a test', function() {
        this.assert('Yay!');
      }),
    )();

    ¿Y qué ventaja tiene esto? Para un test vulgar, ninguna, francamente. Pero si tus pruebas requieren de inicializaciones complejas, la cosa se pone chula.

    context('A context', {
      setup: function() {
        this.foo = 1;
      }
      },
      should('run its setup function', function() {
        this.assertEqual(1, this.foo);
      }),
      context('which is a "nested" context', {
        setup: function() {
          this.foo +=1;
        }
        },
        should('run both setup functions', function() {
          this.assertEqual(2, this.foo);
        })
      )
    )();
    

    Para utilizar jShoulda necesitas… jshoulda.js. Y un documento HTML como el anterior, al que añadirás la referencia a la biblioteca.

    Pero para probarlo no necesitas descargar nada. Pásate por la página del proyecto. Los resultados de pruebas que encontrarás en ella (suponiendo que la visites con JavaScript activado) corresponden al test de ejemplo ejecutado en vivo. Si quieres juguetear, doblecliquea en el código, modifícalo a tu gusto y pulsa el botón. Sugerencia: haz fallar las pruebas. Mola.

    Si estás interesado en jShoulda y tu inglés no es ni mejor ni peor que el mío, lee el tutorial de jShoulda, suscríbete a la lista de correo y, si das con algún bug, arréglalo.

    ]]>
    /al-margen/2008/10/jshoulda-y-jsunit-test-unit-testing-javascript.html/feed
    Pruebas unitarias con QUnit /articulos/2008/07/pruebas-unitarias-con-qunit.html /articulos/2008/07/pruebas-unitarias-con-qunit.html#comments Sat, 26 Jul 2008 19:15:40 +0000 choan /articulos/2008/07/pruebas-unitarias-con-qunit.html
  • Comenzando con jQuery Si todavía no conoces jQuery esta guía escrita por Jörn...
  • jQuery 1.0 jQuery 1.0 está en la calle....
  • Carrusel Continua la lluvia de plugins para jQuery. Esta vez nos...
  • Related posts brought to you by Yet Another Related Posts Plugin.]]>
    Las pruebas unitarias (unit testing) son necesarias y convenientes, ya programes en Ruby, en PHP, en JavaScript o en Cuenca. En esta notita veremos cómo utilizar QUnit –la biblioteca creada para el testeo del núcleo de jQuery– para testear nuestros propios proyectos.

    El documento base

    Comencemos pues, por descargar QUnit y preparar un documento HTML con la siguiente estructura:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html>
    <head>
      <title>My Test Suite</title>
      <link rel="Stylesheet" media="screen" href="ruta/a/testsuite.css" />
      <script type="text/javascript" src="ruta/a/jquery.js"></script>
      <script type="text/javascript" src="ruta/a/testrunner.js"></script>
    
      <style type="text/css" media="screen">
      /* <![CDATA[ */
        #main {
          display: none;
        }
      /* ]]> */
      </style>
    </head>
    
    <body>
      <h1>My Test Suite</h1>
      <h2 id="banner"></h2>
      <h2 id="userAgent"></h2>
    
      <div id="main">
      </div>
    
      <ol id="tests"></ol>
    </body>
    </html>

    El elemento con id="banner" es utilizado por QUnit para mostrar el resultado global de la ejecución de las pruebas. En id="userAgent" se muestra información relativa al navegador utilizado. La lista y el detalle de la ejecución de cada prueba se genera en id="tests".

    En caso de disponer de Firebug, QUnit volcará abundante y útil información en la consola.

    Si necesitamos un fragmento de HTML para la ejecución de las pruebas, lo incluiremos en id="main". Ojo: antes de la ejecución de cada test se devuelve el contenido de este elemento a su estado inicial, esto es, no se mantienen los eventos asignados ni las modificaciones realizadas sobre el árbol en tests anteriores.

    Nuestro primer test

    Al ajo. La ejecución de una prueba implica una llamada a la función test, pasando como parámetros una cadena identificativa y la referencia a una función en la que se realizan las comprobaciones (aserciones).

    La más sencilla de las comprobaciones es ok. Si el parámetro que recibe evalúa a true, el resultado se considera satisfactorio.

    Poniendo los dos últimos párrafos en código:

    test('Prueba minimalista', function(){
      ok(1, 'mi mensajito');
    });

    Incorpora esas líneas a tu documento de pruebas (dentro de un elemento SCRIPT) y échale un vistazo en el navegador. ¿Todo bien?

    Comprobaciones varias

    Por supuesto disponemos de otros métodos de comprobación de resultados, estos son los fundamentales:

    equals(unArgumento, otroArgumento, mensaje)
    Comprueba la igualdad de dos argumentos primitivos.
    isSet(unArray, otroArray, mensaje)
    Comprueba que dos arrays tienen el mismo contenido.
    isObject(unObjeto, otroObjeto, mensaje)
    Comprueba que dos objetos tienen los mismos valores asignados a las mismas propiedades.

    Módulos

    Si utilizamos el mismo documento para probar distintas partes de nuestra biblioteca podemos utilizar la función module para dar nombre a las secciones de la suite.

    module('Widget de votaciones');
    test('tras la inicialización'), function() {
      // inicializamos un widget, etc.
      equals($('a.rate', widget).length, 5, 'contiene 5 estrellicas');
    });
    
    test('etcetera', function() {
      // otro test por aquí
    });

    Expectaciones

    Todo lo que puede fallar, falla (de hecho, si practicas el desarrollo basado en pruebas, lo primero que debería hacer cada test es fallar). Para indicar el total de comprobaciones que se realizarán en una prueba, utilizamos la función expect.

    module('expectaciones');
    test('un test', function() {
      expect(3);
      ok(1, 'una');
      equals(1 + 1, 2, 'dos');
      equals(calculoCasiImposible(), 42, 'y tres');
    });

    De esta manera, si la ejecución de calculoCasiImposible (la tercera comprobación) falla estrepitosamente, la prueba no se da por buena.

    Asincronía y pausas

    Si realizamos peticiones asíncronas en nuestros tests, necesitaremos detener la ejecución de la serie y reanudarla cuando convenga. Los métodos stop y start hacen lo que su nombre parece indicar.

    module('asincronía');
    test('con pausa', function() {
      expect(2);
      ok(1, 'una');
      function my_callback(response, status) {
        start();
        equals(response, '1', 'el servidor devuelve lo que toca');
      }
      // detenemos la ejecución de las pruebas
      stop();
      // lanzamos una petición Ajax
      $.get(some_url, my_callback);
    });
    

    Si quieres empaparte de pruebas escritas con y para QUnit, lo mejor es que descargues una una release completa de jQuery y bucees en sus entrañas.

    Y esto ha sido todo. La semana que viene más y más sexy (si cabe).

    ]]>
    /articulos/2008/07/pruebas-unitarias-con-qunit.html/feed
    Eventos en jQuery 1.2 /articulos/2008/07/eventos-en-jquery-12.html /articulos/2008/07/eventos-en-jquery-12.html#comments Thu, 17 Jul 2008 00:45:58 +0000 choan /articulos/2008/07/eventos-en-jquery-12.html
  • Eventos en jQuery Presentamos un repaso en profundidad a los métodos de jQuery...
  • Un patrón de desarrollo de plugins para jQuery Mike Alsup, autor de jQuery form plugin y otras delicias,...
  • Ajax, eventos y jQuery Un «problema» con el que todo novato de la programación...
  • Related posts brought to you by Yet Another Related Posts Plugin.]]>
    Días ha que escribí una nota sobre los eventos en jQuery. Y hora es de ampliar dicho artículo con las novedades que la serie 1.2 de jQuery añade al respecto.

    Lo de siempre

    Recordemos las bases de la asignación y el manejo de eventos en jQuery. La forma básica de asignar un manejador es la siguiente:

    $('un.selector')
      .bind('click', unaFuncion);

    El manejador (unaFuncion) recibe como parámetro el objeto que representa el evento (normalizado para tener acceso común a sus propiedades en los distintos navegadores). La función se ejecuta en el contexto del elemento al que se ha asignado el manejador.

    function unaFuncion(e) {
    	console.log('Contexto', this);
    	console.log('Evento', e);
    }
    
    $('un.selector')
    	.bind('click', unaFuncion);

    Lo nuevo

    Con jQuery 1.2 podemos asignar un manejador para varios eventos con una sola llamada a bind(). Utilizamos para ello una lista de nombres de evento separados por espacios. Observa que en el manejador tomamos una decisión basada en la propiedad type del objeto del evento.

    function handle(e) {
      var $label = $(this).prev();
      if (e.type == 'focus') {
        $label.hide();
      }
      else if (this.value == '') {
        $label.show();
      }
    }
    
    $('#login input')
      .filter('[type=text], [type=password]')
        .bind('focus blur', handle);

    Otra novedad interesante es la pseudonombrespaciación de eventos, que se puede practicar utilizando el nombre del evento seguido de un punto y un identificador arbitrario al asignar el manejador.

    Y esto, ¿para qué? Para facilitar la eliminación de grupos de manejadores.

    // en algún lugar del código de un drag'n'drop
    $(document)
      .bind('mousemove.rock_n_roll', some_handler)
      .bind('mouseup.rock_n_roll', mouseup_handler);
    
    // en el código de finalización del arrastre
    $(document)
      .unbind('.rock_n_roll');

    Y algo que no es exactamente nuevo pero mola todo y se conoce poco: podemos pasar un objeto de datos en la asignación. El manejador lo recibirá en la propiedad data del evento.

    function handle_click(e) {
      alert(e.data.foo);
    }
    
    $.fn.somePlugin(opts) {
      var settings = $.extend({
        foo: 'bar'
      });
      this.bind('click', settings, handle_click);
      return this;
    }
    
    // mostrará "bar" al activar los enlaces
    // con clase "tigre"
    $('a.tigre').somePlugin();
    
    // mostrará "tolo" al activar los enlaces
    // con clase "leon"
    $('a.leon').somePlugin({ foo: 'tolo' });

    Por último, se incorpora a la API el método triggerHandler que complementa a trigger. La diferencia: triggerHandler dispara los manejadores asignados al evento pero no ejecuta las acciones por defecto del navegador.

    Para más y mejor información y, de paso, practicar el inglés: Release: jQuery 1.2/Events.

    ]]>
    /articulos/2008/07/eventos-en-jquery-12.html/feed
    Ajax, eventos y jQuery /articulos/2008/06/ajax-eventos-y-jquery.html /articulos/2008/06/ajax-eventos-y-jquery.html#comments Wed, 18 Jun 2008 22:38:44 +0000 choan /articulos/2008/06/ajax-eventos-y-jquery.html
  • Eventos en jQuery Presentamos un repaso en profundidad a los métodos de jQuery...
  • Eventos en jQuery 1.2 Días ha que escribí una nota sobre los eventos en...
  • Comenzando con jQuery Si todavía no conoces jQuery esta guía escrita por Jörn...
  • Related posts brought to you by Yet Another Related Posts Plugin.]]>
    Un «problema» con el que todo novato de la programación con jQuery se encuentra tarde o temprano (y las listas de correo lo demuestran) es que el contenido cargado (o generado) dinámicamente no dispara los manejadores de eventos asignados en $(document).ready.

    Y, hoygan, esto es de lo más normal.

    Si repasamos nuestra forma habitual de trabajar con jQuery, y reflexionamos un poquito…

    $(document).ready(function() {
      $('selecciono.algo')
        .bind('click', hazCosasBonitas);
    });

    … recordaremos que estamos utilizando $(document).ready porque no podemos seleccionar elementos que no existen. De ahí que tengamos que esperar a disponer de un árbol DOM completo. De ahí que los manejadores de eventos no afecten a elementos nuevos.

    ¿Entonces?

    Opciones las hay adecuadas a unos casos y a otros. La más sencilla, para casos sencillos, es inicializar los contenidos nuevos una vez los hayamos insertado en el documento.

    Necesitamos, pues, una función que nos permita seleccionar y actuar dentro de un contexto. Recuerda que jQuery provee de selecciones basadas en contexto. Si utilizas

    $('a');

    seleccionarás todos los enlaces del documento. Pero si usas

    $('a', unElementoSelecto);

    seleccionarás los enlaces descendientes de unElementoSelecto.

    Por tanto, podemos crear nuestra función de inicialización según contexto como sigue.

    function initLinks(context) {
      $('a', context)
        .bind('click', hazCosasBonitas)
    }

    Que ejecutaremos desde nuestro manejador para $(document).ready, pasando el documento como contexto de búsqueda:

    $(document).ready(function() {
      initLinks(document);
    });

    Así que estamos como al principio… pero mucho más cerca del final. Lo único que nos queda por hacer es ejecutar initLinks cuando recuperemos nuevo contenido. Si usamos load, el más simplón de los métodos Ajax de jQuery, basta con usar una función de callback:

    function loadNewContent() {
      $('#holder').load('ajax-content.html #response p', function() {
        // `this` apunta al elemento que acabamos de rellenar
        initLinks(this);
      });
    }

    Y esta es la idea básica, que he reflejado en esta pequeña demo en latín.

    Por supuesto, hay otras opciones. Si eres de los que para clavar un clavo usa un plugin, échale un vistazo a LiveQuery; si tienes un serio trasiego de elementos vivos, mejor apostar por la delegación de eventos. Pero esas son otras historias que contaré a su debido momento.

    ]]>
    /articulos/2008/06/ajax-eventos-y-jquery.html/feed
    Taller de jQuery en The Cocktail el 29 de mayo /al-margen/2008/05/taller-de-jquery-en-the-cocktail-el-29-de-mayo.html /al-margen/2008/05/taller-de-jquery-en-the-cocktail-el-29-de-mayo.html#comments Mon, 19 May 2008 14:33:43 +0000 choan /al-margen/2008/05/taller-de-jquery-en-the-cocktail-el-29-de-mayo.html
  • Comenzando con jQuery Si todavía no conoces jQuery esta guía escrita por Jörn...
  • jQuery 1.0 jQuery 1.0 está en la calle....
  • Carrusel Continua la lluvia de plugins para jQuery. Esta vez nos...
  • Related posts brought to you by Yet Another Related Posts Plugin.]]>
    Los muchachos de The Cocktail me han invitado a impartir un taller de jQuery en The Cocktail Academy.

    Será el jueves 29 de mayo de 2008, a partir de las las 19.30h en los locales de Salamanca, 17 (Madrid). Si piensas asistir (es gratis), inscríbete cuanto antes en el wiki de los talleres.

    Una pista de por donde irán los tiros. Hablaremos de:

    • selección de elementos, filtrado, etc.;
    • eventos a fondo, incluyendo delegación de eventos y sus ventajas;
    • navegación por el DOM;
    • algún que otro truquito de lo más chachipiruli.

    Y por supuesto, de algunas razones para usar (o no usar) jQuery.

    Después del taller, estáis todos invitados a pagarme unas cañas.

    ]]>
    /al-margen/2008/05/taller-de-jquery-en-the-cocktail-el-29-de-mayo.html/feed
    Embellecedor de código javascript /al-margen/2007/11/embellecedor-de-codigo-javascript.html /al-margen/2007/11/embellecedor-de-codigo-javascript.html#comments Thu, 22 Nov 2007 12:02:10 +0000 choan /al-margen/2007/11/embellecedor-de-codigo-javascript.html
  • JavaScript Shell Una herramienta que uso a diario. Tan pequeña como un...
  • De cómo comprimir ficheros javascript Ahora que javascript comienza a perder la fama de lenguaje...
  • Herramientas de desarrollo web para Internet Explorer John Hrvatin presenta en IE Blog una serie de herramientas...
  • Related posts brought to you by Yet Another Related Posts Plugin.]]>
    ¿Diste con un código que te gustaría estudiar y está compactado en una línea? Beautify Javascript reformatea cualquier cosa para convertirla en algo legible.

    Visto en Sentido web.

    ]]>
    /al-margen/2007/11/embellecedor-de-codigo-javascript.html/feed