Ver índice de contenidos del libro

Capítulo 11. Eventos personalizados

11.1. Introducción a los eventos personalizados

Todos estamos familiarizados con los eventos básicos — click, mouseover, focus, blur, submit, etc. — que surgen a partir de la interacción del usuario con el navegador.

Los eventos personalizados permiten conocer el mundo de la programación orientada a eventos (en inglés event-driven programming). En este capítulo, se utilizará el sistema de eventos personalizados de jQuery para crear una simple aplicación de búsqueda en Twitter.

En un primer momento puede ser difícil entender el requisito de utilizar eventos personalizados, ya que los eventos convencionales permiten satisfacer todas las necesidades. Sin embargo, los eventos personalizados ofrecen una nueva forma de pensar la programación en JavaScript. En lugar de enfocarse en el elemento que ejecuta una acción, los eventos personalizados ponen la atención en el elemento en donde la acción va a ocurrir. Este concepto brinda varios beneficios:

  • Los comportamientos del elemento objetivo pueden ser ejecutados por diferentes elementos utilizando el mismo código.
  • Los comportamientos pueden ser ejecutados en múltiples, similares elementos objetivos a la vez.
  • Los comportamientos son asociados de forma más clara con el elemento objetivo, haciendo que el código sea más fácil de leer y mantener.

Un ejemplo es la mejor forma de explicar el asunto. Suponga que posee una lámpara incandescente en una habitación de una casa. La lámpara actualmente esta encendida. La misma es controlada por dos interruptores de tres posiciones y un clapper (interruptor activado por aplausos):

<div class="room" id="kitchen">
    <div class="lightbulb on"></div>
    <div class="switch"></div>
    <div class="switch"></div>
    <div class="clapper"></div>
</div>

Ejecutando el clapper o alguno de los interruptores, el estado de la lámpara cambia. A los interruptores o al clapper no le interesan si la lámpara está encendida o apagada, tan solo quieren cambiar su estado

Sin la utilización de eventos personalizados, es posible escribir la rutina de la siguiente manera:

$('.switch, .clapper').click(function() {
    var $light = $(this).parent().find('.lightbulb');
    if ($light.hasClass('on')) {
        $light.removeClass('on').addClass('off');
    } else {
        $light.removeClass('off').addClass('on');
    }
});

Por otro lado, utilizando eventos personalizados, el código queda así:

$('.lightbulb').bind('changeState', function(e) {
    var $light = $(this);
    if ($light.hasClass('on')) {
        $light.removeClass('on').addClass('off');
    } else {
        $light.removeClass('off').addClass('on');
    }
});
 
$('.switch, .clapper').click(function() {
    $(this).parent().find('.lightbulb').trigger('changeState');
});

Algo importante ha sucedido: el comportamiento de la lámpara se ha movido, antes estaba en los interruptores y en el clapper, ahora se encuentra en la misma lámpara.

También es posible hacer el ejemplo un poco más interesante. Suponga que se ha añadido otra habitación a la casa, junto con un interruptor general, como se muestra a continuación:

<div class="room" id="kitchen">
    <div class="lightbulb on"></div>
    <div class="switch"></div>
    <div class="switch"></div>
    <div class="clapper"></div>
</div>
<div class="room" id="bedroom">
    <div class="lightbulb on"></div>
    <div class="switch"></div>
    <div class="switch"></div>
    <div class="clapper"></div>
</div>
<div id="master_switch"></div>

Si existe alguna lámpara encendida en la casa, es posible apagarlas a través del interruptor general, de igual forma si existen luces apagadas, es posible prenderlas con dicho interruptor. Para realizar esta tarea, se agregan dos eventos personalizados más a la lámpara: turnOn y turnOff. A través de una lógica en el evento changeState se decide qué evento personalizado utilizar:

$('.lightbulb')
    .bind('changeState', function(e) {
        var $light = $(this);
        if ($light.hasClass('on')) {
            $light.trigger('turnOff');
        } else {
            $light.trigger('turnOn');
        }
    })
    .bind('turnOn', function(e) {
        $(this).removeClass('off').addClass('on');
    })
    .bind('turnOff', function(e) {
        $(this).removeClass('off').addClass('on');
    });
 
$('.switch, .clapper').click(function() {
    $(this).parent().find('.lightbulb').trigger('changeState');
});
 
$('#master_switch').click(function() {
    if ($('.lightbulb.on').length) {
        $('.lightbulb').trigger('turnOff');
    } else {
        $('.lightbulb').trigger('turnOn');
    }
});

Note como el comportamiento del interruptor general se ha vinculado al interruptor general mientras que el comportamiento de las lámparas pertenece a las lámparas.

Nota Si esta acostumbrado a la programación orientada a objetos, puede resultar útil pensar de los eventos personalizados como métodos de objetos. En términos generales, el objeto al que pertenece el método se crea a partir del selector jQuery. Vincular el evento personalizado changeState a todos los elementos $('.light') es similar a tener una clase llamada Light con un método changeState, y luego instanciar nuevos objetos Light por cada elemento.

Recapitulación: $.fn.bind y $.fn.trigger

En el mundo de los eventos personalizados, existen dos métodos importantes de jQuery: $.fn.bind y $.fn.trigger. En el capítulo dedicado a eventos se explicó la utilización de estos dos métodos para trabajar con eventos del usuario; en este capítulo es importante recordar 2 puntos:

  • El método $.fn.bind toma como argumentos un tipo de evento y una función controladora de evento. Opcionalmente, puede recibir información asociada al evento como segundo argumento, desplazando como tercer argumento a la función controladora de evento. Cualquier información pasada estará disponible a la función controladora a través de la propiedad data del objeto del evento. A su vez, la función controladora recibe el objeto del evento como primer argumento.
  • El método $.fn.trigger toma como argumentos el tipo de evento y opcionalmente, puede tomar un array con valores. Estos valores serán pasados a la función controladora de eventos como argumentos luego del objeto del evento.

A continuación se muestra un ejemplo de utilización de $.fn.bind y $.fn.trigger en donde se utiliza información personalizada en ambos casos:

$(document).bind('myCustomEvent', { foo : 'bar' }, function(e, arg1, arg2) {
    console.log(e.data.foo); // 'bar'
    console.log(arg1); // 'bim'
    console.log(arg2); // 'baz'
});
 
$(document).trigger('myCustomEvent', [ 'bim', 'baz' ]);