7. jQuery Avanzado
7.1. Efectos
jQuery facilita el uso de animaciones y efectos consiguiendo grandes resultados con apenas un par de líneas, como por ejemplo:
-
Mostrar y ocultar elementos
-
Efectos de aparecer y desvanecer elementos
-
Mover elementos a través de la pantalla
Podemos consultar todos los efectos disponibles en http://api.jquery.com/category/effects/
7.1.1. Mostrar y ocultar
Tanto mostrar como ocultar elementos son acciones comunes y sencillas que podemos realizar de manera inmediata o durante un período de tiempo.
Método | Propósito |
---|---|
show() |
Muestra cada elemento del conjunto de resultados, si estaban ocultos |
show(velocidad[,callback]) |
Muestra todos los elementos del conjunto del resultados mediante una animación, y opcionalmente lanza un callback tras completar la animación |
hide() |
Oculta cada elemento del conjunto de resultados, si estaban visibles |
hide(velocidad[,callback]) |
Oculta todos los elementos del conjunto del resultados mediante una animación, y opcionalmente lanza un callback tras completar la animación |
toggle() |
Cambia la visualización (visible u oculto, de manera contraria a su estado) para cada elemento del conjunto de resultados |
toggle(switch) |
Cambia la visualización para cada elemento del conjunto de resultados dependiendo del switch (verdadero muestra todos los elementos, falso para ocultarlos) |
toggle(velocidad[,callback]) |
Cambia la visualización de todos los elementos del conjunto del resultados mediante una animación, y opcionalmente lanza un callback tras completar la animación |
En aquellos métodos que aceptan una velocidad como parámetro, la podemos indicar de manera númerica, especificando la cantidad de milisegundos de duración del efecto, o mediante una cadena con los posibles valores a slow (lento - 600ms), normal (400ms) o fast (rápido - 200ms).
|
Por ejemplo, supongamos que tenemos un cuadrado con varios botones para cambiar su visualización:
<!DOCTYPE html>
<html lang="es">
<head>
<title>Mostrar y Ocultar</title>
<meta charset="UTF-8">
<style type="text/css">
div#laCapa {
width: 250px; height: 180px; margin: 10px; padding: 20px;
background: blue; border: 2px solid black; cursor: pointer;
}
p, span {
font-size: 16pt;
}
button {
margin: 5px;
}
</style>
</head>
<body>
<p>Mostrando y Ocultando un elemento</p>
<div id="laCapa"></div>
<button id="mostrar">Mostrar</button>
<button id="ocultar">Ocultar</button>
<button id="cambiar">Cambiar (Toggle)</button>
</body>
</html>
Y el código para cambiar el estado de visualización con una pequeña animación:
$(function() {
$("#mostrar").click(function() {
$("#laCapa").show("normal");
});
$("#ocultar").click(function() {
$("#laCapa").hide(2000); // ms
});
$("#cambiar").click(function() {
$("#laCapa").toggle("slow");
});
});
7.1.2. Aparecer y desvanecer
Los efectos más comunes se basan en la aparición progresiva y desvanecimiento del contenido de manera completa o hasta una determinada opacidad. jQuery ofrece un conjunto de métodos para estos efectos:
Método | Propósito |
---|---|
fadeIn(velocidad[,callback]) |
El contenido aparece a la velocidad indicada para cada elemento del conjunto de resultados, y opcionalmente lanza un callback tras completar la animación |
fadeOut(velocidad[,callback]) |
El contenido se desvanece a la velocidad indicada para cada elemento del conjunto de resultados, y opcionalmente lanza un callback tras completar la animación |
fadeTo(velocidad,opacidad[,callback]) |
El contenido cambia a la opacidad y velocidad indicadas para cada elemento del conjunto de resultados, y opcionalmente lanza un callback tras completar la animación |
Ya hemos visto que si queremos que se ejecute una función cuando termine la ejecución, la pasaremos como callback:
$(this).fadeOut(1000, function() {
console.log("He acabado");
});
Por ejemplo, para simular un efecto toggle:
$(this).fadeOut(500, function() {
$(this).delay(2000).fadeIn(500);
});
Si hubiésemos puesto la función fuera del parámetro del fadeOut
, se hubiese ejecutado justamente después de iniciarse la animación, y no al finalizar la misma.
$(this).fadeOut(500).css("margin","50 px");
De manera similar al ejemplo anterior, vamos a dibujar un cuadrado con 4 botones para mostrar estos efectos:
<!DOCTYPE html>
<html lang="es">
<head>
<title>Aparecer y Desvanecer</title>
<meta charset="UTF-8">
<style type="text/css">
div#laCapa {
width: 250px; height: 180px; margin: 10px; padding: 20px;
background: blue; border: 2px solid black; cursor: pointer;
}
p, span {
font-size: 16pt;
}
button {
margin: 5px;
}
</style>
</head>
<body>
<p>Aparecer y Desvanecer un elemento</p>
<div id="laCapa"></div>
<button id="aparecer">Aparecer</button>
<button id="desvanecer">Desvanecer</button>
<button id="fade03">Opacidad hasta .3</button>
<button id="fade10">Opacidad hasta 1.0</button>
</body>
</html>
Y el código para cambiar el estado de visualización con una pequeña animación:
$(function() {
$("#aparecer").click(function() {
$("#laCapa").fadeIn(300);
});
$("#desvanecer").click(function() {
$("#laCapa").fadeOut("normal");
});
$("#fade03").click(function() {
$("#laCapa").fadeTo("slow", 0.3);
});
$("#fade10").click(function() {
$("#laCapa").fadeTo("slow", 1.0);
});
});
7.1.3. Enrollar y desenrollar
jQuery ofrece un conjunto de métodos para enrollar y desenrolar los elementos a modo de persiana:
Método | Propósito |
---|---|
slideDown(velocidad[,callback]) |
El contenido se desenrolla a la velocidad indicada modificando la altura de cada elemento del conjunto de resultados , y opcionalmente lanza un callback tras completar la animación |
slideUp(velocidad[,callback]) |
El contenido se enrolla a la velocidad indicada modificando la altura de cada elemento del conjunto de resultados , y opcionalmente lanza un callback tras completar la animación |
slideToggle(velocidad[,callback]) |
Cambia la visualización del contenido enrollando o desenrollando el contenido a la velocidad indicada modificando la altura de cada elemento del conjunto de resultados, y opcionalmente lanza un callback tras completar la animación |
De manera similar al ejemplo anterior, vamos a dibujar un cuadrado con 3 botones para mostrar estos efectos:
<!DOCTYPE html>
<html lang="es">
<head>
<title>Enrollar y Desenrollar</title>
<meta charset="UTF-8">
<style type="text/css">
div#laCapa {
width: 250px; height: 180px; margin: 10px; padding: 20px;
background: blue; border: 2px solid black; cursor: pointer;
}
p, span {
font-size: 16pt;
}
button {
margin: 5px;
}
</style>
</head>
<body>
<p>Enrollando y Desenrollando un elemento</p>
<div id="laCapa"></div>
<button id="enrollar">Enrollar</button>
<button id="desenrollar">Desenrollar</button>
<button id="cambiar">Cambiar (Toggle)</button>
</body>
</html>
Y el código para enrollar y desenrollar con una pequeña animación:
$(function() {
$("#enrollar").click(function() {
$("#laCapa").slideUp("normal");
});
$("#desenrollar").click(function() {
$("#laCapa").slideDown(2000);
});
$("#cambiar").click(function() {
$("#laCapa").slideToggle("slow");
});
});
7.1.4. Creando animaciones
Para crear animaciones personalizadas sobre las propiedades de los elementos llamaremos a la función animate()
, y para detenerlas a stop()
.
Método | Propósito |
---|---|
animate(parámetros, duración, easing, callback) |
Crea una animación personalizada donde parámetros indica un objeto CSS con las propiedades a animar, con una duración y easing (linear o swing) determinados y lanza un callback tras completar la animación |
animate(parametros, opciones) |
Crea una animación personalizada donde parametros indica las propiedades a animar y las opciones de las animación (complete, step, queue) |
stop() |
Detiene todas las animaciones en marcha para todos los elementos |
De manera similar al ejemplo anterior, vamos a dibujar un cuadrado con 4 botones para cambiar el tamaño de un elemento, el tamaño del texto, mover el elemento y hacerlo todo a la vez:
<!DOCTYPE html>
<html lang="es">
<head>
<title>Animaciones</title>
<meta charset="UTF-8">
<style type="text/css">
div#laCapa {
position: relative; width: 250px; height: 180px; margin: 10px; padding: 20px;
background: blue; border: 2px solid black; cursor: pointer;
}
p, span {
font-size: 16pt;
}
button {
margin: 5px;
}
</style>
</head>
<body>
<p>Animaciones</p>
<div id="laCapa">Anímame un poco!</div>
<button id="derecha">Crecer a la derecha</button>
<button id="texto">Texto grande</button>
<button id="mover">Mover la capa</button>
<button id="todo">Todo</button>
</body>
</html>
Y el código para cambiar animar cada uno de los botones:
$(function() {
$("#derecha").click(function() {
$("#laCapa").animate({ width: "500px" }, 1000);
});
$("#texto").click(function() {
$("#laCapa").animate({ fontSize: "24pt" }, 1000);
});
$("#mover").click(function() {
$("#laCapa").animate({ left: "500" }, 1000, "swing");
});
$("#todo").click(function() {
$("#laCapa").animate({ width: "500px", fontSize: "24pt", left: "500" }, 1000, "swing");
});
});
7.1.5. Ejemplo carrusel
Para poner estos efectos en practica y por comparar funcionalidad realizada en JavaScript respecto a la misma con jQuery, vamos a realizar un carrusel de imagenes de manera similar a la realizada en sesiones anteriores.
Para ello, vamos a definir una capa para cada una de las imágenes
<!DOCTYPE html>
<html lang="es">
<head>
<title>Carrusel jQuery</title>
<meta charset="UTF-8">
<style type="text/css">
#carrusel {
height:400px; width:400px;
}
#carrusel div {
position:absolute; z-index: 0;
}
#carrusel div.anterior {
z-index: 1;
}
#carrusel div.actual {
z-index: 2;
}
</style>
</head>
<body>
<h1>Carrusel jQuery</h1>
<div id="carrusel">
<div class="actual"><img src="imagenes/hierba.jpg" width="400" height="400" class="galeria" /></div>
<div><img src="imagenes/hoja.jpg" width="400" height="400" class="galeria" /></div>
<div><img src="imagenes/primavera.jpg" width="400" height="400" class="galeria" /></div>
<div><img src="imagenes/agua.jpg" width="400" height="400" class="galeria" /></div>
</div>
</body>
</html>
A continuación, añadimos el código jQuery para que cada 2 segundos cambie de imagen, teniendo en cuenta que al llegar a la última, vuelva a mostrar la primera:
$(function () {
setInterval("carruselImagenes()", 2000);
});
function carruselImagenes() {
var fotoActual = $('#carrusel div.actual');
var fotoSig = fotoActual.next();
if (fotoSig.length == 0) {
fotoSig = $('#carrusel div:first'); (1)
}
fotoActual.removeClass('actual').addClass('anterior'); (2)
fotoSig.css({ opacity: 0.0 }).addClass('actual') (3)
.animate({ opacity: 1.0 }, 1000, (4)
function () { fotoActual.removeClass('anterior'); }); (5)
}
1 | Si la siguiente foto es la última, seleccionamos la primera foto |
2 | Cambiamos la clase CSS de la foto actual para que se posicione detrás y se oculte |
3 | La siguiente foto la hacemos transparente, la marcamos como actual |
4 | Le añadimos una animación de 1 segundo en la que pasa de transparente a visible |
5 | Al terminar la animación, le quitamos la clase de anterior para que pase al frente |
7.1.6. Deshabilitando los efectos
Si el sistema donde corre nuestra aplicación es poco potente, podemos deshabilitar todos los efectos y animaciones haciendo uso de la propiedad booleana jQuery.fx.off
:
$.fx.off = true;
// Volvemos a activar los efectos
$.fx.off = false;
Al estar deshabilitadas, los elementos aparecerán y desaparecer sin ningún tipo de animación.
7.2. AJAX
Todas las peticiones AJAX realizadas con jQuery se realizan con el método $.ajax()
(http://api.jquery.com/category/ajax/).
Para simplificar el trabajo, jQuery ofrece varios métodos que realmente son sobrecargas sobre el método $.ajax()
. Si comprobamos el código fuente de este función podremos ver como es mucho más complejo que lo que estudiamos en la sesión de JavaScript.
Los métodos que ofrece jQuery son:
Método | Propósito |
---|---|
selector.load(url) |
Incrustra el contenido crudo de la url sobre el selector |
$.get(url, callback, tipoDatos) |
Realiza una petición GET a la url. Una vez recuperada la respuesta, se invocará el callback el cual recuperará los datos del tipoDatos |
$.getJSON(url, callback) |
Similar a |
$.getScript(url, callback) |
Carga un archivo JavaScript de la url mediante un petición GET, y lo ejecuta. |
$.post(url, datos, callback) |
Realiza una petición POST a la url, enviando los datos como parámetros de la petición |
Todos estos métodos devuelven un objeto jqXHR
el cual abstrae el mecanismo de conexión, ya sea un objeto HTMLHttpRequest
, un objeto XMLHTTP
o una etiqueta <script>
.
7.2.1. $.ajax()
Ya hemos comentado que el método $.ajax()
es el que centraliza todas las llamadas AJAX. Pese a que normalmente no lo vamos a emplear directamente, conviene conocer todas las posibilidades que ofrece. Para ello, recibe como parámetro un objeto con las siguientes propiedades:
Propiedad | Propósito |
---|---|
url |
URL a la que se realiza la petición |
type |
Tipo de petición (GET o POST) |
dataType |
Tipo de datos que devuelve la petición, ya sea texto o binario. |
success |
Callback que se invoca cuando la petición ha sido exitosa y que recibe los datos de respuesta como parámetro |
error |
Callback que se invoca cuando la petición ha fallado |
complete |
Callback que se invoca cuando la petición ha finalizado |
Por ejemplo, si quisieramos recuperar el archivo fichero.txt
, realizaríamos una llamada del siguiente modo:
$.ajax({
url: "fichero.txt",
type: "GET",
dataType: "text",
success: todoOK,
error: fallo,
complete: function(xhr, estado) {
console.log("Peticion finalizada " + estado);
}
});
function todoOK(datos) {
console.log("Todo ha ido bien " + datos);
}
function fallo(xhr, estado, msjErr) {
console.log("Algo ha fallado " + msjErr);
}
Si quisiéramos incrustar el contenido recibido por la petición, en vez de sacarlo por consola, lo podríamos añadir a una capa o un párrafo:
function todoOK(datos) {
$("#resultado").append(datos);
}
7.2.2. load()
Si queremos incrustrar contenido proveniente de una URL, con la misma funcionalidad que un include estático en JSP, usaremos el método load(url)
. Por ejemplo:
$("body").load("contacto.html");
De este modo incluiríamos hasta la cabecera del documento html. Para indicarle que estamos interesados en una parte del documento, podemos indicar que cargue el elemento cuya clase CSS sea contenido
.
$("body").load("contacto.html .contenido");
Mediante este método vamos a poder incluir contenido de manera dinámica al lanzarse un evento. Supongamos que tenemos un enlace a contacto.html
. Vamos a modificarlo para que en vez de redirigir a la página, incruste el contenido:
<a href="contacto.html">Contacto</a>
<div id="contenedor"></div>
<script>
$('a').on('click', function(evt) {
var href = $(this).attr('href'); (1)
$('#contenedor').load(href + ' .contenido'); (2)
evt.preventDefault(); (3)
});
</script>
1 | Obtenemos el documento que queremos incluir |
2 | Incrustamos el contenido del documento dentro de la capa con id contenedor |
3 | Evitamos que cargue el enlace |
7.2.3. Recibiendo información del servidor
Mediante $.get(url [,datos], callBack(datosRespuesta) [,tipoDatosRespuesta])
se envía una petición GET con datos
como parámetro. Tras responder el servidor, se ejecutará el callBack con los datos recibidos cuyo tipo son del tipoDatosRespuesta
.
Así pues, el mismo ejemplo visto anteriormente puede quedar reducido al siguiente fragmento:
$.get("fichero.txt", function(datos) {
console.log(datos);
});
En el caso de XML, seguiremos usando el mismo método pero hemos de tener en cuenta el formato del documento. Supongamos que tenemos el siguiente documento heroes.xml
:
<heroe>
<nombre>Batman</nombre>
<email>batman@heroes.com</email>
</heroe>
Para poder recuperar el contenido hemos de tener el cuenta que trabajaremos con las funciones DOM que ya conocemos:
$.get("heroes.xml", function(datos) {
var nombre = datos.getElementsByTagName("nombre")[0];
var email = datos.getElementsByTagName("email")[0];
var val = nombre.firstChild.nodeValue + " " + email.firstChild.nodeValue;
$("#resultado").append(val);
},"xml");
Si la información a recuperar es de tipo JSON, jQuery ofrece el método $.getJSON(url [,datos], callBack(datosRespuesta))
.
Por ejemplo, supongamos que queremos acceder a Flickr para obtener las imágenes que tienen cierta etiqueta:
var flickrAPI = "http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?";
$.getJSON( flickrAPI, {
tags: "proyecto víbora ii",
tagmode: "any",
format: "json"
}, formateaImagenes);
function formateaImagenes(datos) {
$.each(datos.items, function(i, elemento) { (1)
$("<img>").attr("src", elemento.media.m).appendTo("#contenido"); (2)
if (i === 4) { (3)
return false;
}
});
}
1 | la función $.each(colección, callback(índice, elemento)) recorre el array y realiza una llamada al callback para cada uno de los elementos |
2 | Por cada imagen, la anexa al id contenido construyendo una etiqueta img |
3 | Limita el número de imágenes a mostrar en 5. Cuando el callback de $.each() devuelve false , detiene la iteración sobre el array |
$.each()
La utilidad $.each() se trata de un método auxiliar que ofrece jQuery para iterar sobre una colección, ya sea:
Se emplea mucho para tratar la respuesta de las peticiones AJAX. Más información en http://api.jquery.com/jquery.each/ |
Finalmente, en ocasiones necesitamos inyectar código adicional al vuelo. Para ello, podemos recuperar un archivo JavaScript y que lo ejecute a continuación mediante el método $.getScript(urlScript, callback)
. Una vez finalizada la ejecución del script, se invocará al callback.
Supongamos que tenemos el siguiente código en script.js
:
console.log("Ejecutado dentro del script");
$("#resultado").html("<strong>getScript</strong>");
Y el código jQuery que ejecuta el script:
$.getScript("script.js", function(datos, statusTxt) {
console.log(statusTxt);
})
7.2.4. Enviando información al servidor
La función $.post(url, datos, callback(datosRespuesta))
permite enviar datos mediante una petición POST.
Una singularidad es la manera de adjuntar los datos en la petición, ya sea:
-
Creando un objeto cuyos valores obtenemos mediante
val()
. -
Serializando el formulario mediante el método
serialize()
, el cual codifica los elementos del formulario mediante una cadena de texto
Vamos a crear un ejemplo con un formulario sencillo para realizar el envío mediante AJAX:
<form name="formCliente" id="frmClnt" action="#">
<fieldset id="infoPersonal">
<legend>Datos Personales</legend>
<p><label for="nombre">Nombre</label>
<input type="text" name="nombre" id="idNombre" /></p>
<p><label for="correo">Email</label>
<input type="email" name="correo" id="idEmail" /></p>
</fieldset>
<p><button type="submit">Guardar</button></p>
</form>
Y el código que se comunica con el servidor:
$.post()
$('form').on('submit', function(evt) { (1)
evt.preventDefault(); (2)
// var nom = $(this).find('#inputName').val(); (3)
var datos = $(this).serialize(); // nombre=asdf&email=asdf
$.post("/GuardaFormServlet", datos, function (respuestaServidor) { (4)
console.log("Completado " + respuestaServidor);
});
});
1 | Escuchamos el evento de submit |
2 | Desactivar el envío por defecto del formulario |
3 | Obtener el contenido de los campos, lo cual podemos hacerlo campo por campo como en la línea 3, u obtener una representación de los datos como parámetros de una URL mediante el método serialize() como en la línea 4. |
4 | Enviar el contenido a un script del servidor y recuperamos la respuesta. Mediante $.post() le pasaremos el destino del envío, los datos a envíar y una función callback que se llamará cuando el servidor finalice la petición. |
7.2.5. Tipos de datos
Ya hemos visto que podemos trabajar con cuatro tipos de datos. A continuación vamos a estudiarlos para averiguar cuando conviene usar uno u otro:
-
Fragmentos HTML necesitan poco para funcionar, ya que mediante
load()
podemos cargarlos sin necesidad de ejecutar ningún callback. Como inconveniente, los datos puede que no tengan ni la estructura ni el formato que necesitemos, con lo que estamos acoplando nuestro contenido con el externo. -
Archivos JSON, que permiten estructurar la información para su reutilización. Compactos y fáciles de usar, donde la información es auto-explicativa y se puede manejar mediante objetos mediante
JSON.parse()
yJSON.stringify()
. Hay que tener cuidado con errores en el contenido de los archivos ya que pueden provocar efectos colaterales. -
Archivos JavaScript, ofrecen flexibilidad pero no son realmente un mecanismo de almacenamiento, ya que no podemos usarlos desde sistemas heterogéneos. La posibilidad de cargar scripts JavaScripts en caliente permite refactorizar el código en archivos externos, reduciendo el tamaño del código hasta que sea necesario.
-
Archivos XML, han perdido mercado en favor de JSON, pero se sigue utilizando para permitir que sistemas de terceros sin importar la tecnología de acceso puedan conectarse a nuestros sistemas.
A día de hoy, JSON tiene todas las de ganar, tanto por rendimiento en las comunicaciones como por el tamaño de la información a transmitir.
7.2.6. Manejadores de eventos AJAX
jQuery ofrece un conjunto de métodos globales para interactuar con los eventos que se lanzan al realizar una petición AJAX. Estos métodos no los llamamos dentro de la aplicación, sino que es el navegador el que realiza las llamadas.
Método | Propósito |
---|---|
ajaxComplete() |
Registra un manejador que se invocará cuando la petición AJAX se complete |
ajaxError() |
Registra un manejador que se invocará cuando la petición AJAX se complete con un error |
ajaxStart() |
Registra un manejador que se invocará cuando la primera petición AJAX comience |
ajaxStop() |
Registra un manejador que se invocará cuando todas las peticiones AJAX hayan finalizado |
ajaxSend() |
Adjunta una función que se invocará antes de enviar la petición AJAX |
ajaxSuccess() |
Adjunta una función que se invocará cuando una petición AJAX finalice correctamente |
Recordad que estos métodos son globales y se ejecutan para todas las peticiones AJAX de nuestra aplicación, de ahí que se sólo se adjunten al objeto document .
|
Por ejemplo, si antes de recuperar el archivo de texto registramos todos estos manejadores:
$(document).ready(function() {
$(document).ajaxStart(function () {
console.log("AJAX comenzando");
});
$(document).ajaxStop(function () {
console.log("AJAX petición finalizada");
});
$(document).ajaxSend(function () {
console.log("Antes de enviar la información...");
});
$(document).ajaxComplete(function () {
console.log("Todo ha finalizado!");
});
$(document).ajaxError(function (evt, jqXHR, settings, err) {
console.error("Houston, tenemos un problema: " + evt + " - jq:" + jqXHR + " - settings :" + settings + " err:" + err);
});
$(document).ajaxSuccess(function () {
console.log("Parece que ha funcionado todo!");
});
getDatos();
});
function getDatos() {
$.getJSON("http://www.omdbapi.com/?s=batman&callback=?", todoOk);
}
function todoOk(datos) {
console.log("Datos recibidos y adjuntándolos a resultado");
$("#resultado").append(JSON.stringify(datos));
}
Tras ejecutar el código, por la consola aparecerán los siguientes mensajes en el orden en el que se ejecutan:
7.3. Utilidades
A continuación veremos un conjunto de utilidades que ofrece jQuery.
7.3.1. Comprobación de tipos
El siguiente conjunto de funciones de comprobación de tipos, también conocidas como de introspección de objetos, nos van a permitir:
-
Determinar el tipo de un objeto
-
Gestionar el uso de parámetros opcionales
-
Validar parámetros
Función | Propósito |
---|---|
$.isArray(array) |
Determina si array es un Array. Si es un objeto array-like devolverá falso |
$.isFunction(función) |
Determina si función es una Función |
$.isEmptyObject(objeto) |
Determina si objeto esta vacío |
$.isPlainObject(objeto) |
Determina si objeto es un objeto sencillo, creado como un objeto literal (mediante las llaves) o mediante |
$.isXmlDoc(documento) |
Determina si el documento es un documento XML o un nodo XML |
$.isNumeric(objeto) |
Determina si objeto es un valor numérico escalar. |
$.isWindow(objeto) |
Determina si objeto representa una ventana de navegador |
$.type(objeto) |
Obtiene la clase Javascript del objeto. Los posibles valores son |
Para ver estas utilidades en funcionamiento, vamos a crear un ejemplo sobre un fragmento de código que realiza una llamada a una función:
function llamaOtraFuncion(veces, retraso, funcion) {
var i = 0;
( function bucle() {
i++;
funcion();
if (i < veces) {
setTimeout(bucle, retraso);
}
})();
}
function saluda() {
$("#resultado").append("Saludando desde la función <br />");
}
$(function() {
llamaOtraFuncion(3, 500, saluda);
});
A continuación, vamos a modificarlo para asignar valores por defecto:
function llamaOtraFuncion(arg1, arg2, arg3) {
var veces = $.isNumeric(arg1) ? arg1 : 5;
var retraso = $.isNumeric(arg2) ? arg2 : 1000;
var funcion = $.isFunction(arg1) ? arg1 : $.isFunction(arg2) ? arg2 : arg3;
var i = 0;
// resto de código...
De modo que ahora podemos realizar diferentes llamadas sobrecargando los parámetros:
llamaOtraFuncion(3, 500, saluda); // 3 veces con retardo de 0,5 seg
llamaOtraFuncion(saluda); // 5 veces con retardo de 1 seg
llamaOtraFuncion(7, saluda); // 7 veces con retardo de 1 seg
7.3.2. Manipulación de colecciones
El siguiente conjunto de funciones de manipulación de objetos nos permiten trabajar con arrays y objetos simplificando ciertas tareas:
Función | Propósito |
---|---|
$.makeArray(objeto) |
Convierte el objeto en un array. Se utiliza cuando necesitamos llamar a funciones que sólo soportan los arrays, como |
$.inArray(valor, array) |
Determina si el array contiene el valor. Devuelve |
$.unique(array) |
Elimina cualquier elemento duplicado que se encuentre en el array |
$.merge(array1, array2) |
Combina los contenidos de array1 y array2, similar a la función |
$.map(array, callback) |
Construye un nuevo array cuyo contenido es el resultado de llamar al callback para cada elemento, similar a la funcion |
$.grep(array, callback [,invertido]) |
Filtra el array mediante el callback, de modo que añadirá los elementos que pasen la función, la cual recibe un objeto DOM como parámetro, y devuelve un array JavaScript, similar a la función |
A continuación tenemos un fragmento de código con ejemplos de uso de estos métodos:
var miArray = [1, 2, 3, 3, 4, 4, 5];
var miArray2 = [6, 7, 8];
if ($.inArray(4, miArray) != -1) {
console.log("4 esta en el array");
}
$.unique(miArray);
console.log(miArray); // [1, 2, 3, 4, 5]
$.merge(miArray, miArray2);
console.log(miArray);
var miArrayDoble = $.map(miArray, function(elem, indice) {
return indice * 2;
});
console.log(miArrayDoble);
var miArrayFiltrado = $.grep(miArray, function(elem) {
return elem % 2 == 0;
});
console.log(miArrayFiltrado);
Autoevaluación
¿Qué saldrá por la consola tras ejecutar el método |
7.3.3. Copiando objetos
Si tenemos uno o varios objetos de los cuales queremos copiar sus propiedades en uno final, podemos hacer uso del método $.extend(destino, origen)
;
var animal = {
comer: function() {
console.log("Comiendo");
}
}
var perro = {
ladrar: function() {
console.log("Ladrando");
}
}
$.extend(perro, animal);
perro.comer(); // Comiendo
Es decir, permite copiar miembros de un objeto fuente en uno destino, sin realizar herencia, sólo clonando las propiedades. Si hay un conflicto, se sobreescribirán con las propiedades del objeto fuente, y si tenemos múltiples objetos fuentes, de izquierda a derecha.
Si los objetos que vamos a clonar contienen objetos anidados, necesitamos indicarle a jQuery que el clonado debe ser recursivo, mediante un booleano a true
como primer parámetro:
var animal = {
acciones: {
comer: function() {
console.log("Comiendo");
},
sentar: function() {
console.log("Sentando");
}
}
};
var perro = {
acciones: {
ladrar: function() {
console.log("Ladrando");
},
cavar: function() {
console.log("Cavando");
}
}
};
var perroCopia = {};
$.extend(perroCopia, perro); (1)
perroCopia.acciones.ladrar(); // Ladrando
$.extend(true, perroCopia, animal); (2)
perroCopia.acciones.comer(); // Comiendo
perroCopia.acciones.ladrar(); // Ladrando
$.extend(perro, animal); (3)
perro.acciones.comer(); // Comiendo
perro.acciones.ladrar(); // error
1 | Copiamos los atributos de perro en perroCopia , con lo que podemos acceder a las propiedades |
2 | Al hacer una copia recursiva, en vez de sustituir la propiedad acciones de perroCopia por la de animal , las fusiona |
3 | En cambio, si no hacemos la copia recursiva, podemos acceder a las propiedades de animal pero no a las de perro |
Más información en http://api.jquery.com/jquery.extend/
7.4. Plugins
Aunque el núcleo de jQuery ya ofrece multitud de funcionalidad y utilidades, también soporta una arquitectura de plugins para extender la funcionalidad de la librería.
El website de jQuery ofrece un enorme repositorio de plugins en http://plugins.jquery.com/, donde se listan con demos, código de ejemplo y tutoriales para facilitar su uso.
En la siguiente sesión estudiaremos el plugin jQueryUI como un ejemplo de complemento que extiende la librería.
Aunque jQuery ofrece múltitud de plugins que extienden el código, en ocasiones necesitamos ir un poco más allá y nos toca escribir nuestro propio código que podemos empaquetar como un nuevo plugin.
El archivo fuente que contenga nuestro plugin debería cumplir la siguiente convención de nombrado:
jquery.nombrePlugin.js
jquery.nombrePlugin-1.0.js
Además, se recomienda añadir un comentario en la cabecera donde se visualice la versión del mismo.
7.4.1. Creando un plugin
A la hora de crear un plugin, lo primero que asumimos es que jQuery ha cargado. Lo que no podemos asumir es que el alias $
esté disponible. Por ello, dentro de nuestros plugins usaremos el nombre completo jQuery
o definiremos el $
por nosotros mismos.
Conviene recordar que podemos hacer uso de una IIFE para poder usar el $
dentro de nuestro plugin:
(function($) {
// código del plugin
})(jQuery);
7.4.2. Funciones globales
Del mismo modo que jQuery ofrece la función $.ajax()
, la cual no necesita ningún objeto para funcionar, nosotros podemos extender el abanico de funciones de utilidades que ofrece jQuery.
Para añadir una función al espacio de nombre de jQuery únicamente hemos de asignar la función como una propiedad del objeto jQuery
:
(function($) {
$.suma = function(array) { (1)
var total = 0;
$.each(array, function (indice, valor) {
valor = $.trim(valor);
valor = parseFloat(valor) || 0;
total += valor;
});
return total;
};
})(jQuery);
1 | Le asociamos la función a la propiedad de jQuery |
De este modo, vamos a poder llamar a esta función mediante:
var resultado = $.suma([1,2,3,4]); // 10
También podríamos crear un nuevo espacio de nombres para las funciones globales que queramos añadir, y así evitar conflictos que puedan aparecer con otros plugins. Para ello, sólo hemos de asociar a nuestra función global un objeto el cual contenga como propiedades las funciones que queramos añadir como plugin:
(function($) {
$.MathUtils = {
suma : function(array) {
// código de la función
},
media: function(array) {
// código de la función
}
};
})(jQuery);
De este modo, invocaremos a las funciones así:
var resultado = $.MathUtils.suma([1,2,3,4]);
var resultado = $.MathUtils.media([1,2,3,4]);
7.4.3. Métodos de objeto
Si queremos extender las funciones de jQuery, mediante prototipos podemos crear métodos nuevos que se apliquen al objeto jQuery
activo. jQuery utiliza el alias fn
en vez prototype
:
(function($) {
$.fn.nombreNuevaFuncion = function() {
// código nuevo
};
})(jQuery);
Normalmente no sabemos si la función trabajará sobre un sólo objeto o sobre una colección, ya que un selector de jQuery puede devolver cero, uno o múltiples elementos. De modo que una buena práctica es plantear un escenario donde recibimos un array de datos.
La manera más facil de garantizar este comportamiento es iterar sobre la colección mediante el método each()
.
$.fn.nombreNuevaFuncion = function() {
this.each(function() {
// Hacemos algo con cada elemento
});
};
Así pues, si quisiéramos extender jQuery y ofrecer un nuevo método que le cambiase la clase CSS a un nodo haríamos:
(function($) {
$.fn.cambiarClase = function(clase1, clase2) {
this.each(function() { (1)
var elem = $(this); (2)
if (elem.hasClass(clase1)) {
elem.removeClass(clase1).addClass(clase2);
} else if (elem.hasClass(clase2)) {
elem.removeClass(clase2).addClass(clase1);
}
});
};
})(jQuery);
1 | Recorremos la colección de objetos que nos devuelve el selector. En este punto this referencia al objeto devuelto por jQuery |
2 | Cacheamos la referencia al elemento en concreto sobre el que iteramos |
Esto permitirá que posteriormente realicemos una llamada al nuevo método mediante cualquier selector:
$("div").cambiarClase("principal","secundaria");
Encadenar funciones
Al crear una función prototipo, hemos de tener en cuenta que probablemente, después de llamar a nuestra función, es posible que el desarrollador quiera seguir encadenando llamadas.
Es por ello, que es muy importante que la función devuelva un objeto jQuery
para permitir que continúe el encadenamiento. Este objeto normalmente es el mismo que this
.
En nuestro caso, podemos modificar el plugin para devolver el objeto que iteramos:
(function($) {
$.fn.cambiarClase = function(clase1, clase2) {
return this.each(function() { (1)
var elem = $(this);
if (elem.hasClass(clase1)) {
elem.removeClass(clase1).addClass(clase2);
} else if (elem.hasClass(clase2)) {
elem.removeClass(clase2).addClass(clase1);
}
});
};
})(jQuery);
1 | Al devolver cada objeto que iteramos, permitimos el encadenamiento |
Función como parámetro
Al encadenar funciones, si queremos que nuestro plugin reciba como párametro una función, por ejemplo para dar soporte a callbacks, para evitar un mal funcionamiento cuando no se pase ninguna función, hemos de comprobar si es una función e invocarla en dicho caso:
|
Opciones
Conforme los plugins crecen, es una buena práctica permitir que el plugin reciba un objeto con las opciones de configuración del mismo.
$.fn.pluginConConf = function(opciones) {
var confFabrica = {prop: "valorPorDefecto"};
var conf = $.extend(confFabrica, opciones); (1)
return this.each(function() {
// código que trabaja con conf.prop (2)
});
};
1 | Sobreescribe los valores de fabrica del objeto de configuración con el recibido como parámetro |
2 | Dentro de la iteración, usamos los valores que contiene el objeto de configuración |
Además, es conveniente que permitamos modificar los valores de fabrica para permitir mayor flexibilidad. Para ello, extraemos los valores de fábrica del plugin a una propiedad del método:
$.fn.pluginConConf = function(opciones) {
var conf = $.extend({}, $.fn.pluginConConf.confFabrica, opciones); (1)
return this.each(function() {
// código que trabaja con conf.prop
});
};
$.fn.pluginConConf.confFabrica = {prop: "valorPorDefecto"};
1 | Creamos un nuevo objeto de configuración |
De este modo, vamos a poder inicializar los valores de fábrica, incluso desde fuera de un bloque de ready
mediante:
$.fn.pluginConConf.confFabrica.prop = "nuevoValor";
7.5. Rendimiento
A la hora de escribir código jQuery es útil conocer la manera de que tenga el mejor rendimiento sin penalizar la comprensión ni mantenibilidad del código. Sin embargo, es importante tener en mente que la optimización prematura suele ser la raíz de todos los males.
7.5.1. Consejos de rendimiento
Una vez localizado el problema mediante el profiling del código que vimos en la sesión de JavaScript, llega el momento de optimizar el código. Para ello, deberemos:
-
Utilizar la última versión de jQuery, ya que siempre contienen mejoras de rendimiento que repercutirán en nuestra aplicación.
-
Cachear los selectores, para evitar búsquedas innecesarias. Por ejemplo, el siguiente fragmento realiza 1000 búsquedas:
console.time("Sin cachear"); for (var i=0; i < 1000; i++) { var s = $("div"); } console.timeEnd("Sin cachear");
Mientras que así sólo realizamos una:
console.time("Cacheando"); var miCapa = $("div"); for (var i=0; i < 1000; i++) { var s = miCapa; } console.timeEnd("Cacheando");
Y por la consola tendremos que el fragmento que no cachea tarda 8.990ms mientras que el que cachea sólo 0.045ms, es decir, 200 veces menos.
-
Cachear otros elementos, como llamadas a métodos o acceso a propiedades dentro de bucles. Por ejemplo, el siguiente fragmento recorre un conjunto de 1000 capas:
console.time("Sin cachear"); var miCapa = $("div"); var s = 0; for (var i=0; i < miCapa.length; i++) { s += i; } console.timeEnd("Sin cachear");
Mientras que sí extraemos la llamada al método fuera del bucle, accederemos a la propiedad de longitud una sola vez:
console.time("Cacheando"); var miCapa = $("div"); var longitud = miCapa.length; var s = 0; for (var i=0; i < longitud; i++) { s += i; } console.timeEnd("Cacheando");
También podemos cachear llamadas AJAX del siguiente modo:
Ejemplo cacheo llamadas AJAX - http://jsbin.com/kabuju/edit?js,console,outputfunction getDatos(id) { if (!api[id]) { var url = "http://www.omdbapi.com/?s=" + busqueda + "&callback=?"; console.log("Petición a " + url); api[id] = $.getJSON(url); } api[id].done(todoOk).fail(function() { $("#resultado").html("Error"); }); } function todoOk(datos) { console.log("Datos recibidos y adjuntándolos a resultado"); $("#resultado").append(JSON.stringify(datos)); }
-
Cuando sea posible, utilizar propiedades de elementos. En vez de utilizar un método jQuery, en ocasiones, podemos obtener la misma información a partir de una propiedad. Por ejemplo, en vez de obtener el atributo
id
mediante el métodoattr
hacerlo mediante su propiedad:var lento = miCapa.attr("id"); var rapido = miCapa[0].id;
Hay que tener cuidado que normalmente al acceder mediante una propiedad el resultado deja de ser un objeto jQuery, con lo que no vamos a poder encadenar el resultado en una llamada posterior.
Estos y más consejos detallados los promueve jQuery en http://learn.jquery.com/performance/
7.6. Ejercicios
7.6.1. (0.4 ptos) Ejercicio 71. Apuntes 2.0
A partir del ejercicio 62 realizado en la sesión anterior donde modificábamos la página de apuntes, vamos a mejorarlo de manera que:
-
Al cargar la página, todos los capítulos estén ocultos, incluidas las notas al pie.
-
Al pulsar sobre un enlace de la tabla de contenidos, se muestre el contenido de dicho capítulo. Si había un capítulo mostrado, debe ocultarse. Es decir, sólo puede visualizar en pantalla un único capítulo.
-
Tanto la aparición como la desaparición del contenido se debe realizar mediante una animación.
Al cargar la página la apariencia será similar a la siguiente imagen:
Todo el código estará incluido en el archivo ej71.js
.
7.6.2. (0.5 ptos) Ejercicio 72. Star Wars jQuery
A partir del ejercicio 51 sobre Star Wars API, vamos a basarnos en el mismo API pero escribiendo el código mediante jQuery y su API para AJAX.
Ahora vamos a mostrar un listado con todos los personajes. Al elegir uno de los disponibles se mostrará su információn básica, así como el título de las películas en las que aparece. Cuando se cambie el personaje, mediante una animación se ocultará la información del antiguo personaje (borrando sus datos) antes de mostrar sólo la información del nuevo.
Para ello, usaremos la siguiente plantilla:
<!DOCTYPE html>
<html>
<head lang="es">
<meta charset="UTF-8">
<title>Ejercicio 72</title>
</head>
<body>
<h1>Star Wars API</h1>
<h2>Personajes - <span id='total'></span></h2>
Elige entre los principales personajes: <select id='personajes'></select>
<br />
<br />
<div id='personaje' style='display:none; background:ghostwhite'>
Nombre: <span id='nombre'></span><br />
Sexo: <span id='sexo'></span><br />
Especie: <span id='especie'></span><br />
Películas: <ul id='peliculas'></ul><br />
</div>
<script src="jquery-1.11.2.js"></script>
<script src="ej72.js"></script>
</body>
</html>
Al cargar a Obi-Wan la apariencia será similar a la siguiente imagen:
Todo el código estará incluido en el archivo ej72.js
.
7.6.3. (0.3 ptos) Ejercicio 73. Plugin Canificador
A partir del ejercicio 41 donde creamos el módulo Canificador, crear un plugin de jQuery que integre la misma funcionalidad, de modo que podamos utilizarlo tanto de manera global como si fuese un método de objeto:
var cadenaCaniada = $.toCani("Mi cadena");
$("#contenidoCani").toCani();
El plugin debe permitir modificar la configuración base para que el final de la cadena sea diferente a HHH
.
Todo el código estará incluido en el archivo jquery.canificador.js
.