Tutorial JScript y Ajax
Tutorial JScript y Ajax
Tutorial JScript y Ajax
Introducción a AJAX
El término AJAX se presentó por primera vez en el artículo "Ajax: A New Approach to
Web Applications" publicado por Jesse James Garrett el 18 de Febrero de 2005. Hasta
ese momento, no existía un término normalizado que hiciera referencia a un nuevo tipo
de aplicación web que estaba apareciendo.
En las aplicaciones web tradicionales, las acciones del usuario en la página (pinchar en
un botón, seleccionar un valor de una lista, etc.) desencadenan llamadas al servidor. Una
vez procesada la petición del usuario, el servidor devuelve una nueva página HTML al
navegador del usuario.
Esta técnica tradicional para crear aplicaciones web funciona correctamente, pero no
crea una buena sensación al usuario. Al realizar peticiones continuas al servidor, el
usuario debe esperar a que se recargue la página con los cambios solicitados. Si la
aplicación debe realizar peticiones continuas, su uso se convierte en algo molesto
El siguiente esquema muestra la diferencia más importante entre una aplicación web
tradicional y una aplicación web creada con AJAX. La imagen superior muestra la
interación síncrona propia de las aplicaciones web tradicionales. La imagen inferior
muestra la comunicación asíncrona de las aplicaciones creadas con AJAX.
Figura 1.3 Comparación entre las comunicaciones síncronas de las aplicaciones web
tradicionales y las comunicaciones asíncronas de las aplicaciones AJAX (Imagen
original creada por Adaptive Path y utilizada con su permiso)
Las peticiones HTTP al servidor se sustituyen por peticiones JavaScript que se realizan
al elemento encargado de AJAX. Las peticiones más simples no requieren intervención
del servidor, por lo que la respuesta es inmediata. Si la interacción requiere una
respuesta del servidor, la petición se realiza de forma asíncrona mediante AJAX. En
este caso, la interacción del usuario tampoco se ve interrumpida por recargas de página
o largas esperas por la respuesta del servidor.
No se tienen en cuenta los espacios en blanco y las nuevas líneas: como sucede con
XHTML, el intérprete de JavaScript ignora cualquier espacio en blanco sobrante, por lo
que el código se puede ordenar de forma adecuada para su manejo (tabulando las
líneas, añadiendo espacios, creando nuevas líneas, etc.)
Se distinguen las mayúsculas y minúsculas: al igual que sucede con la sintaxis de las
etiquetas y elementos XHTML. Sin embargo, si en una página XHTML se utilizan
indistintamente mayúsculas y minúsculas, la página se visualiza correctamente y el
único problema es que la página no valida. Por el contrario, si en JavaScript se
intercambian mayúsculas y minúsculas, las aplicaciones no funcionan correctamente.
No se define el tipo de las variables: al definir una variable, no es necesario indicar el
tipo de dato que almacenará. De esta forma, una misma variable puede almacenar
diferentes tipos de datos durante la ejecución del programa.
No es obligatorio terminar cada sentencia con el carácter del punto y coma (;): al
contrario de la mayoría de lenguajes de programación, en JavaScript no es obligatorio
terminar cada sentencia con el carácter del punto y coma (;). No obstante, es muy
recomendable seguir la tradición de terminar cada sentencia con el carácter ;
Se pueden incluir comentarios: los comentarios se utilizan para añadir alguna
información relevante al código fuente del programa. Aunque no se visualizan por
pantalla, su contenido se envía al navegador del usuario junto con el resto del
programa, por lo que es necesario extremar las precauciones sobre el contenido de los
comentarios.
JavaScript define dos tipos de comentarios: los de una sola línea y los que ocupan varias
líneas. Los comentarios de una sola línea se definen añadiendo dos barras oblicuas (//)
al principio de cada línea que forma el comentario:
Cuando un comentario ocupa más de una línea, es más eficiente utilizar los comentarios
multilínea, que se definen encerrando el texto del comentario entre los caracteres /* y
*/
2.2. Variables
Las variables se definen mediante la palabra reservada var, que permite definir una o
varias variables simultáneamente:
El primer carácter debe ser una letra o un guión bajo (_) o un dólar ($).
El resto de caracteres pueden ser letras, números, guiones bajos (_) y símbolos
de dólar ($).
var variable6;
function muestraMensaje() {
var mensaje = "Mensaje de prueba";
}
muestraMensaje();
alert(mensaje);
Además de variables locales, también existe el concepto de variable global, que está
definida en cualquier punto del programa (incluso dentro de cualquier función).
function muestraMensaje() {
alert(mensaje);
}
El código JavaScript anterior define una variable fuera de cualquier función. Este tipo
de variables automáticamente se transforman en variables globales y están disponibles
en cualquier punto del programa.
function muestraMensaje() {
mensaje = "Mensaje de prueba";
}
muestraMensaje();
alert(mensaje);
En caso de colisión entre las variables globales y locales, dentro de una función
prevalecen las variables locales:
function muestraMensaje() {
var mensaje = "gana la de dentro";
alert(mensaje);
}
alert(mensaje);
muestraMensaje();
alert(mensaje);
gana la de fuera
gana la de dentro
gana la de fuera
La variable local llamada mensaje dentro de la función tiene más prioridad que la
variable global del mismo nombre, pero solamente dentro de la función.
function muestraMensaje() {
mensaje = "gana la de dentro";
alert(mensaje);
}
alert(mensaje);
muestraMensaje();
alert(mensaje);
gana la de fuera
gana la de dentro
gana la de dentro
La recomendación general es definir como variables locales todas las variables que sean
de uso exclusivo para realizar las tareas encargadas a cada función. Las variables
globales se utilizan para compartir variables entre funciones de forma rápida.
Utilizadas actualmente:
break, else, new, var, case, finally, return, void, catch, for, switch, while,
continue, function, this, with, default, if, throw, delete, in, try, do,
instanceof, typeof
abstract, enum, int, short, boolean, export, interface, static, byte, extends,
long, super, char, final, native, synchronized, class, float, package, throws,
const, goto, private, transient, debugger, implements, protected, volatile,
double, import, public
JavaScript define cinco tipos primitivos: undefined, null, boolean, number y string.
Además de estos tipos, JavaScript define el operador typeof para averiguar el tipo de
una variable.
El operador typeof se emplea para determinar el tipo de dato que almacena una
variable. Su uso es muy sencillo, ya que sólo es necesario indicar el nombre de la
variable cuyo tipo se quiere averiguar:
var variable1 = 7;
typeof variable1; // "number"
var variable2 = "hola mundo";
typeof variable2; // "string"
Los posibles valores de retorno del operador son: undefined, boolean, number,
string para cada uno de los tipos primitivos y object para los valores de referencia y
también para los valores de tipo null.
El tipo undefined corresponde a las variables que han sido definidas y todavía no se les
ha asignado un valor:
var variable1;
typeof variable1; // devuelve "undefined"
El operador typeof no distingue entre las variables declaradas pero no inicializadas y
las variables que ni siquiera han sido declaradas:
var variable1;
typeof variable1; // devuelve "undefined", aunque la variable1 ha
sido declarada
typeof variable2; // devuelve "undefined", la variable2 no ha sido
declarada
Los valores true y false son valores especiales, de forma que no son palabras ni
números ni ningún otro tipo de valor. Este tipo de variables son esenciales para crear
cualquier aplicación, tal y como se verá más adelante.
Cuando es necesario convertir una variable numérica a una variable de tipo boolean,
JavaScript aplica la siguiente conversión: el número 0 se convierte en false y cualquier
otro número distinto de 0 se convierte en true.
Por este motivo, en ocasiones se asocia el número 0 con el valor false y el número 1
con el valor true. Sin embargo, es necesario insistir en que true y false son valores
especiales que no se corresponden ni con números ni con ningún otro tipo de dato.
Las variables numéricas son muy utilizadas en las aplicaciones habituales, ya que
permiten almacenar cualquier valor numérico. Si el número es entero, se indica
directamente. Si el número es decimal, se debe utilizar el punto (.) para separar la parte
entera de la decimal.
Además del sistema numérico decimal, también se pueden indicar valores en el sistema
octal (si se incluye un cero delante del número) y en sistema hexadecimal (si se incluye
un cero y una x delante del número).
var variable1 = 10;
var variable_octal = 034;
var variable_hexadecimal = 0xA3;
JavaScript define tres valores especiales muy útiles cuando se trabaja con números. En
primer lugar se definen los valores Infinity y –Infinity para representar números
demasiado grandes (positivos y negativos) y con los que JavaScript no puede trabajar.
El otro valor especial definido por JavaScript es NaN, que es el acrónimo de "Not a
Number". De esta forma, si se realizan operaciones matemáticas con variables no
numéricas, el resultado será de tipo NaN.
Para manejar los valores NaN, se utiliza la función relacionada isNaN(), que devuelve
true si el parámetro que se le pasa no es un número:
var variable1 = 3;
var variable2 = "hola";
isNaN(variable1); // false
isNaN(variable2); // true
isNaN(variable1 + variable2); // true
Por último, JavaScript define algunas constantes matemáticas que representan valores
numéricos significativos:
De esta forma, para calcular el área de un círculo de radio r, se debe utilizar la constante
que representa al número Pi:
var area = Math.PI * r * r;
El valor de las cadenas de texto se indica encerrado entre comillas simples o dobles:
Las cadenas de texto pueden almacenar cualquier carácter, aunque algunos no se pueden
incluir directamente en la declaración de la variable. Si por ejemplo se incluye un ENTER
para mostrar el resto de caracteres en la línea siguiente, se produce un error en la
aplicación:
Para resolver estos problemas, JavaScript define un mecanismo para incluir de forma
sencilla caracteres especiales (ENTER, Tabulador) y problemáticos (comillas). Esta
estrategia se denomina "mecanismo de escape", ya que se sustituyen los caracteres
problemáticos por otros caracteres seguros que siempre empiezan con la barra \:
Un tabulador \t
Si se quiere incluir... Se debe sustituir por...
JavaScript es un lenguaje de programación "no tipado", lo que significa que una misma
variable puede guardar diferentes tipos de datos a lo largo de la ejecución de la
aplicación. De esta forma, una variable se podría inicializar con un valor numérico,
después podría almacenar una cadena de texto y podría acabar la ejecución del
programa en forma de variable booleana.
Así, JavaScript incluye un método llamado toString() que permite convertir variables
de cualquier tipo a variables de cadena de texto, tal y como se muestra en el siguiente
ejemplo:
JavaScript también incluye métodos para convertir los valores de las variables en
valores numéricos. Los métodos definidos son parseInt() y parseFloat(), que
convierten la variable que se le indica en un número entero o un número decimal
respectivamente.
JavaScript define una clase para cada uno de los tipos de datos primitivos. De esta
forma, existen objetos de tipo Boolean para las variables booleanas, Number para las
variables numéricas y String para las variables de cadenas de texto. Las clases
Boolean, Number y String almacenan los mismos valores de los tipos de datos
primitivos y añaden propiedades y métodos para manipular sus valores.
La propiedad length sólo está disponible en la clase String, por lo que en principio no
debería poder utilizarse en un dato primitivo de tipo cadena de texto. Sin embargo,
JavaScript convierte el tipo de dato primitivo al tipo de referencia String, obtiene el
valor de la propiedad length y devuelve el resultado. Este proceso se realiza de forma
automática y transparente para el programador.
En realidad, con una variable de tipo String no se pueden hacer muchas más cosas que
con su correspondiente tipo de dato primitivo. Por este motivo, no existen muchas
diferencias prácticas entre utilizar el tipo de referencia o el tipo primitivo, salvo en el
caso del resultado del operador typeof y en el caso de la función eval(), como se verá
más adelante.
La principal diferencia entre los tipos de datos es que los datos primitivos se manipulan
por valor y los tipos de referencia se manipulan, como su propio nombre indica, por
referencia. Los conceptos "por valor" y "por referencia" son iguales que en el resto de
lenguajes de programación, aunque existen diferencias importantes (no existe por
ejemplo el concepto de puntero).
Cuando un dato se manipula por valor, lo único que importa es el valor en sí. Cuando se
asigna una variable por valor a otra variable, se copia directamente el valor de la
primera variable en la segunda. Cualquier modificación que se realice en la segunda
variable es independiente de la primera variable.
De la misma forma, cuando se pasa una variable por valor a una función (como se
explicará más adelante) sólo se pasa una copia del valor. Así, cualquier modificación
que realice la función sobre el valor pasado no se refleja en el valor de la variable
original.
var variable1 = 3;
var variable2 = variable1;
variable2 = variable2 + 5;
// Ahora variable2 = 8 y variable1 sigue valiendo 3
En el ejemplo anterior, se utiliza un tipo de dato primitivo que se verá más adelante, que
se llama Date y que se utiliza para manejar fechas. Se crea una variable llamada
variable1 y se inicializa la fecha a 25 de diciembre de 2009. Al constructor del objeto
Date se le pasa el año, el número del mes (siendo 0 = enero, 1 = febrero, ..., 11 =
diciembre) y el día (al contrario que el mes, los días no empiezan en 0 sino en 1). A
continuación, se asigna el valor de la variable1 a otra variable llamada variable2.
Como Date es un tipo de referencia, la asignación se realiza por referencia. Por lo tanto,
las dos variables quedan "unidas" y hacen referencia al mismo objeto, al mismo dato de
tipo Date. De esta forma, si se modifica el valor de variable2 (y se cambia su fecha a
31 de diciembre de 2010) el valor de variable1 se verá automáticamente modificado.
2.4.2.1. Variables de tipo Object
La clase Object por sí sola no es muy útil, ya que su única función es la de servir de
base a partir de la cual heredan el resto de clases. Los conceptos fundamentales de los
objetos son los constructores y la propiedad prototype, tal y como se explicarán en el
siguiente capítulo.
Una utilidad práctica de Object es la conversión entre tipos de datos primitivos y sus
correspondientes tipos de referencia:
Por este motivo, con los valores booleanos normalmente se utilizan tipos de datos
primitivos en vez de objetos de tipo Boolean.
Los errores de redondeo afectan de la misma forma a las variables numéricas creadas
con tipos de datos primitivos. En cualquier caso, al igual que sucede con Boolean, se
recomienda utilizar el tipo de dato primitivo para los números, ya que la clase Number
no aporta mejoras significativas.
La clase String representa una cadena de texto, de forma similar a los tipos de datos
primitivos:
El operador instanceof sólo devuelve como valor true o false. De esta forma,
instanceof no devuelve directamente la clase de la que ha instanciado la variable, sino
que se debe comprobar cada posible tipo de clase individualmente.
2.5. Operadores
Las variables sólo se pueden utilizar para almacenar información. Sin embargo, es muy
habitual que los programas tengan que manipular la información original para
transformarla en otra información. Los operadores son los elementos básicos que se
utilizan para modificar el valor de las variables y para combinar varios valores entre sí
para obtener otro valor.
JavaScript define numerosos operadores, entre los que se encuentran los operadores
matemáticos (suma, resta, multiplicación, división) y los operadores lógicos utilizados
para realizar comparaciones (mayor que, igual, menor que).
var numero1 = 3;
var variable1 = "hola mundo";
Solamente son válidos para las variables numéricas y son un método sencillo de
incrementar o decrementar en 1 unidad el valor de una variable, tal y como se muestra
en el siguiente ejemplo:
var numero = 5;
++numero;
alert(numero); // numero = 6
var numero = 5;
numero = numero + 1;
alert(numero); // numero = 6
var numero = 5;
--numero;
alert(numero); // numero = 4
var numero = 5;
numero = numero - 1;
alert(numero); // numero = 4
Además de estos dos operadores, existen otros dos operadores similares pero que se
diferencian en la forma en la que se realiza el incremento o decremento. En el siguiente
ejemplo:
var numero = 5;
numero++;
alert(numero); // numero = 6
var numero1 = 5;
var numero2 = 2;
numero3 = numero1++ + numero2;
// numero3 = 7, numero1 = 6
var numero1 = 5;
var numero2 = 2;
numero3 = ++numero1 + numero2;
// numero3 = 8, numero1 = 6
2.5.3.1. Negación
variable !variable
true false
variable !variable
false true
var cantidad = 0;
vacio = !cantidad; // vacio = true
cantidad = 2;
vacio = !cantidad; // vacio = false
2.5.3.2. AND
La operación lógica AND combina dos valores booleanos para obtener como resultrado
otro valor de tipo lógico. El resultado de la operación solamente es true si los dos
operandos son true. El operador se indica mediante el símbolo &&:
La operación lógica OR también combina dos valores booleanos para obtener como
resultado otro valor de tipo lógico. El resultado de la operación es true si alguno de los
dos operandos es true. El operador se indica mediante el símbolo ||:
Uno de los operadores matemáticos más singulares cuando se estudia por primera vez es
el módulo, que calcula el resto de la división entera. Si se divide 10 entre 5, la división
es exacta y da un resultado de 2. El resto de esa división es 0, por lo que "módulo de 10
y 5" es igual a 0.
Aunque el operador módulo parece demasiado extraño como para ser útil, en muchas
aplicaciones web reales se utiliza para realizar algunas técnicas habituales, tal y como se
verá más adelante.
var numero1 = 5;
numero1 += 3; // numero1 = numero1 + 3 = 8
numero1 -= 1; // numero1 = numero1 - 1 = 4
numero1 *=2; // numero1 = numero1 * 2 = 10
numero1 /= 2; // numero1 = numero1 / 2 = 2.5
numero1 %= 3; // numero1 = numero1 % 3 = 2
Los operadores relacionales definidos por JavaScript son idénticos a los definidos por
las matemáticas: mayor que (>), menor que (<), mayor o igual (>=), menor o igual (<=),
igual (==) y distinto (!=).
var numero1 = 3;
var numero2 = 5;
resultado = numero1 > numero2; // resultado = false
resultado = numero1 < numero2; // resultado = true
numero1 = 5;
numero2 = 5;
resultado = numero1 >= numero2; // resultado = true
resultado = numero1 <= numero2; // resultado = true
resultado = numero1 == numero2; // resultado = true
resultado = numero1 != numero2; // resultado = false
var numero1 = 5;
resultado = numero1 = 3; // numero1 = 3 y resultado = 3
Para comparar valores, se debe utilizar el operador == (con dos signos de igual):
var numero1 = 5;
resultado = numero1 == 3; // numero1 = 5 y resultado = false
Además de las variables numéricas, también se pueden utilizar variables de tipo cadena
de texto con los operadores relacionales:
Cuando se comparan cadenas de texto con los operadores > y <, el resultado obtenido
puede ser poco intuitivo. JavaScript compara letra a letra comenzando desde la
izquierda hasta que se encuentre una diferencia entre las dos letras. Para determinar si
una letra es mayor o menor que otra, se considera que:
Sin embargo, en el caso del operador "idéntico", las dos variables tienen que ser además
del mismo tipo. Como la primera variable es de tipo numérico y la segunda es una
cadena de texto, aunque sus valores son iguales, el resultado de la operación es false.
Otra forma habitual de añadir nuevos elementos al array es mediante la notación con
corchetes que también se utiliza en otros lenguajes de programación:
El primer elemento del array siempre ocupa la posición 0 (cero) y el tamaño del array
aumenta de forma dinámica a medida que se añaden nuevos elementos.
Los arrays contienen decenas de propiedades y métodos muy útiles para manipular sus
contenidos y realizar operaciones complejas, tal y como se verá más adelante.
Entre las utilidades que proporciona JavaScript, se encuentra la clase Date que permite
representar y manipular valores relacionados con fechas. Para obtener la representación
de la fecha actual, sólo es necesario instanciar la clase sin parámetros:
El constructor de la clase Date permite establecer sólo una fecha o la fecha y hora a la
vez. El formato es (año, mes, dia) o (año, mes, dia, hora, minuto, segundo).
Los meses se indican mediante un valor numérico que empieza en el 0 (Enero) y
termina en el 11 (Diciembre). Los días del mes se cuentan de forma natural desde el día
1 hasta el 28, 29, 30 o 31 dependiendo de cada mes.
A continuación se muestran algunos de los métodos más útiles disponibles para la clase
Date:
2.7. Funciones
Las funciones de JavaScript no suelen definirse mediante la clase Function, sino que se
crean mediante la palabra reservada function:
function suma(a, b) {
return a+b;
}
No es obligatorio que las funciones tengan una instrucción de tipo return para devolver
valores. De hecho, cuando una función no devuelve ningún valor o cuando en la
instrucción return no se indica ningún valor, automáticamente se devuelve el valor
undefined.
Para llamar a la función en cualquier instrucción, se indica su nombre junto con la lista
de parámetros esperados:
Los parámetros que se pasan pueden estar definidos mediante operaciones que se
evalúan antes de pasarlos a la función:
Como JavaScript no define tipos de variables, no es posible asegurar que los parámetros
que se pasan a una función sean los del tipo adecuado para las operaciones que realiza la
función.
Si a una función se le pasan más parámetros que los que ha definido, los parámetros
sobrantes se ignoran. Si se pasan menos parámetros que los que ha definido la función,
al resto de parámetros hasta completar el número correcto se les asigna el valor
undefined.
function sumaCuadrados(a, b) {
function cuadrado(x) { return x*x; }
return cuadrado(a) + cuadrado(b);
}
La función anterior calcula la suma del cuadrado de dos números. Para ello, define en el
interior de la función otra función que calcula el cuadrado del número que se le pasa.
Para obtener el resultado final, la función sumaCuadrados() hace uso de la función
anidada cuadrado().
Las funciones también se pueden crear mediante lo que se conoce como "function
literals" y que consiste en definir la función con una expresión en la que el nombre de la
función es opcional. Debido a esta última característica, también se conocen como
funciones anónimas. A continuación se muestra una misma función definida mediante
el método tradicional y mediante una función anónima:
function suma(a, b) {
return a+b;
}
Como se ha comentado, cuando una función recibe menos parámetros de los que
necesita, inicializa el valor del resto de parámetros a undefined. De esta forma, puede
ser necesario proteger a la aplicación frente a posibles valores incorrectos en sus
parámetros. El método habitual es realizar una comprobación sencilla:
function suma(a, b) {
if(isNaN(b)) {
b = 0;
}
return a + b;
}
La función del ejemplo anterior comprueba que b sea un número para poder realizar
correctamente la suma. En caso de que no lo sea (es decir, que sea null, undefined o
cualquier valor válido distinto de un número) se le asigna el valor 0 para que la función
pueda devolver un resultado válido.
function suma(a, b) {
b = b || 0;
return a + b;
}
Por lo tanto:
suma(3);
suma(3, null);
suma(3, false);
Como el número de argumentos que se pasan a una función de JavaScript puede ser
variable e independiente del número de parámetros incluidos en su definición,
JavaScript proporciona una variable especial que contiene todos los parámetros con los
que se ha invocado a la función. Se trata de un array que se llama arguments y
solamente está definido dentro de cualquier función.
function suma(a, b) {
alert(arguments.length);
alert(arguments[2]);
return a + b;
}
suma(3, 5);
function mayor() {
var elMayor = arguments[0];
for(var i=1; i<arguments.length; i++) {
if(arguments[i] > elMayor) {
elMayor = arguments[i];
}
}
return elMayor;
}
Una última propiedad del objeto arguments que no suele utilizarse habitualmente, pero
que puede ser necesaria en ocasiones es la propiedad callee. La propiedad callee
hace referencia a la función que se está ejecutando. En el siguiente ejemplo se utiliza la
propiedad callee para mostrar el código fuente de la función que se está ejecutando:
function suma(a, b) {
alert(arguments.callee);
return a + b;
}
suma(3, 5);
function suma(a, b) {
alert(arguments.callee.length);
alert(arguments.length);
return a + b;
}
suma(3, 5, 7, 9);
length, calcula la longitud de una cadena de texto (el número de caracteres que la
forman)
Si se indica un final más pequeño que un inicio, JavaScript los considera de forma
inversa, ya que automáticamente asigna el valor más pequeño al inicio y el más grande
al final:
Con esta función se pueden extraer fácilmente las letras que forman una palabra:
var palabra = "Hola";
var letras = palabra.split(""); // letras = ["H", "o", "l", "a"]
2.8.2. Arrays
pop(), elimina el último elemento del array y lo devuelve. El array original se modifica
y su longitud disminuye una unidad.
push(), añade un elemento al final del array. El array original se modifica y aumenta su
longitud una unidad. También es posible añadir más de un elemento a la vez.
Al igual que sucede con otros lenguajes de programación, los objetos se emplean en
JavaScript para organizar el código fuente de una forma más clara y para encapsular
métodos y funciones comunes. La forma más sencilla de crear un objeto es mediante la
palabra reservada new seguida del nombre de la clase que se quiere instanciar:
El objeto laCadena creado mediante el objeto nativo String permite almacenar una
cadena de texto y aprovechar todas las herramientas y utilidades que proporciona
JavaScript para su manejo. Por otra parte, la variable elObjeto almacena un objeto
genérico de JavaScript, al que se pueden añadir propiedades y métodos propios para
definir su comportamiento.
alert(elArray['primero']);
alert(elArray[0]);
El primer alert() muestra el valor 1 correspondiente al valor asociado con la clave
primero. El segundo alert() muestra undefined, ya que como no se trata de un array
normal, sus elementos no se pueden acceder mediante su posición numérica.
alert(elArray['primero']);
alert(elArray.primero);
alert(elArray[0]);
nombreArray.nombreClave = valor;
elArray['primero'];
elArray.primero;
Más adelante se muestra otra forma aún más abreviada y directa de establecer el valor
tanto de los arrays "normales" como de los arrays asociativos.
3.1.1.1. Propiedades
Como los objetos son en realidad arrays asociativos que almacenan sus propiedades y
métodos, la forma más directa para definir esas propiedades y métodos es la notación de
puntos:
elObjeto.id = "10";
elObjeto.nombre = "Objeto de prueba";
Al contrario de lo que sucede en otros lenguajes orientados a objetos, como por ejemplo
Java, para asignar el valor de una propiedad no es necesario que la clase tenga definida
previamente esa propiedad.
También es posible utilizar la notación tradicional de los arrays para definir el valor de
las propiedades:
elObjeto['id'] = "10";
elObjeto['nombre'] = "Objeto de prueba";
3.1.1.2. Métodos
Además de las propiedades, los métodos de los objetos también se pueden definir
mediante la notación de puntos:
elObjeto.muestraId = function() {
alert("El ID del objeto es " + this.id);
}
Uno de los aspectos más importantes del ejemplo anterior es el uso de la palabra
reservada this. La palabra this se suele utilizar habitualmente dentro de los métodos
de un objeto y siempre hace referencia al objeto que está llamado a ese método.
Dentro del método, this apunta al objeto que llama a ese método. En este caso, this
hace referencia a elObjeto. Por tanto, la instrucción del método muestraId es
equivalente a indicar:
El uso de this es imprescindible para crear aplicaciones reales. El motivo es que nunca
se puede suponer el nombre que tendrá la variable (el objeto) que incluye ese método.
Como los programadores pueden elegir libremente el nombre de cada objeto, no hay
forma de asegurar que la siguiente instrucción funcione siempre correctamente:
Además, la palabra this se debe utilizar siempre que se quiera acceder a una propiedad
de un objeto, ya que en otro caso, no se está accediendo correctamente a la propiedad:
function obtieneId() {
return this.id;
}
elObjeto.obtieneId = obtieneId;
Para asignar una función externa al método de un objeto, sólo se debe indicar el nombre
de la función externa sin paréntesis. Si se utilizaran los paréntesis:
function obtieneId() {
return this.id;
}
elObjeto.obtieneId = obtieneId();
Por otra parte, no es obligatorio que el método del objeto se llame igual que la función
externa, aunque es posible que así sea.
Siguiendo este mismo procedimiento, es posible crear objetos complejos que contengan
otros objetos:
Aplicacion.Modulos[0].objetoInicial = inicial;
JavaScript define un par de métodos denominados apply() y call() que son muy
útiles para las funciones. Ambos métodos permiten ejecutar una función como si fuera
un método de otro objeto. La única diferencia entre los dos métodos es la forma en la
que se pasan los argumentos a la función.
El siguiente ejemplo muestra cómo utilizar el método call() para ejecutar una función
como si fuera un método del objeto elObjeto:
function miFuncion(x) {
return this.numero + x;
}
El resto de parámetros del método call() son los parámetros que se pasan a la función.
En este caso, solamente es necesario un parámetro, que es el número que se sumará a la
propiedad numero del objeto.
El método apply() es idéntico al método call(), salvo que en este caso los parámetros
se pasan como un array:
function miFuncion(x) {
return this.numero + x;
}
var elObjeto = new Object();
elObjeto.numero = 5;
var resultado = miFuncion.apply(elObjeto, [4]);
alert(resultado);
En los últimos años, JSON se ha convertido en una alternativa al formato XML, ya que
es más fácil de leer y escribir, además de ser mucho más conciso. No obstante, XML es
superior técnicamente porque es un lenguaje de marcado, mientras que JSON es
simplemente un formato para intercambiar datos.
Como ya se sabe, la notación tradicional de los arrays es tediosa cuando existen muchos
elementos:
Para crear un array normal mediante JSON, se indican sus valores separados por comas
y encerrados entre corchetes. Por lo tanto, el ejemplo anterior se puede reescribir de la
siguiente manera utilizando la notación JSON:
Por su parte, la notación tradicional de los arrays asociativos es igual de tediosa que la
de los arrays normales:
Como JavaScript ignora los espacios en blanco sobrantes, es posible reordenar las
claves y valores para que se muestren más claramente en el código fuente de la
aplicación. El ejemplo anterior se puede rehacer de la siguiente manera añadiendo
nuevas líneas para separar los elementos y añadiendo espacios en blanco para tabular las
claves y para alinear los valores:
var titulos = {
rss: "Lector RSS",
email: "Gestor de email",
agenda: "Agenda"
};
Utilizando JSON, es posible reescribir el ejemplo anterior de forma mucho más concisa:
var modulo = {
titulo : "Lector RSS",
objetoInicial : { estado : 1, publico : 0, nombre : "Modulo RSS",
datos : {} }
};
Los objetos se pueden definir en forma de pares clave/valor separados por comas y
encerrados entre llaves. Para crear objetos vacíos, se utilizan un par de llaves sin
contenido en su interior {}.
Arrays
Objetos
La notación abreviada se puede combinar para crear arrays de objetos, objetos con
arrays, objetos con objetos y arrays, etc. A continuación se muestran algunos ejemplos
de aplicaciones web reales que crean objetos mediante esta notación.
kinds: ['Service','Hours','Days','Product'],
change_kind: function(i) {
if($F('lines_'+i+'_kind')=='Hours') {
$('lines_'+i+'_unit_price').value = $F('default_hourly_rate');
this.update();
}
},
focus_num: function() {
$('invoice_number').focus();
},
use_freight: function() {
return $('invoice_use_freight').checked
},
freight: function() {
return this.use_freight() ?
Number(noCommas($('invoice_freight').value)) : 0 ;
},
...
}
pf.prototype = {
Ed:function(a){this.hm=a},
dh:function(){if(this.eb.Th>0) {var a=Math.random()*100; return
a<this.eb.Th} return false },
Sg:function(a,b,c){ this.Vd=2; this.kb=Ic(a,true); this.Pc=b;
this.Vl=c; this.Be() },
ne:function(a,b,c){ this.Vd=2; this.kb=Ic(a,true); this.Pc=null;
this.Vl=b; if(c){this.vm=false} this.Be()},
...
}
var Prototype = {
Version: '1.6.0.2',
ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
emptyFunction: function() { },
K: function(x) { return x }
};
App.Modules.RssReaderInfos = {
infos: App.Loc.defaultRssReader_infos,
defaultObj: {status:1, share:0, title:"", moduleName:"RssReader",
data:{}}
}
App.Modules.GmailInfos = {
title: App.Loc.defaultGmail_title,
infos: App.Loc.defaultGmail_infos,
defaultObj:{status:1, share:0, title:App.Loc.defaultGmail_title,
moduleName:"Gmail", data:{}},
path: NV_PATH+"modules/gmail/gmail.js?v=5",
ico: NV_PATH+"img/gmail.gif"
}
App.Modules.WeatherInfos = {
title: App.Loc.defaultWeather_title,
infos: App.Loc.defaultWeather_infos,
defaultObj:{status:1, share:0, title:App.Loc.defaultWeather_title,
moduleName:"Weather", data:{town:"FRXX0076"}},
path: NV_PATH+"modules/weather/weather.js?v=2",
ico: NV_PATH+"img/weather.gif"
}
// ############
// EXPORT MANAGER
// ############
var exportManager = {
show: function() {
$('exportButton').src = "/images/b-export-on.gif"
showElement('download_export')
},
hide: function() {
$('exportButton').src = "/images/b-export.gif"
hideElement('download_export')
},
toggle: function() {
Element.visible('download_export') ? this.hide() : this.show()
}
}
var dojo;
if(dj_undef("dojo")){ dojo = {}; }
dojo.version = {
major: 0, minor: 2, patch: 2, flag: "",
revision: Number("$Rev: 2836 $".match(/[0-9]+/)[0]),
toString: function() {
with (dojo.version) {
return major + "." + minor + "." + patch + flag + " (" +
revision + ")";
}
}
};
A partir de los ejemplos anteriores, se deduce que la forma habitual para definir los
objetos en JavaScript se basa en el siguiente modelo creado con la notación JSON:
var objeto = {
"propiedad1": valor_simple_1,
"propiedad2": valor_simple_2,
"propiedad3": [array1_valor1, array1_valor2],
"propiedad4": { "propiedad anidada": valor },
"metodo1": nombre_funcion_externa,
"metodo2": function() { ... },
"metodo3": function() { ... },
"metodo4": function() { ... }
};
Ejercicio 1
Definir la estructura de un objeto que almacena una factura. Las facturas están formadas
por la información de la propia empresa (nombre de la empresa, dirección, teléfono,
NIF), la información del cliente (similar a la de la empresa), una lista de elementos
(cada uno de los cuales dispone de descripción, precio, cantidad) y otra información
básica de la factura (importe total, tipo de iva, forma de pago).
Una vez definidas las propiedades del objeto, añadir un método que calcule el importe
total de la factura y actualice el valor de la propiedad correspondiente. Por último,
añadir otro método que muestre por pantalla el importe total de la factura.
Ver solución
3.2. Clases
Los objetos que se han visto hasta ahora son una simple colección de propiedades y
métodos que se definen para cada objeto individual. Sin embargo, en la programación
orientada a objetos, el concepto fundamental es el de clase.
La forma habitual de trabajo consiste en definir clases a partir de las cuales se crean los
objetos con los que trabajan las aplicaciones. Sin embargo, JavaScript no permite crear
clases similares a las de lenguajes como Java o C++. De hecho, la palabra class sólo
está reservada para su uso en futuras versiones de JavaScript.
En los dos casos, se utiliza la palabra reservada new y el nombre del tipo de objeto que
se quiere crear. En realidad, ese nombre es el nombre de una función que se ejecuta para
crear el nuevo objeto. Además, como se trata de funciones, es posible incluir parámetros
en la creación del objeto.
JavaScript utiliza funciones para simular los constructores de objetos, por lo que estas
funciones se denominan "funciones constructoras". El siguiente ejemplo crea una
función llamada Factura que se utiliza para crear objetos que representan una factura.
Cuando se crea un objeto es habitual pasar al constructor de la clase una serie de valores
para inicializar algunas propiedades. Este concepto también se utiliza en JavaSript,
aunque su realización es diferente. En este caso, la función constructora inicializa las
propiedades de cada objeto mediante el uso de la palabra reservada this.
La función constructora puede definir todos los parámetros que necesita para construir
los nuevos objetos y posteriormente utilizar esos parámetros para la inicialización de las
propiedades. En el caso anterior, la factura se inicializa mediante el identificador de
factura y el identificador de cliente.
Así, el objeto laFactura es de tipo Factura, con todas sus propiedades y métodos y se
puede acceder a ellos utilizando la notación de puntos habitual:
3.2.2. Prototype
Las funciones constructoras no solamente pueden establecer las propiedades del objeto,
sino que también pueden definir sus métodos. Siguiendo con el ejemplo anterior, se
puede crear un objeto completo llamado Factura con sus propiedades y métodos:
this.muestraCliente = function() {
alert(this.idCliente);
}
this.muestraId = function() {
alert(this.idFactura);
}
}
Incluir los métodos de los objetos como funciones dentro de la propia función
constructora, es una técnica que funciona correctamente pero que tiene un gran
inconveniente que la hace poco aconsejable.
Todos los objetos de JavaScript incluyen una referencia interna a otro objeto llamado
prototype o "prototipo". Cualquier propiedad o método que contenga el objeto
prototipo, está presente de forma automática en el objeto original.
Realizando un símil con los lenguajes orientados a objetos, es como si cualquier objeto
de JavaScript heredara de forma automática todas las propiedades y métodos de otro
objeto llamado prototype. Cada tipo de objeto diferente hereda de un objeto prototype
diferente.
En cierto modo, se puede decir que el prototype es el molde con el que se fabrica cada
objeto de ese tipo. Si se modifica el molde o se le añaden nuevas características, todos
los objetos fabricados con ese molde tendrán esas características.
Normalmente los métodos no varían de un objeto a otro del mismo tipo, por lo que se
puede evitar el problema de rendimiento comentado anteriormente añadiendo los
métodos al prototipo a partir del cual se crean los objetos.
this.muestraCliente = function() {
alert(this.idCliente);
}
this.muestraId = function() {
alert(this.idFactura);
}
}
La clase anterior que incluye los métodos en la función constructora, se puede reescribir
utilizando el objeto prototype:
Factura.prototype.muestraCliente = function() {
alert(this.idCliente);
}
Factura.prototype.muestraId = function() {
alert(this.idFactura);
}
Factura.prototype.iva = 16;
var laFactura = new Factura(3, 7); // laFactura.iva = 16
Factura.prototype.iva = 7;
var otraFactura = new Factura(5, 4);
// Ahora, laFactura.iva = otraFactura.iva = 7
El primer objeto creado de tipo Factura dispone de una propiedad llamada iva cuyo
valor es 16. Más adelante, se modifica el prototipo del objeto Factura durante la
ejecución del programa y se establece un nuevo valor en la propiedad iva.
De esta forma, la propiedad iva del segundo objeto creado vale 7. Además, el valor de
la propiedad iva del primer objeto ha cambiado y ahora vale 7 y no 16. Aunque la
modificación del prototipo en tiempo de ejecución no suele ser una operación que se
realice habitualmente, sí que es posible modificarlo de forma accidental.
Ejercicio 2
Modificar el ejercicio anterior del objeto Factura para crear una pseudoclase llamada
Factura y que permita crear objetos de ese tipo. Se deben utilizar las funciones
constructoras y la propiedad prototype.
Ver solución
Si por ejemplo se considera la clase Array, esta no dispone de un método que indique la
posición de un elemento dentro de un array (como la función indexOf() de Java).
Modificando el prototipo con el que se construyen los objetos de tipo Array, es posible
añadir esta funcionalidad:
Array.prototype.indexOf = function(objeto) {
var resultado = -1;
for(var i=0; i<this.length; i++) {
if(this[i] == objeto) {
resultado = i;
break;
}
}
return resultado;
}
El código anterior permite que todos los arrays de JavaScript dispongan de un método
llamado indexOf que devuelve el índice de la primera posición de un elemento dentro
del array o -1 si el elemento no se encuentra en el array, tal y como sucede en otros
lenguajes de programación.
Como se verá más adelante, existen librerías de JavaScript formadas por un conjunto de
utilidades que facilitan la programación de las aplicaciones. Una de las características
habituales de estas librerías es el uso de la propiedad prototype para mejorar las
funcionalidades básicas de JavaScript.
A continuación se muestra el ejemplo de una librería llamada Prototype que utiliza la
propiedad prototype para ampliar las funcionalidades de los arrays de JavaScript:
Object.extend(Array.prototype, {
_each: function(iterator) {
for (var i = 0; i < this.length; i++)
iterator(this[i]);
},
clear: function() {
this.length = 0;
return this;
},
first: function() {
return this[0];
},
last: function() {
return this[this.length - 1];
},
compact: function() {
return this.select(function(value) {
return value != undefined || value != null;
});
},
flatten: function() {
return this.inject([], function(array, value) {
return array.concat(value.constructor == Array ?
value.flatten() : [value]);
});
},
without: function() {
var values = $A(arguments);
return this.select(function(value) {
return !values.include(value);
});
},
indexOf: function(object) {
for (var i = 0; i < this.length; i++)
if (this[i] == object) return i;
return -1;
},
reverse: function(inline) {
return (inline !== false ? this : this.toArray())._reverse();
},
shift: function() {
var result = this[0];
for (var i = 0; i < this.length - 1; i++)
this[i] = this[i + 1];
this.length--;
return result;
},
inspect: function() {
return '[' + this.map(Object.inspect).join(', ') + ']';
}
});
Extender el objeto Array para que permita añadir nuevos elementos al final del array:
Ver solución
String.prototype.toArray = function() {
return this.split('');
}
String.prototype.trim = function() {
return this.replace(/^\s*|\s*$/g, '');
}
La función trim() añadida al prototipo de la clase String hace uso de las expresiones
regulares para detectar todos los espacios en blanco que puedan existir tanto al principio
como al final de la cadena y se sustituyen por una cadena vacía, es decir, se eliminan.
Con este método, es posible definir las funciones asociadas rtrim() y ltrim() que
eliminan los espacios en blanco a la derecha (final) de la cadena y a su izquierda
(principio).
String.prototype.rtrim = function() {
return this.replace(/\s*$/g, '');
}
String.prototype.ltrim = function() {
return this.replace(/^\s*/g, '');
}
String.prototype.trim = function() {
return this.ltrim().rtrim();
}
Otra función muy útil para las cadenas de texto es la de eliminar todas las etiquetas
HTML que pueda contener. Cualquier aplicación en la que el usuario pueda introducir
información, debe tener especial cuidado con los datos introducidos por el usuario.
Mediante JavaScript se puede modificar la clase String para incluir una utilidad que
elimine cualquier etiqueta de código HTML de la cadena de texto:
String.prototype.stripTags = function() {
return this.replace(/<\/?[^>]+>/gi, '');
}
El ejemplo anterior también hace uso de expresiones regulares complejas para eliminar
cualquier trozo de texto que sea similar a una etiqueta HTML, por lo que se buscan
patrones como <...> y </...>.
Ejercicio 4
Extender la clase String para que permita truncar una cadena de texto a un tamaño
indicado como parámetro:
Modificar la función anterior para que permita definir el texto que indica que la cadena
se ha truncado:
Ver solución
Ejercicio 5
Añadir a la clase Array un método llamado sin() que permita filtrar los elementos del
array original y obtenga un nuevo array con todos los valores diferentes al indicado:
Ver solución
Los lenguajes orientados a objetos disponen, entre otros, de los conceptos de herencia
entre clases y de ámbito scope) de sus métodos y propiedades (public, private,
protected).
Sin embargo, JavaScript no dispone de forma nativa ni de herencia ni de ámbitos. Si se
requieren ambos mecanismos, la única opción es simular su funcionamiento mediante
clases, funciones y métodos desarrollados a medida.
try {
var resultado = 5/a;
} catch(excepcion) {
alert(excepcion);
}
El bloque catch permite indicar el nombre del parámetro que se crea automáticamente
al producirse una excepción. Este identificador de la variable sólo está definido dentro
del bloque catch y se puede utilizar para obtener más información sobre la excepción
producida.
En este caso, al intentar dividir el número 5 por la variable a que no está definida, se
produce una excepción que muestra el siguiente mensaje dentro del bloque catch:
Si dentro del bloque try se ejecuta una instrucción de tipo return, continue o break,
también se ejecuta el bloque finally antes de ejecutar cualquiera de esas instrucciones.
Si se produce una excepción en el bloque try y están definidos los bloques catch y
finally, en primer lugar se ejecuta el bloque catch y a continuación el bloque
finally.
try {
if(typeof a == "undefined" || isNaN(a)) {
throw new Error('La variable "a" no es un número');
}
var resultado = 5/a;
} catch(excepcion) {
alert(excepcion);
} finally {
alert("Se ejecuta");
}
En este caso, al ejecutar el script se muestran los dos siguientes mensajes de forma
consecutiva:
Figura 3.2 Manejando las excepciones en JavaScript para mostrar mensajes al usuario
Figura 3.3 El bloque "finally" siempre se ejecuta cuando se produce una excepción en
JavaScript
function funcionExterna() {
var x = "estoy dentro";
function funcionAnidada() { alert(x); }
funcionAnidada();
}
funcionExterna();
Técnicamente, una clausura es una porción de código (normalmente una función) que
se evalúa en un entorno de ejecución que contiene variables de otro entorno de
ejecución. JavaScript crea clausuras para todas las funciones definidas dentro de otra
función. Sin embargo, el uso expreso de clausuras es muy limitado y se reserva sólo
para las técnicas más avanzadas y complejas de JavaScript. Utilizando clausuras es
posible por ejemplo simular el funcionamiento de las propiedades privadas en los
objetos de JavaScript.
Existen recursos online con una explicación detallada del funcionamiento y aplicaciones
de las clausuras en JavaScript, como por ejemplo
http://www.jibbering.com/faq/notes/closures/
3.3.3. Reflexión
Al igual que la mayor parte de los lenguajes de programación más utilizados, JavaScript
define mecanismos que permiten la reflexión sobre los objetos. La reflexión es un
proceso mediante el cual un programa es capaz de obtener información sobre si mismo
y por tanto es capaz de auto modificarse en tiempo de ejecución.
JavaScript emplea el concepto de reflexión para permitir descubrir propiedades y
métodos de objetos externos. El ejemplo más sencillo es el de averiguar si un objeto
posee un determinado método y así poder ejecutarlo. Si se dispone de un objeto llamado
elObjeto, el código necesario para descubrir si posee una determinada propiedad
llamada laPropiedad sería el siguiente:
if(elObjeto.laPropiedad) {
// el objeto posee la propiedad buscada
}
Sin embargo, el código anterior no es del todo correcto, ya que si la propiedad buscada
tiene un valor de false, null o el número 0, el anterior código no se ejecutará
correctamente. En tal caso, el código necesario es el siguiente:
if(typeof(elObjeto.laPropiedad) != 'undefined') {
// el objeto posee la propiedad buscada
}
El ejemplo anterior hace uso del operador typeof, que devuelve el tipo del objeto o
variable que se le pasa como parámetro. Los valores que devuelve este operador son:
undefined, number, object, boolean, string o function.
Ejercicio 6
Sobrescribir el objeto Object para que incluya un método llamado implementa() y que
indique si el objeto posee el método cuyo nombre se le pasa como parámetro.
Ver solución
Antes de poder utilizar sus funciones, DOM transforma internamente el archivo XML
original en una estructura más fácil de manejar formada por una jerarquía de nodos. De
esta forma, DOM transforma el código XML en una serie de nodos interconectados en
forma de árbol.
El árbol generado no sólo representa los contenidos del archivo original (mediante los
nodos del árbol) sino que también representa sus relaciones (mediante las ramas del
árbol que conectan los nodos).
Aunque en ocasiones DOM se asocia con la programación web y con JavaScript, la API
de DOM es independiente de cualquier lenguaje de programación. De hecho, DOM está
disponible en la mayoría de lenguajes de programación comúnmente empleados.
<body>
<p>Esta página es <strong>muy sencilla</strong></p>
</body>
</html>
Antes de poder utilizar la API de DOM, se construye de forma automática el árbol para
poder ejecutar de forma eficiente todas esas funciones. De este modo, para utilizar
DOM es imprescindible que la página web se haya cargado por completo, ya que de
otro modo no existe el árbol de nodos y las funciones DOM no pueden funcionar
correctamente.
Document: es el nodo raíz de todos los documentos HTML y XML. Todos los
demás nodos derivan de él.
DocumentType: es el nodo que contiene la representación del DTD empleado en
la página (indicado mediante el DOCTYPE).
Element: representa el contenido definido por un par de etiquetas de apertura y
cierre (<etiqueta>...</etiqueta>) o de una etiqueta abreviada que se abre y
se cierra a la vez (<etiqueta/>). Es el único nodo que puede tener tanto nodos
hijos como atributos.
Attr: representa el par nombre-de-atributo/valor.
Text: almacena el contenido del texto que se encuentra entre una etiqueta de
apertura y una de cierre. También almacena el contenido de una sección de tipo
CDATA.
CDataSection: es el nodo que representa una sección de tipo <![CDATA[ ]]>.
Comment: representa un comentario de XML.
Se han definido otros tipos de nodos pero que no son empleados habitualmente:
DocumentFragment, Entity, EntityReference, ProcessingInstruction y
Notation.
El siguiente ejemplo de documento sencillo de XML muestra algunos de los nodos más
habituales:
<?xml version="1.0"?>
<clientes>
<!-- El primer cliente -->
<cliente>
<nombre>Empresa SA</nombre>
<sector>Tecnologia</sector>
<notas><![CDATA[
Llamar la proxima semana
]]></notas>
</cliente>
</clientes>
El nodo raíz siempre es el nodo de tipo Document, del que derivan todos los demás
nodos del documento. Este nodo es común para todas las páginas HTML y todos los
documentos XML. A continuación se incluye la etiqueta <clientes>...</clientes>.
Como se trata de una etiqueta, DOM la transforma en un nodo de tipo Element.
Además, como la etiqueta encierra a todos los demás elementos de la página, el nodo
Clientes de tipo Element deriva directamente de Document y todos los demás nodos
del documento derivan de ese nodo.
Al mismo nivel que el comentario, se encuentra la etiqueta <cliente> que define las
características del primer cliente y forma el segundo subnodo del nodo clientes.
Todas las demás etiquetas del documento XML se encuentran encerradas por la etiqueta
<cliente>...</cliente>, por lo que todos los nodos restantes derivarán del nodo
cliente.
La etiqueta <notas> se transforma en tres nodos, ya que contiene una sección de tipo
CData, que a su vez se transforma en un nodo del que deriva el contenido propio de la
sección CData.
Un buen método para comprobar la transformación que sufren las páginas web y
visualizar la jerarquía de nodos creada por DOM es utilizar la utilidad "Inspector DOM"
(o "DOM Inspector") del navegador Mozilla Firefox.
En primer lugar, el objeto Node define las siguientes constantes para la identificación de
los distintos tipos de nodos:
Node.ELEMENT_NODE = 1
Node.ATTRIBUTE_NODE = 2
Node.TEXT_NODE = 3
Node.CDATA_SECTION_NODE = 4
Node.ENTITY_REFERENCE_NODE = 5
Node.ENTITY_NODE = 6
Node.PROCESSING_INSTRUCTION_NODE =7
Node.COMMENT_NODE = 8
Node.DOCUMENT_NODE = 9
Node.DOCUMENT_TYPE_NODE = 10
Node.DOCUMENT_FRAGMENT_NODE = 11
Node.NOTATION_NODE = 12
Además de estas constantes, Node proporciona las siguientes propiedades y métodos:
Valor
Propiedad/Método Descripción
devuelto
nodeName String
El nombre del nodo (no está definido
para algunos tipos de nodo)
nodeValue String
El valor del nodo (no está definido
para algunos tipos de nodo)
nodeType Number
Una de las 12 constantes definidas
anteriormente
ownerDocument Document
Referencia del documento al que
pertenece el nodo
firstChild Node Referencia del primer nodo de la lista
childNodes
childNodes NodeList
Lista de todos los nodos hijo del nodo
actual
Referencia del nodo hermano anterior
previousSibling Node o null si este nodo es el primer
hermano
Referencia del nodo hermano
nextSibling Node siguiente o null si este nodo es el
último hermano
hasChildNodes() Bolean
Devuelve true si el nodo actual tiene
uno o más nodos hijo
Se emplea con nodos de tipo Element.
attributes NamedNodeMap
Contiene objetos de tipo Attr que
definen todos los atributos del
elemento
Añade un nuevo nodo al final de la
appendChild(nodo) Node
lista childNodes
removeChild(nodo) Node Elimina un nodo de la lista
childNodes
replaceChild(nuevoNodo,
Node
Reemplaza el nodo anteriorNodo por
anteriorNodo) el nodo nuevoNodo
Inserta el nodo nuevoNodo antes que
insertBefore(nuevoNodo,
Node la posición del nodo anteriorNodo
anteriorNodo)
dentro de la lista childNodes
Cuando se utiliza DOM en páginas HTML, el nodo raíz de todos los demás se define en
el objeto HTMLDocument. Además, se crean objetos de tipo HTMLElement por cada nodo
de tipo Element del árbol DOM. Como se verá en el siguiente capítulo, el objeto
document es parte del BOM Browser Object Model), aunque también se considera que
es equivalente del objeto Document del DOM de los documentos XML. Por este motivo,
el objeto document también hace referencia al nodo raíz de todas las páginas HTML.
<html>
<head>
<title>Aprendiendo DOM</title>
</head>
<body>
<p>Aprendiendo DOM</p>
<p>DOM es sencillo de aprender</p>
<p>Ademas, DOM es muy potente</p>
</body>
</html>
Utilizando los métodos proporcionados por DOM, es sencillo obtener los elementos
<head> y <body>. En primer lugar, los dos nodos se pueden obtener como el primer y el
último nodo hijo del elemento <html>:
objeto_head.parentNode == objeto_html
objeto_body.parentNode == objeto_html
objeto_body.previousSibling == objeto_head
objeto_head.nextSibling == objeto_body
objeto_head.ownerDocument == document
alert(document.nodeType); // 9
alert(document.documentElement.nodeType); // 1
4.4.3. Atributos
Además del tipo de etiqueta HTML y su contenido de texto, DOM permite el acceso
directo a todos los atributos de cada etiqueta. Para ello, los nodos de tipo Element
contienen la propiedad attributes, que permite acceder a todos los atributos de cada
elemento. Aunque técnicamente la propiedad attributes es de tipo NamedNodeMap,
sus elementos se pueden acceder como si fuera un array. DOM proporciona diversos
métodos para tratar con los atributos:
Los métodos anteriores devuelven un nodo de tipo Attr, por lo que no devuelven
directamente el valor del atributo. Utilizando estos métodos, es posible procesar y
modificar fácilmente los atributos de los elementos HTML:
var p = document.getElementById("introduccion");
var elId = p.attributes.getNamedItem("id").nodeValue; // elId =
"introduccion"
var elId = p.attributes.item(0).nodeValue; // elId =
"introduccion"
p.attributes.getNamedItem("id").nodeValue = "preintroduccion";
De esta forma, el ejemplo anterior se puede reescribir utilizando los nuevos métodos:
var p = document.getElementById("introduccion");
var elId = p.getAttribute("id"); // elId = "introduccion"
p.setAttribute("id", "preintroduccion");
Los métodos presentados hasta el momento permiten acceder a cualquier nodo del árbol
de nodos DOM y a todos sus atributos. Sin embargo, las funciones que proporciona
DOM para acceder a un nodo a través de sus padres obligan a acceder al nodo raíz de la
página y después a sus nodos hijos y a los nodos hijos de esos hijos y así sucesivamente
hasta el último nodo de la rama terminada por el nodo buscado.
Cuando se trabaja con una página web real, el árbol DOM tiene miles de nodos de todos
los tipos. Por este motivo, no es eficiente acceder a un nodo descendiendo a través de
todos los ascendentes de ese nodo.
Para solucionar este problema, DOM proporciona una serie de métodos para acceder de
forma directa a los nodos deseados. Los métodos disponibles son
getElementsByTagName(), getElementsByName() y getElementById().
El siguiente ejemplo muestra cómo obtener todos los párrafos de una página XHTML:
El valor que devuelve la función es un array con todos los nodos que cumplen la
condición de que su etiqueta coincide con el parámetro proporcionado. En realidad, el
valor devuelto no es de tipo array normal, sino que es un objeto de tipo NodeList. De
este modo, el primer párrafo de la página se puede obtener de la siguiente manera:
<p name="prueba">...</p>
<p name="especial">...</p>
<p>...</p>
Normalmente el atributo name es único para los elementos HTML que lo incluyen, por
lo que es un método muy práctico para acceder directamente al nodo deseado. En el
caso de los elementos HTML radiobutton, el atributo name es común a todos los
radiobutton que están relacionados, por lo que la función devuelve una colección de
elementos.
<div id="cabecera">
<a href="/" id="logo">...</a>
</div>
Ejercicio 7
A partir de la página web proporcionada y utilizando las funciones DOM, mostrar por
pantalla la siguiente información:
Ver solución
Ejercicio 8
1. Se debe modificar el protocolo de todas las direcciones de los enlaces. De esta forma,
si un enlace apuntaba a http://prueba, ahora debe apuntar a https://prueba
2. Los párrafos de la página cuyo atributo class es igual a "importante" deben
modificarlo por "resaltado". El resto de párrafos deben incluir un atributo class
igual a "normal".
3. A los enlaces de la página cuyo atributo class sea igual a "importante", se les
añade un atributo "name" con un valor generado automáticamente y que sea igual a
"importante"+i, donde i es un valor numérico cuyo valor inicial es 0 para el primer
enlace.
Ver solución
Hasta ahora, todos los métodos DOM presentados se limitan a acceder a los nodos y sus
propiedades. A continuación, se muestran los métodos necesarios para la creación,
modificación y eliminación de nodos dentro del árbol de nodos DOM.
Los métodos DOM disponibles para la creación de nuevos nodos son los siguientes:
Método Descripción
<html>
<head><title>Ejemplo de creación de nodos</title></head>
<body></body>
</html>
var p = document.createElement("p");
A continuación se crea un nodo de texto que almacena el contenido de texto del párrafo:
var texto = document.createTextNode("Este párrafo no existía en la
página HTML original");
En tercer lugar, se asocia el elemento creado y su contenido de texto (los nodos de tipo
Text son hijos de los nodos de tipo Element):
p.appendChild(texto);
El método appendChild() está definido para todos los diferentes tipos de nodos y se
encarga de añadir un nodo al final de la lista childNodes de otro nodo.
Por último, se añade el nodo creado al árbol de nodos DOM que representa a la página.
Utilizando el método appendChild(), se añade el nodo como hijo del nodo que
representa al elemento <body> de la página:
document.body.appendChild(p);
La página HTML que resulta después de la ejecución del código JavaScript se muestra a
continuación:
<html>
<head><title>Ejemplo de creación de nodos</title></head>
<body>
<p>Este párrafo no existía en la página HTML original</p>
</body>
</html>
Una vez más, es importante recordar que las modificaciones en el árbol de nodos DOM
sólo se pueden realizar cuando toda la página web se ha cargado en el navegador. El
motivo es que los navegadores construyen el árbol de nodos DOM una vez que se ha
cargado completamente la página web. Cuando una página no ha terminado de cargarse,
su árbol no está construido y por tanto no se pueden utilizar las funciones DOM.
Por otra parte, también se pueden utilizar funciones DOM para eliminar cualquier nodo
existente originalmente en la página y cualquier nodo creado mediante los métodos
DOM:
<html>
<head><title>Ejemplo de eliminación de nodos</title></head>
<body>
<p>Este parrafo va a ser eliminado dinamicamente</p>
</body>
</html>
var p = document.getElementsByTagName("p")[0];
Para eliminar cualquier nodo, se emplea la función removeChild(), que toma como
argumento la referencia al nodo que se quiere eliminar. La función removeChild() se
debe invocar sobre el nodo padre del nodo que se va a eliminar. En este caso, el padre
del primer párrafo de la página es el nodo <body>:
var p = document.getElementsByTagName("p")[0];
document.body.removeChild(p);
La página HTML que resulta después de la ejecución del código JavaScript anterior se
muestra a continuación:
<html>
<head><title>Ejemplo de eliminación de nodos</title></head>
<body></body>
</html>
Cuando la página está formada por miles de nodos, puede ser costoso acceder hasta el
nodo padre del nodo que se quiere eliminar. En estos casos, se puede utilizar la
propiedad parentNode, que siempre hace referencia al nodo padre de un nodo.
De esta forma, el ejemplo anterior se puede rehacer para eliminar el nodo haciendo uso
de la propiedad parentNode:
var p = document.getElementsByTagName("p")[0];
p.parentNode.removeChild(p);
Además de crear y eliminar nodos, las funciones DOM también permiten reemplazar un
nodo por otro. Utilizando la misma página HTML de ejemplo, se va a sustituir el
párrafo original por otro párrafo con un contenido diferente. La página original contiene
el siguiente párrafo:
<html>
<head><title>Ejemplo de sustitución de nodos</title></head>
<body>
<p>Este parrafo va a ser sustituido dinamicamente</p>
</body>
</html>
<html>
<head><title>Ejemplo de sustitución de nodos</title></head>
<body>
<p>Este parrafo se ha creado dinámicamente y sustituye al parrafo
original</p>
</body>
</html>
Además de crear, eliminar y sustituir nodos, las funciones DOM permiten insertar
nuevos nodos antes o después de otros nodos ya existentes. Si se quiere insertar un nodo
después de otro, se emplea la función appendChild(). Página HTML original:
<html>
<head><title>Ejemplo de inserción de nodos</title></head>
<body>
<p>Primer parrafo</p>
</body>
</html>
document.body.appendChild(nuevoP);
<html>
<head><title>Ejemplo de inserción de nodos</title></head>
<body>
<p>Primer parrafo</p>
<p>Segundo parrafo</p>
</body>
</html>
Si se quiere insetar el nuevo párrafo delante del párrafo existente, se puede utilizar la
función insertBefore(). Página HTML original:
<html>
<head><title>Ejemplo de inserción de nodos</title></head>
<body>
<p>Primer parrafo</p>
</body>
</html>
Código JavaScript necesario para insertar un nuevo párrafo delante del párrafo
existente:
<html>
<head><title>Ejemplo de inserción de nodos</title></head>
<body>
<p>Segundo parrafo, antes del primero</p>
<p>Primer parrafo</p>
</body>
</html>
Ejercicio 9
Inicialmente, la aplicación cuenta con tres cajas vacías y dos botones. Al presionar el
botón de "Genera", se crean dos números aleatorios. Cada número aleatorio se guarda
en un elemento <p>, que a su vez se guarda en una de las dos cajas superiores.
Una vez generados los números, se presiona el botón "Comparar", que compara el valor
de los dos párrafos anteriores y determina cual es el mayor. El párrafo con el número
más grande, se mueve a la última caja que se utiliza para almacenar el resultado de la
operación.
La página que se proporciona contiene todo el código HTML y CSS necesario. Además,
incluye todo el código JavaScript relativo a la pulsación de los botones y que se
estudiará con detalle en el siguiente capítulo.
Ver solución
Los métodos presentados anteriormente para el acceso a los atributos de los elementos,
son genéricos de XML. La versión de DOM específica para HTML incluye algunas
propiedades y métodos aún más directos y sencillos para el acceso a los atributos de los
elementos HTML y a sus propiedades CSS.
La principal ventaja del DOM para HTML es que todos los atributos de todos los
elementos HTML se transforman en propiedades de los nodos. De esta forma, es posible
acceder de forma directa a cualquier atributo de HTML. Si se considera el siguiente
elemento <img> de HTML con sus tres atributos:
Las ventajas de utilizar esta forma de acceder y modificar los atributos de los elementos
es que el código resultante es más sencillo y conciso. Por otra parte, algunas versiones
de Internet Explorer no implementan correctamente el método setAttribute(), lo que
provoca que, en ocasiones, los cambios realizados no se reflejan en la página HTML.
La única excepción que existe en esta forma de obtener el valor de los atributos HTML
es el atributo class. Como la palabra class está reservada por JavaScript para su uso
futuro, no es posible utilizarla para acceder al valor del atributo class de HTML. La
solución consiste en acceder a ese atributo mediante el nombre alternativo className:
El acceso a las propiedades CSS no es tan directo y sencillo como el acceso a los
atributos HTML. En primer lugar, los estilos CSS se pueden aplicar de varias formas
diferentes sobre un mismo elemento HTML. Si se establecen las propiedades CSS
mediante el atributo style de HTML:
Para acceder al valor de una propiedad CSS, se obtiene la referencia del nodo, se accede
a su propiedad style y a continuación se indica el nombre de la propiedad CSS cuyo
valor se quiere obtener. Aunque parece lógico que en el ejemplo anterior el valor
obtenido sea #C00, en realidad cada navegador obtiene el mismo valor de formas
diferentes:
Si el nombre de la propiedad CSS está formado por tres palabras, se realiza la misma
transformación. De esta forma, la propiedad border-top-style se accede en DOM
mediante el nombre borderTopStyle.
<p id="parrafo">...</p>
Todos los ejemplos anteriores hacen uso de la propiedad style para acceder o
establecer el valor de las propiedades CSS de los elementos. Sin embargo, esta
propiedad sólo permite acceder al valor de las propiedades CSS establecidas
directamente sobre el elemento HTML. En otras palabras, la propiedad style del nodo
sólo contiene el valor de las propiedades CSS establecidas mediante el atributo style
de HTML.
Por otra parte, los estilos CSS normalmente se aplican mediante reglas CSS incluidas en
archivos externos. Si se utiliza la propiedad style de DOM para acceder al valor de una
propiedad CSS establecida mediante una regla externa, el navegador no obtiene el valor
correcto:
// Código HTML
<p id="parrafo">...</p>
// Regla CSS
#parrafo { color: #008000; }
// Código JavaScript
var parrafo = document.getElementById("parrafo");
var color = parrafo.style.color; // color no almacena ningún valor
// Código HTML
<p id="parrafo">...</p>
// Regla CSS
#parrafo { color: #008000; }
A continuación se muestra una función compatible con todos los navegadores, creada
por el programador Robert Nyman y publicada en su blog personal:
// Código HTML
<p id="parrafo">...</p>
// Regla CSS
#parrafo { color: #008000; }
Las tablas son elementos muy comunes en las páginas HTML, por lo que DOM
proporciona métodos específicos para trabajar con ellas. Si se utilizan los métodos
tradicionales, crear una tabla es una tarea tediosa, por la gran cantidad de nodos de tipo
elemento y de tipo texto que se deben crear.
Propiedad/Método Descripción
Propiedad/Método Descripción
Propiedad/Método Descripción
<tfoot>
<tr>
<th scope="col"></th>
<th scope="col">Cabecera columna 1</th>
<th scope="col">Cabecera columna 2</th>
</tr>
</tfoot>
<tbody>
<tr>
<th scope="row">Cabecera fila 1</th>
<td>Celda 1 - 1</td>
<td>Celda 1 - 2</td>
</tr>
<tr>
<th scope="row">Cabecera fila 2</th>
<td>Celda 2 - 1</td>
<td>Celda 2 - 2</td>
</tr>
</tbody>
</table>
Borrar la primera fila de la tabla y la primera fila del cuerpo (sección <tbody>):
Por último, se muestra de forma resumida el código JavaScript necesario para crear la
tabla XHTML del ejemplo anterior:
cabecera = document.createElement('th');
cabecera.setAttribute('scope', 'col');
cabecera.innerHTML = 'Cabecera columna 1';
tabla.rows[0].appendChild(cabecera);
cabecera = document.createElement('th');
cabecera.setAttribute('scope', 'col');
cabecera.innerHTML = 'Cabecera columna 2';
tabla.rows[0].appendChild(cabecera);
cabecera = document.createElement("th");
cabecera.setAttribute('scope', 'row');
cabecera.innerHTML = 'Cabecera fila 1'
tabla.tBodies[0].rows[0].appendChild(cabecera);
tbody.rows[0].insertCell(1);
tbody.rows[0].cells[1].innerHTML = 'Celda 1 - 1';
// También se podría hacer:
// tbody.rows[0].cells[0].appendChild(document.createTextNode('Celda 1
- 1'));
tbody.rows[0].insertCell(2);
tbody.rows[0].cells[2].innerHTML = 'Celda 1 - 2';
El BOM está compuesto por varios objetos relacionados entre sí. El siguiente esquema
muestra los objetos de BOM y su relación:
En el esquema anterior, los objetos mostrados con varios recuadros superpuestos son
arrays. El resto de objetos, representados por un rectángulo individual, son objetos
simples. En cualquier caso, todos los objetos derivan del objeto window.
Si una página emplea frames, cada uno de ellos se almacena en el array frames, que
puede ser accedido numéricamente (window.frames[0]) o, si se ha indicado un nombre
al frame, mediante su nombre (window.frames["nombre del frame"]).
Como todos los demás objetos heredan directa o indirectamente del objeto window, no
es necesario indicarlo de forma explícita en el código JavaScript. En otras palabras:
window.frames[0] == frames[0]
window.document == document
BOM define cuatro métodos para manipular el tamaño y la posición de la ventana:
Los navegadores son cada vez menos permisivos con la modificación mediante
JavaScript de las propiedades de sus ventanas. De hecho, la mayoría de navegadores
permite a los usuarios bloquear el uso de JavaScript para realizar cambios de este tipo.
De esta forma, una aplicación nunca debe suponer que este tipo de funciones están
disponibles y funcionan de forma correta.
function muestraMensaje() {
alert("Han transcurrido 3 segundos desde que me programaron");
}
setTimeout(muestraMensaje, 3000);
function muestraMensaje() {
alert("Han transcurrido 3 segundos desde que me programaron");
}
var id = setTimeout(muestraMensaje, 3000);
En este caso, la estrategia consiste en establecer una cuenta atrás antes de llamar a la
función. Si la función se ejecuta correctamente, en cuanto finalice su ejecución se
elimina la cuenta atrás y continúa la ejecución normal del script. Si por cualquier
motivo la función no se ejecuta correctamente, la cuenta atrás se cumple y la aplicación
puede informar al usuario o reintentar la ejecución de la función.
setInterval(muestraMensaje, 1000);
function muestraMensaje() {
alert("Este mensaje se muestra cada segundo");
}
Algunas de las propiedades más importantes definidas por el objeto document son:
Propiedad Descripción
lastModified La fecha de la última modificación de la página
La URL desde la que se accedió a la página (es decir, la página anterior
referrer
en el array history)
title El texto de la etiqueta <title>
URL La URL de la página actual del navegador
Las propiedades title y URL son de lectura y escritura, por lo que además de obtener su
valor, se puede establecer de forma directa:
Array Descripción
anchors
Contiene todas las "anclas" de la página (los enlaces de tipo <a
name="nombre_ancla"></a>)
applets Contiene todos los applets de la página
Array Descripción
embeds Contiene todos los objetos embebidos en la página mediante la etiqueta
<embed>
forms Contiene todos los formularios de la página
images Contiene todas las imágenes de la página
links
Contiene todos los enlaces de la página (los elementos de tipo <a
href="enlace.html"></a>)
Los elementos de cada array del objeto document se pueden acceder mediante su índice
numérico o mediante el nombre del elemento en la página HTML. Si se considera por
ejemplo la siguiente página HTML:
<html>
<head><title>Pagina de ejemplo</title></head>
<body>
<p>Primer parrafo de la pagina</p>
<a href="otra_pagina.html">Un enlace</a>
<img src="logo.jpg" name="logotipo"/>
<form method="post" name="consultas">
<input type="text" name="id" />
<input type="submit" value="Enviar">
</form>
</body>
</html>
Para acceder a los elementos de la página se pueden emplear las funciones DOM o los
objetos de BOM:
Párrafo: document.getElementsByTagName("p")
Enlace: document.links[0]
Imagen: document.images[0] o document.images["logotipo"]
Formulario: document.forms[0] o document.forms["consultas"]
Una vez obtenida la referencia al elemento, se puede acceder al valor de sus atributos
HTML utilizando las propiedades de DOM. De esta forma, el método del formulario se
obtiene mediante document.forms["consultas"].method y la ruta de la imagen es
document.images[0].src.
Propieda
Descripción
d
Propieda
Descripción
d
El contenido de la URL que se encuentra después del signo # (para los
hash enlaces de las anclas)
http://www.ejemplo.com/ruta1/ruta2/pagina.html#seccion hash =
#seccion
El nombre del servidor
host http://www.ejemplo.com/ruta1/ruta2/pagina.html#seccion host =
www.ejemplo.com
La mayoría de las veces coincide con host, aunque en ocasiones, se eliminan
hostname las www del principio
http://www.ejemplo.com/ruta1/ruta2/pagina.html#seccion
hostname = www.ejemplo.com
La URL completa de la página actual
href http://www.ejemplo.com/ruta1/ruta2/pagina.html#seccion URL =
http://www.ejemplo.com/ruta1/ruta2/pagina.html#seccion
Todo el contenido que se encuentra después del host
pathname http://www.ejemplo.com/ruta1/ruta2/pagina.html#seccion
pathname = /ruta1/ruta2/pagina.html
Si se especifica en la URL, el puerto accedido
http://www.ejemplo.com:8080/ruta1/ruta2/pagina.html#seccion
port = 8080 La mayoría de URL no proporcionan un puerto, por lo que su
port
contenido es vacío
http://www.ejemplo.com/ruta1/ruta2/pagina.html#seccion port =
(vacío)
El protocolo empleado por la URL, es decir, todo lo que se encuentra antes
protocol de las dos barras inclinadas //
http://www.ejemplo.com/ruta1/ruta2/pagina.html#seccion
protocol = http:
Todo el contenido que se encuentra tras el símbolo ?, es decir, la consulta o
search "query string"
http://www.ejemplo.com/pagina.php?variable1=valor1&variable2=v
alor2 search = ?variable1=valor1&variable2=valor2
// Método assign()
location.assign("http://www.ejemplo.com"); // Equivalente a
location.href = "http://www.ejemplo.com"
// Método replace()
location.replace("http://www.ejemplo.com");
// Similar a assign(), salvo que se borra la página actual del array
history del navegador
// Método reload()
location.reload(true);
/* Recarga la página. Si el argumento es true, se carga la página
desde el servidor.
Si es false, se carga desde la cache del navegador */
Aunque es uno de los objetos menos estandarizados, algunas de sus propiedades son
comunes en casi todos los navegadores. A continuación se muestran algunas de esas
propiedades:
Propiedad Descripción
Cadena que representa el nombre del navegador (normalmente es
appCodeName
Mozilla)
appName Cadena que representa el nombre oficial del navegador
appMinorVersion
(Sólo Internet Explorer) Cadena que representa información extra
sobre la versión del navegador
appVersion Cadena que representa la versión del navegador
browserLanguage Cadena que representa el idioma del navegador
cookieEnabled Boolean que indica si las cookies están habilitadas
cpuClass
(Sólo Internet Explorer) Cadena que representa el tipo de CPU del
usuario ("x86", "68K", "PPC", "Alpha", "Other")
javaEnabled Boolean que indica si Java está habilitado
language Cadena que representa el idioma del navegador
mimeTypes Array de los tipos MIME registrados por el navegador
onLine
(Sólo Internet Explorer) Boolean que indica si el navegador está
conectado a Internet
oscpu (Sólo Firefox) Cadena que representa el sistema operativo o la CPU
platform
Cadena que representa la plataforma sobre la que se ejecuta el
navegador
plugins Array con la lista de plugins instalados en el navegador
preference()
(Sólo Firefox) Método empleado para establecer preferencias en el
navegador
Cadena que representa el nombre del producto (normalmente, es
product
Gecko)
productSub
Cadena que representa información adicional sobre el producto
(normalmente, la versión del motor Gecko)
securityPolicy Sólo Firefox
systemLanguage
(Sólo Internet Explorer) Cadena que representa el idioma del sistema
operativo
userAgent
Cadena que representa la cadena que el navegador emplea para
identificarse en los servidores
Propiedad Descripción
userLanguage
(Sólo Explorer) Cadena que representa el idioma del sistema
operativo
userProfile (Sólo Explorer) Objeto que permite acceder al perfil del usuario
El objeto navigator se emplea habitualmente para detectar el tipo y/o versión del
navegador en las aplicaciones cuyo código difiere para cada navegador. Además, se
emplea para detectar si el navegador tiene habilitadas las cookies y Java y también para
comprobar los plugins disponibles en el navegador.
Propiedad Descripción
availHeight Altura de pantalla disponible para las ventanas
availWidth Anchura de pantalla disponible para las ventanas
colorDepth Profundidad de color de la pantalla (32 bits normalmente)
height Altura total de la pantalla en píxel
width Anchura total de la pantalla en píxel
El siguiente ejemplo redimensiona una nueva ventana al tamaño máximo posible según
la pantalla del usuario:
window.moveTo(0, 0);
window.resizeTo(screen.availWidth, screen.availHeight);
Capítulo 6. Eventos
En la programación tradicional, las aplicaciones se ejecutan secuencialmente de
principio a fin para producir sus resultados. Sin embargo, en la actualidad el modelo
predominante es el de la programación basada en eventos. Los scripts y programas
esperan sin realizar ninguna tarea hasta que se produzca un evento. Una vez producido,
ejecutan alguna tarea asociada a la aparición de ese evento y cuando concluye, el script
o programa vuelve al estado de espera.
Por este motivo, muchas de las propiedades y métodos actuales relacionados con los
eventos son incompatibles con los de DOM. De hecho, navegadores como Internet
Explorer tratan los eventos siguiendo su propio modelo incompatible con el estándar.
Cada elemento XHTML tiene definida su propia lista de posibles eventos que se le
pueden asignar. Un mismo tipo de evento (por ejemplo, pinchar el botón izquierdo del
ratón) puede estar definido para varios elementos XHTML y un mismo elemento
XHTML puede tener asociados diferentes eventos.
El nombre de los eventos se construye mediante el prefijo on, seguido del nombre en
inglés de la acción asociada al evento. Así, el evento de pinchar un elemento con el
ratón se denomina onclick y el evento asociado a la acción de mover el ratón se
denomina onmousemove.
La siguiente tabla resume los eventos más importantes definidos por JavaScript:
Por otra parte, las acciones típicas que realiza un usuario en una página web pueden dar
lugar a una sucesión de eventos. Si se pulsa por ejemplo sobre un botón de tipo submit
se desencadenan los eventos onmousedown, onmouseup, onclick y onsubmit.
En las siguientes secciones se presentan las tres formas más utilizadas para indicar el
código que se ejecuta cuando se produce un evento.
Figura 6.2 El usuario pincha con el ratón sobre el botón que se muestra
Figura 6.3 Después de pinchar con el ratón, se muestra un mensaje en una nueva
ventana
El método consiste en incluir un atributo XHTML con el mismo nombre del evento que
se quiere procesar. En este caso, como se quiere mostrar un mensaje cuando se pincha
con el ratón sobre un botón, el evento es onclick.
El contenido del atributo es una cadena de texto que contiene todas las instrucciones
JavaScript que se ejecutan cuando se produce el evento. En este caso, el código
JavaScript es muy sencillo, ya que solamente se trata de mostrar un mensaje mediante la
función alert().
El evento onload es uno de los más utilizados porque, como se vio en el capítulo de
DOM, las funciones de acceso y manipulación de los nodos del árbol DOM solamente
están disponibles cuando la página se carga por completo.
En los eventos de JavaScript, se puede utilizar la palabra reservada this para referirse
al elemento XHTML sobre el que se está ejecutando el evento. Esta técnica es útil para
ejemplos como el siguiente, en el que se modifican las propiedades del elemento que
provoca el evento.
El uso de la variable this puede simplificar todos estos pasos, ya que dentro de un
manejador de eventos, la variable this equivale al elemento que ha provocado el
evento. Así, la variable this es igual al <div> de la página y por tanto
this.style.borderColor permite cambiar de forma directa el color del borde del
<div>:
Haciendo uso de la variable this, el código es mucho más sencillo de escribir, leer y
mantener.
function muestraMensaje() {
alert('Gracias por pinchar');
}
En las funciones externas no es posible utilizar la variable this de la misma forma que
en los manejadores insertados en los atributos XHTML. Por tanto, es necesario pasar la
variable this como parámetro a la función manejadora:
function resalta(elemento) {
switch(elemento.style.borderColor) {
case 'silver':
case 'silver silver silver silver':
case '#c0c0c0':
elemento.style.borderColor = 'black';
break;
case 'black':
case 'black black black black':
case '#000000':
elemento.style.borderColor = 'silver';
break;
}
}
Por otra parte, el ejemplo anterior se complica por la forma en la que los distintos
navegadores almacenan el valor de la propiedad borderColor. Mientras que Firefox
almacena (en caso de que los cuatro bordes coincidan en color) el valor simple black,
Internet Explorer lo almacena como black black black black y Opera almacena su
representación hexadecimal #000000.
Utilizar los atributos XHTML o las funciones externas para añadir manejadores de
eventos tiene un grave inconveniente: "ensucian" el código XHTML de la página.
function muestraMensaje() {
alert('Gracias por pinchar');
}
document.getElementById("pinchable").onclick = muestraMensaje;
Otra ventaja adicional de esta técnica es que las funciones externas pueden utilizar la
variable this referida al elemento que original el evento.
document.getElementById("pinchable");
document.getElementById("pinchable").onclick = ...
document.getElementById("pinchable").onclick = muestraMensaje;
El único inconveniente de este método es que los manejadores se asignan mediante las
funciones DOM, que solamente se pueden utilizar después de que la página se ha
cargado completamente. De esta forma, para que la asignación de los manejadores no
resulte errónea, es necesario asegurarse de que la página ya se ha cargado.
Una de las formas más sencillas de asegurar que cierto código se va a ejecutar después
de que la página se cargue por completo es utilizar el evento onload:
window.onload = function() {
document.getElementById("pinchable").onclick = muestraMensaje;
}
La técnica anterior utiliza una función anónima para asignar algunas instrucciones al
evento onload de la página (en este caso se ha establecido mediante el objeto window).
De esta forma, para asegurar que cierto código se va a ejecutar después de que la página
se haya cargado, sólo es necesario incluirlo en el interior de la siguiente construcción:
window.onload = function() {
...
}
Ejercicio 10
Cuando la sección se oculte, debe cambiar el mensaje del enlace asociado (pista:
propiedad innerHTML).
Ver solución
En este modelo de flujo de eventos, el orden que se sigue es desde el elemento más
específico hasta el elemento menos específico.
<html onclick="procesaEvento()">
<head><title>Ejemplo de flujo de eventos</title></head>
<body onclick="procesaEvento()">
<div onclick="procesaEvento()">Pincha aqui</div>
</body>
</html>
Cuando se pulsa sobre el texto "Pincha aquí" que se encuentra dentro del <div>, se
ejecutan los siguientes eventos en el orden que muestra el siguiente esquema:
El primer evento que se tiene en cuenta es el generado por el <div> que contiene el
mensaje. A continuación el navegador recorre los ascendentes del elemento hasta que
alcanza el nivel superior, que es el elemento document.
Este modelo de flujo de eventos es el que incluye el navegador Internet Explorer. Los
navegadores de la familia Mozilla (por ejemplo Firefox) también soportan este modelo,
pero ligeramente modificado. El anterior ejemplo en un navegador de la familia Mozilla
presenta el siguiente flujo de eventos:
Figura 6.5 Esquema del funcionamiento del "event bubbling" en los navegadores de
Mozilla (por ejemplo, Firefox)
Aunque el objeto window no es parte del DOM, el flujo de eventos implementado por
Mozilla recorre los ascendentes del elemento hasta el mismo objeto window, añadiendo
por tanto un evento más al modelo de Internet Explorer.
Los métodos requieren dos parámetros: el nombre del evento que se quiere manejar y
una referencia a la función encargada de procesar el evento. El nombre del evento se
debe indicar con el prefijo on incluido, como muestra el siguiente ejemplo:
function muestraMensaje() {
alert("Has pulsado el ratón");
}
var elDiv = document.getElementById("div_principal");
elDiv.attachEvent("onclick", muestraMensaje);
function muestraMensaje() {
alert("Has pulsado el ratón");
}
function muestraOtroMensaje() {
alert("Has pulsado el ratón y por eso se muestran estos mensajes");
}
Si el usuario pincha sobre el <div>, se muestran los dos mensajes de aviso que se han
asignado al evento.
La especificación DOM define otros dos métodos similares a los disponibles para
Internet Explorer y denominados addEventListener() y removeEventListener()
para asociar y desasociar manejadores de eventos.
La principal diferencia entre estos métodos y los anteriores es que en este caso se
requieren tres parámetros: el nombre del "event listener", una referencia a la función
encargada de procesar el evento y el tipo de flujo de eventos al que se aplica.
function muestraMensaje() {
alert("Has pulsado el ratón");
}
var elDiv = document.getElementById("div_principal");
elDiv.addEventListener("click", muestraMensaje, false);
function muestraMensaje() {
alert("Has pulsado el ratón");
}
function muestraOtroMensaje() {
alert("Has pulsado el ratón y por eso se muestran estos mensajes");
}
function muestraMensaje() {
alert("Has pulsado el ratón");
}
var elDiv = document.getElementById("div_principal");
elDiv.addEventListener("click", muestraMensaje, false);
El objeto event es el mecanismo definido por los navegadores para proporcionar toda
esa información. Se trata de un objeto que se crea automáticamente cuando se produce
un evento y que se destruye de forma automática cuando se han ejecutado todas las
funciones asignadas al evento.
Internet Explorer permite el acceso al objeto event a través del objeto window:
elDiv.onclick = function() {
var elEvento = window.event;
}
El estándar DOM especifica que el objeto event es el único parámetro que se debe
pasar a las funciones encargadas de procesar los eventos. Por tanto, en los navegadores
que siguen los estándares, se puede acceder al objeto event a través del array de los
argumentos de la función:
elDiv.onclick = function() {
var elEvento = arguments[0];
}
elDiv.onclick = function(elEvento) {
...
}
El funcionamiento de los navegadores que siguen los estándares puede parecer
"mágico", ya que en la declaración de la función se indica que tiene un parámetro, pero
en la aplicación no se pasa ningún parámetro a esa función. En realidad, los
navegadores que siguen los estándares crean automáticamente ese parámetro y lo pasan
siempre a la función encargada de manejar el evento.
A pesar de que el mecanismo definido por los navegadores para el objeto event es
similar, existen numerosas diferencias en cuanto las propiedades y métodos del objeto.
La siguiente tabla recoge las propiedades definidas para el objeto event en los
navegadores de la familia Internet Explorer:
Cadena
type El nombre del evento
de texto
Todas las propiedades salvo repeat son de lectura/escritura y por tanto, su valor se
puede leer y/o establecer.
La siguiente tabla recoge las propiedades definidas para el objeto event en los
navegadores que siguen los estándares:
Número
keyCode Indica el código numérico de la tecla pulsada
entero
Número
pageX Coordenada X de la posición del ratón respecto de la página
entero
Número
pageY Coordenada Y de la posición del ratón respecto de la página
entero
Cadena
type El nombre del evento
de texto
La tecla META es una tecla especial que se encuentra en algunos teclados de ordenadores
muy antiguos. Actualmente, en los ordenadores tipo PC se asimila a la tecla Alt o a la
tecla de Windows, mientras que en los ordenadores tipo Mac se asimila a la tecla
Command.
6.4.2.1. Similitudes
En ambos casos se utiliza la propiedad type para obtener el tipo de evento que se trata:
function procesaEvento(elEvento) {
if(elEvento.type == "click") {
alert("Has pulsado el raton");
}
else if(elEvento.type == "mouseover") {
alert("Has movido el raton");
}
}
elDiv.onclick = procesaEvento;
elDiv.onmouseover = procesaEvento;
Una forma más inmediata de comprobar si se han pulsado algunas teclas especiales, es
utilizar las propiedades shiftKey, altKey y ctrlKey.
Para obtener la posición del ratón respecto de la parte visible de la ventana, se emplean
las propiedades clientX y clientY. De la misma forma, para obtener la posición del
puntero del ratón respecto de la pantalla completa, se emplean las propiedades screenX
y screenY.
6.4.2.2. Diferencias
// Internet Explorer
var objetivo = elEvento.srcElement;
Por el contrario, en los navegadores que siguen los estándares la propiedad keyCode es
igual al código interno en los eventos de "pulsación de teclas" (onkeyup y onkeydown) y
es igual a 0 en los eventos de "escribir con teclas" (onkeypress).
En la práctica, esto supone que en los eventos onkeyup y onkeydown se puede utilizar la
misma propiedad en todos los navegadores:
function manejador(elEvento) {
var evento = elEvento || window.event;
alert("["+evento.type+"] El código de la tecla pulsada es " +
evento.keyCode);
}
document.onkeyup = manejador;
document.onkeydown = manejador;
En este caso, si se carga la página en cualquier navegador y se pulsa por ejemplo la tecla
a, se muestra el siguiente mensaje:
function manejador() {
var evento = window.event;
// Internet Explorer
var codigo = evento.keyCode;
}
document.onkeypress = manejador;
Sin embargo, en los navegadores que no son Internet Explorer, el código anterior es
igual a 0 para cualquier tecla pulsada. En estos navegadores que siguen los estándares,
se debe utilizar la propiedad charCode, que devuelve el código de la tecla pulsada, pero
solo para el evento onkeypress:
function manejador(elEvento) {
var evento = elEvento;
document.onkeypress = manejador;
function manejador(elEvento) {
var evento = elEvento || window.event;
var codigo = evento.charCode || evento.keyCode;
var caracter = String.fromCharCode(codigo);
}
document.onkeypress = manejador;
// Internet Explorer
elEvento.returnValue = false;
function limita(maximoCaracteres) {
var elemento = document.getElementById("texto");
if(elemento.value.length >= maximoCaracteres ) {
return false;
}
else {
return true;
}
}
El objeto event también permite detener completamente la ejecución del flujo normal
de eventos:
// Internet Explorer
elEvento.cancelBubble = true;
Eventos de ratón: se originan cuando el usuario emplea el ratón para realizar algunas
acciones.
Eventos de teclado: se originan cuando el usuario pulsa sobre cualquier tecla de su
teclado.
Eventos HTML: se originan cuando se producen cambios en la ventana del navegador o
cuando se producen ciertas interacciones entre el cliente y el servidor.
Eventos DOM: se originan cuando se produce un cambio en la estructura DOM de la
página. También se denominan "eventos de mutación".
Los eventos de ratón son, con mucha diferencia, los más empleados en las aplicaciones
web. Los eventos que se incluyen en esta clasificación son los siguientes:
Evento Descripción
dblclick Se produce cuando se pulsa dos veces el botón izquierdo del ratón
mouseup Se produce cuando se suelta cualquier botón del ratón que haya sido pulsado
Se produce (de forma continua) cuando el puntero del ratón se encuentra sobre
mousemove
un elemento
Todos los elementos de las páginas soportan los eventos de la tabla anterior.
6.5.1.1. Propiedades
El objeto event contiene las siguientes propiedades para los eventos de ratón:
Las coordenadas del ratón (todas las coordenadas diferentes relativas a los distintos
elementos)
La propiedad type
La propiedad srcElement (Internet Explorer) o target (DOM)
Las propiedades shiftKey, ctrlKey, altKey y metaKey (sólo DOM)
La propiedad button (sólo en los eventos mousedown, mousemove, mouseout,
mouseover y mouseup)
Evento Descripción
6.5.2.1. Propiedades
El objeto event contiene las siguientes propiedades para los eventos de teclado:
La propiedad keyCode
La propiedad charCode (sólo DOM)
La propiedad srcElement (Internet Explorer) o target (DOM)
Las propiedades shiftKey, ctrlKey, altKey y metaKey (sólo DOM)
Evento Descripción
Se produce en el objeto window cuando la página desaparece por completo (al cerrar
unload la ventana del navegador por ejemplo). En el elemento <object> cuando desaparece
el objeto.
reset Se produce cuando se pulsa sobre un botón de tipo reset (<input type="reset">)
Se produce en cualquier elemento que tenga una barra de scroll, cuando el usuario la
scroll
utiliza. El elemento <body> contiene la barra de scroll de la página completa
Uno de los eventos más utilizados es el evento load, ya que todas las manipulaciones
que se realizan mediante DOM requieren que la página esté cargada por completo y por
tanto, el árbol DOM se haya construido completamente.
Evento Descripción
En primer lugar, se crea el objeto que va a englobar todas las propiedades y métodos
relacionados con los eventos:
EventUtil.getEvent = function() {
if(window.event) { // Internet Explorer
return this.formatEvent(window.event);
}
else { // navegadores DOM
return EventUtil.getEvent.caller.arguments[0];
}
};
EventUtil.formatEvent = function(elEvento) {
// Detectar si el navegador actual es Internet Explorer
var esIE = navigator.userAgent.toLowerCase().indexOf('msie')!=-1;
if(esIE) {
elEvento.charCode = (elEvento.type=="keypress") ? elEvento.keyCode
: 0;
elEvento.eventPhase = 2;
elEvento.isChar = (elEvento.charCode > 0);
elEvento.pageX = elEvento.clientX + document.body.scrollLeft;
elEvento.pageY = elEvento.clientY + document.body.scrollTop;
elEvento.preventDefault = function() {
this.returnValue = false;
};
if(elEvento.type == "mouseout") {
elEvento.relatedTarget = elEvento.toElement;
}
else if(elEvento.type == "mouseover") {
elEvento.relatedTarget = elEvento.fromElement
}
elEvento.stopPropagation = function() {
this.cancelBubble = true;
};
elEvento.target = elEvento.srcElement;
elEvento.time = (new Date).getTime();
}
return elEvento;
}
Durante el desarrollo de OWA, se evaluaron dos opciones: un cliente formado sólo por
páginas HTML estáticas que se recargaban constantemente y un cliente realizado
completamente con HTML dinámico o DHTML. Alex Hopmann pudo ver las dos
opciones y se decantó por la basada en DHTML. Sin embargo, para ser realmente útil a
esta última le faltaba un componente esencial: "algo" que evitara tener que enviar
continuamente los formularios con datos al servidor.
Motivado por las posibilidades futuras de OWA, Alex creó en un solo fin de semana la
primera versión de lo que denominó XMLHTTP. La primera demostración de las
posibilidades de la nueva tecnología fue un éxito, pero faltaba lo más difícil: incluir esa
tecnología en el navegador Internet Explorer.
De hecho, el nombre del objeto (XMLHTTP) se eligió para tener una buena excusa que
justificara su inclusión en la librería XML de Internet Explorer, ya que este objeto está
mucho más relacionado con HTTP que con XML.
La aplicación AJAX completa más sencilla consiste en una adaptación del clásico "Hola
Mundo". En este caso, una aplicación JavaScript descarga un archivo del servidor y
muestra su contenido sin necesidad de recargar la página.
<script type="text/javascript">
function descargaArchivo() {
// Obtener la instancia del objeto XMLHttpRequest
if(window.XMLHttpRequest) {
peticion_http = new XMLHttpRequest();
}
else if(window.ActiveXObject) {
peticion_http = new ActiveXObject("Microsoft.XMLHTTP");
}
function muestraContenido() {
if(peticion_http.readyState == 4) {
if(peticion_http.status == 200) {
alert(peticion_http.responseText);
}
}
}
}
window.onload = descargaArchivo;
</script>
</head>
<body></body>
</html>
Todas las aplicaciones realizadas con técnicas de AJAX deben instanciar en primer
lugar el objeto XMLHttpRequest, que es el objeto clave que permite realizar
comunicaciones con el servidor en segundo plano, sin necesidad de recargar las páginas.
Los navegadores que siguen los estándares (Firefox, Safari, Opera, Internet Explorer 7 y
8) implementan el objeto XMLHttpRequest de forma nativa, por lo que se puede obtener
a través del objeto window. Los navegadores obsoletos (Internet Explorer 6 y anteriores)
implementan el objeto XMLHttpRequest como un objeto de tipo ActiveX.
Una vez obtenida la instancia del objeto XMLHttpRequest, se prepara la función que se
encarga de procesar la respuesta del servidor. La propiedad onreadystatechange del
objeto XMLHttpRequest permite indicar esta función directamente incluyendo su código
mediante una función anónima o indicando una referencia a una función independiente.
En el ejemplo anterior se indica directamente el nombre de la función:
peticion_http.onreadystatechange = muestraContenido;
El código anterior indica que cuando la aplicación reciba la respuesta del servidor, se
debe ejecutar la función muestraContenido(). Como es habitual, la referencia a la
función se indica mediante su nombre sin paréntesis, ya que de otro modo se estaría
ejecutando la función y almacenando el valor devuelto en la propiedad
onreadystatechange.
Las instrucciones anteriores realizan el tipo de petición más sencillo que se puede enviar
al servidor. En concreto, se trata de una petición de tipo GET simple que no envía ningún
parámetro al servidor. La petición HTTP se crea mediante el método open(), en el que
se incluye el tipo de petición (GET), la URL solicitada
(http://localhost/prueba.txt) y un tercer parámetro que vale true.
Una vez creada la petición HTTP, se envía al servidor mediante el método send(). Este
método incluye un parámetro que en el ejemplo anterior vale null. Más adelante se ven
en detalle todos los métodos y propiedades que permiten hacer las peticiones al
servidor.
Por último, cuando se recibe la respuesta del servidor, la aplicación ejecuta de forma
automática la función establecida anteriormente.
function muestraContenido() {
if(peticion_http.readyState == 4) {
if(peticion_http.status == 200) {
alert(peticion_http.responseText);
}
}
}
var READY_STATE_UNINITIALIZED = 0;
var READY_STATE_LOADING = 1;
var READY_STATE_LOADED = 2;
var READY_STATE_INTERACTIVE = 3;
var READY_STATE_COMPLETE = 4;
Como se verá más adelante, la respuesta del servidor sólo puede corresponder a alguno
de los cinco estados definidos por las variables anteriores. De esta forma, el código
puede utilizar el nombre de cada estado en vez de su valor numérico, por lo que se
facilita la lectura y el mantenimiento de las aplicaciones.
var peticion_http;
if(peticion_http) {
peticion_http.onreadystatechange = funcion;
peticion_http.open(metodo, url, true);
peticion_http.send(null);
}
}
La función definida admite tres parámetros: la URL del contenido que se va a cargar, el
método utilizado para realizar la petición HTTP y una referencia a la función que
procesa la respuesta del servidor.
function inicializa_xhr() {
if(window.XMLHttpRequest) {
return new XMLHttpRequest();
}
else if(window.ActiveXObject) {
return new ActiveXObject("Microsoft.XMLHTTP");
}
}
function muestraContenido() {
if(peticion_http.readyState == READY_STATE_COMPLETE) {
if(peticion_http.status == 200) {
alert(peticion_http.responseText);
}
}
}
function descargaArchivo() {
cargaContenido("http://localhost/holamundo.txt", "GET",
muestraContenido);
}
var READY_STATE_UNINITIALIZED=0;
var READY_STATE_LOADING=1;
var READY_STATE_LOADED=2;
var READY_STATE_INTERACTIVE=3;
var READY_STATE_COMPLETE=4;
var peticion_http;
if(peticion_http) {
peticion_http.onreadystatechange = funcion;
peticion_http.open(metodo, url, true);
peticion_http.send(null);
}
}
function inicializa_xhr() {
if(window.XMLHttpRequest) {
return new XMLHttpRequest();
}
else if(window.ActiveXObject) {
return new ActiveXObject("Microsoft.XMLHTTP");
}
}
function muestraContenido() {
if(peticion_http.readyState == READY_STATE_COMPLETE) {
if(peticion_http.status == 200) {
alert(peticion_http.responseText);
}
}
}
function descargaArchivo() {
cargaContenido("http://localhost/holamundo.txt", "GET",
muestraContenido);
}
window.onload = descargaArchivo;
</script>
</head>
<body></body>
</html>
Ejercicio 11
Ver solución
Propiedad Descripción
readyState Valor numérico (entero) que almacena el estado de la petición
responseText El contenido de la respuesta del servidor en forma de cadena de texto
responseXML
El contenido de la respuesta del servidor en formato XML. El objeto
devuelto se puede procesar como un objeto DOM
status El código de estado HTTP devuelto por el servidor (200 para una
Propiedad Descripción
respuesta correcta, 404 para "No encontrado", 500 para un error de
servidor, etc.)
statusText
El código de estado HTTP devuelto por el servidor en forma de cadena
de texto: "OK", "Not Found", "Internal Server Error", etc.
Valor Descripción
0 No inicializado (objeto creado, pero no se ha invocado el método open)
1 Cargando (objeto creado, pero no se ha invocado el método send)
2 Cargado (se ha invocado el método send, pero el servidor aún no ha respondido)
Interactivo (se han recibido algunos datos, aunque no se puede emplear la
3
propiedad responseText)
4 Completo (se han recibido todos los datos de la respuesta del servidor)
Método Descripción
abort() Detiene la petición actual
getAllResponseHeaders()
Devuelve una cadena de texto con todas las
cabeceras de la respuesta del servidor
getResponseHeader("cabecera")
Devuelve una cadena de texto con el contenido
de la cabecera solicitada
Responsable de manejar los eventos que se
producen. Se invoca cada vez que se produce un
onreadystatechange cambio en el estado de la petición HTTP.
Normalmente es una referencia a una función
JavaScript
Establece los parámetros de la petición que se
open("metodo", "url")
realiza al servidor. Los parámetros necesarios son
el método HTTP empleado y la URL destino
(puede indicarse de forma absoluta o relativa)
send(contenido) Realiza la petición HTTP al servidor
Permite establecer cabeceras personalizadas en la
setRequestHeader("cabecera",
"valor") petición HTTP. Se debe invocar el método
open() antes que setRequestHeader()
El método open() requiere dos parámetros (método HTTP y URL) y acepta de forma
opcional otros tres parámetros. Definición formal del método open():
Por defecto, las peticiones realizadas son asíncronas. Si se indica un valor false al
tercer parámetro, la petición se realiza de forma síncrona, esto es, se detiene la
ejecución de la aplicación hasta que se recibe de forma completa la respuesta del
servidor.
Los últimos dos parámetros opcionales permiten indicar un nombre de usuario y una
contraseña válidos para acceder al recurso solicitado.
Por otra parte, el método send() requiere de un parámetro que indica la información
que se va a enviar al servidor junto con la petición HTTP. Si no se envían datos, se debe
indicar un valor igual a null. En otro caso, se puede indicar como parámetro una
cadena de texto, un array de bytes o un objeto XML DOM.
El siguiente código ha sido adaptado del excelente libro "Ajax in Action", escrito por
Dave Crane, Eric Pascarello y Darren James y publicado por la editorial Manning.
net.READY_STATE_UNINITIALIZED=0;
net.READY_STATE_LOADING=1;
net.READY_STATE_LOADED=2;
net.READY_STATE_INTERACTIVE=3;
net.READY_STATE_COMPLETE=4;
// Constructor
net.CargadorContenidos = function(url, funcion, funcionError) {
this.url = url;
this.req = null;
this.onload = funcion;
this.onerror = (funcionError) ? funcionError : this.defaultError;
this.cargaContenidoXML(url);
}
net.CargadorContenidos.prototype = {
cargaContenidoXML: function(url) {
if(window.XMLHttpRequest) {
this.req = new XMLHttpRequest();
}
else if(window.ActiveXObject) {
this.req = new ActiveXObject("Microsoft.XMLHTTP");
}
if(this.req) {
try {
var loader = this;
this.req.onreadystatechange = function() {
loader.onReadyState.call(loader);
}
this.req.open('GET', url, true);
this.req.send(null);
} catch(err) {
this.onerror.call(this);
}
}
},
onReadyState: function() {
var req = this.req;
var ready = req.readyState;
if(ready == net.READY_STATE_COMPLETE) {
var httpStatus = req.status;
if(httpStatus == 200 || httpStatus == 0) {
this.onload.call(this);
}
else {
this.onerror.call(this);
}
}
},
defaultError: function() {
alert("Se ha producido un error al obtener los datos"
+ "\n\nreadyState:" + this.req.readyState
+ "\nstatus: " + this.req.status
+ "\nheaders: " + this.req.getAllResponseHeaders());
}
}
function muestraContenido() {
alert(this.req.responseText);
}
function cargaContenidos() {
var cargador = new
net.CargadorContenidos("http://localhost/holamundo.txt",
muestraContenido);
}
window.onload = cargaContenidos;
En el ejemplo anterior, la aplicación muestra un mensaje con los contenidos de la URL
indicada:
Por otra parte, si la URL que se quiere cargar no es válida o el servidor no responde, la
aplicación muestra el siguiente mensaje de error:
El código del cargador de contenidos hace un uso intensivo de objetos, JSON, funciones
anónimas y uso del objeto this. Seguidamente, se detalla el funcionamiento de cada
una de sus partes.
El primer elemento importante del código fuente es la definición del objeto net.
Se trata de una variable global que encapsula todas las propiedades y métodos relativos
a las operaciones relacionadas con las comunicaciones por red. De cierto modo, esta
variable global simula el funcionamiento de los namespaces ya que evita la colisión
entre nombres de propiedades y métodos diferentes.
Aunque el constructor define tres parámetros diferentes, en realidad solamente los dos
primeros son obligatorios. De esta forma, se inicializa el valor de algunas variables del
objeto, se comprueba si se ha definido la función que se emplea en caso de error (si no
se ha definido, se emplea una función genérica definida más adelante) y se invoca el
método responsable de cargar el recurso solicitado (cargaContenidoXML).
net.CargadorContenidos.prototype = {
cargaContenidoXML:function(url) {
...
},
onReadyState:function() {
...
},
defaultError:function() {
...
}
}
defaultError:function() {
alert("Se ha producido un error al obtener los datos"
+ "\n\nreadyState:" + this.req.readyState
+ "\nstatus: " + this.req.status
+ "\nheaders: " + this.req.getAllResponseHeaders());
}
onReadyState: function() {
var req = this.req;
var ready = req.readyState;
if(ready == net.READY_STATE_COMPLETE) {
var httpStatus = req.status;
if(httpStatus == 200 || httpStatus == 0) {
this.onload.call(this);
} else {
this.onerror.call(this);
}
}
}
Tras comprobar que la respuesta del servidor está disponible y es correcta, se realiza la
llamada a la función que realmente procesa la respuesta del servidor de acuerdo a las
necesidades de la aplicación.
this.onload.call(this);
Como ya se vio en los capítulos anteriores, el método call() es uno de los métodos
definidos para el objeto Function(), y por tanto disponible para todas las funciones de
JavaScript. Empleando el método call() es posible obligar a una función a ejecutarse
sobre un objeto concreto. En otras palabras, empleando el método call() sobre una
función, es posible que dentro de esa función el objeto this se resuelva como el objeto
pasado como parámetro en el método call().
cargaContenidoXML:function(url) {
if(window.XMLHttpRequest) {
this.req = new XMLHttpRequest();
}
else if(window.ActiveXObject) {
this.req = new ActiveXObject("Microsoft.XMLHTTP");
}
if(this.req) {
try {
var loader=this;
this.req.onreadystatechange = function() {
loader.onReadyState.call(loader);
}
this.req.open('GET', url, true);
this.req.send(null);
} catch(err) {
this.onerror.call(this);
}
}
}
En primer lugar, se obtiene una instancia del objeto XMLHttpRequest en función del
tipo de navegador. Si se ha obtenido correctamente la instancia, se ejecutan las
instrucciones más importantes del método cargaContenidoXML:
this.req.onreadystatechange = function() {
this.onReadyState.call(loader);
}
Sin embargo, desde el interior de esa función anónima si es posible acceder a las
variables definidas en la función exterior que la engloba. Así, desde el interior de la
función anónima sí que es posible acceder a la instancia del objeto
net.CargadorContenidos que se almacenó anteriormente.
var loader=this;
this.req.onreadystatechange = function() {
// loader.onReadyState.call(loader);
loader.onReadyState();
}
Ejercicio 12
La página HTML proporcionada incluye una zona llamada ticker en la que se deben
mostrar noticias generadas por el servidor. Añadir el código JavaScript necesario para:
1. De forma periódica cada cierto tiempo (por ejemplo cada segundo) se realiza
una petición al servidor mediante AJAX y se muestra el contenido de la
respuesta en la zona reservada para las noticias.
2. Además del contenido enviado por el servidor, se debe mostrar la hora en la que
se ha recibido la respuesta.
3. Cuando se pulse el botón "Detener", la aplicación detiene las peticiones
periódicas al servidor. Si se vuelve a pulsar sobre ese botón, se reanudan las
peticiones periódicas.
4. Añadir la lógica de los botones "Anterior" y "Siguiente", que detienen las
peticiones al servidor y permiten mostrar los contenidos anteriores o posteriores
al que se muestra en ese momento.
5. Cuando se recibe una respuesta del servidor, se resalta visualmente la zona
llamada ticker.
6. Modificar la aplicación para que se reutilice continuamente el mismo objeto
XMLHttpRequest para hacer las diferentes peticiones.
Ver solución
El objeto XMLHttpRequest puede enviar parámetros tanto con el método GET como
con el método POST de HTTP. En ambos casos, los parámetros se envían como una
serie de pares clave/valor concatenados por símbolos &. El siguiente ejemplo muestra
una URL que envía parámetros al servidor mediante el método GET:
http://localhost/aplicacion?parametro1=valor1¶metro2=valor2¶me
tro3=valor3
La principal diferencia entre ambos métodos es que mediante el método POST los
parámetros se envían en el cuerpo de la petición y mediante el método GET los
parámetros se concatenan a la URL accedida. El método GET se utiliza cuando se
accede a un recurso que depende de la información proporcionada por el usuario. El
método POST se utiliza en operaciones que crean, borran o actualizan información.
<form>
<label for="fecha_nacimiento">Fecha de nacimiento:</label>
<input type="text" id="fecha_nacimiento" name="fecha_nacimiento"
/><br/>
<label for="telefono">Telefono:</label>
<input type="text" id="telefono" name="telefono" /><br/>
<div id="respuesta"></div>
var READY_STATE_COMPLETE=4;
var peticion_http = null;
function inicializa_xhr() {
if(window.XMLHttpRequest) {
return new XMLHttpRequest();
}
else if(window.ActiveXObject) {
return new ActiveXObject("Microsoft.XMLHTTP");
}
}
function crea_query_string() {
var fecha = document.getElementById("fecha_nacimiento");
var cp = document.getElementById("codigo_postal");
var telefono = document.getElementById("telefono");
function valida() {
peticion_http = inicializa_xhr();
if(peticion_http) {
peticion_http.onreadystatechange = procesaRespuesta;
peticion_http.open("POST", "http://localhost/validaDatos.php",
true);
peticion_http.setRequestHeader("Content-Type", "application/x-www-
form-urlencoded");
var query_string = crea_query_string();
peticion_http.send(query_string);
}
}
function procesaRespuesta() {
if(peticion_http.readyState == READY_STATE_COMPLETE) {
if(peticion_http.status == 200) {
document.getElementById("respuesta").innerHTML =
peticion_http.responseText;
}
}
}
peticion_http.setRequestHeader("Content-Type", "application/x-www-
form-urlencoded");
peticion_http.send(query_string);
peticion_http.setRequestHeader("Content-Type", "application/x-www-
form-urlencoded");
Por otra parte, el método send() es el que se encarga de enviar los parámetros al
servidor. En todos los ejemplos anteriores se utilizaba la instrucción send(null) para
indicar que no se envían parámetros al servidor. Sin embargo, en este caso la petición si
que va a enviar los parámetros.
La función anterior obtiene el valor de todos los campos del formulario y los concatena
junto con el nombre de cada parámetro para formar la cadena de texto que se envía al
servidor. El uso de la función encodeURIComponent() es imprescindible para evitar
problemas con algunos caracteres especiales.
Las sustituciones más conocidas son las de los espacios en blanco por %20, y la del
símbolo & por %26. Sin embargo, como se muestra en el siguiente ejemplo, también se
sustituyen todos los acentos y cualquier otro carácter que no se puede incluir
directamente en una URL:
En las aplicaciones reales, las validaciones de datos mediante AJAX sólo se utilizan en
el caso de validaciones complejas que no se pueden realizar mediante el uso de código
JavaScript básico. En general, las validaciones complejas requieren el uso de bases de
datos: comprobar que un nombre de usuario no esté previamente registrado, comprobar
que la localidad se corresponde con el código postal indicado, etc.
Ejercicio 13
1. Crear un script que compruebe con AJAX y la ayuda del servidor si el nombre
escogido por el usuario está libre o no.
2. El script del servidor se llama compruebaDisponibilidad.php y el parámetro
que contiene el nombre se llama login.
3. La respuesta del servidor es "si" o "no", en función de si el nombre de usuario
está libre y se puede utilizar o ya ha sido ocupado por otro usuario.
4. A partir de la respuesta del servidor, mostrar un mensaje al usuario indicando el
resultado de la comprobación.
Ver solución
Se han añadido tres nuevos parámetros: el método HTTP empleado, los parámetros que
se envían al servidor junto con la petición y el valor de la cabecera content-type.
if(contentType) {
this.req.setRequestHeader("Content-Type", contentType);
}
this.req.send(parametros);
net.READY_STATE_UNINITIALIZED=0;
net.READY_STATE_LOADING=1;
net.READY_STATE_LOADED=2;
net.READY_STATE_INTERACTIVE=3;
net.READY_STATE_COMPLETE=4;
// Constructor
net.CargadorContenidos = function(url, funcion, funcionError, metodo,
parametros, contentType) {
this.url = url;
this.req = null;
this.onload = funcion;
this.onerror = (funcionError) ? funcionError : this.defaultError;
this.cargaContenidoXML(url, metodo, parametros, contentType);
}
net.CargadorContenidos.prototype = {
cargaContenidoXML: function(url, metodo, parametros, contentType) {
if(window.XMLHttpRequest) {
this.req = new XMLHttpRequest();
}
else if(window.ActiveXObject) {
this.req = new ActiveXObject("Microsoft.XMLHTTP");
}
if(this.req) {
try {
var loader = this;
this.req.onreadystatechange = function() {
loader.onReadyState.call(loader);
}
this.req.open(metodo, url, true);
if(contentType) {
this.req.setRequestHeader("Content-Type", contentType);
}
this.req.send(parametros);
} catch(err) {
this.onerror.call(this);
}
}
},
onReadyState: function() {
var req = this.req;
var ready = req.readyState;
if(ready == net.READY_STATE_COMPLETE) {
var httpStatus = req.status;
if(httpStatus == 200 || httpStatus == 0) {
this.onload.call(this);
}
else {
this.onerror.call(this);
}
}
},
defaultError: function() {
alert("Se ha producido un error al obtener los datos"
+ "\n\nreadyState:" + this.req.readyState
+ "\nstatus: " + this.req.status
+ "\nheaders: " + this.req.getAllResponseHeaders());
}
}
La flexibilidad del objeto XMLHttpRequest permite el envío de los parámetros por otros
medios alternativos a la tradicional query string. De esta forma, si la aplicación del
servidor así lo requeire, es posible realizar una petición al servidor enviando los
parámetros en formato XML.
A continuación se modifica el ejemplo anterior para enviar los datos del usuario en
forma de documento XML. En primer lugar, se modifica la llamada a la función que
construye la query string:
function valida() {
peticion_http = inicializa_xhr();
if(peticion_http) {
peticion_http.onreadystatechange = procesaRespuesta;
peticion_http.open("POST", "http://localhost/validaDatos.php",
true);
var parametros_xml = crea_xml();
peticion_http.setRequestHeader("Content-Type", "application/x-www-
form-urlencoded");
peticion_http.send(parametros_xml);
}
}
function crea_xml() {
var fecha = document.getElementById("fecha_nacimiento");
var cp = document.getElementById("codigo_postal");
var telefono = document.getElementById("telefono");
El método send() del objeto XMLHttpRequest permite el envío de una cadena de texto
y de un documento XML. Sin embargo, en el ejemplo anterior se ha optado por una
solución intermedia: una cadena de texto que representa un documento XML. El motivo
es que no existe a día de hoy un método robusto y que se pueda emplear en la mayoría
de navegadores para la creación de documentos XML completos.
En este caso, se modifica la respuesta del servidor para que no sea un texto sencillo,
sino que la respuesta esté definida mediante un documento XML:
<respuesta>
<mensaje>...</mensaje>
<parametros>
<telefono>...</telefono>
<codigo_postal>...</codigo_postal>
<fecha_nacimiento>...</fecha_nacimiento>
</parametros>
</respuesta>
function procesaRespuesta() {
if(peticion_http.readyState == READY_STATE_COMPLETE) {
if(peticion_http.status == 200) {
var documento_xml = peticion_http.responseXML;
var root = documento_xml.getElementsByTagName("respuesta")[0];
var telefono =
parametros.getElementsByTagName("telefono")[0].firstChild.nodeValue;
var fecha_nacimiento =
parametros.getElementsByTagName("fecha_nacimiento")[0].firstChild.node
Value;
var codigo_postal =
parametros.getElementsByTagName("codigo_postal")[0].firstChild.nodeVal
ue;
document.getElementById("respuesta").innerHTML = mensaje +
"<br/>" + "Fecha nacimiento = " + fecha_nacimiento + "<br/>" + "Codigo
postal = " + codigo_postal + "<br/>" + "Telefono = " + telefono;
}
}
}
El mecanismo para obtener los datos varía mucho según cada documento XML, pero en
general, se trata de obtener el valor almacenado en algunos elementos XML que a su
vez pueden ser descendientes de otros elementos. Para obtener el primer elemento que
se corresponde con una etiqueta XML, se utiliza la siguiente instrucción:
Una vez obtenido el elemento, para obtener su valor se debe acceder a su primer nodo
hijo (que es el nodo de tipo texto que almacena el valor) y obtener la propiedad
nodeValue, que es la propiedad que guarda el texto correspondiente al valor de la
etiqueta:
var tfno =
parametros.getElementsByTagName("telefono")[0].firstChild.nodeValue;
Ejercicio 14
Modificar la lista anterior para que muestre enlaces para cada uno de los nombres
alternativos. Al pinchar sobre el enlace de un nombre alternativo, se copia en el cuadro
de texto del login del usuario.
Ver solución
Aunque el formato XML está soportado por casi todos los lenguajes de programación,
por muchas aplicaciones y es una tecnología madura y probada, en algunas ocasiones es
más útil intercambiar información con el servidor en formato JSON.
JSON es un formato mucho más compacto y ligero que XML. Además, es mucho más
fácil de procesar en el navegador del usuario. Afortunadamente, cada vez existen más
utilidades para procesar y generar el formato JSON en los diferentes lenguajes de
programación del servidor (PHP, Java, C#, etc.)
El ejemplo mostrado anteriormente para procesar las respuestas XML del servidor se
puede reescribir utilizando respuestas JSON. En este caso, la respuesta que genera el
servidor es mucho más concisa:
{
mensaje: "...",
parametros: {telefono: "...", codigo_postal: "...", fecha_nacimiento:
"..." }
}
function procesaRespuesta() {
if(http_request.readyState == READY_STATE_COMPLETE) {
if(http_request.status == 200) {
var respuesta_json = http_request.responseText;
var objeto_json = eval("("+respuesta_json+")");
Sin embargo, esta propiedad solamente devuelve la respuesta del servidor en forma de
cadena de texto. Para trabajar con el código JSON devuelto, se debe transformar esa
cadena de texto en un objeto JSON. La forma más sencilla de realizar esa conversión es
mediante la función eval(), en la que deben añadirse paréntesis al principio y al final
para realizar la evaluación de forma correcta:
Una vez realizada la transformación, el objeto JSON ya permite acceder a sus métodos
y propiedades mediante la notación de puntos tradicional. Comparado con las respuestas
XML, este procedimiento permite acceder a la información devuelta por el servidor de
forma mucho más simple:
// Con JSON
var fecha_nacimiento = objeto_json.parametros.fecha_nacimiento;
// Con XML
var parametros = root.getElementsByTagName("parametros")[0];
var fecha_nacimiento =
parametros.getElementsByTagName("fecha_nacimiento")[0].firstChild.node
Value;
Además de las librerías para JavaScript, están disponibles otras librerías para muchos
otros lenguajes de programación habituales. Empleando la librería desarrollada para
Java, es posible procesar la petición JSON realizada por un cliente:
import org.json.JSONObject;
...
String cadena_json = "{propiedad: valor, codigo_postal: otro_valor}";
JSONObject objeto_json = new JSONObject(cadena_json);
String codigo_postal = objeto_json.getString("codigo_postal");
Ejercicio 15
Rehacer el ejercicio 14 para procesar respuestas del servidor en formato JSON. Los
cambios producidos son:
Ver solución
7.7. Seguridad
La ejecución de aplicaciones JavaScript puede suponer un riesgo para el usuario que
permite su ejecución. Por este motivo, los navegadores restringen la ejecución de todo
código JavaScript a un entorno de ejecución limitado y prácticamente sin recursos ni
permisos para realizar tareas básicas.
Las aplicaciones JavaScript no pueden leer ni escribir ningún archivo del sistema en el
que se ejecutan. Tampoco pueden establecer conexiones de red con dominios distintos
al dominio en el que se aloja la aplicación JavaScript. Además, un script sólo puede
cerrar aquellas ventanas de navegador que ha abierto ese script.
http://www.ejemplo.com:8080/scripts/codigo2.js
https://www.ejemplo.com/scripts/codigo2.js
http://192.168.0.1/scripts/codigo2.js
http://scripts.ejemplo.com/codigo2.js
8.1.1. Contexto
El mayor inconveniente de este tipo de listas se produce cuando existen un gran número
de opciones posibles. Si se considera por ejemplo el caso de una tienda, en la primera
lista desplegable se pueden mostrar decenas de productos y en la segunda lista se
muestran los diferentes modelos de cada producto y sus precios.
Por otra parte, se puede optar por recargar completamente la página cada vez que se
selecciona un valor diferente en la primera lista desplegable. Sin embargo, recargar la
página entera cada vez que se selecciona un valor, aumenta la carga en el servidor y el
tiempo de espera del usuario.
Una posible solución intermedia consiste en actualizar las listas desplegables mediante
AJAX. Los valores de la primera lista se incluyen en la página web y cuando se
selecciona un valor de esta lista, se realiza una consulta al servidor que devuelve los
valores que se deben mostrar en la otra lista desplegable.
Ejercicio 16
Crear un script que cargue de forma dinámica mediante AJAX la lista de provincias de
un país y la lista de los municipios de cada provincia seleccionada.
<provincias>
<provincia>
<codigo>01</codigo>
<nombre>Álava</nombre>
</provincia>
...
</provincias>
Para insertar las opciones en la lista desplegable, se pueden utilizar dos técnicas:
<municipios>
<municipio>
<codigo>0014</codigo>
<nombre>Alegría-Dulantzi</nombre>
</municipio>
...
</municipios>
Ver solución
Ejercicio 17
Modificar el ejercicio anterior para soportar las respuestas del servidor en formato
JSON. Los cambios introducidos son los siguientes:
Ver solución
Figura 8.2 Aspecto final del teclado virtual construido con AJAX
Ejercicio 18
Cada uno de los teclados correspondientes a un idioma se carga desde el servidor, para
no sobrecargar la aplicación. El teclado de un idioma concreto está formado por varios
teclados alternativos o variantes. Así, se encuentra el teclado normal para las teclas que
se muestran inicialmente, el teclado caps con las teclas que se escriben al pulsar sobre
la tecla Bloq. Mayúsculas, el teclado shift que contiene los símbolos que se escriben
al pulsar sobre la tecla Shift y el teclado altgr que contiene los símbolos que se
pueden escribir después de pulsar la tecla Alt Gr.
Por tanto, cada idioma tiene cuatro teclados diferentes: normal, caps, shift y altgr.
Inicialmente, el script proporciona el objeto teclados con un elemento llamado es que
contiene los cuatro teclados correspondientes al idioma español.
Figura 8.3 Detalle del teclado para el idioma español y la variante "normal"
Figura 8.4 Detalle del teclado para el idioma español y la variante "caps"
Figura 8.5 Detalle del teclado para el idioma español y la variante "shift"
Figura 8.6 Detalle del teclado para el idioma español y la variante "altgr"
1) Crear una función llamada cargaTeclado() que muestre en cada tecla virtual el
valor de la tecla correspondiente al teclado de un idioma y una variante determinados.
4) Añadir la lógica para tratar las "teclas especiales". Para ello, añadir un evento
adecuado que llame a la función pulsaTeclaEspecial() cuando el usuario pulse sobre
Enter, Tabulador, Barra Espaciadora y Borrado (BackSpace). En cada caso, se
debe añadir al array de teclas pulsadas el carácter correspondiente: \n, \t, espacio en
blanco y el borrado de la última tecla pulsada.
5) Modificar la función mostrarContenidos() para que antes de mostrar las teclas que
se han pulsado, convierta los caracteres especiales en caracteres correctos para
mostrarlos en un elemento HTML: las nuevas líneas (\n) se transforman en <br/>, los
espacios en blanco se transforman en y el tabulador (\t) se transforma en
.
6) Cuando se pulsa la tecla Bloq. Mayús. o Shift o Alt Gr, se debe cambiar la
variante del teclado actual. Para ello, existen las variantes caps para las mayúsculas,
shift para los símbolos de la tecla Shift y altgr para los símbolos que aparecen
cuando se pulsa la tecla AltGr. Añadir a estas teclas especiales el evento adecuado para
que se ejecute la función pulsaTeclaEspecial() en la que se deben realizar las tareas
que correspondan a cada tecla. Además, debería crearse una variable global llamada
estado que almacene en todo momento el estado de pulsación de estas teclas
especiales, ya que el resultado no es el mismo si se pulsa la tecla de mayúsculas estando
o no estando pulsada anteriormente.
7) Una vez configurado el script básico del teclado virtual, se van a añadir los elementos
relativos a la comunicación con el servidor. En primer lugar, al cargar la página se
muestran en una lista desplegable todos los idiomas disponibles. El script del servidor
se llama tecladoVirtual.php y el envío de parámetros se realiza mediante POST. Para
cargar los idiomas disponibles, el parámetro que se debe utilizar es accion y su valor es
listaIdiomas. La respuesta del servidor es un objeto JSON con los códigos y nombres
de cada idioma, además del código del idioma que se carga al principio:
Figura 8.7 Lista desplegable con los idiomas disponibles para el teclado virtual
Los teclados de cada idioma con todas sus variantes también se descargan desde el
servidor. El script es tecladoVirtual.php, en este caso la acción es cargaTeclado y
se debe pasar otro parámetro llamado idioma con el código del idioma cuyo teclado se
quiere cargar desde el servidor.
Figura 8.8 Detalle del teclado para el idioma ruso y la variante "normal"
Figura 8.9 Detalle del teclado para el idioma griego y la variante "normal"
10) Se puede añadir una pequeña mejora visual al teclado virtual: existe una clase de
CSS llamada pulsada y que se puede utilizar para resaltar de forma clara la tecla que se
ha pulsado. Utilizar esa clase para iluminar durante un breve espacio de tiempo la tecla
pulsada en cada momento.
Descargar archivo ZIP con la página HTML, las imágenes y el script tecladoVirtual.php
Ver solución
8.3. Autocompletar
8.3.1. Contexto
Algunas veces, se presenta al usuario un cuadro de texto en el que tiene que introducir
un valor que pertenece a un grupo muy grande de datos. Algunos casos habituales son:
una dirección de correo electrónico que pertenezca a la libreta de direcciones del
usuario, el nombre válido de un municipio de un país, el nombre de un empleado de una
empresa grande, etc.
En la mayoría de casos, utilizar una lista desplegable que muestre todos los valores es
completamente inviable, ya que pueden existir miles de posibles valores. Por otra parte,
un cuadro de texto simple resulta de poca utilidad para el usuario. La solución consiste
en combinar un cuadro de texto y una lista desplegable mediante AJAX.
Ejercicio 19
1) Al cargar la página, se debe crear un elemento HTML de tipo <div> en el que se van
a mostrar las sugerencias enviadas por el servidor.
2) Cuando se pulse una tecla sobre el cuadro de texto, se debe ejecutar la función
autocompleta(). Desde esta función, se debe llamar a la función responsable de
obtener la lista de municipios del servidor. El script se llama
autocompletaMunicipios.php, el parámetro que se envía mediante POST, se llama
municipio y debe contener la cadena de texto escrita por el usuario.
El servidor responde con un array en formato JSON con la lista de municipios cuyo
nombre comienza por el texto enviado. Ejemplo de respuesta del servidor:
5) Para mejorar el rendimiento de la aplicación, añadir una cache para las sugerencias.
Cada vez que se recibe una lista de sugerencias del servidor, se almacena en un objeto
que relaciona el texto que ha introducido el usuario y la respuesta del servidor. Ejemplo:
{
"a": ["Ababuj", "Abades", "Abadía", "Abadín", "Abadiño", "Abáigar",
"Abajas", "Abaltzisketa", "Abánades", "Abanilla", "Abanto y Ciérvana-
Abanto Zierbena", "Abanto", "Abarán", "Abarca de Campos", "Abárzuza",
"Abaurregaina/Abaurrea Alta", "Abaurrepea/Abaurrea Baja", "Abegondo",
"Abejar", "Abejuela", "Abella de la Conca"],
Ver solución
9.1.1. Contexto
Las aplicaciones JavaScript ejecutadas en los navegadores tienen unas restricciones muy
estrictas en cuanto a su seguridad. Además de no poder acceder a recursos locales como
archivos y directorios, los scripts solamente pueden realizar conexiones de red con el
mismo dominio al que pertenece la aplicación JavaScript.
Ejercicio 20
En otras palabras, se trata de "hacer un ping" a través de la red mediante AJAX para
comprobar los equipos que se quieren monitorizar.
1) Cuando se cargue la página, se debe construir "el mapa de red" que muestra todos los
servidores que se van a monitorizar. Para construir este mapa, se proporciona la página
web básica y un objeto llamado nodos, que contiene los datos correspondientes a los
equipos que se quieren monitorizar.
Para mostrar cada nodo que se van a monitorizar, se crean dinámicamente elementos de
tipo <div> que muestran el nombre de cada nodo, su URL y una zona de datos en la que
se mostrará más adelante cierta información del nodo. A continuación se muestra un
ejemplo del posible código XHTML que se puede utilizar:
4) La función que se ejecuta de forma periódica (por ejemplo cada 10 segundos) debe
realizar un "ping" a cada uno de los equipos. Hacer un ping consiste en intentar
establecer una conexión con el servidor utilizando el método HEAD de HTTP (en vez de
los tradicionales métodos GET o POST).
5) La función que procesa las respuestas correctamente recibidas, debe obtener el valor
de alguna cabecera HTTP como por ejemplo Date. Si la respuesta es correcta, mostrar
en la zona <span id="datos"></span> de cada nodo el valor de la cabecera Server,
que indica el tipo de servidor web que utiliza el nodo remoto.
Cuando se está realizando una consulta al servidor, la propiedad border de CSS debe
ser igual a 3px solid #000000.
Cuando la respuesta recibida es correcta, la clase CSS del <div> es "on" y el valor de
la propiedad border es 3px solid #00FF00.
Cuando la respuesta es errónea, la clase CSS del <div> es "off" y el valor de la
propiedad border es 3px solid #FF0000.
Ver solución
Figura 9.3 Mensaje de aviso que muestra Internet Explorer al intentar establecer
conexiones de red con servidores remotos
Ejercicio 21
A partir de la página web que se proporciona, completar el script para realizar un lector
avanzado de canales RSS.
5) Una vez obtenida la URL del canal RSS, se descarga su contenido. Para obtener los
contenidos del canal RSS, es conveniente utilizar un proxy que permita saltarse la
restricción de JavaScript para realizar conexiones de red remotas. El script se llama
proxy.php y admite dos parámetros GET llamados url (que es la URL que se quiere
descargar) y ct (Content-Type del contenido que se está descargando, que es muy
importante cuando se quieren recibir contenidos de tipo XML).
6) Después de descargar el contenido del canal RSS, se debe procesar su contenido para
obtener cada uno de sus elementos y almacenarlos en un array global llamado canal.
<?xml version="1.0"?>
<rss version="2.0">
<channel>
<title>Ejemplo de canal 2.0</title>
<link>http://www.ejemplo_no_real.com</link>
<description>Se trata de un ejemplo de canal RSS 2.0, sencillo pero
completo</description>
<item>
<title>El primer elemento</title>
<link>http://www.ejemplo_no_real.com/elementos/001.html</link>
<description>Esta es la descripción del primer
elemento.</description>
<pubDate>Sun, 18 Feb 2007 15:04:27 GMT</pubDate>
</item>
<item>
<title>El segundo elemento</title>
<link> http://www.ejemplo_no_real.com/elementos/002.html </link>
<description> Esta es la descripción del primer
elemento.</description>
<pubDate>Sun, 18 Feb 2007 15:04:27 GMT</pubDate>
</item>
...
<item>
<title>El elemento N</title>
<link> http://www.ejemplo_no_real.com/elementos/00n.html </link>
<description> Esta es la descripción del elemento N.</description>
<pubDate>Sun, 18 Feb 2007 15:04:27 GMT</pubDate>
</item>
</channel>
</rss>
El formato del array elementos puede ser cualquiera que permita almacenar para cada
elemento su titular, descripción, enlace y fecha de publicación.
7) Una vez descargado y procesado el canal RSS, mostrar sus elementos tal y como se
indica en la siguiente imagen:
Figura 9.4 Aspecto final del lector RSS construido con AJAX
Descargar archivo ZIP con la página HTML y los scripts descubreRss.php y proxy.php
Ver solución
Google Maps fue una de las primeras aplicaciones basadas en AJAX de uso masivo por
parte de los usuarios. Su gran éxito ha provocado que todos sus rivales hayan copiado el
funcionamiento de sus mapas.
Además, Google ofrece de forma gratuita una API con la que poder desarrollar
aplicaciones a medida basadas en los mapas de Google, integrar los mapas en otras
aplicaciones e incluso hacer "mash-up" o mezclas de Google Maps y otras aplicaciones
web que también disponen de una API pública.
Antes de utilizar la API de los mapas de Google, es necesario obtener una clave
personal y única para cada sitio web donde se quiere utilizar. El uso de la API es
gratuito para cualquier aplicación que pueda ser accedida libremente por los usuarios.
La clave de la API se puede obtener desde: http://www.google.com/apis/maps/
Para usos comerciales de la API también existen servicios de pago que requieren el uso
de otras claves.
Las claves se solicitan por cada ruta del servidor. De esta forma, si se solicita una clave
para http://www.misitio.com/ruta1, cualquier aplicación o página que se encuentre bajo
esa ruta del servidor podrá hacer uso de la API de los mapas con esa clave.
Las claves de la API de Google Maps consisten en una cadena de texto muy larga con
un aspecto similar al siguiente: ABQIAAAA30JtKUU8se-
7KKPRGSfCMBT2yXp_ZAY8_ufC3CFXhHIE1NvwkxRZNdns2BwZvEY-V68DvlyUYwi1-Q.
Una vez obtenida la clave, cualquier página que quiera hacer uso de la API debe enlazar
el siguiente archivo de JavaScript:
<script
src="http://maps.google.com/maps?file=api&v=2&hl=es&key=AB
QIAAAA30JtKUU8se-7KKPRGSfCMBT2yXp_ZAY8_ufC3CFXhHIE1NvwkxRZNdns2BwZvEY-
V68DvlyUYwi1-Q" type="text/javascript"></script>
Los parámetros necesarios son file, que indica el tipo de archivo que se quiere cargar
(en este caso la API), v, que indica la versión de la API (en este caso 2), hl, que permite
indicar el idioma en el que se muestran los mapas (si no se indica este parámetro, los
mapas se muestran en inglés) y el parámetro key, que contiene la clave que se acaba de
obtener.
Una vez obtenida la clave, es muy sencillo crear el primer mapa de Google:
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-
1" />
<title>Ejemplo de uso de Google Maps</title>
<script
src="http://maps.google.com/maps?file=api&v=2&hl=es&key=AB
QIAAAA30JtKUU8se-7KKPRGSfCMBT2yXp_ZAY8_ufC3CFXhHIE1NvwkxRZNdns2BwZvEY-
V68DvlyUYwi1-Q" type="text/javascript"></script>
<script type="text/javascript">
function load() {
if (GBrowserIsCompatible()) {
var latitud = 48.858729;
var longitud = 2.352448;
var zoom = 15;
var mapa = new GMap2(document.getElementById("mapa"));
mapa.setCenter(new GLatLng(latitud, longitud), zoom);
}
}
</script>
</head>
Figura 9.5 Mapa sencillo creado con la API de Google Maps y las coordenadas de
longitud y latitud indicadas
Una vez definido el contenedor del mapa, se establecen los eventos necesarios en la
página que lo contiene:
window.onload = load;
window.onunload = GUnload;
if (GBrowserIsCompatible()) {
...
}
Para crear un nuevo mapa, se utiliza la clase GMap2, que representa un mapa en una
página. Las páginas pueden contener más de un mapa, pero cada uno de ellos hace
referencia a una instancia diferente de la clase GMap2. El único argumento obligatorio
para crear el mapa es la referencia al elemento que contendrá el mapa (obtenida
mediante su id):
Una vez instanciada la clase GMap2, el mapa ya ha sido creado. Sin embargo, el mapa no
se muestra correctamente hasta que se indique en que punto geográfico está centrado. El
método setCenter() permite centrar un mapa y opcionalmente, indicar el nivel de
zoom y el tipo de mapa que se muestra:
El punto geográfico en el que está centrado el mapa se indica mediante un objeto de tipo
GLatLng() que toma dos parámetros: el primero es el valor de la latitud del punto
geográfico y el otro parámetro indica la longitud de esa posición geográfica. De forma
opcional se puede indicar el nivel de zoom del mapa.
La latitud puede tomar un valor entre +90 (90 grados al norte del ecuador) y -90 (90
grados al sur del ecuador), la longitud puede tomar un valor entre +180 (180 grados al
este de Greenwitch) y -180 (180 grados al oeste de Greenwitch). El nivel de zoom
puede variar entre 1 (en el que se ve la Tierra entera) y 18 (en el que se ven los detalles
de cada calle). No obstante, se debe tener en cuenta que no todos los puntos geográficos
disponen de todos los niveles de zoom.
Después de crear un mapa básico, es muy sencillo añadir los controles para aumentar o
disminuir el nivel de zoom y el control que indica el tipo de mapa que se muestra:
mapa.addControl(new GSmallMapControl());
mapa.addControl(new GMapTypeControl());
Ahora, el mapa permite variar el nivel de zoom y mostrar otro tipo de mapa, como se
muestra en la siguiente imagen:
Figura 9.6 Mapa sencillo creado con la API de Google Maps y que permite variar su
nivel de zoom y el tipo de mapa que se muestra
Google Maps incluye una documentación muy extensa sobre el uso de la API y todos
los métodos disponibles. La documentación inicial con ejemplos básicos se puede
consultar en: http://www.google.com/apis/maps/documentation/
Los mapas de Google permiten controlar un gran número de eventos, pudiendo asociar
funciones o ejecutar directamente código JavaScript cada vez que se produce un evento.
El siguiente ejemplo muestra un mensaje de tipo alert() con las coordenadas del
centro del mapa cada vez que el usuario suelta el mapa después de haberlo movido:
Además del evento moveend existen muchos otros, como por ejemplo move que permite
ejecutar cualquier función de forma repetida a medida que el usuario mueve el mapa.
Los mapas también permiten colocar marcadores para mostrar una posición. El
siguiente ejemplo hace uso del evento click sobre los mapas para mostrar marcadores:
GEvent.addListener(mapa, "click", function(marcador, punto) {
mapa.addOverlay(new GMarker(punto));
});
Ahora, cada vez que se pulsa sobre un punto del mapa, se añade un nuevo marcador en
ese punto:
Figura 9.7 Los eventos de la API de los mapas de Google permiten añadir marcadores
de posición cada vez que se pincha en un punto del mapa
Cada vez que se pincha, se crea un nuevo marcador con las coordenadas de la posición
en la que se ha pinchado:
mapa.addOverlay(nuevoMarcador);
Ejercicio 22
1) En la misma página se debe incluir otro mapa que muestre las antípodas del punto
geográfico inicial del primer mapa. Este segundo mapa no debe mostrar ningún control
de zoom ni de tipo de mapa.
Figura 9.9 Aspecto del mapa principal y del mapa secundario que muestra el lugar
geográfico definido como "antípodas" del lugar mostrado en el mapa principal
2) Al mover el primer mapa, el segundo mapa debe mostrar en todo momento las
antípodas de ese lugar. Además, el zoom y el tipo de mapa también debe estar
sincronizado, de forma que el segundo mapa muestre en todo momento el mismo nivel
de zoom y el mismo tipo de mapa que el primero.
Ver solución
Ejercicio 23
La disponibilidad de una API pública, sencilla, gratuita y muy potente permite integrar
los mapas de Google con cualquier otra aplicación abierta que genere información
geoespacial.
Figura 9.11 Mapa de previsión metereológica construido con los mapas de Google y
que utiliza iconos personalizados para mostrar cada marcador de posición
[
{ latlon: [42.779275360242, -2.63671875], prediccion: "tormentas" },
{ latlon: [43.245202722034, -8.32763671875], prediccion: "nieve" },
{ latlon: [42.228517356209, -7.36083984375], prediccion: "lluvia" },
...
{ latlon: [41.54147766679, -3.75732421875], prediccion: "nublado" },
]
Ver solución
Por todo lo anterior, han surgido librerías y frameworks específicos para el desarrollo de
aplicaciones con JavaScript. Utilizando estas librerías, se reduce el tiempo de desarrollo
y se tiene la seguridad de que las aplicaciones funcionan igual de bien en cualquiera de
los navegadores más populares.
Afortunadamente, las versiones más recientes del framework disponen de una completa
documentación de todas las funciones y métodos que componen su API. La
documentación incluye la definición completa de cada método, sus atributos y varios
ejemplos de uso: http://www.prototypejs.org/api
La primera función que se estudia cuando se está aprendiendo Prototype es tan útil
como impronunciable: $(). La "función dólar" es un atajo mejorado de la función
document.getElementById().
// Con JavaScript
var elemento = document.getElementById('primero');
// Con Prototype
var elemento = $('primero');
// Con JavaScript
var elemento1 = document.getElementById('primero');
var elemento2 = document.getElementById('segundo');
// Con Prototype
var elementos = $('primero', 'segundo');
Otra de las funciones más útiles de Prototype es $F(), que es similar a la anterior
función, pero se utiliza para obtener directamente el valor de los campos de formulario:
// Con Prototype
$F("municipio")
<select id="municipio">
<option>...</option>
</select>
// Con JavaScript
document.getElementById("municipio").options[document.getElementById("
municipio").selectedIndex].value
// Con Prototype
$F("municipio")
Prototype incluye una función muy útil llamada $A(), para convertir en array
"cualquier cosa que se parezca a un array". Algunas funciones de JavaScript, como por
ejemplo getElementsByTagName() devuelven objetos de tipo NodeList o
HTMLCollection, que no son arrays, aunque pueden recorrerse como tales.
<select id="lista">
<option value="1">Primer valor</option>
<option value="2">Segundo valor</option>
<option value="3">Tercer valor</option>
</select>
// 'lista_nodos' es una variable de tipo NodeList
var lista_nodos = $('lista').getElementsByTagName('option');
Una función similar a $A() es $H(), que crea arrays asociativos (también llamados
"hash") a partir del argumento que se le pasa:
Por último, Prototype incluye la función $R() para crear rangos de valores. El rango de
valores se crea desde el valor del primer argumento hasta el valor del segundo
argumento. El tercer argumento de la función indica si se excluye o no el último valor
(por defecto, el tercer argumento vale false, que indica que sí se incluye el último
valor).
Los rangos que se pueden crear van mucho más allá de simples sucesiones numéricas.
La "inteligencia" de la función $R() permite crear rangos tan avanzados como los
siguientes:
Por último, una función muy útil que se puede utilizar con cadenas de texto, objetos y
arrays de cualquier tipo es inspect(). Esta función devuelve una cadena de texto que
es una representación de los contenidos del objeto. Se trata de una utilidad
imprescindible cuando se están depurando las aplicaciones, ya que permite visualizar el
contenido de variables complejas.
camelize(): convierte una cadena de texto separada por guiones en una cadena con
notación de tipo CamelCase
dasherize(): modifica los guiones bajos (_) de una cadena de texto por guiones
medios (-)
Prototype incluye muchas utilidades relacionadas con los formularios y sus elementos.
A continuación se muestran las más útiles para los campos de un formulario:
Field.clear(): borra el valor de cada campo que se le pasa (admite uno o más
parámetros)
Field.present(): devuelve true si los campos que se le indican han sido rellenados
por parte del usuario, es decir, si contienen valores no vacíos (admite uno o más
parámetros)
Field.select(): selecciona el valor del campo (solo para los campos en los que se
pueda seleccionar su texto)
Además de las funciones específicas para los campos de los formularios, Prototype
también define utilidades para los propios formularios completos. Todas las funciones
requieren un solo parámetro: el identificador o el objeto del formulario.
Form.serialize(): devuelve una cadena de texto de tipo "query string" con el valor de
todos los campos del formulario ("campo1=valor1&campo2=valor2&campo3=valor3")
Form.getInputs(): devuelve un array con todos los elementos de tipo <input> del
formulario. Admite otros dos parámetros para filtrar los resultados. El segundo
parámetro indica el tipo de <input> que se quiere obtener y el tercer parámetro indica
el nombre del elemento <input>.
Las utilidades añadidas a los arrays de JavaScript es otro de los puntos fuertes de
Prototype:
var array = ["1", "2", 3, ["a", "b", "c", ["A", "B", "C"] ] ];
array.indexOf(3); // 2
array.indexOf("C"); // -1
var array = ["1", "2", 3, ["a", "b", "c", ["A", "B", "C"] ] ];
array.reverse();
// array = [["a", "b", "c", ["A", "B", "C"]], 3, "2", "1"]
shift(): devuelve el primer elemento del array y lo extrae del array (el array se
modifica y su longitud disminuye en 1 elemento)
without(): devuelve el array del que se han eliminado todos los elementos que
coinciden con los argumentos que se pasan a la función. Permite filtrar los contenidos
de un array
Gracias a Enumerable, se pueden recorrer los arrays de forma mucho más eficiente:
// Array original
var vocales = ["a", "e", "i", "o", "u"];
El método select(), que es un alias del método findAll(), permite filtrar los
contenidos de un array:
Otro método útil es pluck(), que permite obtener el valor de una misma propiedad para
todos los elementos de la colección:
El método partition() permite asignar una función propia para decidir si un elemento
se considera true o false. En el siguiente ejemplo, se divide un array con letras en dos
grupos, el de las vocales y el de las consonantes:
El método invoke() permite ejecutar una función para todos los elementos de la
colección:
Try.these(): permite probar varias funciones de forma consecutiva hasta que una de
ellas funcione. Es muy útil para las aplicaciones que deben funcionar correctamente en
varios navegadores diferentes.
var Ajax = {
getTransport: function() {
return Try.these(
function() {return new XMLHttpRequest()},
function() {return new ActiveXObject('Msxml2.XMLHTTP')},
function() {return new ActiveXObject('Microsoft.XMLHTTP')}
) || false;
},
activeRequestCount: 0
}
MiClase = Class.create();
MiClase.prototype = {
initialize: function(a, b) {
this.a = a;
this.b = b;
}
}
Object.extend(objetoDestino, objetoOrigen);
Esta función es muy útil para que las aplicaciones definan una serie de opciones por
defecto y puedan tener en cuenta las opciones establecidas por cada usuario:
Object.extend(Number.prototype, {
toColorPart: function() {
return this.toPaddedString(2, 16);
},
succ: function() {
return this + 1;
},
times: function(iterator) {
$R(0, this, true).each(iterator);
return this;
},
toJSON: function() {
return isFinite(this) ? this.toString() : 'null';
}
});
new Ajax.Request('/ruta/hasta/pagina.php', {
method: 'post',
asynchronous: true,
postBody: 'parametro1=valor1¶metro2=valor2',
onSuccess: procesaRespuesta,
onFailure: muestraError
});
Como es habitual, para establecer la función que procesa la respuesta del servidor, se
indica el nombre de la función sin paréntesis. Las funciones externas asignadas para
procesar la respuesta, reciben como primer parámetro el objeto que representa la
respuesta del servidor. Haciendo uso de este objeto, las funciones pueden acceder a
todas las propiedades habituales:
function procesaRespuesta(respuesta) {
alert(respuesta.responseText);
}
A continuación se incluye una tabla con todas las opciones que se pueden definir para el
método Ajax.Request():
Opción Descripción
method El método de la petición HTTP. Por defecto es POST
Lista de valores que se envían junto con la petición. Deben estar
parameters formateados como una query string:
parametro1=valor1¶metro2=valor2
Indica la codificación de los datos enviados en la petición. Su
encoding
valor por defecto es UTF-8
Controla el tipo de petición que se realiza. Por defecto es true, lo
asynchronous que indica que la petición realizada al servidor es asíncrona, el tipo
de petición habitual en las aplicaciones AJAX
postBody Contenido que se envía en el cuerpo de la petición de tipo POST
Indica el valor de la cabecera Content-Type utilizada para realizar
contentType la petición. Su valor por defecto es application/x-www-form-
urlencoded
requestHeaders
Array con todas las cabeceras propias que se quieren enviar junto
con la petición
onComplete Permiten asignar funciones para el manejo de las distintas fases de
onLoaded on404 la petición. Se pueden indicar funciones para todos los códigos de
on500 estado válidos de HTTP
onSuccess
Permite indicar la función que se encarga de procesar las
respuestas correctas de servidor
onFailure
Se emplea para indicar la función que se ejecuta cuando la
respuesta ha sido incorrecta
Permite indicar la función encargada de manejar las peticiones
onException erróneas en las que la respuesta del servidor no es válida, los
argumentos que se incluyen en la petición no son válidos, etc.
<div id="info"></div>
new Ajax.Updater('info', '/ruta/hasta/pagina.php');
<div id="info">
<ul>
<li>Lorem ipsum dolor sit amet</li>
<li>Consectetuer adipiscing elit</li>
<li>Curabitur risus magna, lobortis</li>
</ul>
</div>
Opción Descripción
Indica cómo se inserta el contenido HTML en el elemento indicado.
insertion Puede ser Insertion.Before, Insertion.Top, Insertion.Bottom o
Insertion.After
Si la respuesta del servidor incluye scripts en su contenido, esta opción
evalScripts permite indicar si se ejecutan o no. Su valor por defecto es false, por lo
que no se ejecuta ningún script
<div id="titulares"></div>
new Ajax.PeriodicalUpdater('titulares', '/ruta/hasta/pagina.php', {
frequency:30 });
El código anterior actualiza, cada 30 segundos, el contenido del <div> con la respuesta
recibida desde el servidor.
Opción Descripción
Número de segundos que se espera entre las peticiones. El valor por defecto
frequency
es de 2 segundos
decay Indica el factor que se aplica a la frecuencia de actualización cuando la
Opción Descripción
última respuesta del servidor es igual que la anterior. Ejemplo: si la
frecuencia es 10 segundos y el decay vale 3, cuando una respuesta del
servidor sea igual a la anterior, la siguiente petición se hará 3 * 10 = 30
segundos después de la última petición
Por último, Ajax.Responders permite asignar de forma global las funciones que se
encargan de responder a los eventos AJAX. Una de las principales utilidades de
Ajax.Responders es la de indicar al usuario en todo momento si se está realizando
alguna petición AJAX.
Ajax.Responders.register({
onCreate: function() {
if($('info') && Ajax.activeRequestCount> 0) {
$('info').innerHTML = Ajax.activeRequestCount + "peticiones
pendientes";
}
},
onComplete: function() {
if($('info') && Ajax.activeRequestCount> 0) {
$('info').innerHTML = Ajax.activeRequestCount + "peticiones
pendientes";
}
}
});
function procesaEvento(e) {
// Obtener el elemento que ha originado el evento (el DIV)
var elemento = Event.element(e);
Método/Propiedad Descripción
element()
Devuelve el elemento que ha originado el evento (un div, un
botón, etc.)
isLeftClick() Indica si se ha pulsado el botón izquierdo del ratón
pointerX()
pointerY() Posición x e y del puntero del ratón
stop() Detiene la propagación del evento
observers()
Devuelve un array con todos los eventos registrados en la
página
Además, Event define una serie de constantes para referirse a las teclas más habituales
que se manejan en las aplicaciones (tabulador, ENTER, flechas de dirección, etc.) Las
constantes definidas son KEY_BACKSPACE, KEY_TAB, KEY_RETURN, KEY_ESC, KEY_LEFT,
KEY_UP, KEY_RIGHT, KEY_DOWN, KEY_DELETE.
Prototype también incluye otros métodos útiles para la gestión de eventos con
formularios:
function ejecutaFuncion(f) {
f();
}
ejecutaFuncion(objeto.funcion);
ejecutaFuncion(funcion2);
El código anterior define en primer lugar la variable global nombre y le asigna el valor
Estoy fuera. A continuación, se define un objeto con un atributo llamado también
nombre y con un método sencillo que muestra el valor del atributo utilizando la palabra
reservada this.
Si se ejecuta la función del objeto a través de una referencia suya (mediante la función
ejecutaFuncion()), la palabra reservada this se resuelve en el objeto window y por
tanto el mensaje que se muestra es Estoy fuera. Sin embargo, si se utiliza el método
bind(objeto) sobre la función, siempre se ejecuta considerando su contexto igual al
objeto que se pasa como parámetro al método bind().
function muestraOculta() {
// Obtener el ID del elemento
var id = this.id;
id = id.split('_');
id = id[1];
window.onload = function() {
document.getElementById('enlace_1').onclick = muestraOculta;
document.getElementById('enlace_2').onclick = muestraOculta;
document.getElementById('enlace_3').onclick = muestraOculta;
}
function muestraOculta() {
var id = (this.id).split('_')[1];
$('contenidos_'+id).toggle();
$('enlace_'+id).innerHTML = (!$('contenidos_'+id).visible()) ?
'Ocultar contenidos' : 'Mostrar contenidos';
}
window.onload = function() {
$R(1, 3).each(function(n) {
Event.observe('enlace_'+n, 'click', muestraOculta);
});
}
Otro de los ejercicios anteriores realizaba peticiones AJAX al servidor para comprobar
si un determinado nombre de usuario estaba libre. El código original de JavaScript era:
var READY_STATE_COMPLETE = 4;
var peticion_http = null;
function inicializa_xhr() {
if(window.XMLHttpRequest) {
return new XMLHttpRequest();
}
else if(window.ActiveXObject) {
return new ActiveXObject("Microsoft.XMLHTTP");
}
}
function comprobar() {
var login = document.getElementById("login").value;
peticion_http = inicializa_xhr();
if(peticion_http) {
peticion_http.onreadystatechange = procesaRespuesta;
peticion_http.open("POST",
"http://localhost/compruebaDisponibilidad.php", true);
peticion_http.setRequestHeader("Content-Type", "application/x-www-
form-urlencoded");
peticion_http.send("login="+login+"&nocache="+Math.random());
}
}
function procesaRespuesta() {
if(peticion_http.readyState == READY_STATE_COMPLETE) {
if(peticion_http.status == 200) {
var login = document.getElementById("login").value;
if(peticion_http.responseText == "si") {
document.getElementById("disponibilidad").innerHTML = "El
nombre elegido ["+login+"] está disponible";
}
else {
document.getElementById("disponibilidad").innerHTML = "NO está
disponible el nombre elegido ["+login+"]";
}
}
}
}
window.onload = function() {
document.getElementById("comprobar").onclick = comprobar;
}
Con Prototype se puede conseguir el mismo comportamiento con tres veces menos de
líneas de código:
function comprobar() {
var login = $F('login');
var url = 'http://localhost/compruebaDisponibilidad.php?nocache=' +
Math.random();
var peticion = new Ajax.Request(url, {
method: 'post',
postBody: 'login='+login,
onSuccess: function(respuesta) {
$('disponibilidad').innerHTML = (respuesta.responseText == 'si')
?
'El nombre elegido ['+login+'] está disponible' : 'NO está
disponible el nombre elegido ['+login+']';
},
onFailure: function() { alert('Se ha producido un error'); }
});
}
window.onload = function() {
Event.observe('comprobar', 'click', comprobar);
}
El siguiente código hace uso de script.aculo.us para conseguir el mismo resultado con
un 90% menos de líneas de código:
window.onload = function() {
// Crear elemento de tipo <div> para mostrar las sugerencias del
servidor
var elDiv = Builder.node('div', {id:'sugerencias'});
document.body.appendChild(elDiv);
Opción Descripción
El nombre del parámetro que se envía al servidor con el texto
paramName escrito por el usuario. Por defecto es igual que el atributo name
del cuadro de texto utilizado para autocompletar
tokens
Permite autocompletar más de un valor en un mismo cuadro de
texto. Más adelante se explica con un ejemplo.
Número mínimo de caracteres que el usuario debe escribir antes
minChars de que se realice la petición al servidor. Por defecto es igual a 1
carácter.
Elemento que se muestra mientras se realiza la petición al
servidor y que se vuelve a ocultar al recibir la respuesta del
indicator servidor. Normalmente es una imagen animada que se utiliza
para indicar al usuario que en ese momento se está realizando
una petición al servidor
Función que se ejecuta después de que el usuario seleccione un
elemento de la lista de sugerencias. Por defecto el
updateElement
comportamiento consiste en seleccionar el elemento, mostrarlo
en el cuadro de texto y ocultar la lista de sugerencias. Si se
indica una función propia, no se ejecuta este comportamiento por
defecto.
Similar a la opción updateElement. En este caso, la función
afterUpdateElement indicada se ejecuta después de la función por defecto y no en
sustitución de esa función por defecto.
La opción tokens permite indicar los caracteres que separan los diferentes elementos de
un cuadro de texto. En el siguiente ejemplo:
jQuery comparte con Prototype muchas ideas e incluso dispone de funciones con el
mismo nombre. Sin embargo, su diseño interno tiene algunas diferencias drásticas
respecto a Prototype, sobre todo el "encadenamiento" de llamadas a métodos.
La función básica de jQuery y una de las más útiles tiene el mismo nombre que en
Prototype, ya que se trata de la "función dolar": $(). A diferencia de la función de
Prototype, la de jQuery es mucho más que un simple atajo mejorado de la función
document.getElementById().
La cadena de texto que se pasa como parámetro puede hacer uso de Xpath o de CSS
para seleccionar los elementos. Además, separando expresiones con un carácter "," se
puede seleccionar un número ilimitado de elementos.
Las posibilidades de la función $() van mucho más allá de estos ejemplos sencillos, ya
que soporta casi todos los selectores definidos por CSS 3 (algo que dispondrán los
navegadores dentro de varios años) y también permite utilizar XPath:
Una de las utilidades más interesantes de jQuery está relacionada con el evento onload
de la página. Las aplicaciones web más complejas suelen utilizar un código similar al
siguiente para iniciar la aplicación:
window.onload = function() {
...
};
jQuery propone el siguiente código para ejecutar las instrucciones una vez que se ha
cargado la página:
$(document).ready(function() {
...
});
La gran ventaja del método propuesto por jQuery es que la aplicación no espera a que se
carguen todos los elementos de la página, sino que sólo espera a que se haya descargado
el contenido HTML de la página, con lo que el árbol DOM ya está disponible para ser
manipulado. De esta forma, las aplicaciones JavaScript desarrolladas con jQuery pueden
iniciarse más rápidamente que las aplicaciones JavaScript tradicionales.
En realidad, ready() no es más que una de las muchas funciones que componen el
módulo de los eventos. Todos los eventos comunes de JavaScript (click, mousemove,
keypress, etc.) disponen de una función con el mismo nombre que el evento. Si se
utiliza la función sin argumentos, se ejecuta el evento:
// Ejecuta el evento 'onclick' en todos los párrafos de la página
$('p').click();
Entre las utilidades definidas por jQuery para los eventos se encuentra la función
toggle(), que permite ejecutar dos funciones de forma alterna cada vez que se pincha
sobre un elemento:
$("p").toggle(function(){
alert("Me acabas de activar");
},function(){
alert("Me acabas de desactivar");
});
En el ejemplo anterior, la primera vez que se pincha sobre el elemento (y todas las veces
impares), se ejecuta la primera función y la segunda vez que se pincha el elemento (y
todas las veces pares) se ejecuta la segunda función.
Las aplicaciones web más avanzadas incluyen efectos visuales complejos para construir
interacciones similares a las de las aplicaciones de escritorio. jQuery incluye en la
propia librería varios de los efectos más comunes:
Todas las funciones relacionadas con los efectos visuales permiten indicar dos
parámetros opcionales: el primero es la duración del efecto y el segundo parámetro es la
función que se ejecuta al finalizar el efecto visual.
Otros efectos visuales incluidos son los relacionados con el fundido o "fading"
(fadeIn() muestra los elementos con un fundido suave, fadeOut() oculta los
elementos con un fundido suave y fadeTo() establece la opacidad del elemento en el
nivel indicado) y el despliegue de elementos (slideDown() hace aparecer un elemento
desplegándolo en sentido descendente, slideUp() hace desaparecer un elemento
desplegándolo en sentido ascendente, slideToggle() hace desaparecer el elemento si
era visible y lo hace aparecer si no era visible).
Como sucede con Prototype, las funciones y utilidades relacionadas con AJAX son
parte fundamental de jQuery. El método principal para realizar peticiones AJAX es
$.ajax() (importante no olvidar el punto entre $ y ajax). A partir de esta función
básica, se han definido otras funciones relacionadas, de más alto nivel y especializadas
en tareas concretas: $.get(), $.post(), $.load(), etc.
$.ajax(opciones);
Al contrario de lo que sucede con Prototype, la URL que se solicita también se incluye
dentro del array asociativo de opciones. A continuación se muestra el mismo ejemplo
básico que se utilizó en Prototype realizado con $.ajax():
$.ajax({
url: '/ruta/hasta/pagina.php',
type: 'POST',
async: true,
data: 'parametro1=valor1¶metro2=valor2',
success: procesaRespuesta,
error: muestraError
});
La siguiente tabla muestra todas las opciones que se pueden definir para el método
$.ajax():
Opción Descripción
async
Indica si la petición es asíncrona. Su valor por defecto es true, el
habitual para las peticiones AJAX
Permite indicar una función que modifique el objeto XMLHttpRequest
beforeSend antes de realizar la petición. El propio objeto XMLHttpRequest se pasa
como único argumento de la función
Permite establecer la función que se ejecuta cuando una petición se ha
completado (y después de ejecutar, si se han establecido, las funciones de
complete
success o error). La función recibe el objeto XMLHttpRequest como
primer parámetro y el resultado de la petición como segundo argumento
Indica el valor de la cabecera Content-Type utilizada para realizar la
contentType petición. Su valor por defecto es application/x-www-form-
urlencoded
data Información que se incluye en la petición. Se utiliza para enviar
Opción Descripción
parámetros al servidor. Si es una cadena de texto, se envía tal cual, por lo
que su formato debería ser parametro1=valor1¶metro2=valor2.
También se puede indicar un array asociativo de pares clave/valor que se
convierten automáticamente en una cadena tipo query string
El tipo de dato que se espera como respuesta. Si no se indica ningún
valor, jQuery lo deduce a partir de las cabeceras de la respuesta. Los
posibles valores son: xml (se devuelve un documento XML
dataType
correspondiente al valor responseXML), html (devuelve directamente la
respuesta del servidor mediante el valor responseText), script (se
evalúa la respuesta como si fuera JavaScript y se devuelve el resultado) y
json (se evalúa la respuesta como si fuera JSON y se devuelve el objeto
JavaScript generado)
Indica la función que se ejecuta cuando se produce un error durante la
error
petición. Esta función recibe el objeto XMLHttpRequest como primer
parámetro, una cadena de texto indicando el error como segundo
parámetro y un objeto con la excepción producida como tercer parámetro
Permite considerar como correcta la petición solamente si la respuesta
ifModified recibida es diferente de la anterior respuesta. Por defecto su valor es
false
Indica si se transforman los datos de la opción data para convertirlos en
processData una cadena de texto. Si se indica un valor de false, no se realiza esta
transformación automática
Permite establecer la función que se ejecuta cuando una petición se ha
completado de forma correcta. La función recibe como primer parámetro
success
los datos recibidos del servidor, previamente formateados según se
especifique en la opción dataType
timeout
Indica el tiempo máximo, en milisegundos, que la petición espera la
respuesta del servidor antes de anular la petición
type
El tipo de petición que se realiza. Su valor por defecto es GET, aunque
también se puede utilizar el método POST
url La URL del servidor a la que se realiza la petición
Además de la función $.ajax() genérica, existen varias funciones relacionadas que son
versiones simplificadas y especializadas de esa función. Así, las funciones $.get() y
$.post() se utilizan para realizar de forma sencilla peticiones GET y POST:
<div id="info"></div>
// Con Prototype
new Ajax.Updater('info', '/ruta/hasta/pagina.php');
// Con jQuery
$('#info').load('/ruta/hasta/pagina.php');
Al igual que sucedía con la función $.get(), la función $.load() también dispone de
una versión específica denominada $.loadIfModified() que carga la respuesta del
servidor en el elemento sólo si esa respuesta es diferente a la última recibida.
jQuery dispone de varias funciones para la manipulación de las propiedades CSS de los
elementos. Todas las funciones se emplean junto con una selección de elementos
realizada con la función $().
La función $() permite seleccionar elementos (nodos DOM) de la página de forma muy
sencilla. jQuery permite, además, seleccionar nodos relacionados con la selección
realizada. Para seleccionar nodos relacionados, se utilizan funciones de filtrado y
funciones de búsqueda.
Los filtros son funciones que modifican una selección realizada con la función $() y
permiten limitar el número de nodos devueltos.
$('a')
.filter('.pinchame')
.click(function(){
alert('Estás abandonando este sitio web');
})
.end()
.filter('ocultame')
.click(function(){
$(this).hide();
return false;
})
.end();
El código anterior obtiene todos los enlaces de la página $('a') y aplica diferentes
funciones manejadoras del evento click en función del tipo de enlace. Aunque se
podrían incluir dos instrucciones diferentes para realizar cada filtrado, la función end()
permite encadenar varias selecciones.
El segundo grupo de funciones para la manipulación de nodos DOM está formado por
los buscadores, funciones que buscan/seleccionan nodos relacionados con la selección
realizada. De esta forma, jQuery define la función children() para obtener todos los
nodos hijo o descendientes del nodo actual, parent() para obtener el nodo padre o
nodo ascendente del nodo actual (parents() obtiene todos los ascendentes del nodo
hasta la raíz del árbol) y siblings() que obtiene todos los nodos hermano del nodo
actual, es decir, todos los nodos que tienen el mismo nodo padre que el nodo actual.
La navegación entre nodos hermano se puede realizar con las funciones next() y
pev() que avanzan o retroceden a través de la lista de nodos hermano del nodo actual.
Las funciones after() y before() añaden el contenido indicado como parámetro antes
de cada uno de los elementos seleccionados. La función wrap() permite "envolver" un
elemento con el contenido indicado (se añade parte del contenido por delante y el resto
por detrás).
La función empty() vacía de contenido a un elemento, remove() elimina los elementos
seleccionados del árbol DOM y clone() copia de forma exacta los nodos
seleccionados.
Recorrer arrays y objetos también es muy sencillo con jQuery, gracias a la función
$.each(). El primer parámetro de la función es el objeto que se quiere recorrer y el
segundo parámetro es el código de la función que lo recorre (a su vez, a esta función se
le pasa como primer parámetro el índice del elemento y como segundo parámetro el
valor del elemento):
// Recorrer arrays
var vocales = ['a', 'e', 'i', 'o', 'u'];
// Recorrer objetos
var producto = { id: '12DW2', precio: 12.34, cantidad: 5 };
Como sucedía con Prototype, cuando se rehace una aplicación JavaScript con jQuery, el
resultado es un código muy conciso pero que mantiene su facilidad de lectura y
comprensión.
function muestraOculta() {
// Obtener el ID del elemento
var id = this.id;
id = id.split('_');
id = id[1];
window.onload = function() {
document.getElementById('enlace_1').onclick = muestraOculta;
document.getElementById('enlace_2').onclick = muestraOculta;
document.getElementById('enlace_3').onclick = muestraOculta;
}
function muestraOculta() {
var id = (this.id).split('_')[1];
$('contenidos_'+id).toggle();
$('enlace_'+id).innerHTML = (!$('contenidos_'+id).visible()) ?
'Ocultar contenidos' : 'Mostrar contenidos';
}
window.onload = function() {
$R(1, 3).each(function(n) {
Event.observe('enlace_'+n, 'click', muestraOculta);
});
}
$(document).ready(function(){
$.each([1, 2, 3], function(i, n){
$('#enlace_'+n).toggle(
function() { $('#contenidos_'+n).toggle(); $(this).html('Mostrar
contenidos'); },
function() { $('#contenidos_'+n).toggle(); $(this).html('Ocultar
contenidos'); }
);
})
});
El código anterior utiliza la función toggle() como evento que permite alternar la
ejecución de dos funciones y como función que oculta un elemento visible y muestra un
elemento oculto.
Otro de los ejercicios anteriores realizaba peticiones AJAX al servidor para comprobar
si un determinado nombre de usuario estaba libre. El código original de JavaScript era:
var READY_STATE_COMPLETE=4;
var peticion_http = null;
function inicializa_xhr() {
if(window.XMLHttpRequest) {
return new XMLHttpRequest();
}
else if(window.ActiveXObject) {
return new ActiveXObject("Microsoft.XMLHTTP");
}
}
function comprobar() {
var login = document.getElementById("login").value;
peticion_http = inicializa_xhr();
if(peticion_http) {
peticion_http.onreadystatechange = procesaRespuesta;
peticion_http.open("POST",
"http://localhost/compruebaDisponibilidad.php", true);
peticion_http.setRequestHeader("Content-Type", "application/x-www-
form-urlencoded");
peticion_http.send("login="+login+"&nocache="+Math.random());
}
}
function procesaRespuesta() {
if(peticion_http.readyState == READY_STATE_COMPLETE) {
if(peticion_http.status == 200) {
var login = document.getElementById("login").value;
if(peticion_http.responseText == "si") {
document.getElementById("disponibilidad").innerHTML = "El
nombre elegido ["+login+"] está disponible";
}
else {
document.getElementById("disponibilidad").innerHTML = "NO está
disponible el nombre elegido ["+login+"]";
}
}
}
}
window.onload = function() {
document.getElementById("comprobar").onclick = comprobar;
}
Con Prototype se puede conseguir el mismo comportamiento con tres veces menos de
líneas de código:
function comprobar() {
var login = $F('login');
var url = 'http://localhost/compruebaDisponibilidad.php?nocache=' +
Math.random();
var peticion = new Ajax.Request(url, {
method:'post',
postBody:'login='+login,
onSuccess: function(respuesta) {
$('disponibilidad').innerHTML = (respuesta.responseText == 'si')
?
'El nombre elegido ['+login+'] está disponible' : 'NO está
disponible el nombre elegido ['+login+']';
},
onFailure: function() { alert('Se ha producido un error'); }
});
}
window.onload = function() {
Event.observe('comprobar', 'click', comprobar);
}
function comprobar() {
var login = $('#login').value;
var peticion = $.ajax({
url: 'http://localhost/compruebaDisponibilidad.php?nocache=' +
Math.random(),
type: 'POST',
data: { login: login },
success: function(respuesta) {
$('#disponibilidad').html((respuesta.responseText == 'si') ?
'El nombre elegido ['+login+'] está disponible' :
'NO está disponible el nombre elegido ['+login+']');
},
error: function() { alert('Se ha producido un error'); }
});
}
$(document).ready(function(){
$('#comprobar').click(comprobar);
});
La función setTimeout() se puede emplear para establecer una cuenta atrás al iniciar
una nueva petición. Si el servidor responde antes de que expire la cuenta atrás, se
elimina esa cuenta atrás y se continúa con la ejecución normal de la aplicación. Si el
servidor no responde y la cuenta atrás finaliza, se ejecuta una función encargada de
detener la petición, reintentarla, mostrar un mensaje al usuario, etc.
function muestraMensaje() {
...
if(peticion_http.readyState == READY_STATE_COMPLETE) {
if(peticion_http.status == 200) {
// Si se ha recibido la respuesta del servidor, eliminar la
cuenta atrás
clearTimeout(cuentaAtras);
...
}
}
}
function expirada() {
// La cuentra atrás se ha cumplido, detener la petición HTTP
pendiente
peticion_http.abort();
alert("Se ha producido un error en la comunicación con el servidor.
Inténtalo un poco más adelante.");
}
Además de la falta de respuesta del servidor, las aplicaciones AJAX deben estar
preparadas para otros tipos de respuestas que pueden generar los servidores. El tipo de
respuesta se comprueba mediante el valor del atributo status del objeto
XMLHTTPRequest.
A continuación se muestran las tablas de los códigos de estado más comunes que
pueden devolver los servidores:
Códigos de información
Códigos de redirección
contenido
Temporary Se trata de un código muy similar al 302, ya que indica que el recurso
307
Redirect solicitado se encuentra de forma temporal en otra URL
417 Expectation Failed El servidor no puede procesar la petición porque al menos uno de
status statusText Explicación
En sistemas operativos de tipo Linux es todavía más sencillo unir varios archivos en
uno solo:
La única consideración que se debe tener en cuenta con este método es el de las
dependencias entre archivos. Si por ejemplo el archivo1.js contiene funciones que
dependen de otras funciones definidas en el archivo3.js, los archivos deberían unirse
en este otro orden:
ShrinkSafe es una de las herramientas que proporciona el framework Dojo y que puede
ser utilizada incluso de forma online. Los creadores de la aplicación aseguran de que es
la herramienta más segura para reducir el tamaño del código, ya que no modifica ningún
elemento que pueda provocar errores en la aplicación.
Los ofuscadores utilizan diversos mecanismos para hacer casi imposible de entender el
código fuente de una aplicación. Manteniendo el comportamiento de la aplicación,
consiguen ensuciar y dificultar tanto el código que no es mayor problema que alguien
pueda acceder a ese código.
El programa ofuscador Jasob ofrece un ejemplo del resultado de ofuscar cierto código
JavaScript. Este es el código original antes de ofuscarlo:
//------------------------------------------------------
// Calculate salary for each employee in "aEmployees".
// "aEmployees" is array of "Employee" objects.
//------------------------------------------------------
function CalculateSalary(aEmployees)
{
var nEmpIndex = 0;
while (nEmpIndex < aEmployees.length)
{
var oEmployee = aEmployees[nEmpIndex];
oEmployee.fSalary = CalculateBaseSalary(oEmployee.nType,
oEmployee.nWorkingHours);
if (oEmployee.bBonusAllowed == true)
{
oEmployee.fBonus = CalculateBonusSalary(oEmployee.nType,
oEmployee.nWorkingHours,
oEmployee.fSalary);
}
else
{
oEmployee.fBonus = 0;
}
oEmployee.sSalaryColor = GetSalaryColor(oEmployee.fSalary +
oEmployee.fBonus);
nEmpIndex++;
}
}
Al sustituir todos los nombres de las variables y de las funciones por nombres de una
sola letra, es prácticamente imposible comprender el código del programa. En
ocasiones, también se utilizan ofuscadores de este tipo con el propósito de reducir el
tamaño del código fuente.
La aplicación packer es gratuita, se puede acceder via web y consigue una excelente
compresión del código original. También se puede utilizar jsjuicer, que está disponible
como aplicación descargable y también se puede utilizar vía web.
11.4. Evitar el problema de los dominios
diferentes
Como ya se ha explicado y se ha podido comprobar en algunos de los ejercicios, los
navegadores imponen restricciones muy severas a las conexiones de red que se pueden
realizar mediante AJAX. Esta característica se conoce como "el problema de los
dominios diferentes" (en inglés, "cross-domain problem").
La solución más sencilla para resolver este problema consiste en configurar el servidor
web del servidor1. Si se utiliza el servidor web Apache, para configurar el proxy
transparente, se habilita el módulo mod_rewrite y se añaden las siguientes directivas a
la configuración de Apache:
RewriteEngine on
RewriteRule ^/ruta/al/recurso$
http://www.servidor2.com/ruta/al/recurso [P]
Figura 11.2 Utilizando un proxy transparente, los scripts pueden acceder a los recursos
que se encuentren en cualquier servidor
Además de utilizar el servidor web como proxy transparente, también es posible diseñar
un proxy a medida mediante software. Yahoo por ejemplo ofrece una extensa
documentación para los desarrolladores de aplicaciones web. Entre esta documentación,
se encuentra un artículo sobre el uso de proxys para evitar el problema de las peticiones
externas de AJAX: http://developer.yahoo.com/javascript/howto-proxy.html
Además, Yahoo ofrece un proxy de ejemplo realizado con PHP y que puede ser
utilizado para conectar aplicaciones JavaScript con sus servicios web.
Librerías y frameworks:
Otras utilidades:
Ajaxload: utilidad para construir los pequeños iconos animados que las
aplicaciones AJAX suelen utilizar para indicar al usuario que se está realizando
una petición.
MiniAjax: decenas de ejemplos reales de aplicaciones AJAX listas para
descargar (galerías de imágenes, reflejos para imágenes, formularios, tablas
reordenables, etc.)
Pragmatic Ajax: A Web 2.0 Primer, Justin Gehtland, Ben Galbraith, Dion
Almaer (ISBN—13: 978-0976694083). Se trata de un libro muy práctico que
incluye muchos ejemplos reales fáciles de entender a pesar de su complejidad.
Ver más información sobre el libro.
Ajax in Action, Dave Crane, Eric Pascarello, Darren James (ISBN-13: 978-
1932394610). Aunque no es tan práctico como el anterior, el código de los
ejemplos incluidos está muy bien programado, lo que ayuda a crear aplicaciones
muy profesionales. Ver más información sobre el libro.
Ajax Hacks, Bruce Perry (ISBN-13: 978-0596101695). Colección de trucos y
pequeñas utilidades listas para copiar+pegar en las aplicaciones reales. Ver más
información sobre el libro.
Capítulo 14. Ejercicios resueltos
14.1. Ejercicio 1
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-
1" />
<title>Ejercicio 1 - Objetos</title>
<script type="text/javascript">
// Estructura básica del objeto Factura
var factura = {
empresa: {
nombre: "Nombre de la empresa",
direccion: "Dirección de la empresa",
telefono: "900900900",
nif: ""
},
cliente: {
nombre: "Nombre del cliente",
direccion: "Dirección del cliente",
telefono: "600600600",
nif: "XXXXXXXXX"
},
elementos: [
{ descripcion: "Producto 1", cantidad: 0, precio: 0 },
{ descripcion: "Producto 2", cantidad: 0, precio: 0 },
{ descripcion: "Producto 3", cantidad: 0, precio: 0 }
],
informacion: {
baseImponible: 0,
iva: 16,
total: 0,
formaPago: "contado"
}
};
factura.muestraTotal = function() {
this.calculaTotal();
alert("TOTAL = " + this.informacion.total + " euros");
}
factura.muestraTotal();
</script>
</head>
<body>
</body>
</html>
14.2. Ejercicio 2
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-
1" />
<title>Ejercicio 2 - Clases</title>
<script type="text/javascript">
// Definición de la clase Cliente
function Cliente(nombre, direccion, telefono, nif) {
this.nombre = nombre;
this.direccion = direccion;
this.telefono = telefono;
this.nif = nif;
}
<body>
</body>
</html>
14.3. Ejercicio 3
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-
1" />
<title>Ejercicio 3 - Prototype</title>
<script type="text/javascript">
// Funcion que añade elementos al final del array
Array.prototype.anadir = function(elemento) {
this[this.length] = elemento;
}
if (!permitir) {
if(!(this.contiene(elemento))) {
this[this.length] = elemento;
}
}
else {
this[this.length] = elemento;
}
}
<body>
</body>
</html>
14.4. Ejercicio 4
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-
1" />
<title>Ejercicio 4 - Prototype</title>
<script type="text/javascript">
// Funcion que trunca la longitud de una cadena
String.prototype.truncar = function(longitud) {
longitud = longitud || 10;
if(this.length > longitud) {
return this.substring(0, longitud);
}
else {
return this;
}
}
<body>
</body>
</html>
14.5. Ejercicio 5
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-
1" />
<title>Ejercicio 5 - Prototype</title>
<script type="text/javascript">
// Devuelve un array sin los elementos que coinciden con
// el elemento que se pasa como parámetro
Array.prototype.sin = function(elemento) {
var filtrado = [];
for(var i=0; i<this.length; i++) {
// Es importante utilizar el operador !== para comprobar
// que los elementos no sean exactamente iguales
if(this[i] !== elemento) {
filtrado.push(this[i]);
}
}
return filtrado;
}
<body>
</body>
</html>
var objeto1 = {
muestraMensaje: function(){
alert("hola mundo");
}
};
var objeto2 = {
a: 0,
b: 0,
suma: function() {
return this.a + this.b;
}
};
alert(objeto1.implementa("muestraMensaje"));
alert(objeto1.implementa("suma"));
alert(objeto2.implementa("muestraMensaje"));
alert(objeto2.implementa("suma"));
alert(objeto1.implementa("implementa"));
// Un objeto vacío también debería incluir el método "implementa"
alert({}.implementa('implementa'));
</script>
</head>
<body>
</body>
</html>
14.7. Ejercicio 7
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-
1" />
<title>Ejercicio 7 - DOM</title>
<script type="text/javascript">
window.onload = function() {
// Numero de enlaces de la pagina
var enlaces = document.getElementsByTagName("a");
alert("Numero de enlaces = "+enlaces.length);
<body>
<p>Lorem ipsum dolor sit amet, <a href="http://prueba">consectetuer
adipiscing elit</a>. Sed mattis enim vitae orci. Phasellus libero.
Maecenas nisl arcu, consequat congue, commodo nec, commodo ultricies,
turpis. Quisque sapien nunc, posuere vitae, rutrum et, luctus at,
pede. Pellentesque massa ante, ornare id, aliquam vitae, ultrices
porttitor, pede. Nullam sit amet nisl elementum elit convallis
malesuada. Phasellus magna sem, semper quis, faucibus ut, rhoncus non,
mi. <a href="http://prueba2">Fusce porta</a>. Duis pellentesque, felis
eu adipiscing ullamcorper, odio urna consequat arcu, at posuere ante
quam non dolor. Lorem ipsum dolor sit amet, consectetuer adipiscing
elit. Duis scelerisque. Donec lacus neque, vehicula in, eleifend
vitae, venenatis ac, felis. Donec arcu. Nam sed tortor nec ipsum
aliquam ullamcorper. Duis accumsan metus eu urna. Aenean vitae enim.
Integer lacus. Vestibulum venenatis erat eu odio. Praesent id
metus.</p>
14.8. Ejercicio 8
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-
1" />
<title>Ejercicio 8 - DOM</title>
<script type="text/javascript">
window.onload = function() {
// Todos los enlaces deben cambiarse por un protocolo más seguro:
"https://"
var enlaces = document.getElementsByTagName("a");
for(var i=0; i<enlaces.length; i++) {
var href_anterior = enlaces[i].getAttribute('href');
var nuevo_href = href_anterior.replace('http://', "https://");
// También se puede utilizar la función split()
// var trozos = href_anterior.split('://');
// var nuevo_href = 'https://' + trozos[1];
enlaces[i].setAttribute('href', nuevo_href);
}
<body>
<p>Lorem ipsum dolor sit amet, <a class="importante"
href="http://www.prueba.com">consectetuer adipiscing elit</a>. Sed
mattis enim vitae orci. Phasellus libero. Maecenas nisl arcu,
consequat congue, commodo nec, commodo ultricies, turpis. Quisque
sapien nunc, posuere vitae, rutrum et, luctus at, pede. Pellentesque
massa ante, ornare id, aliquam vitae, ultrices porttitor, pede. Nullam
sit amet nisl elementum elit convallis malesuada. Phasellus magna sem,
semper quis, faucibus ut, rhoncus non, mi. <a class="importante"
href="http://prueba2">Fusce porta</a>. Duis pellentesque, felis eu
adipiscing ullamcorper, odio urna consequat arcu, at posuere ante quam
non dolor. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
Duis scelerisque. Donec lacus neque, vehicula in, eleifend vitae,
venenatis ac, felis. Donec arcu. Nam sed tortor nec ipsum aliquam
ullamcorper. Duis accumsan metus eu urna. Aenean vitae enim. Integer
lacus. Vestibulum venenatis erat eu odio. Praesent id metus.</p>
14.9. Ejercicio 9
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-
1" />
<title>Ejercicio 9 - Crear, eliminar y modidicar nodos DOM</title>
<script type="text/javascript">
function genera() {
// Generar números aleatorios entre 0 y 10
var numero1 = (Math.random()*10).toFixed();
var numero2 = (Math.random()*10).toFixed();
function compara() {
// Obtener los nodos padre de los párrafos
var primero = document.getElementById("primero");
var segundo = document.getElementById("segundo");
// Obtener los párrafos (existen varios métodos...)
var parrafo1 = primero.getElementsByTagName("p")[0];
var parrafo2 = segundo.firstChild;
<style type="text/css">
#primero, #segundo, #resultado {width: 150px; height: 150px; border:
thin solid silver; background: #F5F5F5; float: left; margin:20px;
font-size: 6em; color: #333; text-align: center; padding: 5px; font-
family:Arial, Helvetica, sans-serif;}
#primero p, #segundo p, #resultado p {margin:.2em 0;}
#resultado {margin-left:1.3em; border-color: black;}
.clear {clear:both;}
#compara {margin-left:11em;}
#genera {font-size:1.2em; margin-left:8em;}
</style>
</head>
<body>
<div id="primero"></div>
<div id="segundo"></div>
<div class="clear"></div>
<div id="resultado"></div>
</body>
</html>
<script type="text/javascript">
function muestraOculta() {
// Obtener el ID del elemento
var id = this.id;
id = id.split('_');
id = id[1];
window.onload = function() {
document.getElementById('enlace_1').onclick = muestraOculta;
document.getElementById('enlace_2').onclick = muestraOculta;
document.getElementById('enlace_3').onclick = muestraOculta;
}
</script>
</head>
<body>
<br/>
<br/>
<p id="contenidos_3">[3] Lorem ipsum dolor sit amet, consectetuer
adipiscing elit. Sed mattis enim vitae orci. Phasellus libero.
Maecenas nisl arcu, consequat congue, commodo nec, commodo ultricies,
turpis. Quisque sapien nunc, posuere vitae, rutrum et, luctus at,
pede. Pellentesque massa ante, ornare id, aliquam vitae, ultrices
porttitor, pede. Nullam sit amet nisl elementum elit convallis
malesuada. Phasellus magna sem, semper quis, faucibus ut, rhoncus non,
mi. Duis pellentesque, felis eu adipiscing ullamcorper, odio urna
consequat arcu, at posuere ante quam non dolor. Lorem ipsum dolor sit
amet, consectetuer adipiscing elit. Duis scelerisque.</p>
<a id="enlace_3" href="#">Ocultar contenidos</a>
</body>
</html>
14.11. Ejercicio 11
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Ejercicio 11 - Estados de la petición AJAX</title>
<style type="text/css">
body { font: 13px Arial, Helvetica, sans-serif; }
h2 { margin-bottom: 0; font-size: 1.2em; }
#recurso, #enviar { padding: .3em; font-size: 1.2em; }
#principal { float: left; width: 70%; }
#secundario { float: right; width: 25%; }
#contenidos, #estados, #cabeceras, #codigo {
border: 2px solid #CCC;
background: #FAFAFA;
padding: 1em;
white-space: pre;
}
#contenidos {
min-height: 400px;
max-height: 600px;
overflow: scroll;
}
#estados { min-height: 200px; }
#cabeceras { min-height: 200px; }
#codigo { min-height: 100px; font-size: 1.5em; }
</style>
<script type="text/javascript">
String.prototype.transformaCaracteresEspeciales = function() {
return unescape(escape(this).
replace(/%0A/g, '<br/>').
replace(/%3C/g, '<').
replace(/%3E/g, '>'));
}
window.onload = function() {
// Cargar en el input text la URL de la página
var recurso = document.getElementById('recurso');
recurso.value = location.href;
function cargaContenido() {
// Borrar datos anteriores
document.getElementById('contenidos').innerHTML = "";
document.getElementById('estados').innerHTML = "";
// Realizar petición
tiempoInicial = new Date();
var recurso = document.getElementById('recurso').value;
peticion.open('GET', recurso+'?nocache='+Math.random(), true);
peticion.send(null);
}
// Función de respuesta
function muestraContenido() {
var tiempoFinal = new Date();
var milisegundos = tiempoFinal - tiempoInicial;
if(peticion.readyState == 4) {
if(peticion.status == 200) {
var contenidos = document.getElementById('contenidos');
contenidos.innerHTML =
peticion.responseText.transformaCaracteresEspeciales();
}
muestraCabeceras();
muestraCodigoEstado();
}
}
function muestraCabeceras() {
var cabeceras = document.getElementById('cabeceras');
cabeceras.innerHTML =
peticion.getAllResponseHeaders().transformaCaracteresEspeciales();
}
function muestraCodigoEstado() {
var codigo = document.getElementById('codigo');
codigo.innerHTML = peticion.status + "<br/>" +
peticion.statusText;
}
</script>
</head>
<body>
<form action="#">
URL: <input type="text" id="recurso" size="70" />
<input type="button" id="enviar" value="Mostrar contenidos" />
</form>
<div id="principal">
<h2>Contenidos del archivo:</h2>
<div id="contenidos"></div>
<div id="secundario">
<h2>Estados de la petición:</h2>
<div id="estados"></div>
<h2>Código de estado:</h2>
<div id="codigo"></div>
</div>
</body>
</html>
14.12. Ejercicio 12
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Ejercicio 12 - Actualización periódica de contenidos</title>
<style type="text/css">
body { margin: 0; }
#contenidos { padding: 1em; }
#ticker {
height: 20px;
padding: .3em;
border-bottom: 1px solid #CCC;
background: #FAFAFA;
font-family: Arial, Helvetica, sans-serif;
}
#ticker strong { margin-right: 1em; }
#acciones {
position: absolute;
top: 3px;
right: 3px;
}
</style>
<script type="text/javascript">
Number.prototype.toString = function(){
if (this < 10) {
return '0' + this;
}
else {
return this;
}
}
window.onload = function(){
intervalo = setInterval(descargaNoticia, 1000);
document.getElementById('detener').onclick = detener;
document.getElementById('anterior').onclick = anterior;
document.getElementById('siguiente').onclick = siguiente;
}
function descargaNoticia(){
if (peticion == null) {
if (window.XMLHttpRequest) {
peticion = new XMLHttpRequest();
}
else {
peticion = new ActiveXObject("Microsoft.XMLHTTP");
}
}
else {
peticion.abort();
}
peticion.onreadystatechange = procesaNoticia;
peticion.open('GET',
'http://localhost/RUTA_HASTA_ARCHIVO/generaContenidos.php'+'?nocache='
+Math.random(), true);
peticion.send(null);
}
function procesaNoticia(){
if (peticion.readyState == 4) {
if (peticion.status == 200) {
var fechaHora = new Date();
var hora = fechaHora.getHours().toString() + ":" +
fechaHora.getMinutes().toString() + ":" +
fechaHora.getSeconds().toString();
noticias.push({
hora: hora,
titular: peticion.responseText
});
muestraNoticia(noticias[noticias.length - 1]);
}
}
}
function detener(){
clearInterval(intervalo);
this.value = 'Iniciar';
this.onclick = iniciar;
}
function iniciar(){
intervalo = setInterval(descargaNoticia, 1000);
this.value = 'Detener';
this.onclick = detener;
numeroElemento = null;
}
function anterior(){
var detener = document.getElementById('detener');
clearInterval(intervalo);
detener.value = 'Iniciar';
detener.onclick = iniciar;
if (numeroElemento == null) {
numeroElemento = noticias.length - 1;
}
if (numeroElemento > 0) {
numeroElemento--;
}
function siguiente(){
var detener = document.getElementById('detener');
clearInterval(intervalo);
detener.value = 'Iniciar';
detener.onclick = iniciar;
if (numeroElemento == null) {
numeroElemento = noticias.length - 1;
}
function muestraNoticia(noticia){
var ticker = document.getElementById('ticker');
ticker.innerHTML = "<strong>" + noticia.hora + "</strong> " +
noticia.titular;
ticker.style.backgroundColor = '#FFFF99';
setTimeout(limpiaTicker, 300);
}
function limpiaTicker(){
var ticker = document.getElementById('ticker');
ticker.style.backgroundColor = '#FAFAFA';
}
</script>
</head>
<body>
<div id="ticker"></div>
<div id="acciones">
<input type="button" id="detener" value="Detener"/>
<input type="button" id="anterior" value="« Anterior" />
<input type="button" id="siguiente" value="Siguiente »" />
</div>
<div id="contenidos">
<h1>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</h1>
<p>Proin tristique condimentum sem. Fusce lorem sem, laoreet nec,
laoreet et, venenatis nec, ligula.
Nunc dictum sodales lorem. Fusce turpis. Nullam semper, ipsum ut
ultrices mattis, nulla magna luctus
purus, sit amet vehicula magna magna vel velit.</p>
<p>Ut eros magna, congue in, sodales ac, facilisis ac, dolor. Aenean
faucibus pellentesque est. Proin
cursus. Vivamus mollis enim in magna. Donec urna risus, convallis
eget, aliquet non, auctor sit amet, leo.
Duis tellus purus, pharetra in, cursus sed, posuere semper, lorem.
Fusce eget velit nec felis tempus
gravida. Donec et augue vitae nulla posuere hendrerit. Nulla vehicula
scelerisque massa. Phasellus eget
lorem id quam molestie ultrices. Integer ac ligula sit amet lectus
condimentum euismod. Sed malesuada
orci eu neque.</p>
</div>
</body>
</html>
14.13. Ejercicio 13
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-
1" />
<title>Ejercicio 13 - Comprobar disponibilidad del login</title>
<script type="text/javascript">
var READY_STATE_COMPLETE=4;
var peticion_http = null;
function inicializa_xhr() {
if (window.XMLHttpRequest) {
return new XMLHttpRequest();
} else if (window.ActiveXObject) {
return new ActiveXObject("Microsoft.XMLHTTP");
}
}
function comprobar() {
var login = document.getElementById("login").value;
peticion_http = inicializa_xhr();
if(peticion_http) {
peticion_http.onreadystatechange = procesaRespuesta;
peticion_http.open("POST",
"http://localhost/ajax/compruebaDisponibilidad.php", true);
peticion_http.setRequestHeader("Content-Type", "application/x-www-
form-urlencoded");
peticion_http.send("login="+login+"&nocache="+Math.random());
}
}
function procesaRespuesta() {
if(peticion_http.readyState == READY_STATE_COMPLETE) {
if (peticion_http.status == 200) {
var login = document.getElementById("login").value;
if(peticion_http.responseText == "si") {
document.getElementById("disponibilidad").innerHTML = "El
nombre elegido ["+login+"] está disponible";
}
else {
document.getElementById("disponibilidad").innerHTML = "NO está
disponible el nombre elegido ["+login+"]";
}
}
}
}
window.onload = function() {
document.getElementById("comprobar").onclick = comprobar;
}
</script>
</head>
<body>
<h1>Comprobar disponibilidad del login</h1>
<form>
<label for="login">Nombre de usuario:</label>
<input type="text" name="login" id="login" />
<a id="comprobar" href="#">Comprobar disponibilidad...</a>
</form>
<div id="disponibilidad"></div>
</body>
</html>
14.14. Ejercicio 14
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-
1" />
<title>Ejercicio 14 - Comprobar disponibilidad del login y mostrar
alternativas</title>
<script type="text/javascript">
var READY_STATE_COMPLETE=4;
var peticion_http = null;
function inicializa_xhr() {
if (window.XMLHttpRequest) {
return new XMLHttpRequest();
} else if (window.ActiveXObject) {
return new ActiveXObject("Microsoft.XMLHTTP");
}
}
function comprobar() {
var login = document.getElementById("login").value;
peticion_http = inicializa_xhr();
if(peticion_http) {
peticion_http.onreadystatechange = procesaRespuesta;
peticion_http.open("POST",
"http://localhost/ajax/compruebaDisponibilidadXML.php", true);
peticion_http.setRequestHeader("Content-Type", "application/x-www-
form-urlencoded");
peticion_http.send("login="+login+"&nocache="+Math.random());
}
}
function procesaRespuesta() {
if(peticion_http.readyState == READY_STATE_COMPLETE) {
if (peticion_http.status == 200) {
var login = document.getElementById("login").value;
var documento_xml = peticion_http.responseXML;
var raiz = documento_xml.getElementsByTagName("respuesta")[0];
var disponible =
raiz.getElementsByTagName("disponible")[0].firstChild.nodeValue;
if(disponible == "si") {
document.getElementById("disponibilidad").innerHTML = "El
nombre elegido ["+login+"] está disponible";
}
else {
var mensaje = "NO está disponible el nombre elegido
["+login+"]. Puedes probar con las siguientes alternativas.";
var alternativas =
raiz.getElementsByTagName("alternativas")[0];
var logins = alternativas.getElementsByTagName("login");
mensaje += "<ul>";
for(var i=0; i<logins.length; i++) {
mensaje += "<li><a href=\"#\"
onclick=\"selecciona('"+logins[i].firstChild.nodeValue+"'); return
false\">"+logins[i].firstChild.nodeValue+"<\/a><\/li>";
}
mensaje += "<\/ul>";
document.getElementById("disponibilidad").innerHTML = mensaje;
}
}
}
}
function selecciona(login) {
var cuadroLogin = document.getElementById("login");
cuadroLogin.value = login;
}
window.onload = function() {
document.getElementById("comprobar").onclick = comprobar;
}
</script>
</head>
<body>
<h1>Comprobar disponibilidad del login y mostrar alternativas</h1>
<form>
<label for="login">Nombre de usuario:</label>
<input type="text" name="login" id="login" />
<a id="comprobar" href="#">Comprobar disponibilidad...</a>
</form>
<div id="disponibilidad"></div>
</body>
</html>
14.15. Ejercicio 15
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-
1" />
<title>Ejercicio 15 - Comprobar disponibilidad del login y mostrar
alternativas</title>
<script type="text/javascript">
var READY_STATE_COMPLETE=4;
var peticion_http = null;
function inicializa_xhr() {
if (window.XMLHttpRequest) {
return new XMLHttpRequest();
} else if (window.ActiveXObject) {
return new ActiveXObject("Microsoft.XMLHTTP");
}
}
function comprobar() {
var login = document.getElementById("login").value;
peticion_http = inicializa_xhr();
if(peticion_http) {
peticion_http.onreadystatechange = procesaRespuesta;
peticion_http.open("POST",
"http://localhost/ajax/compruebaDisponibilidadJSON.php", true);
peticion_http.setRequestHeader("Content-Type", "application/x-www-
form-urlencoded");
peticion_http.send("login="+login+"&nocache="+Math.random());
}
}
function procesaRespuesta() {
if(peticion_http.readyState == READY_STATE_COMPLETE) {
if (peticion_http.status == 200) {
var login = document.getElementById("login").value;
var respuesta_json = peticion_http.responseText;
var respuesta = eval("("+respuesta_json+")");
if(respuesta.disponible == "si") {
document.getElementById("disponibilidad").innerHTML = "El
nombre elegido ["+login+"] está disponible";
}
else {
var mensaje = "NO está disponible el nombre elegido
["+login+"]. Puedes probar con las siguientes alternativas.";
mensaje += "<ul>";
for(var i in respuesta.alternativas) {
mensaje += "<li><a href=\"#\"
onclick=\"selecciona('"+respuesta.alternativas[i]+"'); return
false\">"+respuesta.alternativas[i]+"<\/a><\/li>";
}
mensaje += "<\/ul>";
document.getElementById("disponibilidad").innerHTML = mensaje;
}
}
}
}
function selecciona(login) {
var cuadroLogin = document.getElementById("login");
cuadroLogin.value = login;
}
window.onload = function() {
document.getElementById("comprobar").onclick = comprobar;
}
</script>
</head>
<body>
<h1>Comprobar disponibilidad del login y mostrar alternativas</h1>
<form>
<label for="login">Nombre de usuario:</label>
<input type="text" name="login" id="login" />
<a id="comprobar" href="#">Comprobar disponibilidad...</a>
</form>
<div id="disponibilidad"></div>
</body>
</html>
14.16. Ejercicio 16
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-
1" />
<title>Ejercicio 16 - Listas desplegables encadenadas</title>
<script type="text/javascript">
var peticion = null;
function inicializa_xhr() {
if (window.XMLHttpRequest) {
return new XMLHttpRequest();
} else if (window.ActiveXObject) {
return new ActiveXObject("Microsoft.XMLHTTP");
}
}
function muestraProvincias() {
if (peticion.readyState == 4) {
if (peticion.status == 200) {
var lista = document.getElementById("provincia");
var documento_xml = peticion.responseXML;
var provincias =
documento_xml.getElementsByTagName("provincias")[0];
var lasProvincias =
provincias.getElementsByTagName("provincia");
lista.options[0] = new Option("- selecciona -");
/*
var codigo_html = "";
codigo_html += "<option>- selecciona -<\/option>";
for(var i=0; i<lasProvincias.length; i++) {
var codigo =
lasProvincias[i].getElementsByTagName("codigo")[0].firstChild.nodeValu
e;
var nombre =
lasProvincias[i].getElementsByTagName("nombre")[0].firstChild.nodeValu
e;
codigo_html += "<option
value=\""+codigo+"\">"+nombre+"<\/option>";
}
function cargaMunicipios() {
var lista = document.getElementById("provincia");
var provincia = lista.options[lista.selectedIndex].value;
if(!isNaN(provincia)) {
peticion = inicializa_xhr();
if (peticion) {
peticion.onreadystatechange = muestraMunicipios;
peticion.open("POST",
"http://localhost/RUTA_HASTA_ARCHIVO/cargaMunicipiosXML.php?nocache="
+ Math.random(), true);
peticion.setRequestHeader("Content-Type", "application/x-www-
form-urlencoded");
peticion.send("provincia=" + provincia);
}
}
}
function muestraMunicipios() {
if (peticion.readyState == 4) {
if (peticion.status == 200) {
var lista = document.getElementById("municipio");
var documento_xml = peticion.responseXML;
var municipios =
documento_xml.getElementsByTagName("municipios")[0];
var losMunicipios =
municipios.getElementsByTagName("municipio");
window.onload = function() {
peticion = inicializa_xhr();
if(peticion) {
peticion.onreadystatechange = muestraProvincias;
peticion.open("GET",
"http://localhost/RUTA_HASTA_ARCHIVO/cargaProvinciasXML.php?nocache="+
Math.random(), true);
peticion.send(null);
}
document.getElementById("provincia").onchange = cargaMunicipios;
}
</script>
</head>
<body>
<h1>Listas desplegables encadenadas</h1>
<form>
<label for="provincia">Provincia</label>
<select id="provincia">
<option>Cargando...</option>
</select>
<br/><br/>
<label for="municipio">Municipio</label>
<select id="municipio">
<option>- selecciona una provincia -</option>
</select>
</form>
</body>
</html>
14.17. Ejercicio 17
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-
1" />
<title>Ejercicio 17 - Listas desplegables encadenadas</title>
<script type="text/javascript">
var peticion = null;
function inicializa_xhr() {
if (window.XMLHttpRequest) {
return new XMLHttpRequest();
} else if (window.ActiveXObject) {
return new ActiveXObject("Microsoft.XMLHTTP");
}
}
function muestraProvincias() {
if (peticion.readyState == 4) {
if (peticion.status == 200) {
var lista = document.getElementById("provincia");
var provincias = eval('(' + peticion.responseText + ')');
function cargaMunicipios() {
var lista = document.getElementById("provincia");
var provincia = lista.options[lista.selectedIndex].value;
if(!isNaN(provincia)) {
peticion = inicializa_xhr();
if (peticion) {
peticion.onreadystatechange = muestraMunicipios;
peticion.open("POST",
"http://localhost/RUTA_HASTA_ARCHIVO/cargaMunicipiosJSON.php?nocache="
+ Math.random(), true);
peticion.setRequestHeader("Content-Type", "application/x-www-
form-urlencoded");
peticion.send("provincia=" + provincia);
}
}
}
function muestraMunicipios() {
if (peticion.readyState == 4) {
if (peticion.status == 200) {
var lista = document.getElementById("municipio");
var municipios = eval('(' + peticion.responseText + ')');
lista.options.length = 0;
var i=0;
for(var codigo in municipios) {
lista.options[i] = new Option(municipios[codigo], codigo);
i++;
}
}
}
}
window.onload = function() {
peticion = inicializa_xhr();
if(peticion) {
peticion.onreadystatechange = muestraProvincias;
peticion.open("GET",
"http://localhost/RUTA_HASTA_ARCHIVO/cargaProvinciasJSON.php?nocache="
+Math.random(), true);
peticion.send(null);
}
document.getElementById("provincia").onchange = cargaMunicipios;
}
</script>
</head>
<body>
<h1>Listas desplegables encadenadas</h1>
<form>
<label for="provincia">Provincia</label>
<select id="provincia">
<option>Cargando...</option>
</select>
<br/><br/>
<label for="municipio">Municipio</label>
<select id="municipio">
<option>- selecciona una provincia -</option>
</select>
</form>
</body>
</html>
14.18. Ejercicio 18
El archivo util.js que utiliza el ejercicio se incluye en el archivo ZIP de la solución
completa.
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-
1">
<title>Ejercicio 18 - Teclado virtual</title>
function descargaTeclado(idioma) {
var cargador = new
net.CargadorContenidosCompleto("http://localhost/RUTA_HASTA_EL_ARCHIVO
/tecladoVirtual.php?nocache="+Math.random(),
function() { teclados[idioma] =
eval('('+this.req.responseText+')'); },
null,
"POST",
"accion=cargaTeclado&idioma="+idioma,
"application/x-www-form-urlencoded",
false);
}
function cargaTeclado() {
// Obtener o descargar el teclado requerido
if(teclados[tecladoIdioma] == null) {
descargaTeclado(tecladoIdioma);
}
var teclado = teclados[tecladoIdioma][tecladoVariante];
document.getElementById('tecla_especial_'+teclasEspeciales[i]).onclick
= pulsaTeclaEspecial;
}
tecladoActivo = teclado;
}
function pulsaTecla() {
var teclaPulsada = this.id.replace(/tecla_/gi, "");
var caracter = tecladoActivo[teclaPulsada];
teclasPulsadas.push(caracter);
mostrarContenidos();
}
function apagaTecla() {
for(var i in tecladoActivo) {
if(tecladoActivo[i]) {
document.getElementById('tecla_'+i).className =
document.getElementById('tecla_'+i).className.replace(/pulsada/ig,
"");
}
}
for(var i in teclasEspeciales) {
if(teclasEspeciales[i]) {
document.getElementById('tecla_especial_'+teclasEspeciales[i]).classNa
me =
document.getElementById('tecla_especial_'+teclasEspeciales[i]).classNa
me.replace(/pulsada/ig, "");
}
}
}
function pulsaTeclaEspecial() {
var teclaPulsada = this.id.replace(/tecla_especial_/gi, "");
switch(teclaPulsada) {
case 'borrado':
teclasPulsadas.pop();
break;
case 'tabulador':
teclasPulsadas.push('\t');
break;
case 'enter':
teclasPulsadas.push('\n');
break;
case 'espaciadora':
teclasPulsadas.push(' ');
break;
case 'mayusculas':
cargaVarianteTeclado('mayusculas');
break;
case 'shift_izquierdo':
cargaVarianteTeclado('shift_izquierdo');
break;
case 'shift_derecho':
cargaVarianteTeclado('shift_derecho');
break;
case 'altgr':
cargaVarianteTeclado('altgr');
break;
}
mostrarContenidos();
}
function cargaVarianteTeclado(variante) {
var nombreVariante = {mayusculas: 'caps', shift_izquierdo: 'shift',
shift_derecho: 'shift', altgr: 'altgr'};
if(estado[variante] == true) {
estado[variante] = false;
tecladoVariante = 'normal';
}
else {
estado[variante] = true;
tecladoVariante = nombreVariante[variante];
}
cargaTeclado();
}
document.getElementById(elemento).innerHTML = contenido;
}
function muestraIdiomas() {
var respuesta = eval('('+this.req.responseText+')');
var lista = document.getElementById("idiomas");
var i=0;
for(var codigo in respuesta.idiomas) {
// Añadir idiomas a la lista desplegable
lista.options[i] = new Option(respuesta.idiomas[codigo], codigo);
i++;
// Crear los objetos que almacenan los teclados de cada idioma
teclados[codigo] = null;
}
function cambiaIdioma() {
var lista = document.getElementById("idiomas");
tecladoIdioma = lista.options[lista.selectedIndex].value;
cargaTeclado();
}
function guarda() {
var cargador = new
net.CargadorContenidosCompleto("http://localhost/RUTA_HASTA_EL_ARCHIVO
/tecladoVirtual.php?nocache="+Math.random(),
function() {
mostrarContenidos(unescape(this.req.responseText),
"contenidoGuardado"); },
null,
"POST",
"accion=guardar&contenido="+escape(teclasPulsadas.join("")),
"application/x-www-form-urlencoded");
}
window.onload = function() {
var cargador = new
net.CargadorContenidosCompleto("http://localhost/RUTA_HASTA_EL_ARCHIVO
/tecladoVirtual.php?nocache="+Math.random(),
muestraIdiomas,
null,
"POST",
"accion=listaIdiomas",
"application/x-www-form-urlencoded");
setInterval(guarda, 30 * 1000);
}
/*
Los teclados de cada idioma y algunas ideas de implementación son
originales del JavaScript Virtual Keyboard, cuya nota de copyright
se incluye a continuación:
*/
/*
* JavaScript Virtual Keyboard, version 2.2
*
* Copyright (C) 2006-2007 Dmitriy Khudorozhkov
*
* This software is provided "as-is", without any express or implied
warranty.
* In no event will the author be held liable for any damages arising
from the
* use of this software.
*
* Permission is granted to anyone to use this software for any
purpose,
* including commercial applications, and to alter it and redistribute
it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must
not
* claim that you wrote the original software. If you use this
software
* in a product, an acknowledgment in the product documentation
would be
* appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must
not be
* misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source
distribution.
*
* - Dmitriy Khudorozhkov, [email protected]
*/
/*
El diseño del teclado es obra de Chris Hester y se puede descargar
desde http://www.designdetector.com/archives/05/03/KeyboardDemo.php
*/
</script>
</head>
<body>
<div id="led-active"> </div>
<div id="led2"> </div>
<div id="led3"> </div>
<div id="keyboard">
<div id="row1">
<kbd id="esc">Esc</kbd><kbd>F1</kbd><kbd>F2</kbd><kbd>F3</kbd><kbd
id="f4">F4</kbd>
<kbd>F5</kbd><kbd>F6</kbd><kbd>F7</kbd><kbd
id="f8">F8</kbd><kbd>F9</kbd><kbd>F10</kbd>
<kbd>F11</kbd><kbd class="rightgap">F12</kbd><kbd
class="narrow">Impr<br>Pant</kbd>
<kbd class="narrow">Bloq<br>Des</kbd><kbd class="narrow">Pausa</kbd>
<span class="lock">Bloq<br>Num</span><span
class="lock">Bloq<br>Mayus</span>
<span class="lock">Bloq<br>Des</span>
</div>
<div id="row2">
<kbd id="tecla_0"></kbd><kbd id="tecla_1"></kbd><kbd
id="tecla_2"></kbd>
<kbd id="tecla_3"></kbd><kbd id="tecla_4"></kbd><kbd
id="tecla_5"></kbd>
<kbd id="tecla_6"></kbd><kbd id="tecla_7"></kbd><kbd
id="tecla_8"></kbd>
<kbd id="tecla_9"></kbd><kbd id="tecla_10"></kbd><kbd
id="tecla_11"></kbd>
<kbd id="tecla_12"></kbd>
<kbd id="tecla_especial_borrado" class="rightgap backspace"><img
src="imagenes/backspace.gif" alt="" height="19" width="52"></kbd>
<kbd class="narrow">Insert</kbd><kbd class="narrow">Inicio</kbd><kbd
class="narrow rightgap">Re<br>Pag</kbd>
<kbd class="narrow">Bloq<br>Num</kbd><kbd>/</kbd><kbd>*</kbd><kbd>-
</kbd>
</div>
<div>
<kbd id="tecla_especial_tabulador" class="tab"><img
src="imagenes/tab.gif" alt="" height="27" width="23"></kbd>
<kbd id="tecla_13"></kbd><kbd id="tecla_14"></kbd><kbd
id="tecla_15"></kbd><kbd id="tecla_16"></kbd>
<kbd id="tecla_17"></kbd><kbd id="tecla_18"></kbd><kbd
id="tecla_19"></kbd><kbd id="tecla_20"></kbd>
<kbd id="tecla_21"></kbd><kbd id="tecla_22"></kbd><kbd
id="tecla_23"></kbd><kbd id="tecla_24"></kbd>
<kbd id="tecla_especial_enter" class="rightgap enter-top"><img
src="imagenes/enter.gif" alt="" height="24" width="24"></kbd>
<kbd class="narrow delete">Supr</kbd><kbd
class="narrow">Fin</kbd><kbd class="narrow rightgap">Av<br>Pag</kbd>
<kbd>7<br><span class="narrow">Inicio</span></kbd><kbd>8<br><img
src="imagenes/arrow-up-small.gif" alt="" height="13" width="21"></kbd>
<kbd>9<br><span class="narrow">RePag</span></kbd><kbd class="numpad-
top">+</kbd>
</div>
<div>
<kbd id="tecla_especial_mayusculas" class="narrow capslock">Bloq
Mayus</kbd>
<kbd id="tecla_25"></kbd><kbd id="tecla_26"></kbd><kbd
id="tecla_27"></kbd><kbd id="tecla_28"></kbd>
<kbd id="tecla_29"></kbd><kbd id="tecla_30"></kbd><kbd
id="tecla_31"></kbd><kbd id="tecla_32"></kbd>
<kbd id="tecla_33"></kbd><kbd id="tecla_34"></kbd><kbd
id="tecla_35"></kbd><kbd id="tecla_36"></kbd>
<kbd id="enter-bottom"> </kbd><kbd>4<br><img
src="imagenes/arrow-left-small.gif" alt="" height="7"
width="13"></kbd>
<kbd>5</kbd><kbd>6<br><img src="imagenes/arrow-right-small.gif"
alt="" height="7" width="13"></kbd>
<kbd class="numpad-bottom"> </kbd>
</div>
<div>
<kbd id="tecla_especial_shift_izquierdo" class="narrow shift-
left"><img src="imagenes/shift.gif" alt="" height="20"
width="17"></kbd>
<kbd>><br><</kbd><kbd id="tecla_37"></kbd><kbd
id="tecla_38"></kbd><kbd id="tecla_39"></kbd>
<kbd id="tecla_40"></kbd><kbd id="tecla_41"></kbd><kbd
id="tecla_42"></kbd><kbd id="tecla_43"></kbd>
<kbd id="tecla_44"></kbd><kbd id="tecla_45"></kbd><kbd
id="tecla_46"></kbd>
<kbd id="tecla_especial_shift_derecho" class="shift-right"><img
src="imagenes/shift.gif" alt="" height="20" width="17"></kbd>
<kbd id="arrow-up"><img src="imagenes/arrow-up.gif" alt=""
height="22" width="21"></kbd>
<kbd>1<br><span class="narrow">Fin</span></kbd>
<kbd>2<br><img src="imagenes/arrow-down-small.gif" alt=""
height="13" width="21"></kbd>
<kbd>3<br><span class="narrow">AvPag</span></kbd>
<kbd class="numpad-top narrow">Enter</kbd>
</div>
<div>
<kbd id="ctrl-left" class="narrow">Ctrl</kbd>
<kbd id="windows-left"><img src="imagenes/windows-key.gif"
alt="Windows Key" height="13" width="16"></kbd>
<kbd id="alt-left" class="narrow">Alt</kbd>
<kbd id="tecla_especial_espaciadora" class="spacebar"> </kbd>
<kbd id="tecla_especial_altgr" class="narrow alt-right">Alt
Gr</kbd>
<kbd id="windows-right"><img src="imagenes/windows-key.gif" alt=""
height="13" width="16"></kbd>
<kbd id="application"><img src="imagenes/windows-application-
key.gif" alt="" height="13" width="12"></kbd>
<kbd id="ctrl-right" class="narrow rightgap">Ctrl</kbd>
<kbd><img src="imagenes/arrow-left.gif" alt="" height="19"
width="21"></kbd>
<kbd><img src="imagenes/arrow-down.gif" alt="" height="22"
width="21"></kbd>
<kbd class="rightgap"><img src="imagenes/arrow-right.gif" alt=""
height="19" width="21"></kbd>
<kbd class="numpad-0" id="_0">0<br><span
class="narrow">Ins</span></kbd>
<kbd>.<br><span class="narrow">Supr</span></kbd>
<kbd class="numpad-bottom"> </kbd>
</div>
</div>
<select id="idiomas"><option>Cargando...</option></select>
<div id="contenido"></div>
</html>
14.19. Ejercicio 19
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-
1" />
<title>Ejercicio 19 - Autocompletar</title>
<script type="text/javascript">
var peticion = null;
var elementoSeleccionado = -1;
var sugerencias = null;
var cacheSugerencias = {};
function inicializa_xhr() {
if (window.XMLHttpRequest) {
return new XMLHttpRequest();
} else if (window.ActiveXObject) {
return new ActiveXObject("Microsoft.XMLHTTP");
}
}
Array.prototype.formateaLista = function() {
var codigoHtml = "";
codigoHtml = "<ul>";
for(var i=0; i<this.length; i++) {
if(i == elementoSeleccionado) {
codigoHtml += "<li class=\"seleccionado\">"+this[i]+"</li>";
}
else {
codigoHtml += "<li>"+this[i]+"</li>";
}
}
codigoHtml += "</ul>";
return codigoHtml;
};
function autocompleta() {
var elEvento = arguments[0] || window.event;
var tecla = elEvento.keyCode;
if(cacheSugerencias[texto] == null) {
peticion = inicializa_xhr();
peticion.onreadystatechange = function() {
if(peticion.readyState == 4) {
if(peticion.status == 200) {
sugerencias = eval('('+peticion.responseText+')');
if(sugerencias.length == 0) {
sinResultados();
}
else {
cacheSugerencias[texto] = sugerencias;
actualizaSugerencias();
}
}
}
};
peticion.open('POST',
'http://localhost/RUTA_HASTA_ARCHIVO/autocompletaMunicipios.php?nocach
e='+Math.random(), true);
peticion.setRequestHeader('Content-Type', 'application/x-www-
form-urlencoded');
peticion.send('municipio='+encodeURIComponent(texto));
}
else {
sugerencias = cacheSugerencias[texto];
actualizaSugerencias();
}
}
}
function sinResultados() {
document.getElementById("sugerencias").innerHTML = "No existen
municipios que empiecen con ese texto";
document.getElementById("sugerencias").style.display = "block";
}
function actualizaSugerencias() {
elementoSeleccionado = -1;
muestraSugerencias();
}
function seleccionaElemento() {
if(sugerencias[elementoSeleccionado]) {
document.getElementById("municipio").value =
sugerencias[elementoSeleccionado];
borraLista();
}
}
function muestraSugerencias() {
var zonaSugerencias = document.getElementById("sugerencias");
zonaSugerencias.innerHTML = sugerencias.formateaLista();
zonaSugerencias.style.display = 'block';
}
function borraLista() {
document.getElementById("sugerencias").innerHTML = "";
document.getElementById("sugerencias").style.display = "none";
}
window.onload = function() {
// Crear elemento de tipo <div> para mostrar las sugerencias del
servidor
var elDiv = document.createElement("div");
elDiv.id = "sugerencias";
document.body.appendChild(elDiv);
document.getElementById("municipio").onkeyup = autocompleta;
document.getElementById("municipio").focus();
}
</script>
<style type="text/css">
body {font-family: Arial, Helvetica, sans-serif;}
#sugerencias {width:200px; border:1px solid black; display:none;
margin-left: 83px;}
#sugerencias ul {list-style: none; margin: 0; padding: 0; font-
size:.85em;}
#sugerencias ul li {padding: .2em; border-bottom: 1px solid silver;}
.seleccionado {font-weight:bold; background-color: #FFFF00;}
</style>
</head>
<body>
<h1>Autocompletar texto</h1>
<form>
<label for="municipio">Municipio</label>
<input type="text" id="municipio" name="municipio" size="30" />
<input type="text" id="oculto" name="oculto" style="display:none;"
/>
</form>
</body>
</html>
14.20. Ejercicio 20
El archivo util.js que utiliza el ejercicio se incluye en el archivo ZIP de la solución
completa.
function monitoriza() {
intervalo = setInterval(monitorizaNodos, 1000);
}
function detiene() {
clearInterval(intervalo);
}
function monitorizaNodos() {
for(var i=0; i<nodos.length; i++) {
document.getElementById("nodo"+i).style.border = "3px solid
#000000";
var ping = new net.CargadorContenidosCompleto(nodos[i].url,
procesaPing, noDisponible, "HEAD");
}
}
function procesaPing() {
if(new Date(this.req.getResponseHeader("Date"))) {
var numeroNodo = calculaNumeroNodo(this.url);
document.getElementById("nodo"+numeroNodo).style.border = "3px
solid #00FF00";
document.getElementById("nodo"+numeroNodo).className = "on";
document.getElementById("datos"+numeroNodo).innerHTML =
this.req.getResponseHeader("Server");
}
}
function noDisponible() {
var numeroNodo = calculaNumeroNodo(this.url);
document.getElementById("nodo"+numeroNodo).style.border = "3px solid
#FF0000";
document.getElementById("nodo"+numeroNodo).className = "off";
}
function calculaNumeroNodo(url) {
for(var i=0; i<nodos.length; i++) {
if(nodos[i].url == url) {
return i;
}
}
}
window.onload = function() {
// Crear elemento de tipo <div> para mostrar cada uno de los nodos
for(i=0; i<nodos.length; i++) {
var nodo = document.createElement("div");
nodo.id = "nodo"+i;
nodo.innerHTML = "<strong>" + nodos[i].nombre + "</strong><br>" +
nodos[i].url + "<span id=\"datos"+i+"\"></span>";
document.getElementById("mapa_red").appendChild(nodo);
document.getElementById("nodo"+i).className = "normal";
}
</script>
<style type="text/css">
body {font-size:14px; font-family:Arial, Helvetica, sans-serif;}
.normal, .consulta, .on, .off {width: 140px; text-align: center;
margin: .5em; padding: .5em; }
form {display: inline; }
.normal {background-color: #FFFFFF; border: 3px solid #C0C0C0;}
.consulta {border:3px solid #000000;}
.on {background-color: #00CC00; border: 3px solid #00FF00;}
.off {background-color: #CC0000; border: 3px solid #FF0000;}
#mapa_red {border:5px solid #D0D0D0; float: left; padding: 1em 0;
margin: 1em 0; width: 50%;}
#mapa_red div { float: left; margin: 1em; height: 5em; width: 35%;}
div span {display:block; padding:.3em;}
</style>
</head>
<body>
<h1>Consola de monitorización</h1>
<form>
<input type="button" id="monitoriza" value="Monitorizar"></input>
<input type="button" id="detiene" value="Detener"></input>
</form>
<div id="mapa_red"></div>
</body>
</html>
14.21. Ejercicio 21
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-
1" />
<title>Ejercicio 21 - RSS</title>
<script type="text/javascript">
Object.prototype.get = function(etiqueta) {
return this.getElementsByTagName(etiqueta)[0].textContent;
}
var rss = {
canal: {},
items: []
};
window.onload = function(){
document.getElementById('mostrar').onclick = cargaRss;
}
function cargaRss(){
// Obtener URL de RSS
borrarLog();
log('Averiguar la URL del canal RSS');
function descubreRss(url){
var peticion = new XMLHttpRequest();
peticion.onreadystatechange = function(){};
peticion.open('GET',
'http://localhost/RUTA_HASTA_ARCHIVO/descubreRss.php?url=' +
encodeURIComponent(url), false);
peticion.send(null);
return peticion.responseText;
}
function descargaRss(url){
var peticion = new XMLHttpRequest();
peticion.onreadystatechange = procesaRss;
peticion.open('GET',
'http://localhost/RUTA_HASTA_ARCHIVO/proxy.php?url=' +
encodeURIComponent(url) + '&ct=text/xml', true);
peticion.send(null);
function procesaRss(){
if (peticion.readyState == 4) {
if (peticion.status == 200) {
var xml = peticion.responseXML;
if(canal.getElementsByTagName('image').length > 0) {
var url_imagen =
canal.getElementsByTagName('image')[0].getElementsByTagName('url')[0].
textContent;
rss.canal.titulo = '<img src="'+url_imagen+'"
/>'+rss.canal.titulo;
}
var enlace =
canal.getElementsByTagName('link')[0].textContent;
rss.canal.enlace = enlace;
rss.items[i] = {
titulo: titulo,
enlace: enlace,
descripcion: descripcion,
fecha: fecha
};
}
muestraRss();
}
}
}
}
function muestraRss(){
document.getElementById('noticias').style.display = 'block';
document.getElementById('titulares').innerHTML = '';
document.getElementById('contenidos').innerHTML = '';
function muestraElemento(indice){
var item = rss.items[indice];
var html = "";
html += "<h1><a href=\"" + item.enlace + "\">" + item.titulo +
"</a></h1>";
if (item.fecha != undefined) {
html += "<h2>" + item.fecha + "</h2>";
}
html += "<p>" + item.descripcion + "</p>";
document.getElementById("contenidos").innerHTML = html;
}
function log(mensaje){
document.getElementById('info').innerHTML += mensaje + "<br/>";
}
function borrarLog(){
document.getElementById('info').innerHTML = "";
}
</script>
<style type="text/css">
body { font-family: Arial, Helvetica, sans-serif; }
form { margin: 0; }
#info { margin: 0; font-size: .7em; color: #777; }
#noticias {
position: absolute;
width: 80%;
margin-top: 1em;
border: 2px solid #369;
padding: 0;
display: none;
}
#titulo { background-color: #DDE8F3; padding: .3em; border-bottom: 1px
solid #369; }
#titulares { width: 20%; float: left; border: none; border-right: 1px
solid #D9E5F2; }
#contenidos { margin-left: 22%; padding: 0px 20px; vertical-align:
top; }
#titulo h2 { font-weight: bold; color: #00368F; font-size: 1.4em;
margin: 0; }
#titulares ul { list-style: none; margin: 0; padding: 0; }
#titulares ul li { border-bottom: 1px solid #EDEDED; padding: 6px;
line-height: 1.4em; }
#titulares a { display: block; font-size: 12px; color: #369; }
#titulares a:hover { text-decoration: none; color: #C00; }
#contenidos h1 { font-weight: bold; color: #00368F; font-size: 1.4em;
padding: .2em; margin: .3em 0 0 0; }
#contenidos h2 { font-weight: bold; color: #888; font-size: .9em;
padding: .2em; margin: .3em 0 0 0; }
#contenidos p { color: #222; font-size: 1.1em; padding: 4px; line-
height: 1.5em; }
</style>
</head>
<body>
<form action="#">
<input type="text" size="40" id="url"
value="http://www.microsiervos.com" />
<input type="button" value="Mostrar RSS" id="mostrar" />
<div id="info"></div>
</form>
<div id="noticias">
<div id="titulo"></div>
<div id="titulares"></div>
<div id="contenidos"></div>
</div>
</body>
</html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-
1" />
<title>Ejercicio 22 - Google Maps</title>
<script
src="http://maps.google.com/maps?file=api&v=2&hl=es&key=AB
QIAAAA30JtKUU8se-7KKPRGSfCMBT2yXp_ZAY8_ufC3CFXhHIE1NvwkxRZNdns2BwZvEY-
V68DvlyUYwi1-Q" type="text/javascript"></script>
<script type="text/javascript">
function load() {
if (GBrowserIsCompatible()) {
// Variables para el mapa
var lat = 42.845007;
var lon = -2.673;
var zoom = 5;
window.onload = load;
window.onunload = GUnload;
</script>
<style type="text/css">
#map, #map2 {border:1px solid black; float: left;}
</style>
</head>
<body>
<div id="map" style="width: 500px; height: 300px"></div>
<div id="map2" style="width: 500px; height: 300px"></div>
</body>
</html>
14.23. Ejercicio 23
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-
1" />
<title>Ejercicio 23 - Google Maps</title>
<script
src="http://maps.google.com/maps?file=api&v=2&hl=es&key=AB
QIAAAA30JtKUU8se-7KKPRGSfCMBT2yXp_ZAY8_ufC3CFXhHIE1NvwkxRZNdns2BwZvEY-
V68DvlyUYwi1-Q" type="text/javascript"></script>
<script type="text/javascript">
var map = null;
var mgr = null;
var lat = 40.41558722527384;
var lon = -3.6968994140625;
var zoom = 6;
var puntos = {};
var peticion = null;
function inicializa_xhr() {
if(window.XMLHttpRequest) {
return new XMLHttpRequest();
} else if (window.ActiveXObject) {
return new ActiveXObject("Microsoft.XMLHTTP");
}
}
function load() {
if(GBrowserIsCompatible()) {
map = new GMap2(document.getElementById("map"));
map.setCenter(new GLatLng(lat, lon), zoom);
map.setMapType(G_SATELLITE_MAP);
setInterval(cargaPrediccion, 3000);
}
}
function cargaPrediccion() {
peticion = inicializa_xhr();
peticion.onreadystatechange = muestraPrediccion;
peticion.open('GET',
'http://localhost/RUTA_HASTA_ARCHIVO/previsionMeteorologica.php?nocach
e='+Math.random(), true);
peticion.send(null);
}
function muestraPrediccion() {
if(peticion.readyState == 4) {
if(peticion.status == 200) {
puntos = eval("("+peticion.responseText+")");
map.clearOverlays();
mgr = new GMarkerManager(map);
mgr.addMarkers(getMarcadores(), 3);
mgr.refresh();
}
}
}
function getMarcadores() {
var marcadores = [];
for (var i=0; i<puntos.length; ++i) {
var marcador = new GMarker(getPunto(i), { icon: getIcono(i) });
marcadores.push(marcador);
}
return marcadores;
}
function getPunto(i) {
var punto = puntos[i];
var lat = punto.latlon[0];
var lon = punto.latlon[1];
return new GLatLng(lat, lon);
}
function getIcono(i) {
var punto = puntos[i];
var icono = new GIcon();
icono.image = "imagenes/" + punto.prediccion + ".png";
icono.iconAnchor = new GPoint(16, 16);
icono.iconSize = new GSize(32, 32);
return icono;
}
</script>
</head>