Introduccion A Javascript
Introduccion A Javascript
Javascript
JJ Merelo
[email protected]
1ª edición
A José Antonio Vacas, mis alumnos de AAP,
los alumnos del curso virtual, y todo el que ha
echado una mano corrigiendo errores y
comentando esto, empezando por Marcos
Taracido.
Índice
Índice de contenido
¿Javascript? Pero si es muy fácil....................8
Para finalizar................................................50
Agradecimientos..........................................51
Bibliografía...................................................51
El modelo de objetos...................................52
Usando GreaseMonkey................................61
Eventos........................................................68
JQuery: Introducción....................................70
Agradecimientos .........................................91
Bibliografía...................................................91
Clientes REST.............................................107
Usando Ajax...............................................109
Bibliografía y enlaces.................................118
Introducción al lenguaje de
programación Javascript
•Aprender qué es Javascript
•Aprender la sintaxis de Javascript
•Usar objetos y clases en Javascript
•Realizar un pequeño ejemplo en Javascript
<script type='text/javascript'
src='hola.js'></script>
El problema es que, en este caso, la orden print
se interpreta como impresión por impresora, y
habrá que cambiarla por otra que signifique lo
mismo, escribir en el dispositivo de salida que se
esté usando:
document.writeln('Hola, Mundo')
Lo que también se puede escribir directamente
así:
<script type='text/javascript'>
document.writeln('Hola,Mundo')
</script>
Estos programas se pueden usar con cualquier
editor de texto, Emacs, Sublime Text o Notepad+
+; también con los entornos integrados, que te
ofrecerán ventajas adicionales como completar las
variables y los nombres de los comandos.
Estructuras de control
Vistas ya las mil y una formas de escribir cosas en
la pantalla, procedamos a temas más escabrosos,
como lo que viene siendo hacer algo realmente.
Por ejemplo, un bucle que cree una tabla HTML,
como se hace en el siguiente programilla:
var tabla="table";
var celda="td";
var fila="tr";
var matriz = [1,2,3];
print( "<"+tabla+">");
for (i in matriz ) { print( "<"+fila+">");
for ( j in matriz ) {
print("<"+celda+">"
+matriz[i]*matriz[j]
+"</"+celda+">");
}
print ("</"+fila+">\n");
}
print ("</"+tabla+">");
Este programa tiene dos bucles anidados, que
imprimen un producto dentro de una tabla. La
salida será tal que así (ver el fuente para la
estructura):
1 2 3
2 4 6
3 6 9
El programa es menos complicado de lo que
parece. Para declarar variables en JS se usa el
genérico var, aunque también se pueden declarar
tipos específicos. Para no pillarnos los dedos,
usamos var. En realidad, tampoco hace falta:
simplemente usando una variable aparece
mágicamente. Con las matrices ocurre igual (es
decir, se declaran y se les asigna valor
directamente) : matriz lo es, y simplemente se
declaran sus valores entre corchetes. Ojo con los
nombres de variables, que a diferencia de otros
lenguajes, distinguen entre mayúsculas y
minúsculas. esta_variable es diferente de
esta_Variable.
JS puede usar un tipo de bucle que tienen la
apariencia habitual (en lenguajes derivados del C),
y se pueden usar igual que en C, pero haremos un
bucle que recorra la matriz, usando un iterador i,
que en este caso se comporta como una variable
de bucle de las de toda la vida. Usamos el + para
concatenación de cadenas, y poco más. El resto es
como el C, o el Java. De hecho, se pueden usar los
bucles clásicos con comparación e incremento,
como se muestra en tabla1.js:
function marca( m ) {
return "<"+m+">";
}
function finmarca( m ) {
return "</"+m+">";
}
function celda( contenido ) {
return marca("td")
+contenido
+finmarca("td");
}
La principal diferencia con respecto al anterior es
el uso de funciones. Las funciones en JS tienen
una estructura bastante clásica: function
nombre-de-función (param1, param2...). Una vez
más, se nota que JS no e un lenguaje con tipos
fuertes, pudiendo pasar los parámetros sin tipo, y
adaptándose dentro de la función al tipo
necesario. Se pasan por valor, es decir, que las
modificaciones al parámetro formal no se
trasladan a la variable que se use. Además, se
pueden declarar donde a uno le dé la gana. Para
llamarlas tampoco hay que hacer nada especial,
se usa el clásico paréntesis. La salida es
exactamente la misma que antes. El también
clásico return devuelve un valor.
El ámbito de las variables es el bloque donde
aparecen o se declaran, pero hay que tener en
cuenta que, a efectos de JS, una página web es un
programa. Se pueden declarar variables en la
cabecera del documento HTML, y estarán
accesibles en cualquier otro sitio, siempre que
esté mas adelante en el documento. También
habrá que tener en cuenta, en caso de que esté
incluido en una página web, que aunque la
declaración de una subrutina afecte a todo el
programa, puede que esa parte de la página no se
haya cargado todavía, con lo que no estará
disponible. Una vez más, la programación
distribuida no es totalmente igual a la
programación en otros lenguajes.
Clases y objetos en Javascript
Javascript es un lenguaje basado en objetos,
aunque un tanto peculiar; en realidad, de casi
todas las características de un lenguaje orientado
a objetos, solo tiene los objetos, e incluso estos
son un tanto peculiares. Por eso no es
exactamente dirigido a objetos u orientado a
objetos. Las características las veremos en el
siguiente programa, que podría servir para hacer
quinielas.
equipos.splice(Math.floor( equipos.lengt
h*Math.random()), 1);
quiniela[i] =
new Partido( equipo1, equipo2 );
}
for ( i in quiniela ) {
print( "Partido "
+ (parseInt(i)+1)
+": " + quiniela[i].local
+ " - " + quiniela[i].visitante);
}
Con lo primero que nos enfrentamos es con una
nueva forma de definir una matriz o Array: ya que
sabemos que JS es OO, pues usamos una forma
OO de definirlo, mediante la orden new, que, como
en Java y en C++, crea un nuevo objeto llamando
al constructor del mismo. En este caso le pasamos
directamente los elementos que constituyen el
vector o array, pero podríamos haberle pasado el
tamaño de esta forma: var myArray = new
Array(33); Los objetos así creados son objetos
de pleno derecho, y se puede acceder a sus
propiedades con métodos usando también una
sintaxis clásica: el puntito . tras el nombre de la
variable. Por ejemplo, myArray.length devolvería
el tamaño de la matriz
Pero como lo que se trata en este programa es de
definir nosotros una clase, lo hacemos en las
líneas siguientes, en la función Partido, que
convencionalmente ponemos en mayúscula, para
indicar que es un nombre de clase. En realidad,
una clase en JS es una función dentro de la cual se
le asigna un valor a la variable this, como en
esta: cada uno de los elementos de la variable
this será una variable de instancia. Como se ve,
aquí no hay encapsulación ni perrito que le ladre.
Lo que vamos a crear es un vector de estos
partidos, e irle asignando valores extraidos
aleatoriamente. Mientras tanto, usamos los
métodos que llevan objetos de clases estándar JS;
igual que otros lenguajes tienen librería estándar,
JS tiene clases estándar: Array, que ya hemos
visto, y Math. Lo que usamos de Math son métodos
de clase, no de instancia, tales como
Math.random, que genera un número aleatorio
entre 0 y 1. También se usa un método de
instancia, splice, que extrae una parte del vector
de equipos; extraemos el seleccionado, para que
dé por saco mientras generamos el resto de la
guiniela.
Y el objeto lo creamos mediante una clásica
llamada:
Matrices asociativas
Pero hay más matrices, aparte de las lineales: JS,
como muchos otros lenguajes, permite trabajar
con matrices asociativas (también llamadas
diccionarios o hashes). En una matriz asociativa, la
clave es una cadena, en vez de un número, lo que
le da mucha más flexibilidad a la hora de
almacenar información. En un vector, se accede a
cada uno de los elementos del vector a través de
un índice numérico, y eso implica también un
orden en su estructura (y, a veces, una
continuidad en su almacenamiento, aunque no
necesariamente tiene que ser así). Es decir, un
vector lineal es un grupo de parejas (0, valor[0], 1,
valor[1],...., n, valor[n]). De hecho, como los
números suelen ser sucesivos, muchas veces se
dan por sobreentendidos, de forma que para
trabajar con un vector (ejecutar una operación
sobre sus valores, por ejemplo) sólo se usan sus
valores: valor[0], valor[1],..., valor[n].
Sin embargo, una matriz asociativa, diccionario,
mapa o Tabla_hash (o simplemente hash) está
compuesto por una serie de pares (cadena
alfanumérica, valor): (cadena1, valor1,
cadena2,valor2... cadenan, valorn). Los valores
están asociados a su cadena correspondiente; de
forma que se accede a los valores a través de la
cadena alfanumérica usada para indexarlos, que
se suele denominar clave (key). Casi todos los
lenguajes de programación tienen alguna forma de
usar estas matrices asociativas. Por ejemplo, en
Perl:
load('Partido.js');
var equipos= new Array('Madrid',
'Barça', 'Atleti', 'Geta', 'Betis', 'Depor',
'Sevilla', 'Graná');
function jornada( estosEquipos ) {
var equiposAqui = new Array;
equiposAqui =
equiposAqui.concat(estosEquipos);
var midsize =
equiposAqui.length/2;
var quiniela =
new Array( midsize );
var unox2 = new Array( '1','x','2');
for ( var i=0; i < midsize ; i++ ) {
var equipo1 =
equiposAqui.splice(Math.floor( equiposA
qui.length*Math.random()) , 1);
var equipo2 =
equiposAqui.splice(Math.floor( equiposA
qui.length*Math.random()), 1);
quiniela[i] =
new Partido( equipo1, equipo2 );
quiniela[i].setResultado( unox2[Math.flo
or( 3*Math.random()) ]);
}
return quiniela;
}
var quinielas = new Array;
for ( var i = 0; i < 10; i ++ ){
quinielas[i] = jornada( equipos );
}
var resultados= new Array;
for ( var i in equipos ){
resultados[equipos[i]]=0;
}
for ( var i = 0; i < quinielas.length; i ++
){
for ( var j = 0;
j < quinielas[i].length;
j ++ ) {
var local = quinielas[i]
[j].local;
var visitante = quinielas[i]
[j].visitante;
var resultado = quinielas[i]
[j].resultado;
if ( resultado == 1 ){
resultados[local]+=3;
} else if ( resultado == 'x' ){
resultados[local]+=1;
resultados[visitante]
+=1;
} else { // resultado == 2
resultados[visitante]
+=3
}
}
}
for ( var i in resultados ) {
print( i + ": " + resultados[i])
}
En parte, este programa es similar a los
anteriores: la parte que generaba cada jornada
está ahora en una función, que devuelve un array
de resultados, que se guardan en el array
quinielas. Hemos sacado, además, la definición
de la clase Partido a un fichero externo, que
cargamos con load. Por otro lado, como a la
función jornada se le pasa una referencia al
vector con los equipos, tenemos que copiarlo a
una variable local, definiéndola (equiposAqui), y
concatenándole (concat) el vector que se le pasa
por valor, que es igual que copiarlo, pero seo hace
en una sola orden.
El truco está a partir de la definición de la variable
resultados. Esta variable es una matriz asociativa
que contendrá la puntuación de los equipos, y
estará indexada por el nombre del equipo. Se
declara igual que los demás arrays, y, para
inicializarlo, vamos extrayendo los valores del
vector de equipos, y usándolos como clave:
resultados[equipos[i]]=0; equipos[i]
valdrá sucesivamente Barça, Graná... y así se irán
inicializando a 0 los valores correspondientes. Si
no se inicializan, la primera vez que se usa una
variable tiene el valor NaN, con el que no se puede
hacer nada. Es así de arisco.
Más adelante se va recorriendo en un bucle doble
los partidos de cada una de las jornadas, y
asignando puntuación dependiendo del resultado
de la quiniela. Se usa la construcción if... else
if ... else, que funciona de la forma habitual,
aunque también podríamos haber usado switch,
como en el siguiente programa, que en lo único
que cambia es en estas líneas:
switch (resultado) {
case '1':
resultados[local]+=3;
break;
case 'x':
resultados[local]+=1;
resultados[visitante]+=1;
break;
default:
resultados[visitante]+=3;
}
}
y que viene a ser como el anterior, pero con cases
en vez de ifs. Vamos, tres cuartos de lo mismo.
Manejando Objetos
En realidad, todo en Javascript es un objeto, y
especialmente los vectores: tanto los vectores
tradicionales como las matrices asociativas como
los objetos se representan internamente de la
misma forma, y te puedes referir a ellos de
diferentes maneras. Vamos a usar el depurador
interactivo para verlo, ejecutando simplemente
rhino, smjs o kjs en la línea de comandos. Una
vez hecho, tecleamos las siguientes órdenes:
js> foo = new Array
js> foo.cero='Cero'
Cero
js> foo[1] = 'Uno'
Uno
js> foo['dos'] = 'Dos'
Dos
js> foo.dos
Dos
js> foo['cero']
Cero
js> for ( i in foo) { print(foo[i]);}
correquetepillo
js> print(este_foo.privada)
undefined
Es tan secreta, de hecho, que ni siquiera te dice
que no existe: simplemente que su valor está
indefinido.
El propio estándard Javascript (ECMAScript) define
una serie de clases que se pueden instanciar, que
corresponderían a la librería estándar (o librería
estándar de clases) en otros lenguajes. Una de
ellas ya la hemos visto: la clase Array. Otra es la
clase String, que se usa para manejar cadenas
alfanuméricas, chorros de 0s y 1s.
js> var cadena = new String("1");
js> print(cadena + 1)
11
La clase String tiene una serie de métodos que
permiten hacer lo habitual con las cadenas:
encadenarlas, dividirlas, y buscar cosas.
js> var nombres = "Pedro, Lucas,
Juan".split(", ");
js> print(nombres[0])
Pedro
En este caso, split es un método de la clase
String, y lo estamos aplicando directamente sobre
la cadena "Pedro, Lucas, Juan", que, de por si,
es un objeto de esa clase. split divide la cadena
usando los caracteres que le pasamos, y da lugar
a un Array con tantos componentes como resulte.
De camino, podría haber una clase para escribir y
leer ficheros, porque con el rato que llevamos,
todavía no hemos visto ninguna, y, además,
cualquier lenguaje decente escribe y lee ficheros.
Es más, es que muchos no hacen otra cosa, ¿no?
Pues no. El estándar JS no define ningún tipo de
rutina de E/S. Pero si usamos el intérprete Rhino
(en vez de SpiderMonkey, que es el que hemos
venido usando), podemos usar clases de Java
directamente, lo que complica terriblemente el
programa, pero ahí está, de todas formas:
Madrid : 25
Atleti : 33
Ponferradina : 44
donde usamos eval, que interpreta una expresión
en Javascript como si del propio intérprete se
tratara. Las expresiones se pueden anidar, para
dar lugar a objetos más complejos
js> eval("var objeto2 = { Madrid : 25, Atleti:
33, Ponferradina: { casa: 33, fuera: 44} }");
js> for (i in objeto2) { print( i + " : "+
objeto2[i] )};
Madrid : 25
Atleti : 33
Ponferradina : [object Object]
Que parece más raro de la cuenta, pero que, con
un poco de código, se podría también imprimir.
Y JSON, precisamente, es uno de los formatos de
intercambio, el más simple, el que se usa en AJAX,
y un modo de acceder también a servicios web
como geonames. Lo hacemos en el siguiente
ejemplo: json-geonames.html, del que extraemos
el código en JS (fichero geonames_call.js)
getLocation(jData) {
if (jData == null) {
return;
}
var html = '<ul>';
var geonames = jData.geonames;
for (i=0;i<geonames.length;i++) {
var name = geonames[i];
html = html+"<li><em>"
+name.name
+ "</em> - Latitud: "
+ name.lat +', longitud: '
+ name.lng+ "</li>";
}
html+="</ul>";
document
.getElementById('resultDiv')
.innerHTML = html;
}
function search() {
request =
'http://ws.geonames.org/searchJSON?
country=ES&q=' +
encodeURIComponent(document.getEle
mentById('q').value) +
'&maxRows=10&callback=getLocation';
aObj = new
JSONscriptRequest(request);
aObj.buildScriptTag();
aObj.addScriptTag(); }
Este ejemplo es un poco complicado, sobre todo,
por el mecanismo que usa para hacer la llamada, y
que está contenido en el fichero jsr_class.js, que
contiene la clase JSONscriptRequest, que será la
que usemos para construir la llamada. La
mecánica es la siguiente: cada vez que se
introduce un nombre de un pueblo o ciudad de
España, se genera un evento, y se llama a la
función search. Esta función construye una
petición, dentro de la cual está incluido
callback=getLocation. El truco es que esa
petición genera un callback tal que cuando se
recibe la respuesta se llama a la función
getLocation (definida más arriba).
Esa función recibe como variable en jData el
resultado que, al estar en formato JSON, ya está,
de hecho, en el formato de un objeto en JS. Por
eso, en el bucle dentro de la función getLocation
se trabaja directamente con los datos obtenidos
sin necesidad de hacer ningún tipo de parsing.
#!/usr/bin/smjs
load('Nuevo_partido.js');
var equipos= new Array('Madrid',
'Barça', 'Atleti', 'Geta', 'Betis', 'Depor',
'Sevilla', 'Graná');
function jornada( estosEquipos ) {
var equiposAqui = new Array;
var imprime = function( local, visitante,
resultado ) {
print("Impimiendo \n");
return "- " + local + " vs. " +
visitante + " resultado "+ resultado;
};
equiposAqui =
equiposAqui.concat(estosEquipos);
var midsize = equiposAqui.length/2;
var quiniela = new Array( midsize );
var unox2 = new Array( '1','x','2');
for ( var i=0; i < midsize ; i++ ) {
var equipo1 =
equiposAqui.splice(Math.floor( equiposA
qui.length*Math.random()) , 1);
var equipo2 =
equiposAqui.splice(Math.floor( equiposA
qui.length*Math.random()), 1);
quiniela[i] = new
Nuevo_partido( equipo1, equipo2 );
quiniela[i].setResultado( unox2[Math.flo
or( 3*Math.random()) ]);
quiniela[i].set_to_string( imprime );
}
return quiniela;
}
var quinielas = new Array;
for ( var i = 0; i < 10; i ++ ) {
quinielas[i] = jornada( equipos );
}
var resultados= new Array;
for ( var i in equipos ) {
resultados[equipos[i]]=0;
}
for ( var i = 0;
i < quinielas.length;
i ++ ) {
for ( var j = 0;
j < quinielas[i].length;
j ++ ) {
var local = quinielas[i]
[j].local;
var visitante = quinielas[i]
[j].visitante;
var resultado = quinielas[i]
[j].resultado;
switch (resultado) {
case '1':
resultados[local]+=3; break;
case 'x':
resultados[local]+=1;
resultados[visitante]+=1; break;
default:
resultados[visitante]+=3;
}
}
}
for ( var i in resultados ) {
print( i + ": " + resultados[i])
}
La parte nueva de este programa está en en la
línea 9, donde se define la variable imprime. Se
define una función sin nombre (lo que se suele
denominar un closure o función anónima) a la que
podemos acceder mediante la variable que le
hemos asignado. Lo importante de esta sintaxis es
que las funciones son varibles de pleno derecho,
que podemos usar como parámetros de otras
funciones; esto se usará de forma extensiva
cuando veamos jQuery y node.js.
CommonJS, una infraestructura
común para carga de módulos
Uno de los problemas de JS es que, al haber sido
desarrollado principalmente para trabajar en el
navegador, carece de una serie de librerías
comunes para trabajar en el servidor o en
aplicaciones de escritorio. CommonJS es un intento
de dar tal infraestructura. Principalmente se trata
de proveer una serie de especificaciones para
hacer cosas comunes, desde o más simple, que es
crear un módulo o librería hasta cosas más
complejas: interacción con consola o con línea de
órdenes.
exports.Un_Partido = function
(local,visitante,resultado) {
this.local = local;
this.visitante=visitante;
this.resultado=resultado;
this.setResultado = setResultado;
this.toString = toString;
this.set_to_string = set_to_string;
this.impresor = _toString;
}
El único cambio ha sido que en vez de definir la
función directamente, se define como un atributo
de exports. El resto, al ser atributos de ese
objeto, no hace falta que lo definamos de la misma
forma. Al llamarlo también habrá un pequeño
cambio. Mientras que antes teníamos que hacer
un eval sobre lo cargado, ahora basta con
(programa usa_partido.js):
var un_partido =
require('./Un_Partido.js');
var este_partido = new
un_partido.Un_Partido( 'este','otro','1');
console.log('Resultado ' +
este_partido.toString());
Este módulo ya se comporta como el resto de los
módulos de Node, haciendo falta usar sólo require
(con el camino completo) para cargarlo. Ahora,
con require lo que definimos es un objeto, y las
funciones son atributos de ese objeto; por lo que a
la hora de declarar nuevos objetos de esa clase
tendremos que hacerlo con new
un_partido.Un_Partido. A partir de ahí el objeto
generado se comporta exactamente igual que
cualquier otro objeto, como podemos ver usando
console.
A diferencia de casi todos los lenguajes de
scripting, no hay un modo estándar de instalar
módulos Javascript, aunque algunos intérpretes
(notablemente Node.js, del que hablaremos luego)
sí lo tienen. De hecho, ni siquiera common.js es
universal, existiendo otras convenciones que le
hacen la competencia tales como require.js. La
principal ventaja de common.js es su aceptación
por parte de node.js, precisamente y su uso en
NPM, por eso cabe suponer que el resto
empezarán, más o menos, a usarlo. En todo caso,
son enfoques diferentes, uno se concentra en la
forma de cargar el módulo mientras que otro se
concentra en la forma de empaquetarlo.
Para finalizar
Cualquiera de los recursos que listo ahí abajo
pueden resultar útiles para ampliar información
sobre JavaScript. Quizás también pueda ser
interesante usar alguna librería que facilite su uso
como Mochikit o Prototype. También el Google
Web Toolkit permite desarrollar en AJAX
programando sólo en Java, aunque pueda que el JS
generado necesite algún retoque adicional.
Agradecimientos
Agradezco a los comentaristas de los diferentes
anuncios que publiqué en Barrapunto sus
comentarios y sugerencias. También a Javier
Espigares por la lectura y comentarios sobre las
versiones previas de este tema y el anterior.
Bibliografía
Hay dos libros fundamentales para aprender JS,
aunque están muy enfocados a JS en el
navegador: Javascript: The Definitive Guide, el
libro del rinoceronte, editado por O'Reilly, que está
disponible como recurso eletrónico en la UGR y
JavaScript Bible, de Danny Goodman, un tocho
considerable, en el que hay de todo, y que viene
con un útil CD con ejemplos. También está como
recurso electrónico. Hay muchos más recursos,
algunos de ellos disponibles de forma electrónica.
JavaScript en el navegador
y JQuery
•Trabajar con Javascript en el navegador
•Usar librerías populares de JavaScript en ese
contexto
El modelo de objetos
En realidad es difícil encontrar un enfoque como el
de este tema, centrado en JS como lenguaje y no
como un chisme más dentro del navegador.
Eventualmente, habrá que tratar con esto, así que
este momento es tan bueno como cualquier otro.
En realidad, la mayor diferencia entre JS-sin-
navegador y JS-con-navegador es el bagaje de
objetos con el que tiene que trabajar y también el
modelo que se va a usar para entrada y salida: la
propia página en la que está inserto el programa.
function putBloque(value) {
var ejs =
document.getElementById('ej.T1.'+valu
e);
document.getElementById('resultado1')
.innerHTML=ejs.textContent;
}
Un par de líneas sólo de JS: una para buscar el
elemento (la primera) y la segunda para extraer su
contenido (textContent) e introducirlo en otro, el
elemento resultado1 que teníamos preparado al
efecto. innerHTML es el HTML interno de un
elemento: al asignarle un valor, efectivamente,
sustituimos parte del contenido de la página
dinámicamente. ¿No es una maravilla?
Usando GreaseMonkey
No se sabe porqué los temas de JS tienen tanta
relación con los primates, pero el hecho es que
GreaseMonkey es un plugin para los navegadores
Mozilla que permite instalar en el navegador
programillas JS específicos de una página o grupo
de páginas. Para trabajar con él, lo primero que
hay que hacer es instalarlo y reiniciar el
navegador.
span.setAttribute('style','background:lig
htblue');
if ( secs > 0 ) {
var ahref =
document.createElement('a');
ahref.setAttribute('href','#'+anchors[se
cs-1]);
var txt=
document.createTextNode('^');
ahref.appendChild(txt);
span.appendChild(ahref);
}
if ( secs < h2.length -1 ) {
span.appendChild(document.createText
Node(' | '));
var ahref =
document.createElement('a');
ahref.setAttribute('href','#'+anchors[se
cs+1]);
var txt=
document.createTextNode('v');
ahref.appendChild(txt);
span.appendChild(ahref);
}
a_nodes[secs].parentNode.insertBefore(
span,a_nodes[secs]);
}
Este programa añade unas flechitas de navegación
a los apuntes de esta asignatura, de forma que se
pueda pasar de cada sección a la anterior a la
siguiente (de ahí lo de aap-nav). Tiene dos partes:
la primera parte halla las etiquetas de navegación,
y la segunda las inserta. Tres partes, de hecho, si
incluimos las declaraciones del principio, que son
para uso y disfrute del propio GreaseMonkey. Las
dos primeras son terminológicas: cómo se llama, y
qué espacio de nombres usa. Ésto es para
distinguir scripts con el mismo nombre de
diferentes fuentes. La tercera, una descripción,
aparece en los directorios correspondientes y
cuando gestionamos los scripts.
El cuarto apartado es el más importante. Es un
patrón de las páginas en las que va a funcionar el
script. Éste no tendría sentido fuera de las páginas
de las asignaturas que imparto, así que incluimos
en el mismo simplementa a las que hay en ese
directorio. Cuando el navegador cargue alguna
página con ese patrón, GM lo detectará y cargará
el script.
El programa en sí hace uso de los elementos
explicados en la sección anterior: extrae del
documento los títulos de capítulo (h2) y de ahí los
anchor (a name) y sus atributos, metiendo todo lo
metible en un bucle. Hace falta tenerlos todos en
un array, por eso se usa un segundo bucle para
insertar en la página los elementos de navegación.
Este segundo bucle crea elementos a tutiplén,
usando createElement (para crear un elemento),
setAttribute (para poner su atributo) y
createTextNode (para meter texto dentro de los
elementos). Luego, a modo de injerto, se van
metiendo los elementos unos dentro de otros
usando appendChild. Y, finalmente, se insertan
los elementos creados en el documento en la
penúltima línea:
a_nodes[secs].parentNode.insertBefore(span,a_no
des[secs]);
que navega desde el ancla hasta su padre
(parentNode) e inserta antes del mismo
(insertBefore) el span que hemos creado
previamente. El resultado, si todo ha ido bien,
deberías verlo en este mismo tutorial.
Además, hace uso de algunas funciones propias de
GM: GM_log, que escribe en la consola de
Javascript. Muy útil para depurarlo, inútil en
producción; pero si abres la consola de JS verás los
mensajes que ha usado.
Eventos
Para entender bien el uso de JavaScript en el
navegador es conveniente introducir el concepto
de eventos. Se trata simplemente de señales
generadas por el mismo, o bien registradas por el
usuario (es decir, introducidas por el usuario
cuando suceda algo determinado). El navegador,
por ejemplo, genera un evento cuando el ratón
entra o sale de un elemento, cuando se carga la
página, o cuando se pulsa el ratón sobre un
elemento activo. He la lista de todos los eventos y
de los elementos a los que afectan. Los eventos
permiten por un lado trabajar con un patrón de
programación reactiva, en la que se reacciona a lo
que va sucediendo en el navegador y, a la vez, un
cierto grado de concurrencia porque cada evento
sucede en una hebra diferente. También se trabaja
de forma asíncrona, porque en muchos casos no
se llevarán a cabo de forma secuencial sino
cuando se cumplan ciertas condiciones.
JQuery: Introducción
JQuery es una librería en JavaScript que está
diseñada principalmente para simplificar la
creación de programas y permitir crear interfaces
ricos de usuario. JQuery se ha popularizado desde
su creación en el año 2006 hasta el punto que se
calcula que se usa en más de la mitad de los sitios
más populares. Por supuesto, es software libre con
una licencia MIT. Ha sido aceptada también e
integrada por casi todas las grandes empresas que
crean herramientas de desarrollo de software e
incluso Google aloja directamente una copia de
JQuery que se puede usar desde cualquier
programa.
<html> <head>
<meta http-equiv="Content-Type"
content="text/html;
charset=iso-8859-15">
<script
src="http://ajax.googleapis.com/ajax/lib
s/jquery/1.9.1/jquery.min.js"></script>
<title>Probando ready de
jQuery</title> </head>
<body> <h1>Esta es una página que
no tiene gran cosa</h1> <p>Pero
podría tenerla.</p> <script
type='text/javascript'> $
(document).ready(function()
{ alert('Ahora estamos listos'); });
</script>
En este caso, usamos como se ha indicado antes
la copia de JQuery proporcionada por Google, que,
como cualquier otra librería JS, debe ser incluida
en nuestra página para ser usada. Por otro lado, la
única función que usamos de Jquery está tras el
párrafo: cuando el documento está listo (ready),
lanzamos un alert. Este script funciona
exactamente igual que como el que habíamos
visto anteriormente.
De hecho, se puede simplificar e incluso ahorrar la
orden para pasar directamente a la función que
queremos que se active cuando se cargue la
página.
JQuery también simplifica el uso de selectores
para extraer elementos del DOM, usando la misma
sintaxis que hemos visto arriba: $("selector")
permite extraer una serie de elementos que
cumplan esa sintaxis que, como hemos visto más
arriba, es la misma que se usa en las CSS. Lo
vemos en el siguiente ejemplo
<script type='text/javascript'>
$(function() {
var hachedoses ='';
$("h2").each( function() {
hachedoses
+= this.textContent + " - ";
} );
alert(hachedoses);
$("#cambiando")
.html( hachedoses );
});
</script>
<h2>Este es un H2</h2> <h2>Este es
otro H2</h2> <H2>Y este, lo
adivinaste, otro</H2> <div
id='cambiando' style='border:dashed'>
</div>
En este ejemplo, primero se recorren los
elementos h2 pero en vez de hacerse a partir de
un bucle se usa directamente el objeto generado
por el selector y que aplica a cada uno de ellos
una función anónima; en este caso la función
concatena a hachedoses el contenido en texto del
elemento. Usamos el alert principalmente para
que se vea el contenido del div definido más
abajo vacío y posteriormente con el contenido que
se le añade en la última línea del script, que usa
como selector el equivalente a un elemento con el
id #cambiando.
Introducción a node.js
•Conocer node.js y saber sus conceptos
fundamentales.
•Aprender los conceptos básicos de los
servicios web basados en REST, la
representación de datos usada y cómo
implementarlos en node.js
•Realizar prototipos rápidos de cliente y
servidor de servicio web usando node.js
#!/usr/bin/node
var saludo = new Object;
saludo.hola = 'mundo';
console.log( saludo );
La primera línea es exclusivamente para sistemas
Linux (que son, por otro lado, los únicos serios
para desarrollo de software); en ella habrá que
poner el camino completo al intérprete de node;
este es una opción, como /usr/local/bin/node;
con ella y haciendo ejecutable el fichero con chmod
+x node.js podemos ejecutarlo y obtener el
siguiente resultado
jmerelo@penny:~/servicios-
web/ejemplos$ ./guenas.js { hola:
'mundo' }
En otro entorno (o si no se quiere hacer al fichero
ejecutable), con escribir
jmerelo@penny:~/servicios-
web/ejemplos$ node guenas.js
es suficiente. En cualquier caso, la salida será la
misma. Y la explicación también: definimos un
objeto saludo en la primera línea, y en la segunda
le asignamos el valor mundo a la variable de
instancia hola, o visto de otro modo, el valor
mundo a la clave hola. console.log imprime la
cadena en la salida, escribiendo directamente (y
además en JSON) el valor de la misma.
Sin embargo, node.js no es un intérprete habitual,
tiene una forma particular de hacer las cosas:
asíncronamente. Veremos, por ejemplo, como leer
un fichero, el de las quinielas que hemos usado
hasta ahora.
#!/usr/local/bin/node
fs = require('fs');
fs.readFile('quiniela.datos', 'utf8',
function(err,datos) {
if (err) { return console.log(err); };
var filas = datos.split("\n");
for ( var f in filas ) {
var cachos = filas[f].split(" ");
var partido = { 'local':
cachos[0], 'visitante': cachos[1],
'resultado': cachos[2] };
console.log( partido );
}
});
En este programa (que actúa sobre este fichero de
datos) se usa el intérprete node.js, lo que se ve en
la primera línea, que no hace falta en Windows
(aunque se tendrá que ejecutar desde línea de
órdenes poniendo explícitamente node fichero.js).
En la segunda vemos que se carga una librería
usando require, el mecanismo común para cargar
un módulo y evaluarlo, que, además, crea un
objeto que se puede usar; lo usamos más adelante
para leer un fichero. fs se refiere a filesystem, o
sistema de ficheros, y es el módulo que contiene
una serie de funciones para interaccionar con el
mismo.
La siguiente línea es la que usa un modo de
actuación propio de node.js. Como ya se ha
indicado (varias veces), node funciona de forma
asíncrona. En general, el patrón de las funciones
en node, en vez de ser haz_a(); haz_b(); que
ejecutaría haz_a, y, tras terminar, ejecutaría
haz_b, es haz_a(parametros, haz_b);
haz_c() que viene a decir ejecuta haz_a sobre
unos parametros y, cuando veas que has
terminado, llama a la función haz_b; fijaros que se
trata de un puntero a función, no una llamada a la
misma (no lleva paréntesis). Pero dependiendo de
lo que tarde haz_a, haz_c podría ejecutarse antes
que haz_b. En general, la secuencia de las líneas
no tiene por qué ser la secuencia de ejecución de
las funciones, eso es precisamente lo que significa
la asincronía. Eso no quiere decir que no se pueda
usar como cualquier otro lenguaje, sólo que hay
que tener cuidado. Y, por otro lado, permite
responder muy rápidamente a eventos sin
bloquear la operación; cada evento inicia una
hebra y se van procesando en paralelo.
Vamos a la orden específica: efectivamente, con
readFile leemos el fichero. Los dos primeros
argumentos son el nombre del fichero y, a
continuación, la codificación, que es obligatorio
usar. Y continuación el callback del que hemos
hablado: una función que se ejecuta cuando se
termine; se trata de una función anónima tal como
las que hemos visto en apartados anteriores. El
hecho de que se ejecute asíncronamente quiere
decir que fs.readFile se ejecuta y se deja el evento
generado; si hubiera una orden a continuación se
ejecutaría inmediatamente. Esto le permite a
node.js leer las cosas con mucha eficiencia, y
hacer una serie de operaciones que no se pueden
hacer fácilmente con otros lenguajes.
Concentrémonos en la función. Tiene dos
argumentos: err y datos. Si hay un error, estará
en la primera variable (que comprobamos) y si no,
el resultado irá a la segunda variable. Es decir,
cuando se ejecute la acción, se llamará a la
función con dos argumentos, uno de los cuales
será null. Vemos también que se usa
console.log para escribir en la consola; console
es un objeto que equivaldría al document del DOM,
salvo que no tiene ningún tipo de estructura; tiene
la ventaja de que si se escribe una estructura de
datos compleja, la "desplegará".
El resto del programa es más o menos habitual;
usamos la clase que hemos definido anteriormente
para genera un objeto de cada tipo e imprimirlo
usando console.log. El resultado será más o
menos así:
#!/usr/bin/node
var https = require('https');
var user =process.argv[2]?
process.argv[2]:'JJ';
var options = { host: 'api.github.com',
path: '/users/'+user, method: 'GET' };
var req =
https.get('https://api.github.com/users/'
+user,
function(res){
res.setEncoding('utf8');
res.on('data', function
(datos_JSON) {
var datos=
JSON.parse(datos_JSON);
console.log('Login: '
+ datos.login
+ "\nNombre: "
+ datos.name
+ "\n");
});
});
req.end();
Usamos la librería recién instalada para
descargarnos información de un usuario de
GitHub, usando la librería llamada $JSON$, que se
instala con Node. La forma de petición es la
asíncrona habitual en Node: se hace la petición y
se le pasa la función a la que hay que llamar
cuando se reciba la respuesta, como en el caso
anterior de apertura de un fichero. A la función se
la llama con tres parámetros: o bien err, en caso
de que se produzca un error, o bien response y
body en caso de que la respuesta sea correcta.
body contendrá el texto de la respuesta, que habrá
que decodificar (o imprimir tal cual, en caso de
que se trate de HTML); response es una estructura
de datos compleja, que podemos imprimir con
console.log (y saldrá un montón de cosas,
incluyendo la versión de HTTP, las cabeceras, y
mucha información más), pero que contiene, entre
otras cosas, el estado de la petición, con un código
del protocolo HTTP. En el programa anterior se
comprobaba sólo si había error o no; ahora demás
comprobamos que el código devuelto es el
correcto, es decir, 200. Si hubiera un código 400, o
500, o incluso un 201, tendríamos que interpretar
la respuesta de otra forma.
#!/usr/bin/node
//cabecera
console.log('Content-Type: text/plain;
charset=UTF-8');
//contenido
var una_variable=['uno','dos',{ tres:
'tres'}];
console.log('');
console.log(una_variable);
Para ejecutarlo no hay más que copiarlo a un
directorio determinado con permisos de ejecución
para otros (chmod +x hola-js.cgi).La primera
envía una cabecera al cliente que le indica el tipo
que se usa; la segunda parte es la que
efectivamente envía el contenido, en este caso
una variable en JSON (recordad que console.log
escribe en salida estándar, y convierte las
estructuras de datos a JSON).
Node, por su naturaleza asíncrona, realmente no
es el mejor sistema para trabajar con JavaScript en
un servidor que incluya otros lenguajes. Sin
embargo, se puede usar Javascript de muchas
maneras diferentes: SilkJS, por ejemplo, es un
intérprete de JS que incluye también un servidor
web; o TeaJS es un sistema para crear CGIs basado
en el intérprete rápido de JS de Google. Por no
introducir más herramientas, no los vamos a ver
aquí, pero conviene tener en cuenta que existen
este tipo de soluciones que pueden convivir en un
servidor como Apache o NGINX con otros
lenguajes como Ruby o Perl.
var http=require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-
Type': 'text/plain'});
res.end('Ahí estamos\n');
}).listen(8080, '127.0.0.1');
console.log('Server running at
http://127.0.0.1:8080/');
Este programa simplemente escribirá "Ahí
estamos" en el navegador cuando se solicite el
URL. Nada complicado, pero tampoco lo es el
programa: se usa un módulo http que es estándar
en Node en la primera línea del programa; se crea
un servidor con createServer. Esta orden recibe
como parámetro la función a la que hay que llamar
cada vez que se reciba una petición. Cuando se
recibe una petición, se llama a una función que
escribe primero la cabecera HTTP (writeHead) y
termina (end) el servicio de la misma escribiendo
el contenido que nos aparecerá en el navegador.
http.createServer crea un objeto y lo devuelve;
en este caso, no lo asignamos a ninguna variable,
sino que sobre el mismo objeto (anónimo) le
decimos con listen en qué puerto (8080) y
dirección (la del propio ordenador, there's no
place like 127.0.0.1) va a escuchar el servidor. Es
una orden que se ejecuta de forma asíncrona, con
lo que lo que crea es un callback que se llamará
cada vez que se llame a ese URL. Sólo los puertos
por encima de 1024 están accesibles al usuario,
así que tendréis que usar un número en ese rango
(como 8080 o 12121, todos por debajo de 65535).
El mensaje se escribe en pantalla de forma
síncrona, es decir que a partir de que se escriba
ese mensaje sabremos que podemos usar el
servidor.
Evidentemente, si queremos crear un servidor que
haga algo tendremos que usar las peticiones que
se reciban para dar una respuesta variable. En el
var http=require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-
Type': 'text/plain'}); res.end('Ahí
estamos ' + req.url);
}).listen(8081, '127.0.0.1');
console.log('Servidor ejecutándose en
http://127.0.0.1:8081/');
La principal diferencia entre este programa y el
anterior es, aparte del puerto usado (8081 en vez
de 8080) la línea en la que escribe algo, y en la
que usa la variable req, un objeto que contiene
información sobre la petición, y entre otras cosas
el URL (una vez eliminada la parte del servidor)
que se ha usado; este URL es el que se escribe a
continuación de "Ahí estamos", tal cual.
En general, para programar un servicio web habrá
que trabajar con esa petición (que será la que
reciba la orden del API) y actuar según la misma, y
teniendo en cuenta también la orden HTTP que se
use (PUT, GET o la que sea). Esto lo veremos un
poco más adelante.
A partir de ahí se puede construir un mínimo
interfaz REST para responder a una serie de
peticiones. La idea básica es que las funciones a
las que tendremos que llamar estarán
indentificadas por el URL que se use para pedirlas.
Por ejemplo, el programa rest-minimo.js
var http=require('http');
var puerto=process.argv[2]?
process.argv[2]:8080;
http.createServer(function (req, res) {
res.writeHead(200, {'Content-
Type': 'text/plain'});
var split_url=req.url.split("/");
if ( split_url[1] == '' ) {
res.end('Portada');
} else if ( split_url[1] == 'proc' ) {
res.end('No es la portada');
} else {
res.end('No entiendo la
petición');
}
}).listen(puerto, '127.0.0.1');
console.log('Server running at
http://127.0.0.1:'+puerto+'/');
En este programa procesamos, no sólo
imprimimos, la variable req. Es una estructura de
datos con un montón de cosas (insertad un
console.log si queréis verlo), pero de la que vamos
a usar solamente el camino. La idea es que el URL
lo que describe es un recurso, no un fichero, así
que nosotros procesamos el URL partiéndolo en
sus diferentes componentes. Si en el primer
componente no hay nada, damos la portada; si
hay, por ejemplo, proc, haríamos otra cosa
diferente, y eventualmente si se trata de un URL
desconocido devolvemos un mensaje diferente.
Adicionalmente, hemos introducido en este
programa un puerto que se toma de la línea de
órdenes. process.argv contiene información
sobre la línea de órdenes y otras cosas; en el 2º
elemento es donde está, precisamente, el primer
argumento de la línea de órdenes. El puerto por
omisión será 8080 (lo que se ve en la segunda
línea), pero si se pasa algún argumento (y es un
puerto válido) se usará ese valor.
Algunos sitios web como Heroku o Nodester
permiten publicar de forma gratuita aplicaciones
web hechas con node.js. Pueden ser bastante
útiles para crear prototipos o para hacer pruebas,
incluso para alojar prácticas de alguna asignatura.
Para finalizar
Hay muchas más cosas que se pueden hacer con
Node. Por ejemplo, un gestor de ventanas. Con
appjs puedes construir aplicaciones cliente-
servidor con su propia ventana, igual que con el
más veterano node-webkit.
Si se quiere trabajar principalmente en el
navegador, jQuery funciona de forma muy similar
a node: es un entorno asíncrono para crear
aplicaciones desde el navegador fácilmente, sin
tener que escribir demasiado código JavaScript.
Trasladar un programa de node a JQuery, es
bastante directo, y existen diversidad de
ampliaciones (plugins) para jQuery que hacen la
vida (todavía) más fácil.
Por otro lado, cualquier lenguaje de scripting como
Python o Perl permite crear también arquitecturas
cliente y servidor, sólo que no se pueden incluir en
el navegador (o usar la experiencia que tenemos
con el mismo). Sin embargo, especialmente
cuando se trate sólo de consumir servicios web,
pueden ser la opción más adecuada.
En cuanto a recurso para hacer preguntas y
obtener respuestas interesantes, StackOverflow es
un recurso imprescindible. Recuerda que tu karma
aumentará también cuando contestes preguntas.
Agradecimientos
Agradezco a los lectores en Twitter, especialmente
@danielribes, sugerencias sobre este material.
Bibliografía
Como recursos adicionales, las páginas de
Javascript en Mozilla.org, el estándar completo,
Eloquent Javascript y el curso de Javascript de
Víctor Rivas Santos.
Por último, Javascript: The good parts es un
manual bastante completo que menciona muchos
trucos para trabajar con este lenguaje.
Específicamente de node.js, se puede empezar por
esta pregunta en StackOverflow, para seguir con
el sitio de node.js en español, que incluye enlaces
a nodebeginner, el libro para principiantes en
node.js. La traducción tiene algunos errores, pero
es legible. Finalmente, Opsou nos ofrece una lista
de tutoriales en español. Finalmente, también hay
una cuenta de Twitter (no demasiado activa).
También el libro inserto (o cualquier otro
recomendado, a esta alturas hay una cantidad
ingente de bibliografía sobre node.js).
bash$ curl -i
https://api.github.com/users/JJ/orgs
Para llevar a cabo este ejemplo hay que instalar
curl, un programa que en una primera
aproximación es simplemente un descargador de
páginas web pero que en segunda se puede usar
como un completo cliente REST; en este caso -i te
incluye las cabeceras en la salida, con lo que
producirá algo de este estilo
#!/usr/bin/node
var https = require('https');
var user =process.argv[2]?
process.argv[2]:'JJ';
var options =
{ host: 'api.github.com',
path: '/users/'+user,
method: 'GET' };
var req =
https.request(options, function(res) {
res.setEncoding('utf8');
res.on('data', function (datos_JSON) {
var datos=JSON.parse(datos_JSON);
console.log('Login: ' + datos.login+ "\
nNombre: " + datos.name + "\n");
});
});
req.end();
Este programa descarga información de un usuario
en JSON y la procesa. Toma el usuario que se pase
por la línea de órdenes, o bien usa JJ por defecto,
dando un resultado así
var express=require('express');
var app = express.createServer();
app.get('/', function (req, res)
{ res.send('Portada'); });
app.get('/proc', function (req, res)
{ res.send('No es la portada'); });
app.listen(8080);
console.log('Server running at
http://127.0.0.1:8080/');
Para empezar, express nos evita todas las
molestias de tener que procesar nosotros la línea
de órdenes: directamente escribimos una función
para cada respuesta que queramos tener, lo que
facilita mucho la programación. Las órdenes
reflejan directamente las órdenes de HTTP a las
que queremos responder, en este caso get y por
otro lado se pone directamente la función para
cada una de ellas. Dentro de cada función de
respuesta podemos procesar las órdenes que
queramos.
Por otro lado, se usa send en vez de end para
enviar el resultado. Lo que viene a ser lo mismo, s
más o menos, aunque send es más flexible,
admitiendo todo tipo de datos que son procesados
para enviar al cliente la respuesta correcta.
Tampoco hace falta establecer explícitamente el
tipo MIME que se devuelve, encargándose send del
mismo.
Con el mismo express se pueden generar
aplicaciones no tan básicas ejecutándolo de la
forma siguiente:
node_modules/express/bin/express
prueba-rest
Se indica el camino completo a la aplicación
binaria, que sería el puesto. Con esto se genera un
directorio prueba-rest. Cambiándoos al mismo y
escribiendo simplemente npm install se
instalarán las dependencias necesarias. La
aplicación estará en el fichero app.js, lista para
funcionar, pero evidentemente habrá que
adaptarla a nuestras necesidades particulares.
El acceso a los parámetros de la llamada y la
realización de diferentes actividades según el
mismo se denomina enrutado. En express se
pueden definir los parámetros de forma bastante
simple, usando marcadores precedidos por :. Por
ejemplo, si queremos tener diferentes contadores
podríamos usar el programa siguiente:
var express=require('express');
var app = express();
var contadores = new Array;
var puerto=process.argv[2]?
process.argv[2]:8080;
app.get('/', function (req, res)
{ res.send('Portada'); });
app.put('/contador/:id', function( req,res
) { contadores[req.params.id] = 0;
res.send( { creado:
req.params.id } ); });
app.get('/contador/:id', function (req,
res) { res.send( "{ "+req.params.id+":
"+ contadores[req.params.id] +
"}" ); });
app.post('/contador/:id', function (req,
res) { contadores[req.params.id]++;
res.send( "{ "+req.params.id+": "+
contadores[req.params.id] + "}" ); });
app.listen(puerto);
console.log('Server running at
http://127.0.0.1:'+puerto+'/');
Este programa (express-count.js) introduce otras
dos órdenes REST: PUT, que, como recordamos,
sirve para crear nuevos recurso y es idempotente
(se puede usar varias veces con el mismo
resultado), y además POST. Esa orden la vamos a
usar para crear contadores a los que
posteriormente accederemos con get. PUT no es
una orden a la que se pueda acceder desde el
navegador, así que para usarla necesitaremos
hacer algo así desde la línea de órdenes: curl -X
PUT http://127.0.0.1:8080/contador/primero
para lo que previamente habrá que haber
instalado curl, claro. Esta orden llama a PUT
sobre el programa, y crea un contador que se
llama primero. Una vez creado, podemos acceder
a él desde la línea de órdenes o desde el
navegador (desde el navegador se generan
peticiones GET y POST solamente).
Clientes REST
Tampoco es complicado escribir con node.js un
cliente REST. Se puede hacer mediante peticiones
http, pero por supuesto es más fácil escribir un
cliente usando la librería restler, que se instala de
la misma forma que hemos visto anteriormente
con npm. Una vez instalada, se puede escribir un
cliente como este al utilísimo crea-contadores
anterior.
#!/usr/local/bin/node
var rest = require('restler');
var url =
'http://127.0.0.1:8080/contador/';
process.argv.forEach(function (val,
index, array) {
if ( index > 1 ) {
rest.put( url +
val ).on('complete', function( data ) {
console.log( data );
} );
}
});
El cliente es bastante simple, y lo que hace es
crear tantos contadores como argumentos le
pasamos por la línea de órdenes. Tras definir un
par de variables (ojo con la segunda, tiene que
contener el URL del sitio donde vamos a hacer la
consulta, el número y la dirección puede ser otro
cualquiera, lo que no variará será contador si
estamos usando el programa anterior), usamos la
variable process.argv que contiene los
argumentos de la línea de órdenes.
Sobre ese objeto ejecutamos un bucle, forEach
recorre los elementos de un objeto llamando sobre
cada uno de ellos una función con tres
argumentos: el índice y el elemento que se está
recorriendo en ese momento, y el array completo,
que en este caso no vamos a usar. Además, los
argumentos están en realidad a partir del segundo
elemento; los dos primeros contienen el camino a
node y el camino completo al programa que se
está ejecutando.
La clientela REST se usa con rest.put. Vamos a
crear un contador con el nombre val que se envía
desde la línea de órdenes, para lo que creamos el
URL del mismo simplemente concatenando las dos
cadenas.
Recordemos que node.js actúa de forma
asíncrona, por lo que lo que hacemos con esa
orden es crear un callback cuando (on) la petición
se haya completado (complete). Ese callback
simplemente te dice cual ha sido la respuesta y la
imprime.
Usando Ajax
Aunque inicialmente AJAX era un acrónimo de
Asynchronous Javascript and XML, hoy en día se
ha dejado de usar como tal y viene a abarcar
todas las tecnologías asíncronas de interacción
cliente servidor, usando cualquier formato de
serialización (aunque más generalmente JSON) y
en el cliente (aunque generalmente se trata de
JavaScript, pero puede ser también Dart u otro
lenguaje insertado en el navegador, como Java).
Con lo visto hasta ahora ya podemos intentar
hacer un programa cliente con otro servidor en
Ajax. En algún tema anterior hemos introducido la
J de Javascript; también hemos visto como trabajar
con JSON desde el servidor, que sería la X, y nos
falta la A. A se refiere a Asíncrono, y se trata de
que las peticiones desde el cliente (el navegador)
no lo bloqueen mientras el servidor contesta (si lo
hiciera, para el caso se podría generar una página
nueva cada vez que se hiciera cualquier
interacción). En la práctica, el AJAX se basa en una
clase de Javascript, XMLHttpRequest, que hace
una petición al servidor, y crea un evento que se
dispara en el navegador cuando se produce la
respuesta. Puede haber varias peticiones de este
estilo funcionando simultáneamente, de forma que
el navegador se comporta, en realidad, como si se
tratara de un interfaz de usuario.
Un programa AJAX, por tanto, tiene dos partes. La
parte servidor se suele programar habitualmente
para que responda a un interfaz REST, pero esto
es simplemente una convención. Podíamos, por
ejemplo, usar los programas que hemos visto
anteriormente
En cuanto al cliente, hay que tener en cuenta que
únicamente se pueden hacer peticiones al mismo
dominio desde el que se ha descargado la página
en la que se haya inserto. Es decir, sólo puedes
hacer peticiones a dominio.com desde páginas
que te hayas descargado desde dominio.com. Por
eso es importante que el sistema que tenga el API
REST sea capaz también de servir las páginas; es
lo que vamos a hacer en el siguiente ejemplo.
Necesitaremos tres ficheros para ejecutar el
programa. El primero es el servidor en node.js:
var fs = require('fs');
var express=require('express');
var app = express.createServer();
var contadores = new Array;
var portada =
fs.readFileSync('sumar_formulario.html'
,'utf8');
app.get('/', function (req, res)
{ res.send(portada); });
app.get('/js/:page', function (req, res)
{ var js =
fs.readFileSync(req.params.page);
res.contentType('text/javascript');
res.send(js); });
app.put('/contador/:id', function( req,res
) { contadores[req.params.id] = 0;
res.send('Creado contador '+
req.params.id ); });
app.post('/contador/:id', function (req,
res) { contadores[req.params.id]++;
res.contentType('application/json');
res.send( { resultado:
contadores[req.params.id] } );
console.log( { 'Post': contadores} ); });
app.get('/contador/:id', function (req,
res)
{ res.contentType('application/json');
res.send( "{ 'resultado': " +
contadores[req.params.id] + "}\n" ); });
app.get('/suma/:id1/:id2', function (req,
res) { res.send( { resultado:
contadores[req.params.id1] +
contadores[req.params.id2]} ); });
app.listen(8080);
console.log('Server running at
http://127.0.0.1:8080/');
Este código es similar al que hemos usado
anteriormente, salvo que respondemos a más
comandos REST: GET, PUT y POST. PUT crea el
contador, POST lo incrementa y finalmente GET
obtiene el resultado; recordemos que GET debe
ser idempotente y dejar al servidor en el mismo
estado. Estos dos últimos, además, devuelven el
resultado en JSON, y no en texto. Lo normal sería
que entendieran varios formatos (incluyendo texto
y HTML), pero por lo pronto lo dejaremos así.
También hay que tener en cuenta que este
servidor tiene que servir todos los ficheros, no sólo
el API REST. Por eso se ha creado otro seudo-
comando que lee un fichero y lo sirve como JS.
Ojo, este tipo de órdenes son un potencial hueco
de seguridad. Lo dejamos así por simplicidad, no
porque sea la forma adecuada que debería tener
una aplicación en producción.
La página web incluye lo mínimo necesario: el
script JS incluido y un formulario para solicitar el
nombre del contador que se va a incrementar. El
URL del formulario incluye el "camino" ficticio al
que responderá el servidor REST, que incluye js.
Ese fichero, precisamente, es el que vemos aquí:
function cuenta() {
request = new XMLHttpRequest();
var
contador= document
.getElementById('contador')
.value;
var peticion_str =
'/contador/'+contador;
request.open('POST', peticion_str ,
true);
request.onreadystatechange=
escribe_resultado ; request.send(null);
}
function escribe_resultado(){
if ( request.readyState == 4 ) {
if ( request.status == 200 ) {
var json;
eval ( 'json = '+
request.responseText );
console.log(json);
document.getElementById('Resultado').
innerHTML= 'Resultado = '+
json.resultado
}
}
}
Por lo pronto vemos cómo funciona en JS "clásico",
para entender un poco mejor el mecanismo que
sigue. Luego más adelante veremos que en JQuery
se puede hacer de forma mucho más simple. El
código tiene dos funciones: la que hace la llamada
(cuenta) y la que responde a la misma, el callback
(escribe_resultado). La primera construye el
URL y hace la petición POST para incrementar el
contador, y con onreadystatechange establece la
llamada para cuando llegue asíncronamente el
resultado.
Esta segunda función recibe el JSON y usa un
simple evaluador para extraer su resultado, previa
comprobación de que efectivamente se ha
recibido la respuesta completa y de forma
efectiva; la respuesta la escribe en un div.
Para hacer funcionar este programa tendremos
que crear previamente los contadores usando
cualquier otro programa
En realidad, es mucho más fácil hacerlo con
JQuery. En esta web de Codeko muestran como
funcionan las órdenes básicas. El formulario sería
bastante similar, aunque hemos tenido que
modificar el servidor para que muestre diferentes
páginas principales. El principal cambio será,
obviamente, en el código usado para la solicitud
Ajax, que usará jQuery en vez de JS puro. Helo
aquí:
$(document).ready(function() {
$("#formulario")
.change(function(){
$.get('/contador/'
+$('#contador').val(),
function( data) {
$('#Resultado').html('Resultado
'+ data.resultado);
});
});
} );
Este pequeño programa tiene todo lo compacto y
críptico a lo que nos tiene acostumbrados jQuery.
Como es habitual, se ejecuta sólo cuando se ha
cargado la página y usa el programa para añadir
funcionalidad, eventos, al HTML en vez de tener el
propio evento definido en el mismo; lo clásico en
JS (y jQuery) es dividir el código de la
funcionalidad. Lo que hace es que crea un evento
sobre el formulario tal que al cambiar llame a una
función anónima.
Esa función, en un par de líneas, hace lo mismo
que previamente con unas cuantas líneas en JS:
hace una petición get (en la que usa también los
selectores jQuery para extraer el valor, contenido
en $val()$, del elemento del formulario) y una vez
obtenido el resultado usa el selector del elemento
correspondiente, #Resultado, para insertarlo.
Adicionalmente, jQuery esconde el mecanismo
subyacente de llamada haciéndolo independiente
del navegador. Si XMLHttpRequest funciona, lo
usa; si no, usa el mecanismo nativo.
Juan J. Merelo