jQuery: el mal uso de return false

19 Abr 2011

Hace ya algún tiempo, Doug Neiner escribía un artículo sobre este mismo tema que es interesante rescatar dado que se aún se continúa cayendo en el mismo error (o, al menos, mala práctica): el uso injustificado del return false en jQuery.

Introducción

Frecuentemente veo en el código de otros desarrolladores la coletilla de terminar siempre una función o un callback jQuery utilizando un return false.

Este recurso es, probablemente, una de las primeras cosas que se aprenden cuando comenzamos a desarrollar aplicaciones y que tiene como fin evitar el comportamiento por defecto del navegador frente a un evento.

Así, solemos encontrar códigos como el siguiente:

$('form').sumbit( function(){
  // Send the form via Ajax
  var formData = $(this).serialize();
  $.post('anywhere.php', formData, function( data ){
    alert( 'Form Sent! ');
  } );
  return false;
});

¿Os resulta familiar? En el ejemplo anterior, estamos detectando el evento submit de un formulario para realizar una petición al servidor con los datos que contiene vía Ajax. Para evitar que el navegador envie los datos por su cuenta (esto es haciendo el realmente el submit), utilizamos el return false con el que detenemos la ejecución.

Pues es una mala práctica!

Ahí queda eso! Es una mala práctica porque si lo que queremos es evitar el comportamiento por defecto del navegador, disponemos, de métodos propios para hacerlo en lugar de recurrir al return.

La explicación sobre por qué resulta poco ortodoxo utilizar return false es que hacemos más cosas además de la simple cancelación del evento: estamos evitando el concepto de propagación y deteniendo de forma inmediata la ejecución de los posibles callbacks que puedan aparecer con posterioridad.

Para cada uno de estos comportamientos, jQuery cuenta sus propios métodos los cuales deberíamos conocer y manejar: event.preventDefault(), event.stopPropagation() y event.stopInmediatePropagation().

Escenario

Pensemos en el siguiente escenario: una galería fotográfica donde podemos ver una preview de las imágenes pinchando encima.

El código HTML podría ser el siguiente:

<div id="container">
  <div class="picture">
    <a href="/path/pic1_big.jpg"><img alt="pic1" src="/path/pic1_thumb.jpg" /></a>
    <span class="title">A beautiful Pic</span>
    <span class="author">Someone</span>
  </div>
  <div class="picture">
    <a href="/path/pic2_big.jpg"><img alt="pic2" src="/path/pic2_thumb.jpg" /></a>
    <span class="title">Another Pic</span>
    <span class="author">Someone</span>
  </div>
</div>
<div id="image_preview">
  <img alt="preview" src="/path/default_preview.jpg" />
</div>

Un marcado sencillo: tenemos un contenedor general con dos imágenes (pequeñas) dentro de sus correspondientes capas; cada imagen tiene un enlace al archivo original (grande), su nombre y su autor. Luego hay un segundo contenedor donde visualizaremos la preview de aquella imagen seleccionada.

Escribamos el código jQuery para que, al pinchar sobre cada imagen, la original se envíe a la capa preview:

$(document).ready(function(){
  $('.picture a').click( function(){
    var $this = $(this),
        $href = $this.attr('href');
 
    $('#image_preview img').attr( 'src', $href );
    return false;
  });
});

A primera vista, el código funciona como se espera. Si pinchamos sobre una imagen, se captura la dirección del enlace enviándola a la imagen preview para que la cargue. Para evitar que el navegador siga dicho enlace redirigiendo al usuario, cancelamos con un return false.

Añadamos ahora una nueva funcionalidad: cada vez que pinchemos sobre la capa que contiene una imagen, añadiremos una clase para indicar que está seleccionada:

// Inside Document Ready
$('.picture').click( function(){
  // Remove the old item selected class
  $('.picture_selected').removeClass('picture_selected');
  // Add the selected class to this one
  $(this).addClass('picture_selected');
});

Con esto conseguimos que, pinchando sobre una capa, esta quedaría seleccionada (entendemos que la clase picture_selected tiene, por ejemplo, un border rojo para indicarlo).

Pero, ¿y si pinchamos sobre la imagen que está dentro? ¿Se seleccionaría así también su capa? La respuesta es no. Al añadir el return false al click de la imagen, estamos cancelando también la propagación de eventos desde la misma hacia su contenedor. Por lo tanto, la clase picture_selected, nunca llega a añadirse.

jQuery event.preventDefault

El caso más frecuente es el que hemos mencionado arriba: deseamos cancelar o evitar el comportamiento por defecto del navegador frente a un determinado evento. Puede ser por ejemplo, el envío de un formulario o seguir un enlace como el ejemplo anterior.

El siguiente código muestra la forma correcta de hacerlo:

$('.picture a').click( function( e ){
  e.preventDefault();
  var $this = $(this),
      $href = $this.attr('href');
 
  $('#image_preview img').attr( 'src', $href );
});

Si observamos, hemos introducido un argumento, e, y ejecutado sobre él el método preventDefault().

Todos los eventos manejados por jQuery se transmiten al callback mediante el primer argumento de la función anónima que lo ejecuta. Por convención, se utiliza la letra e (event) para guardar dicho evento: cachearlo permite efectuar operaciones sobre él como, en este caso, la cancelación de su comportamiento por defecto.

Con esta sencilla instrucción, conservamos la posibilidad de delegar eventos que el return false prohibe y nuestro código funciona ahora correctamente.

Este comportamiento, sería también aplicable a aquellos eventos aplicados tanto con live como con delegate:

$("a").click(function () {
    // do something
    return false;
});
 
$("a").live("click", function () {
    // THIS WON'T FIRE
});

jQuery event.stopPropagation

En ocasiones, podemos necesitar el comportamiento contrario: evitar que un evento se propague sin que afecte al comportamiento natural del navegador.

Podríamos añadir al ejemplo anterior, un enlace hacia la web del autor en cada fotografía. En este caso, puede no interesarnos el que la capa quede seleccionada, ya que abandonamos la página.

El marcado HTML quedaría así:

 <div class="picture">
  <a href="/path/pic2_big.jpg"><img alt="pic2" src="/path/pic2_thumb.jpg" /></a>
  <span class="title">Another Pic</span>
  <span class="author">
    <a class="author_link" href="http://www.someonesite.com">Someone</a>
  </span>
</div>

Para evitar disparar el evento asociado a la capa picture al pinchar sobre el nuevo enlace, usaríamos el siguiente código:

// Inside Document Ready
$('.author_link').click( function( e ){
  // Don't cancel the browser's default action
  // and don't bubble this event!
  e.stopPropagation();
})

Las ventaja de no utilizar en este caso un return false son obvias: evitariamos que se disparara el evento asociado a la capa pero también estaríamos impidiendo la redirección.

jQuery event.stopImmediatePropagation

En jQuery, todos los eventos asociados a un elemento se ejecutan en el orden en que han sido añadidos. Este metodo permite detener desde su ejecución cualquier otro futuro evento aplicado sobre el mismo objeto.

El siguiente código muestra cómo funciona:

$("div a").click(function () {
   // Do something
});
 
$("div a").click(function ( e ) {
   // Do something else
   e.stopImmediatePropagation();
});
 
$("div a").click(function () {
   // THIS NEVER FIRES
});
 
$("div").click(function () {
   // THIS NEVER FIRES
});

Este tipo de códigos, aunque son raros durante un desarrollo, pueden darse como resultado de añadir bibliotecas de terceros que monitorizan los eventos de determinados elementos. Conocerlo nunca está de más.

Conclusión

Como resumen de todo lo anterior, podemos extraer que el método del return false no es la mejor manera en determinados escenarios para cancelar el comportamiento natural del navegador. Su uso debería limitarse a aquellos casos en los que se prentenda tanto anular este comportamiento como impedir la propagación de eventos. Aún en este supuesto, las guías de estilo jQuery recomiendan ejecutar las instrucciones anteriores por separado:

$("a").click(function ( e ) {
   e.preventDefault();
   e.stopPropagation();
});

Como siempre, la idea es mantener un código lo más flexible posible utilizando las herramientas del lenguajes diseñadas para casa caso concreto.

Más:

{17} Comentarios.

  1. Madman

    Geniales las explicaciones!

    Hace bastante tiempo me encontré con un problema derivado de esta mala práctica y que me trajo algún que otro quebradero de cabeza. A raíz de eso descubrí estos métodos de jQuery, pero me costó un poco entenderlos en su día, no estaban tan bien explicados como aquí XD

  2. Jesus

    Pues yo esto lo conocía, pero siempre he usado return false por que funcionaba. Con estas explicaciones, obviamente, no habrá más return false ^^

  3. Ignacio Sánchez

    He hecho varios return false;. No me gustaba usarlos porque no conocía cómo funcionaban estas tres funciones. Creo que ya no hace falta usarlos 🙂

    Muchas gracias!

  4. reactivo

    Genial, muy conciso y bien explicado. Yo tenía un conocimiento difuso acerca del tema, pero esto me aclara las ideas y me ayuda a ponerlas en orden para evitar algunos comportamientos indeseados que me han aparecido con el uso de preventDefault, return false y demás… ¡Gracias!

  5. Matias Mancini

    A mi no me responde la fn stopPropagation , puede ser por el ejemplo?

    Originalmente agregamos el evento click a todos los links dentro del div picture.

    $('.picture a').click(function(e)

    Pero cuando agregamos otro link y evitamos la propagación, no tiene ningun evento asociado al div padre.
    El link del autor simplemente tiene dos eventos click asociados, no tiene eventos para prevenir un bubbling.

    Es correcto o todavia no me desperte??

  6. iven
    • Carlos Benítez

      Por si no has leído el artículo, se cita a la fuente en la primera línea del primer párrafo 😉
      Saludos.

  7. Reghyna

    Hola! necesito ayuda por favor, tengo un problema trabajando con un formulario visible en una ventana modal ( manejada con div’s), tengo la siguiente estructura :

    <form name="thumbnail" action="” method=”post”>
    <input type="hidden" name="cmbcategory" value="
    <input type="submit" name="upload_thumbnail" value= id=”save_thumb” />

    y se definio :

    $(document).ready(function () {
    $(‘#save_thumb’).click(function() {
    alert(“val”);
    ….
    });
    });

    Pero nunca llega a ejecutarse el evento definido para el boton del formulario.
    El div.img_up esta con ‘display:block’.

    agradecida de antemano..

    • Carlos Benítez

      El marcado de tu botón submit no es correcto; debería ser algo así:

      Es decir, que te faltan las comillas en la propiedad ‘value’.
      El resto es correcto, por lo que debería funcionar.

      Si no lo hace, revisa la consola de Firebug en Firefox donde te indicará el posible error por el que no funciona.
      Saludos!

  8. Maximiliano

    Hola Carlos
    Cual sería la forma correcta de propagar un evento “click” en un vínculo, que evite el comportamiento normal del navegador, y que en lugar de eso, modifique dinámicamente un sector de la pantalla.
    Como por ejemplo botones que carguen una lista de imágenes en otro sector de la pantalla:

    $(‘.boton’).delegate( ‘a’, ‘click’, function( e ){
    e.stopPropagation();
    $.post(“listaFotos1.php”, { variable:variable }, function(data){
    $(“div#listaFotos”).html(data);
    });
    });

    Quizás esto tenga algún error de sintaxis, porque lo escribí rápido a modo de ejemplo, eso no importa, mi pregunta es:
    ¿cual seria la forma correcta de hacer algo como esto, y que el navegador tome cada una de las acciones del usuario, y que al hacer un history.back, o hacer click en volver del navegador o teclear backspace, se vuelva a los pasos anteriores y no se vaya de esa pantalla el navegador ignorando todo lo anterior, como si se tratase de un flash?
    Este comportamiento se puede apreciar por ejemplo en Gmail.
    Donde se puede realizar click y diferente tipo de acciones que van quedando como eventos históricos, y la pantalla nunca es refrescada.

    No se si soy claro con la duda.
    Muchas Gracias por todo.

  9. Interesante, solo que, puesto que has ejemplificado la forma “Erronea” o de “Mala Practica” el envío de un formulario, deberias tambien ejemplificar el mismo envio de un formulario de forma Correcta!!! Está muy interesante el dato!!!

    • Carlos Benítez

      La idea es tener en cuenta que return false no es buena práctica y denota que no se sabe bien qué se está haciendo o lo qué se está buscando conseguir.

      Siempre que se pueda, hay que recurrir a los métodos nativos preventDefault y stopPropagation para obtener el comportamiento deseado. En el caso del formulario, el preventDefault sería lo que debería sustituir al return false

      Saludos!

  10. Agustin

    Yo tenía un conocimiento algo difuso acerca del tema, pero esto me aclaro las ideas. Gracias.

  11. OSCAR

    Eres mi Dios me salvaste el cuello 😛 GRACIAS!!!!

  12. Jose

    Muy buen articulo! gracias!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *