1. Code
  2. JavaScript

Antipatrones y buenas prácticas de jQuery

Hace mucho tiempo, en una galaxia muy, muy lejana, JavaScript era un lenguaje odiado. De hecho, "odiado" es un eufemismo; JavaScript era un lenguaje despreciado. Por ello, los desarrolladores lo trataban como tal, y sólo se adentraban en las aguas de JavaScript cuando necesitaban darle un toque a sus aplicaciones. A pesar de que el lenguaje JavaScript tiene mucho de bueno, debido a la ignorancia generalizada, pocos se tomaron el tiempo de aprenderlo adecuadamente. En cambio, como algunos de ustedes recordarán, el uso estándar de JavaScript implicaba una cantidad significativa de copiar y pegar.
Scroll to top

Spanish (Español) translation by Kelly Gianella (you can also view the original English article)

Hace mucho tiempo, en una galaxia muy, muy lejana, JavaScript era un lenguaje odiado. De hecho, "odiado" es un eufemismo; JavaScript era un lenguaje despreciado. Por ello, los desarrolladores lo trataban como tal, y sólo se adentraban en las aguas de JavaScript cuando necesitaban darle un toque a sus aplicaciones. A pesar de que el lenguaje JavaScript tiene mucho de bueno, debido a la ignorancia generalizada, pocos se tomaron el tiempo de aprenderlo adecuadamente. En cambio, como algunos de ustedes recordarán, el uso estándar de JavaScript implicaba una cantidad significativa de copiar y pegar.

"No te molestes en aprender qué hace el código, o si sigue las mejores prácticas; ¡sólo pégalo!" - El peor consejo

Debido a que el auge de jQuery reavivó el interés por el lenguaje JavaScript, gran parte de la información en la web es un poco imprecisa.

Irónicamente, resulta que gran parte de lo que la comunidad de desarrolladores odiaba tenía muy poco que ver con el lenguaje JavaScript en sí. No, la verdadera amenaza bajo la máscara era el DOM, o "Modelo de Objetos del Documento", que, especialmente en ese momento, era terriblemente inconsistente de un navegador a otro. "Claro, puede que funcione en Firefox, pero ¿qué pasa con IE8? Ok, puede que funcione en IE8, pero ¿qué pasa con IE7?". ¡La lista seguía incansablemente!

Por suerte, a partir de hace unos cinco años, la comunidad de JavaScript vería un cambio increíble para mejor, ya que se introdujeron al público bibliotecas como jQuery. Estas bibliotecas no sólo proporcionaban una sintaxis expresiva que resultaba especialmente atractiva para los diseñadores de páginas web, sino que también conseguían igualar la situación, al incluir en su API las soluciones para las distintas peculiaridades de los navegadores. Activa $.ajax y deja que jQuery haga la parte difícil. Si avanzamos hasta hoy, la comunidad de JavaScript está más viva que nunca, en gran parte gracias a la revolución de jQuery.

Debido a que el auge de jQuery reavivó el interés por el lenguaje JavaScript, gran parte de la información en la web es un poco imprecisa Esto se debe menos a la ignorancia de los redactores y más al hecho de que todos estábamos aprendiendo. Se necesita tiempo para que surjan las mejores prácticas.

Por suerte, la comunidad ha madurado enormemente desde aquellos días. Antes de que nos sumerjamos en algunas de estas mejores prácticas, vamos a exponer algunos malos consejos que han circulado por la web.


No utilices jQuery

El problema de este tipo de consejos es que llevan la idea de la preoptimización al extremo.

Al igual que Ruby on Rails, la primera introducción de muchos desarrolladores a JavaScript fue a través de jQuery. Esto llevó a un ciclo común: aprender jQuery, enamorarse, profundizar en el JavaScript de vainilla y subir de nivel.

Aunque ciertamente no hay nada de malo en este ciclo, abrió el camino a innumerables artículos, que recomendaban a los usuarios no utilizar jQuery en diversas situaciones, debido a "problemas de rendimiento". No sería raro leer que es mejor usar bucles for vainilla, sobre $.each. O, en algún momento, habrás leído que es una buena práctica utilizar document.getElementsByClassName sobre el motor Sizzle de jQuery, porque es más rápido.

El problema de este tipo de consejos es que llevan la idea de la preoptimización al extremo, y no tienen en cuenta las diversas inconsistencias de los navegadores -¡las cosas que jQuery arregló por nosotros! Ejecutar una prueba y observar un ahorro de unos pocos milisegundos en miles de repeticiones no es razón para abandonar jQuery y su elegante sintaxis. El tiempo se invierte mucho mejor ajustando partes de la aplicación que realmente marcan la diferencia, como el tamaño de las imágenes.


Múltiples objetos jQuery

Este segundo anti-patrón, de nuevo, fue el resultado de la comunidad (incluyendo a su servidor en un momento dado) de no entender completamente lo que estaba ocurriendo bajo el capó de jQuery. Como tal, es probable que te encuentres (o escribas tú mismo) código que envuelve un elemento en el objeto jQuery innumerables veces dentro de una función.

1
$('button.confirm').on('click', function() {
2
    // Do it once
3
    $('.modal').modal();
4
5
    // And once more
6
    $('.modal').addClass('active');
7
8
    // And again for good measure
9
    $('modal').css(...);
10
});

Aunque este código puede parecer, a primera vista, inofensivo (y la verdad es que lo es, en el gran esquema de las cosas), estamos siguiendo la mala práctica de crear múltiples instancias del objeto jQuery. Cada vez que hacemos referencia a $('.modal'), se está generando un nuevo objeto jQuery. ¿Es eso inteligente?

Piensa en el DOM como una piscina: cada vez que llamas a $('.modal'), jQuery se lanza a la piscina y busca las monedas (o elementos) asociados. Cuando se consulta repetidamente el DOM para el mismo selector, básicamente se están lanzando esas monedas de nuevo al agua, sólo para saltar y encontrarlas de nuevo.

Encadena siempre los selectores si pretendes utilizarlos más de una vez. El fragmento de código anterior puede ser refactorizado a:

1
$('button.confirm').on('click', function() {
2
    $('.modal')
3
        .modal()
4
        .addClass('active')
5
        .css(...);
6
});

Como alternativa, utilice el "caché".

1
$('button.confirm').on('click', function() {
2
    // Do it ONLY once
3
    var modal = $('.modal');
4
5
    modal.modal();
6
    modal.addClass('active');
7
    modal.css(...);
8
});

Con esta técnica, jQuery salta al conjunto del DOM un total de una vez, en lugar de tres.


Rendimiento del selector

Se presta demasiada atención al rendimiento del selector.

Aunque no es tan omnipresente en estos días, no hace mucho tiempo, la web fue bombardeada por innumerables artículos sobre la optimización del rendimiento del selector en jQuery. Por ejemplo, ¿es mejor usar $('div p') o $('div').find('p')?

¿Listo para la verdad? No importa. Es ciertamente una buena idea tener una comprensión básica de la forma en que el motor Sizzle de jQuery analiza sus consultas del selector de derecha a izquierda (lo que significa que es mejor ser más específico al final de su selector, en lugar de al principio). Y, por supuesto, cuanto más específico sea, mejor. Claramente, $('a.button') es mejor para el rendimiento que $('.button'), debido a que, con el primero, jQuery es capaz de limitar la búsqueda sólo a los elementos de anclaje de la página, en lugar de a todos los elementos.

Sin embargo, más allá de eso, se presta demasiada atención al rendimiento del selector. En caso de duda, confíe en que el equipo de jQuery está formado por los mejores desarrolladores de JavaScript del sector. Si hay un aumento de rendimiento en la biblioteca, ellos lo habrán descubierto. Y, si no son ellos, uno de los miembros de la comunidad enviará un pull request.

Con esto en mente, se consciente de tus selectores, pero no te preocupes demasiado por las implicaciones de rendimiento, a menos que pueda verbalizar por qué es necesario hacerlo.


El infierno de las llamadas

jQuery ha fomentado el uso generalizado de las funciones de devolución de llamada, que ciertamente pueden proporcionar una buena comodidad. En lugar de declarar una función, simplemente utilice una función de devolución de llamada. Por ejemplo:

1
$('a.external').on('click', function() {
2
    // this callback function is triggered
3
    // when .external is clicked
4
});

Seguro que has escrito mucho código parecido a éste; ¡yo lo he hecho! Cuando se utilizan con moderación, las funciones anónimas de devolución de llamada son muy útiles. El problema se produce más adelante, cuando entramos en... ¡el infierno de las devoluciones de llamada (sonido de trueno de disparo)!

El infierno de las devoluciones de llamada es cuando tu código se indenta numerosas veces, a medida que continúas anidando funciones de devolución de llamada.

Considere el siguiente código bastante común a continuación:

1
$('a.data').on('click', function() {
2
    var anchor = $(this);
3
    $(this).fadeOut(400, function() {
4
        $.ajax({
5
            // ...
6
            success: function(data) {
7
                anchor.fadeIn(400, function() {
8
                    // you've just entered callback hell
9
                });
10
            }
11
        });
12
    });
13
});

Como regla básica, cuanto más indentado esté tu código, más probable es que haya un olor a código. O, mejor aún, pregúntate si mi código se parece a la V voladora de los Mighty Ducks.

Cuando se refactoriza un código como éste, la clave es preguntarse: "¿Cómo podría probarse esto?". Dentro de este código aparentemente simple, un oyente de eventos está vinculado a un enlace, el elemento se desvanece, se realiza una llamada AJAX, al tener éxito, el elemento se desvanece de nuevo, presumiblemente, los datos resultantes se añadirán en alguna parte. ¡Seguro que es mucho lo que hay que probar!

¿No sería mejor dividir este código en piezas más manejables y comprobables? Ciertamente. Aunque se puede optimizar aún más, un primer paso para mejorar este código podría ser:

1
var updatePage = function(el, data) {
2
    // append fetched data to DOM
3
};
4
5
var fetch = function(ajaxOptions) {
6
    ajaxOptions = ajaxOptions || {
7
        // url: ...
8
        // dataType: ...
9
        success: updatePage
10
    };
11
12
    return $.ajax(ajaxOptions);
13
};
14
15
$('a.data').on('click', function() {
16
    $(this).fadeOut(400, fetch);
17
});

Incluso mejor, si tiene una variedad de acciones para desencadenar, contenga los métodos relevantes dentro de un objeto.

Piensa que en un restaurante de comida rápida, como McDonalds, cada trabajador es responsable de una tarea. Joe prepara las patatas fritas, Karen registra a los clientes y Mike asa las hamburguesas. Si los tres miembros de la plantilla lo hicieran todo, se producirían diversos problemas de mantenimiento. Cuando hay que introducir cambios, tenemos que reunirnos con cada persona para discutirlos. Sin embargo, si, por ejemplo, mantenemos a Joe centrado exclusivamente en las patatas fritas, en caso de que tengamos que ajustar las instrucciones para preparar las patatas fritas, sólo tendremos que hablar con Joe y con nadie más. Deberías adoptar un enfoque similar para tu código; cada función es responsable de una tarea.

En el código anterior, la función fetch simplemente activa una llamada AJAX a la URL especificada. La función updatePage acepta algunos datos y los añade al DOM. Ahora, si queremos probar una de estas funciones para asegurarnos de que funciona como se espera, por ejemplo, el método updatePage, podemos simular el objeto de datos y enviarlo a la función. Es muy fácil.


Reinventar la rueda

Es importante recordar que el ecosistema jQuery ha madurado mucho en los últimos años. Lo más probable es que si necesitas un componente en particular, alguien ya lo haya creado. Ciertamente, continúe construyendo plugins para aumentar su comprensión de la biblioteca jQuery (de hecho, escribiremos uno en este artículo), pero, para el uso en el mundo real, refiérase a cualquier posible plugin existente antes de reinventar la rueda.

Por ejemplo, ¿necesitas un selector de fecha para un formulario? Ahórrate el trabajo y aprovecha la biblioteca jQuery UI, impulsada por la comunidad y altamente probada.

Una vez que se hace referencia a la biblioteca jQuery UI necesaria y a la hoja de estilos asociada, el proceso de añadir un selector de fecha a una entrada es tan fácil como hacerlo:

1
<input id="myDateInput" type="text">
2
3
<script>
4
$("#myDateInput").datepicker({
5
   dateFormat: 'yy-mm-dd'
6
}); 
7
8
// Demo: https://jsbin.com/ayijig/2/
9
</script>

O, ¿qué tal un acordeón? Claro, podrías escribir esa funcionalidad tú mismo, o en su lugar, una vez más, tomar ventaja de jQuery UI.

Sólo tiene que crear las marcas necesarias para tu proyecto.

1
<div id="accordion">
2
    <h3><a href="#">Chapter 1</a></h3>
3
    <div><p>Some text.</p></div>
4
5
    <h3><a href="#">Chapter 2</a></h3>
6
    <div><p>Some text.</p></div>
7
8
    <h3><a href="#">Chapter 3</a></h3>
9
    <div><p>Some text.</p></div>
10
11
    <h3><a href="#">Section 4</a></h3>
12
    <div><p>Some text.</p></div>
13
</div>

A continuación, conviértalo automáticamente en un acordeón.

1
$(function() {
2
    $("#accordion").accordion();
3
});

¿Y si pudieras crear fichas en treinta segundos?

Crear el marcado:

1
<div id="tabs">
2
    <ul>
3
        <li><a href="#tabs-1">About Us</a></li>
4
        <li><a href="#tabs-2">Our Mission</a></li>
5
        <li><a href="#tabs-3">Get in Touch</a></li>
6
    </ul>
7
    <div id="tabs-1">
8
        <p>About us text.</p>
9
    </div>
10
    <div id="tabs-2">
11
        <p>Our mission text.</p>
12
    </div>
13
    <div id="tabs-3">
14
        <p>Get in touch text.</p>
15
    </div>
16
</div>

Y activar el plugin.

1
$(function() {
2
    $("#tabs").tabs();
3
});

Ya está. Ni siquiera requiere un conocimiento notable de JavaScript.


Desarrollo de plugins

Ahora vamos a profundizar en algunas de las mejores prácticas para la construcción de plugins de jQuery, que seguramente harás en algún momento de tu carrera como desarrollador.

Utilizaremos un plugin MessageBox relativamente sencillo como demostración para nuestro aprendizaje. Siéntete libre de trabajar con él; de hecho, ¡hazlo!

La tarea: implementar la funcionalidad necesaria para mostrar cuadros de diálogo, utilizando la sintaxis, $.message('SAY TO THE USER'). De esta forma, por ejemplo, antes de borrar un registro definitivamente, podemos pedir al usuario que confirme la acción, en lugar de recurrir a cuadros de alert inflexibles y feos.


Paso 1

El primer paso es averiguar cómo "activar" $.message. En lugar de extender el prototipo de jQuery, para los requisitos de este plugin, sólo necesitamos adjuntar un método al espacio de nombres de jQuery.

1
(function($) {
2
    $.message = function(text) {
3
    console.log(text);
4
  };
5
})(jQuery);

Es tan fácil como eso; ¡adelante, pruébalo! Cuando llames a $.message('Aquí está mi mensaje'), esa cadena debería registrarse en la consola del navegador (Mayúsculas + Comando + i, en Google Chrome).


Paso 2

Aunque no hay suficiente espacio para cubrir el proceso de prueba del plugin, este es un paso importante que debes investigar. Hay una increíble sensación de seguridad que se produce, cuando se refactoriza el código probado.

Por ejemplo, cuando se utiliza la suite de pruebas de jQuery, QUnit, podríamos probar el código del paso 1 escribiendo:

1
 module('jQuery.message', {
2
    test('is available on the jQuery namespace', 1, function() {
3
        ok($.message, 'message method should exist');
4
    });
5
 });

La función ok, disponible a través de QUnit, simplemente afirma que el primer argumento es un valor verdadero. Si el método del message no existe en el espacio de nombres de jQuery, entonces se devolverá false, en cuyo caso la prueba falla.

Siguiendo el patrón de desarrollo dirigido por pruebas, este código sería el primer paso. Una vez que hayas observado que la prueba falla, el siguiente paso sería añadir el método del message, en consecuencia.

Aunque, en aras de la brevedad, este artículo no profundizará en el proceso de desarrollo dirigido por pruebas en JavaScript, se recomienda consultar el repositorio de GitHub de este proyecto para revisar todas las pruebas del plugin: https://github.com/JeffreyWay/MessageBox/blob/master/test/MessageBox_test.js


Paso 3

Un método de mensaje que no hace nada no ayuda a nadie. Tomemos el mensaje proporcionado, y mostrémoslo al usuario. Sin embargo, en lugar de incrustar un enorme globo de código en el método $.message, simplemente usaremos la función para instanciar e inicializar un objeto Message.

1
(function($) {
2
    "use strict";
3
4
    var Message = {
5
        initialize: function(text) {
6
            this.text = text;
7
8
            return this;
9
        }
10
    };
11
12
    $.message = function(text) {
13
        // Needs polyfill for IE8--
14
        return Object.create(Message).initialize(text);
15
    };
16
})(jQuery);

Este enfoque no sólo hace que el objeto Message sea más comprobable, sino que también es una técnica más limpia que, entre otras cosas, protege de nuevo el infierno de las devoluciones de llamada. Piensa en este objeto Message como la representación de una sola caja de mensajes.


Paso 4

Si Message representa una sola caja de mensajes, ¿cuál será el HTML para una? Creemos un div con la clase message-box, y hagámoslo disponible para la instancia de Message, a través de una propiedad el.

1
var Message = {
2
    initialize: function(text) {
3
        this.el = $('<div>', {
4
            'class': 'message-box',
5
            'style': 'display: none'
6
        });
7
        this.text = text;
8
9
        return this;
10
    }
11
};

Ahora, el objeto tiene una referencia inmediata al div envolvente de la caja de mensajes. Para acceder a él, podríamos hacer:

1
var msg = Object.create(Message).initialize();
2
3
// [<div class=​"message-box" style=​"display:​ none">​</div>​] 
4
console.log(msg.el);

Recuerda que ahora tenemos un fragmento de HTML, pero aún no ha sido insertado en el DOM. Esto significa que no tenemos que preocuparnos de ningún reflujo innecesario, al añadir contenido al div.


Paso 5

El siguiente paso es tomar la cadena de mensajes proporcionada, e insertarla en el div. ¡Así de fácil!

1
initialize: function(text) {
2
    // ...
3
    this.el.html(this.text);
4
}
5
6
// [<div class=​"message-box" style=​"display:​ none">​Here is an important message​</div>​]

Sin embargo, es poco probable que queramos insertar el texto directamente en el div. Lo más realista es que el cuadro de mensajes tenga una plantilla. Aunque podríamos dejar que el usuario del plugin creara una plantilla y la referenciara, vamos a simplificar las cosas y limitar la plantilla al objeto Message.

1
 var Message = {
2
    template: function(text, buttons) {
3
        return [
4
            '<p class="message-box-text">' + text + '</p>',
5
            '<div class="message-box-buttons">',
6
                buttons,
7
            '</div>'
8
        ].join('');
9
10
    // ...
11
  };

En situaciones en las que no tienes más remedio que anidar el HTML en tu JavaScript, un enfoque popular es almacenar los fragmentos de HTML como elementos dentro de una matriz, y luego unirlos en una cadena de HTML.

En este fragmento de arriba, el texto del mensaje se inserta ahora en un párrafo con la clase message-box-text. También hemos establecido un lugar para los botones, que implementaremos en breve.

Ahora, el método de inicialización se puede actualizar a:

1
initialize: function(text) {
2
    // ...
3
    this.el.html(this.template(text, buttons));
4
}

Cuando se activa, construimos la estructura del cuadro de mensajes:

1
<div class="message-box" style="display: none;">
2
    <p class="message-box-text">Here is an important message.</p>
3
    <div class="message-box-buttons></div>
4
</div>

Para proyectos más complejos, considere el uso del popular motor de plantillas Handlebars.


Paso 6

Para que el cuadro de mensajes sea lo más flexible posible, el usuario del plugin necesita tener la capacidad de especificar opcionalmente, entre otras cosas, qué botones deben presentarse al usuario - como "Okay", "Cancel", "Yes", etc. Deberíamos poder añadir el siguiente código:

1
$.message('Are you sure?', {
2
    buttons: ['Yes', 'Cancel']
3
});

...y generar un cuadro de mensajes con dos botones.

Para implementar esta funcionalidad, primero hay que actualizar la definición de $.message.

1
$.message = function(text, settings) {
2
    var msg = Object.create(Message);
3
    msg.initialize(text, settings);
4
5
    return msg;
6
};

Ahora, el objeto de configuración se pasará al método de inicialización. Vamos a actualizarlo.

1
initialize: function(text, settings) {
2
    this.el = $('<div>', {'class': 'message-box', 'style': 'display: none'});
3
    this.text = text;
4
    this.settings = settings
5
6
    this.el.html(this.template(text, buttons));
7
}

No está mal, pero ¿qué pasa si el usuario del plugin no especifica ninguna configuración? Ten siempre en cuenta las distintas formas en que se puede utilizar tu plugin.


Paso 7

Asumimos que el usuario del plugin describirá qué botones presentar, pero, para estar seguros, es importante proporcionar un conjunto de valores por defecto, que es una buena práctica al crear plugins.

1
$.message = function(text, settings) {
2
    var msg = Object.create(Message);
3
    msg.initialize(text, settings);
4
5
    return msg;
6
};
7
8
$.message.defaults = {
9
    icon: 'info',
10
    buttons: ['Okay'],
11
    callback: null
12
};

Con este enfoque, si el usuario del plugin necesita modificar los valores predeterminados, sólo tiene que actualizar $.message.defaults, según sea necesario. Recuerde: nunca oculte los valores predeterminados al usuario. Haz que estén disponibles para "el exterior".

Aquí, hemos establecido algunos valores por defecto: el icono, los botones y una función de devolución de llamada, que debe ser activada, una vez que el usuario ha hecho clic en uno de los botones de la caja de mensajes.

jQuery ofrece una forma útil de anular las opciones por defecto de un plugin (o de cualquier objeto, en realidad), a través de su método extend.

1
initialize: function(text, buttons) {
2
    // ...
3
    this.settings = $.extend({}, $.message.defaults, settings);
4
}

Con esta modificación, this.settings será ahora igual a un nuevo objeto. Si el usuario del plugin especifica alguna configuración, ésta anulará el objeto por defaults del plugin.


Paso 8

Si queremos añadir un icono personalizado a la caja de mensajes, dependiendo de la acción, será necesario añadir una clase CSS al elemento, y permitir al usuario aplicar una imagen de fondo, en consecuencia.

Dentro del método de initialize, añade:

1
this.el.addClass('message-box-' + this.settings.icon);

Si no se especifica ningún icono en el objeto de settings, se utiliza el predeterminado, info: .message-box-info. Ahora, podemos ofrecer una variedad de clases CSS (más allá del alcance de este tutorial), que contienen varios iconos para el cuadro de mensajes. Aquí hay un par de ejemplos para empezar.

1
.message-box-info {
2
    background: url(path/to/info/icon.png) no-repeat;
3
}
4
5
.message-box-warning {
6
    background: url(path/to/warning/icon.png) no-repeat;
7
}

Idealmente, como parte de su plugin MessageBox, querrá incluir una hoja de estilo externa que contenga el estilo básico para la caja de mensajes, estas clases y un puñado de iconos.


Paso 9

El plugin ahora acepta un array de botones para aplicar a la plantilla, pero todavía no hemos escrito la funcionalidad para hacer que esa información sea utilizable. El primer paso es tomar un array de valores de botones, y traducirlo a las entradas HTML necesarias. Crea un nuevo método en el objeto Message para manejar esta tarea.

1
createButtons: function(buttons) {}

jQuery.map es un método útil que aplica una función a cada elemento dentro de un array, y devuelve un nuevo array con las modificaciones aplicadas. Esto es perfecto para nuestras necesidades. Para cada elemento de la matriz de botones, como ['Yes', 'No'], queremos reemplazar el texto con una entrada HTML, con el valor establecido, en consecuencia.

1
createButtons: function(buttons) {
2
  return $.map(buttons, function(button) {
3
    return '<input type="submit" value="' + button + '">';
4
  }).join('');
5
}

A continuación, actualiza el método de initialize para llamar a este nuevo método.

1
initialize: function(text, settings) {
2
    this.el = $('<div>', {'class': 'message-box', 'style': 'display: none'});
3
    this.text = text;
4
    this.settings = $.extend({}, $.message.defaults, settings);
5
6
    var buttons = this.createButtons(this.settings.buttons);
7
    this.el.html(this.template(text, buttons));
8
9
    return this;
10
}

Paso 10

¿De qué sirve un botón si no ocurre nada cuando el usuario hace clic en él? Un buen lugar para almacenar todos los escuchadores de eventos para una vista es dentro de un método especial de events en el objeto asociado, como este:

1
initialize: function() {
2
    // ...
3
    this.el.html(this.template(text, buttons));
4
    this.events();
5
},
6
7
events: function() {
8
  var self = this;
9
10
  this.el.find('input').on('click', function() {
11
    self.close();
12
    if ( typeof self.settings.callback === 'function' ) {
13
      self.settings.callback.call(self, $(this).val());
14
    }
15
  });
16
}

Este código es un poco más complejo, debido al hecho de que el usuario del plugin necesita tener la capacidad de desencadenar su propia función de devolución de llamada, cuando se hace clic en un botón en el cuadro de mensajes. El código simplemente determina si se ha registrado una función de callback y, si es así, la activa y envía el valor del botón seleccionado. Imagine que un cuadro de mensajes ofrece dos botones: "Aceptar" y "Cancelar". El usuario del plugin necesita tener una forma de capturar el valor del botón pulsado, y responder en consecuencia.

¿Notas dónde llamamos a self.close()? Ese método, que aún no ha sido creado, es responsable de una cosa: cerrar y eliminar el cuadro de mensajes del DOM.

1
close: function() {
2
  this.el.animate({
3
    top: 0,
4
    opacity: 'hide'
5
  }, 150, function() {
6
    $(this).remove();
7
  });
8
}

Para añadir un poco de estilo, al ocultar la caja de mensajes, en el transcurso de 150 milisegundos, desvanecemos la caja y hacemos una transición hacia arriba.


Paso 11

¡La funcionalidad ha sido implementada! El último paso es presentar el cuadro de mensajes al usuario. Añadiremos un último método show en el objeto Message, que insertará la caja de mensajes en el DOM, y la posicionará.

1
show: function() {
2
  this.el.appendTo('body').animate({
3
    top: $(window).height() / 2 - this.el.outerHeight() / 2,
4
    opacity: 'show'
5
  }, 300);
6
}

Sólo hace falta un simple cálculo para posicionar la caja verticalmente en el centro de la ventana del navegador. Con eso en su lugar, ¡el plugin está completo!

1
$.message = function(text, settings) {
2
  var msg = Object.create(Message).initialize(text, settings);
3
4
  msg.show();
5
6
  return msg;
7
};

Paso 12

Para usar tu nuevo plugin, simplemente llama a $.message() y pasa un mensaje y cualquier configuración aplicable, así:

1
$.message('The row has been updated.');

O, para solicitar la confirmación de alguna acción destructiva:

1
$.message('Do you really want to delete this record?', {
2
    buttons: ['Yes', 'Cancel'],
3
    icon: 'alert',
4
    callback: function(buttonText) {
5
        if ( buttonText === 'Yes' ) {
6
            // proceed and delete record
7
        }
8
    }
9
});

Reflexiones finales

Para aprender más sobre el desarrollo de jQuery, se recomienda consultar mi curso en este sitio, "30 días para aprender jQuery".

A lo largo de la construcción de este plugin de ejemplo de MessageBox, han surgido una serie de buenas prácticas, como evitar el infierno de las devoluciones de llamada, escribir código comprobable, hacer que las opciones por defecto estén disponibles para el usuario del plugin, y asegurar que cada método es responsable de exactamente una tarea.

Aunque ciertamente se podría conseguir el mismo efecto incrustando innumerables funciones de devolución de llamada dentro de $.message, hacerlo no suele ser una buena idea, e incluso se considera un antipatrón

Recuerda las tres claves para un código mantenible y unos plugins y scripts jQuery flexibles:

  1. ¿Podría probar esto? Si no, refactorice y divida el código en trozos.
  2. ¿He ofrecido la posibilidad de anular mi configuración por defecto?
  3. ¿Estoy siguiendo malas prácticas o haciendo suposiciones?

Para aprender más sobre el desarrollo de jQuery, te animo a consultar mi curso screencast en este sitio, "30 días para aprender jQuery".