Bienvenidos sean a este post, hoy veremos como actualizar en tiempo-real a nuestra pagina de inicio.
En el post anterior logramos integrar a Socket.IO con nuestra aplicacion, en realidad solo lo hemos aplicado al microservicio de login, y este resulto invisible para nosotros, lo que trabajaremos a partir de hoy es como enviar eventos cuando un se aplica el CRUD a una nota, para que el codigo escuche a cualquiera de estos y actue apropiadamente, para poner esto en accion debemos usar el codigo que trabajamos hasta el post anterior, en caso de no tenerlo les dejo un link para descargarlo:
Una vez descargado simplemente extraen los dos directorios en el PC donde trabajan y ya esta preparado para ser utilizado, en este caso nos centraremos en el directorio notas para trabajar, el directorio users lo necesitamos para nuestro microservicio de usuarios, y nuestro primer paso sera ir al directorio models para modificar al archivo Notas.mjs donde al inicio del mismo agregaremos la siguiente linea:
import EventEmitter from 'events';
En este caso vuelve un viejo amigo como es el emisor de eventos del paquete events, sobre este hablamos en este post, nuestro siguiente paso sera tomar la clase AlmacenNotasAbs y la modificaremos de la siguiente manera:
export class AlmacenNotasAbs extends EventEmitter {
static almacen() {}
async cerrar() {}
async create(clave, titulo, cuerpo) {}
async read(clave) {}
async update(clave, titulo, cuerpo) {}
async delete(clave) {}
async listarClaves() {}
async contar() {}
emitirCreado(nota) { this.emit('notacreada', nota); }
emitirCambiado(nota) { this.emit('notacambiada', nota); }
emitirBorrado(clave) { this.emit('notaborrada', clave); }
}
Para esta ocasion la primera modificacion sera en la clase dado que hara sera heredera de EventEmitter, lo siguiente sera agregar una funcion estatica llamada almacen, ya hablaremos de esto un poco mas adelante, luego hemos agregado tres nuevos metodos, para enviar los eventos de la creacion, modificacion y eliminacion de una nota respectivamente, observen que solo pasamos la notificacion del mismo por el momento, nuestro siguiente paso sera ir al archivo notas-sequelizar.mjs en el mismo directorio y haremos una serie de modificaciones, en este archivo iremos a la definicion de la clase AlmacenNotasSequlz, heredera de la clase anterior, y tomaremos el bloque del metodo update y lo modificaremos de la siguiente manera:
async update(clave, titulo, cuerpo) {
await conectarDB();
const nota = await SQNota.findOne({ where: {clave: clave}});
if (!nota) {
throw new Error(`Nota no encontrada para ${clave}`);
} else {
await SQNota.update({
titulo: titulo,
cuerpo: cuerpo },
{ where: { clave: clave }});
let nota = await this.read(clave);
this.emitirCambiado(nota);
return nota;
}
}
En este caso agregamos primero un else para diferenciar entre el error cuando no exista la nota y cuando lo modifica al encontrarlo, nuestra siguiente modificacion sera en el bloque del else donde primero crearemos un objeto con el resultado de leer nuestra nota, luego emitimos el evento informando que la nota ha sido modificada, para finalmente devolver el objeto que hemos creado anteriormente, en realidad la unica verdadera modificacion fue agregar el envio del evento pero el resto sigue trabajando de la misma forma, ahora pasemos al metodo create para modificarlo de la siguiente manera:
async create(clave, titulo, cuerpo) {
await conectarDB();
const sqnota = await SQNota.create({
clave: clave,
titulo: titulo,
cuerpo: cuerpo });
const nota = new Nota(sqnota.clave,
sqnota.titulo,
sqnota.cuerpo);
this.emitirCreado(nota);
return nota;
}
En este caso hicimos un pequeño cambio, en este caso eliminamos la linea que envia directamente el resultado de la nueva nota, y la reemplazamos por un objeto que contiene a esta nota, emitimos el evento de creado y luego devolvemos el objeto, continuemos con el tema modificando el metodo delete de la siguiente manera:
async delete(clave) {
await conectarDB();
await SQNota.destroy({ where: { clave: clave }});
this.emitirBorrado(clave);
}
En este caso solo agregamos el disparador del evento de borrado, el resto no se modifico, con esto realizado nuestro siguiente paso sera ir al archivo app.mjs donde haremos un par de modificaciones, nuestras primeras modificaciones sera tomara esta linea:
import { router as indexRouter } from './routes/index.mjs';
Y lo modificaremos de la siguiente manera:
import {
router as indexRouter,
inicio as inicioHome
} from './routes/index.mjs';
En este caso no solo importaremos a router sino tambien a la funcion inicio con otro nombre y esta la agregaremos en un momento nada mas, nuestra siguiente modificacion sera tomar la siguiente linea:
import { router as notasRouter } from './routes/notas.mjs';
Para modificar de la siguiente forma:
import {
router as notasRouter,
inicio as iniciaNotas
} from './routes/notas.mjs';
Al igual que en el caso anterior agregamos al metodo inicio con otro nombre para diferenciarlo con respecto al anterior, por el momento no se preocupen porque en un momento hablaremos sobre ambos metodos, nuestro siguiente paso sera buscar el siguiente bloque de codigo:
usarModeloNotas(process.env.MODELO_NOTAS ? process.env.MODELO_NOTAS :
"memoria")
.then(almacen => {})
.catch(error => {onError({ code: 'ENOTESSTORE', error}); });
Y lo modificaremos de la siguiente manera:
usarModeloNotas(process.env.MODELO_NOTAS ? process.env.MODELO_NOTAS :
"memoria")
.then(almacen => {
inicioHome();
iniciaNotas();
})
.catch(error => {onError({ code: 'ENOTESSTORE', error}); });
En este caso solamente agregamos las nuevos metodos de inicio que agregaremos en un momento, si observan los agregamos en una funcion llamada almacen, como la declarada en AlmacenNotasAbs, y al estar declarada como estatica podra ser accedida desde cualquier punto del programa, el resto sigue de la misma forma, uun cambio mas que debemos hacer que no lo hice en el post anterior es el siguiente:
const io = socketio(server);
Y lo haremos de la siguiente manera:
export const io = socketio(server);
Esto es asi porque necesitaremos de este objeto para poder importarlo en otros archivos, nuestro siguiente paso sera ir al archivo notas.mjs en el directorio routes y agregaremos el siguiente bloque de codigo al final del mismo:
export function inicio(){}
Recuerdan que en app.mjs importabamos desde este archivo a esta funcion, bueno por el momento lo dejamos en blanco porque este tema lo veremos en otro post pero lo agregamos asi para que no tire un error al momento de importarlo, nuestro siguiente paso sera ir al archivo index.mjs en el directorio routes y al inicio agregaremos la siguiente linea:
import { io } from '../app.mjs';
Este nos importara el objeto de Socket.IO que hemos creado en ese archivo, sobre esto hablamos en el post anterior, nuestro siguiente paso sera modificar la funcion que nos rutea el inicio de la aplicacion:
router.get('/', async (req, res, next) => {
try {
const listaclaves = await notas.listarClaves();
const promesaClave = listaclaves.map(clave => {
return notas.read(clave);
});
const listanotas = await Promise.all(promesaClave);
res.render('index', { title: 'Notas',
listanotas: listanotas,
twtLogin: twtLogin,
user: req.user ? req.user : undefined
});
} catch (err) {
next(err);
}
});
Lo modificaremos de la siguiente manera:
router.get('/', async (req, res, next) => {
try {
const listanotas = await getListaClaveTitulos();
res.render('index', { title: 'Notas',
listanotas: listanotas,
twtLogin: twtLogin,
user: req.user ? req.user : undefined
});
} catch (err) {
next(err);
}
});
Basicamente sacamos la parte encargada de generar la lista de notas para reemplazarlas con una sola funcion, el resto no sufrio ninguna modificacion, nuestro siguiente paso sera agregar la ultima antes mencionada y para ello la agregaremos a continuacion del anterior:
async function getListaClaveTitulos() {
const listaClave = await notas.listarClaves();
const promesaClaves = listaClave.map(clave => notas.read(clave));
const listanotas = await Promise.all(promesaClaves);
return listanotas.map(nota => {
return { clave: nota.clave, titulo: nota.titulo };
});
}
Este nos servira para generar una lista con todas las claves y titulos de las notas almacenadas, basicamente trasladamos lo que teniamos en el bloque anterior aqui, generamos toda la lista de las claves y titulos de las notas almacenadas y las devolvemos, a continuacion agregaremos la siguiente funcion:
const emitirTituloNotas = async () => {
const listanotas = await getListaClaveTitulos();
io.of('/home').emit('titulonotas', { listanotas });
};
Si recuerdan cuando hablamos de como trabaja Socket.IO en este post dijimos que la comunicacion entre el servidor y el cliente se hace mediante namespaces y rooms, para definirlo se utiliza:
objeto_socket.of('/namespace')
En nuestro caso no es otro que el contenedor de /home, siempre tiene el estilo Unix, luego mediante el metodo emit estamos enviando el mensaje desde el servidor unicamente a los navegadores o clientes conectados en este namespace, y en este caso solamente trabajaremos con el evento titulonotas, enviando el objeto generado la funcion anterior, sobre este envio de dato hablaremos en un momento, ahora agreguemos la siguiente funcion:
export function inicio() {
io.of('/home').on('connect', socket => {
debug('conexion socketio en /home');
});
notas.on('notacreada', emitirTituloNotas);
notas.on('notacambiada', emitirTituloNotas);
notas.on('notaborrada', emitirTituloNotas);
}
Recuerdan que en app.mjs agregamos dos importaciones a una funcion llamada inicio?, en el caso anterior lo dejamos en blanco pero en este si lo definiremos, observen que volvemos a usar el namespace pero en lugar de usar emit utilizamos a on, este se utiliza para hacer que el codigo del lado del servidor sepa cuando un navegador se conecta a este namespace, por esta razon una vez conectado lo primero que hacemos es informar que se conecto mediante socketio, despues tendremos tres escuchadores para los eventos que creamos en Notas.mjs al comienzo, este namespace no lo comentamos pero sera el relacionado principalmente a nuestro pagina de inciado y sobre todo este existira desde el inicio porque desde que se inicia la aplicacion se llama a esta funcion, con esto ya tenemos toda la parte tecnica establecida solo nos resta la parte encargada de los views, para ello debemos ir principalmente al directorio views y en este primero trabajaremos con el archivo layout.hbs y lo modificaremos de la siguiente manera:
<!DOCTYPE html>
<html>
<head>
<title>{{title}}</title>
<meta charset="utf-8">
<meta name="viewport"
content="width=device=width, initial-scale=1,
shrink-to-fit=no">
<link rel='stylesheet'
href='/assets/vendor/bootstrap/css/bootstrap.css' >
<link rel='stylesheet'
href='/assets/stylesheets/style.css' />
</head>
<body>
{{> header }}
{{{ body }}}
</body>
</html>
Basicamente en esta pagina eliminamos todos los llamados de script para cargar los distintos temas, como son el tema, jquery, y los iconos, pero no se preocupen porque pronto los volveremos a ingresar, pero antes debemos hacer una pequeña modificacion mas, al final de los archivos:
- borrarnota.hbs
- login.hbs
- editarnota.hbs
- error.hbs
- vistanota.hbs
En cada uno agregaremos al final de todo codigo la siguiente linea:
{{> footerjs}}
Que sera la encargada de cargar todos los scripts que eliminamos de layout.hbs, pero de esto hablaremos en un momento, tomemos por ejemplo error.hbs al agregarle la linea anterior debe quedar asi:
views/error.hbs
<h1>{{message}}</h1>
<h2>{{error.status}}</h2>
<pre>{{error.stack}}</pre>
{{> footerjs}}
Nuestro siguiente paso sera crear el archivo footerjs.hbs en el directorio partials y le agregeramos el siguiente codigo:
partials/footerjs.hbs
/assets/vendor/jquery/jquery.min.js
/assets/vendor/popper.js/popper.min.js
/assets/vendor/bootstrap/js/bootstrap.min.js
/assets/vendor/feather-icons/feather.js
<script>feather.replace();</script>
Esto es basicamente toda la seccion de scripts que eliminamos de layout.hbs, no se preocupen que este se cargara cada vez que lo necesitemos manteniendo todo funcional como era antes, pero todavia nos faltan algunas modificaciones mas, para ello debemos ir al archivo index.hbs en el directorio views y agregaremos el siguiente segmento de codigo al final del archivo:
<script>
$(document).ready(function() {
console.log('iniciando socket.io');
var socket = io('/home');
socket.on('connect', socket => {
console.log('conexion de socketio en /home');
});
socket.on('titulonotas', function(datos) {
var listanotas = datos.listanotas;
$('#titulonotas').empty();
for(var i=0; i < listanotas.length; i++) {
datosnota = listanotas[i];
$('#titulonotas')
.append('<a class="btn btn-lg btn-block btn-outline-dark" href="/notas/ver?clave='
+ datosnota.clave + '">' + datosnota.titulo + '</a>');
}
});
});
</script>
Primero llamaremos a nuestro footerjs para que cargue los scripts en este, luego llamaremos a otro que utiliza socket.io, si se preguntan como carga ese script sin que lo hayamos redireccionado, esto es gracias a que socket.io se encarga de todo esto por nosotros, despues tenemos un bloque de codigo de jQuery, este es el tipo bloque que se ejecuta una vez que la pagina (documento) se ha cargado completamente, en este caso primero crearemos un objeto de tipo socket.io con el namespace que creamos anteriormente, volvemos a utilizar el metodo on para indicar que se ha conectado mediante este, despues usaremos otro escuchador pero para el evento titulonotas, y este basicamente se encarga primero de limpiar el div llamado titulonotas y luego vamos llenandolo con los botones y los datos de listanotas, en si es el mismo codigo que estuvimos trabajando hasta ahora pero lo creamos mediante jQuery, con esto tenemos un 99.9% creado pero nos falta un solo detalle, para ello en el mismo archivo deben buscar la siguiente linea:
Nota:
Tengan cuidado al escribir el bloque de jQuery porque puede ocasionar que no funcione, esa fue la mejor sintaxis que pude encontrar.
<div class="col-12 btn-group-vertical" role="group">
Y la modificaremos de la siguiente manera:
<div class="col-12 btn-group-vertical" id="titulonotas" role="group">
Lo unico que hicimos fue agregarle el id para que el selector de jQuery pueda encontrarlo, el resto no recibe ningun cambio, ahora si tenemos todo para poder probarlo, pero para verificar su funcionamiento vamos a necesitar como minimo tres navegadores abiertos, para poder ver como trabaja las actualizaciones en tiempo real, para ello les dejo el siguiente video
En el video podemos ver como al crear o eliminar una nota, automaticamente estas acciones se vieron reflejadas en las otras paginas de inicio, con esto ya tenemos el Socket.IO en nuestra pagina de inicio pero todavia nos falta un tema mas, del cual hablaremos en el proximo post pero antes de finalizar les dejo un link con todo los archivos del proyecto y los modificados en este post:
En resumen, hoy hemos establecido a Socket.IO en nuestra pagina de Inicio, hemos hecho todas las modificaciones necesarias para enviar y trabajar con los eventos que se encargan de esto, y hemos visto como al trabajar con las notas ya sea borrarlo o crearlo se actualiza automaticamente en la pagina de inicio, espero les haya sido de utilidad sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.


Donación
Es para mantenimento del sitio, gracias!
$1.50
