Mostrando entradas con la etiqueta aurelia. Mostrar todas las entradas
Mostrando entradas con la etiqueta aurelia. Mostrar todas las entradas

domingo, 19 de febrero de 2017

Chat con Websockets - Aurelia y Express (Parte 4 - Enviando y recibiendo mensajes)

Hola de nuevo, ya que el backend y el frontend están conectados, es hora de enviar y recibir mensajes de las personas con las que nos encontramos chateando. Para esto descargaremos el frontend y el backend con los siguientes comandos (puedes seguir todo el proceso para llegar hasta acá en posts anteriores):
Para el backend:

$ git clone https://github.com/feardarkness/chat-backend.git
$ git checkout tags/connect-be-fe

Para iniciar ingresamos a la carpeta chat-backend y ejecutamos:

$ npm start

Para el frontend:

$ git clone https://github.com/feardarkness/chat-frontend.git
$ git checkout tags/connect-be-fe

Para iniciar ingresamos a la carpeta chat-frontend y ejecutamos:

$ NODE_ENV=dev au run --watch

Ahora modifiquemos el código para mandar mensajes, es necesario modificar tanto el backend como el frontend, así que dividiremos los cambios en dos.

FRONTEND

Es necesario cambiar (src/resources/elements/chat-windows.html) el textarea por un div para ir agregando los mensajes que se nos envien, además se agrega un input text para anotar nuestro alias en el chat y se agrega un tag form para que cuando presionemos la tecla enter el formulario se envie (cuando presionamos enter, el primer botón submit que está en el formulario se presiona).

<template>
  <div class="row" id="chat">
    <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
        <div id="chat-content" ref="messages"></div>
    </div>
    <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
      <div class="form-group">
        <div class="col-sm-2">
          <input type="text" class="form-control"
                 placeholder="Usuario"
                 value.bind="username">
        </div>
        <form submit.delegate="sendMessage()">
          <div class="col-sm-5">
            <input type="text" class="form-control"
               id="mensaje" placeholder="Escriba el mensaje"
               value.bind="message" autofocus>
          </div>
          <div class="col-sm-2">
            <button type="submit" class="btn btn-primary">Enviar Mensaje</button>
          </div>
        </form>
      </div>
    </div>
  </div>
</template>

Modificaremos los estilos (src/assets/main.scss) para que el div de los mensajes tenga un alto de 200 píxeles y lo marcaremos con un borde sólido de 2 píxeles con las esquinas redondeadas.

@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap";

#chat{
  #chat-content {
    border: 2px solid #000000;
    border-radius: 5px;
    height:200px;
    overflow: auto;
    padding: 4px;
    margin: 5px 0px;

    .chat-message {

      .username {
        font-style: italic;
      }
    }
  }
}

Por último para el frontend, modificaremos el viewModel (src/resources/elements/chat-window.js) para que en el momento en que nos llegue un mensaje del backend lo agreguemos al div donde mostraremos los mensajes del chat.

this.socket.on('be-message', (data) => {
      this.logger.debug('Event with data', data);
      $(this.messages).append(
        this.createMessageDiv(
          data.message,
          data.username
        )
      );

    });

Además, agregaremos al viewModel (src/resources/elements/chat-window.js) la opción de generar una cadena aleatoria para establecer el nombre de usuario y agregar el mensaje con jquery al div, el archivo completo quedaría así:

import io from 'socket.io';
import {LogManager} from 'aurelia-framework';
import $ from 'bootstrap';

export class ChatWindow {
  message = '';
  username = '';

  constructor() {
    this.logger = LogManager.getLogger('chat-window-element');
    this.username = this.randomString();
  }

  attached() {
    this.logger.debug('Attached...');
    this.socket = io('http://127.0.0.1:8081');

    this.socket.on('connect', () =>{
      this.logger.debug('Socket connected...');
    });

    this.socket.on('be-message', (data) => {
      this.logger.debug('Event with data', data);
      $(this.messages).append(
        this.createMessageDiv(
          data.message,
          data.username
        )
      );

    });

    this.socket.on('disconnect', () =>{
      this.logger.debug('Socket disconnected...');
    });
  }

  createMessageDiv(message, username) {
    return $('<div class="chat-message"><span class="username">'
                    + username + ':</span> ' + message + '</div>');
  }

  sendMessage() {
    this.socket.emit('fe-message', {
      message: this.message,
      username: this.username
    });
  }

  randomString() {
    const letrasDisponibles = `ABCDEFGHIJKLMNOPQRSTUVWXYZ
                      abcdefghijklmnopqrstuvwxyzÑñ0123456789`;
    let cadena = '';
    '1234567'.split('').forEach(() => {
      const pos = Math.floor(Math.random() * letrasDisponibles.length);
      cadena += letrasDisponibles.charAt(pos);
    });
    return cadena;
  }
}

BACKEND

En el backend se implementará el envió de mensajes a todos los clientes conectados, en caso de recibir un mensaje del frontend (fe-message), emitimos ese mensaje a todos los clientes (be-message). Por ahora no realizamos ninguna validación, eso se implementara en un post posterior pero es necesario saber que las validaciones son MUY IMPORTANTES, y en ningún caso se debe confiar en los datos enviados por el cliente.

import express from 'express';
import http from 'http';
import debugModule from 'debug';
import socketIO from 'socket.io';

const app = express();
const server = http.Server(app);
const io = socketIO(server);
const debug = debugModule('chat-backend');

server.listen(8081, () => {
  debug('Servidor escuchando en el puerto 8081');
});

io.on('connection', (socket) => {
  debug('Alguien esta conectado!!!');

  socket.emit('be-message', {
    message: 'Bienvenido usuario!!!',
    username: 'Servidor',
  });

  socket.on('fe-message', (data) => {
    debug('Usuario conectado', data);
    io.emit('be-message', data);
  });
});

Ahora veamos un ejemplo de como debería quedarnos el chat:

Ejemplo de comunicación entre usuarios
Antes de realizar las pruebas en nuestros equipos es necesario deshabilitar browsersync que esta habilitado por defecto en Aurelia. Ingresar a 127.0.0.1:3001, sync options -> disable all -> habilitar code sync. Esto hará que los cambios que realicemos en una ventana no se repitan en otra (si no deshabilitamos estas opciones, las acciones que realicemos se repetirán en todos los navegadores donde tengamos abierta la aplicación).

Y eso es todo por ahora, para descargar la aplicación es necesario realizar los siguientes pasos:

BACKEND
$ git clone https://github.com/feardarkness/chat-backend.git
$ git checkout tags/send-receive-messages

FRONTEND
$ git clone https://github.com/feardarkness/chat-frontend.git
$ git checkout tags/send-receive-messages

Keep coding >_<


domingo, 12 de febrero de 2017

Chat con Websockets - Aurelia y Express (Parte 3 - conectando frontend y backend)

Hola nuevamente, ahora que ya tenemos la estructura del proyecto preparada, es hora de ver si podemos conectarnos al socket, para esto crearemos la estructura del proyecto con aurelia y utilizaremos bootstrap-sass en el proyecto. Como ya se realizo estos pasos en otros posts, no los repetiré acá, pero si pondré los comandos necesarios para bajar el proyecto de github y darle el nombre de chat-frontend (se tendrá dos proyectos, uno con nodejs, express y socket.io para el backend, y otro con aurelia para el frontend).

$ git clone https://github.com/feardarkness/aurelia_bootstrap_sass_starter
$ mv aurelia_bootstrap_sass_starter/ chat-frontend
$ cd chat-frontend
$ rm -rf .git
$ git init
$ npm install
$ NODE_ENV=dev au run watch

Antes de continuar, revisaremos algunos conceptos necesarios; el protocolo HTTP no almacena ningún estado, abrimos el navegador, enviamos un mensaje al servidor, se procesa, se devuelve la respuesta y se cierra la conexión; no se guarda ningún estado, usualmente para que el navegador nos recuerde es necesario guardar la identidad en una cookie. Además, toda conexión es iniciada por el cliente y cada interacción entre el cliente y el servidor es aislada una de la otra.

Los websockets por otro lado son full-duplex (conexiones bidirecionales) y abren conexiones persistentes del navegador al servidor. Una vez que se ha realizado la conexión, esta se mantiene abierta hasta que uno de los dos extremos (cliente o servidor) decida cerrarla. Ya que la conexión se ha establecido se puede enviar mensajes en cualquier momento (ideal para un chat :D).

Ahora, continuando con la aplicación, crearemos un textarea y un input, nada muy elaborado pero lo suficiente como para ver el funcionamiento de nuestros sockets.
Primero agregamos la librería socket.io con

$ npm install socket.io-client --save

Ahora, agregamos la libreria al archivo aurelia_project/aurelia.json, dentro de dependencies:

{
  "name": "socket.io",
  "path": "../node_modules/socket.io-client/dist",
  "main": "socket.io"
},

Además, crearemos un elemento nuevo y le agregaremos los elementos básicos para hacer el chat:

$ au generate element
      chat-window

Modificaremos el elemento recién creado, le agregaremos un textarea donde aparecerán los mensajes, un input cuyo valor estará enlazado con el atributo message del viewmodel y un botón para enviar el mensaje con la acción sendMessage (src/resources/elements/chat-window.html).

<template>
  <div class="row" id="chat">
    <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
        <textarea id="chat-content" rows="10"></textarea>
    </div>
    <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
      <div class="form-group">
        <label for="mensaje"
           class="col-sm-1 control-label text-right">Mensaje:</label>
        <div class="col-sm-5">
          <input type="text" class="form-control"
             id="mensaje" placeholder="Escriba el mensaje que desea enviar"
             value.bind="message">
        </div>
        <div class="col-sm-2">
          <button type="submit" class="btn btn-primary"
                  click.delegate="sendMessage()">Enviar Mensaje</button>
        </div>
      </div>
    </div>
  </div>
</template>

También es necesario modificar el viewmodel del elemento recien creado (src/resources/elements/chat-window.js), agregaremos el logger, y algunos mensajes con el websocket.

import io from 'socket.io';
import {LogManager} from 'aurelia-framework';

export class ChatWindow {
  message = '';

  constructor() {
    this.logger = LogManager.getLogger('chat-window-element');
  }

  attached() {
    this.logger.debug('Attached...');
    this.socket = io('http://127.0.0.1:8081');

    this.socket.on('connect', () =>{
      this.logger.debug('Socket connected...');
    });

    this.socket.on('be-message', (data) => {
      this.logger.debug(`Event with data: ${data}`);
    });

    this.socket.on('disconnect', () =>{
      this.logger.debug('Socket disconnected...');
    });
  }

  sendMessage() {
    this.socket.emit('fe-message', {
      message: this.message
    });
  }
}

Importamos socket.io, y cuando el elemento está enlazado con el DOM (attached) creamos la conección con el backend (127.0.0.1 puerto 8081), además, agregamos un mensaje cuando el socket logré conectarse (on connect), un mensaje cuando el servidor nos hable con el evento be-message (on be-message) y un mensaje cuando el elemento se desconecté del socket del backend (on disconnect). Además, cuando el botón del frontend se presione emitiremos un mensaje (emit) con el nombre fe-message.

Agregamos unos estilos muy básicos en el archivo source/assets/main.scss:

#chat{
  #chat-content {
    width: 100%;
  }
}

Y eso es todo para el frontend. Ahora es necesario levantar el backend (chat-backend), puedes clonarlo con:

$ git clone https://github.com/feardarkness/chat-backend.git
$ git checkout tags/express-debug

Modificaremos el backend para recibir el mensaje enviado por el frontend (index.js):

import express from 'express';
import http from 'http';
import debugModule from 'debug';
import socketIO from 'socket.io';

const app = express();
const server = http.Server(app);
const io = socketIO(server);
const debug = debugModule('chat-backend');

server.listen(8081, () => {
  debug('Servidor escuchando en el puerto 8081');
});

io.on('connection', (socket) => {
  debug('Alguien esta conectado!!!');

  socket.emit('be-message', {
    message: 'Bienvenido usuario!!!',
  });

  socket.on('fe-message', (data) => {
    debug('Usuario conectado', data);
  });
});

Se agrega un listener para cuando un ciente se conecte (on connection), y se responde un mensaje de bienvenida al usuario (socket emit be-message); además le indicamos al socket que muestre el mensaje "Usuario conectado" y los datos enviados en caso de recibir un mensaje tipo "fe-message" (on fe-message).

Ahora iniciamos el backend con:

$ npm start

Y podemos observar la interacción entre el backend y el frontend a través de sockets.

Backend y Frontend comunicándose

Y eso es todo por ahora, para obtener el backend del proyecto:

$ git clone https://github.com/feardarkness/chat-backend.git
$ git checkout tags/connect-be-fe

Para obtener el frontend del proyecto:

$ git clone https://github.com/feardarkness/chat-frontend.git
$ git checkout tags/connect-be-fe

Y eso es todo por ahora, keep coding >_<

sábado, 4 de febrero de 2017

Binding y Templating en Aurelia

Hello again!!! en este post se mostrará algunas de las principales características del motor de templates de Aurelia que harán nuestro trabajo con la interfaz de usuario mucho más simple de realizar. Todos los templates en Aurelia deben encontrarse entre los tags template, ejm:

<template>
  <require from="./assets/main.css"></require>
  <require from="./resources/elements/bootstrap-input"></require>

  <div>
    <bootstrap-input></bootstrap-input>
  </div>
</template>

Primero, ¿cómo obtenemos una hoja de estilos (css) o un plugin javascript? Para esto es necesario utilizar el tag require de la siguiente manera:

<!-- Para obtener un css -->
<require from="./assets/main.css"></require>

<!-- Para obtener un elemento no es necesario agregar la extensión -->
<require from="../resources/elements/bootstrap-input"></require>

En el primer caso, utilizamos ./ ó ../ para indicar que la ruta es relativa a la vista donde nos encontramos. En caso de no tener ./ ó ../, la ruta es relativa a la raíz de la aplicación. Solo se debe colocar una extensión en caso de que se este importando un elemento que es únicamente HTML o una hoja de estilos.

En caso de tener más de un elemento con el mismo nombre o si se quiere utilizar otro nombre es necesario especificar el atributo as, ejemplo:

<template>
  <require from="./assets/main.css"></require>
  <require from="./resources/elements/bootstrap-input"
          as="otro"></require>

  <div>
    <otro></otro>
  </div>
</template>

Ahora, ¿cómo agregamos texto dinámico en nuestra templeta?, este proceso se conoce como interpolación y es sumamente simple en Aurelia, se utiliza ${algo}.

<div class="row">
  <!-- Imprime la suma de 10 y 15, o sea 25 -->
  <p>${ 10 + 15 }</p>

  <!-- Imprime 50-->
  <p>${ 5 * 10 }</p>

  <!-- Imprime la cadena 'Soy una cadena' -->
  <p>${ 'Soy una cadena' }</p>

  <!-- Imprime el valor de la variable message del ViewModel -->
  <p>${ message }</p>
</div>

Además, es posible hacer referencia a un elemento en el DOM, para esto solamente se necesita agregar el atributo ref al elemento y luego se lo puede referenciar tanto en el ViewModel como en la misma templeta.

Por ejemplo, en el siguiente fragmento de código le damos el nombre campoNombre al input, para luego obtener su valor en el tag <p>.

<div class="col-sm-10">
  <input type="text" id="nombre" name="nombre" ref="campoNombre">
  <p>${campoNombre.value}</p>
</div>

Otra característica importante es que podemos "enlazarnos" (binding) a propiedades de los tags html, por ejemplo, podemos enlazarnos a la propiedad style de la siguiente manera:

    <div class="col-sm-10" style.bind="campoNombre.value">
      <input type="text" id="nombre" name="nombre" ref="campoNombre">
      <p>${campoNombre.value}</p>
    </div>

En este caso, se cambia el estilo del div de acuerdo a lo que se tiene anotado en el input, como se puede ver, se hizo una referencia al input con el nombre campoNombre, y luego se utilizo el valor (value) de este elemento en el div con style.bind. En caso de querer trabajar con Explorer o Edge, es necesario hacer el bind a la propiedad css que es un alias de style.

También se puede hacer un bind al value de un elemento como se ve a continuación, además, como los bindings son two-way por defecto, cambiando el valor de un input se reflejan los cambios en el otro.

    <div class="col-sm-10" style.bind="campoNombre.value">
      <input type="text" id="nombre" name="nombre" ref="campoNombre">
      <p>${campoNombre.value}</p>
      <input type="text" id="nombre1" name="nombre1" value.bind="campoNombre.value">
    </div>

Ejemplo de bind!!!
Además de bind, que es two-way por defecto para los controles de formularios, se puede utilizar one-way (fluye del view-model a la vista), one-time (fluye solamente una vez del view-model a la vista) y two-way.

Tambien es posible enlazarnos a eventos, para esto se utiliza trigger o delegate. La regla de oro es utilizar delegate siempre que sea posible hacerlo. Como ejemplo, se agregará unos eventos al input recientemente creado:

      <input type="text" id="nombre1" name="nombre1"
              focus.trigger="handleEvent($event)"
              keypress.delegate="handleEvent($event)"
              blur.trigger="handleEvent($event)">
      <p>${evento}</p>

Y el manejador simplemente será:

  evento = 'Aún no han ocurrido eventos';

  handleEvent(event) {
    this.evento = event;
    return true;
  }

Bind a eventos


Para los eventos focus y blur es necesario utilizar trigger (ninguno de estos elementos burbujea; en general, como los elementos en html se encuentran uno dentro del otro el burbujeo implica que el evento se va replicando a los padres del elemento en que ocurrió el evento), mientras que para el evento keypress se utilizará delegate (keypress si burbujea), además se esta llamando una función con el parámetro $event que Aurelia establece para enviarnos el evento que se ha realizado. Como comentario extra, en la función handleEvent se debe hacer un return true para que el evento se propague (En general, el término burbujear y propagar se utilizan con el mismo significado)

Al realizar templetas en Aurelia, contamos con algunos atributos de control como:

  • if
  • show
  • repeat.for
Cambiaremos el mismo ejemplo para mostrar el tag p cuando el valor del primer input sea 'mostrar', para esto utilizaremos if.bind, con la verificación de que el valor del input sea igual a mostrar:

<input type="text" id="nombre" name="nombre" ref="campoNombre">
      <p if.bind="campoNombre.value === 'mostrar'">${campoNombre.value}</p>

if-bind en vivo!!!!

Además, vamos a utilizar repeat para mostrar una lista. Existen algunas variables extras en el contexto del repeat que nos ayudan a identificar varias propiedades del elemento que se esta repitiendo, las cuales son:
  • $index: con el índice del iterador
  • $first: primer elemento
  • $last: último elemento
  • $even: elementos pares
  • $odd: elementos impares
En el ejemplo, creamos una lista en el view model y luego la mostramos en pantalla:

export class App {

  listaDeFrameworks = ['Aurelia', 'EmberJS', 'Angular', 'React', 'Vue', 'Backbone'];
}

<div>
  <ul>
    <li repeat.for="framework of listaDeFrameworks"
        style="${$odd? 'background-color:yellow' : 'background-color: cyan'}">
      ${$index}. ${framework}
      <span if.bind="$first"> - Primer elemento</span>
      <span if.bind="$last"> - Último elemento</span>
    </li>
  </ul>
</div>
Ejemplo de la lista

Y eso es todo por ahora, para obtener el ejemplo desde github:

$ git clone https://github.com/feardarkness/HotelReservationAppFE.git
$ git checkout tags/templating

And thats it, keep coding >_<

lunes, 23 de enero de 2017

El ciclo de vida de un componente en Aurelia

Continuando con la serie de posts acerca del framework Aurelia para el lado del cliente, me parece pertinente, antes de pasar a temas más complejos, conocer el ciclo de vida de un componente en Aurelia.

Desde el momento en que una vista comienza a cargar hasta el instante en que la aplicación es visible para el cliente, se llama a una serie de callbacks en un orden determinad, hecho que es importante conocer para aplicar cambios dinamicamente en la pantalla en el momento en que realmente es posible hacerlo. Por ejemplo, si se quiere buscar un elemento con jquery por el id, el elemento debe encontrarse en el DOM actualmente.

Se debe tomar en cuenta que no solamente los componentes pasan por este ciclo de vida, sino también los elementos personalizados (custom elements) e incluso cualquier modelo (view-model) generado, además, no es necesario implementarlos todos, sino solamente los que se necesiten.

Los callbacks y el orden en que se llaman son:
  1. constructor (): el contructor, que es el sitio principal donde se obtienen los elementos inyectados.
  2. created (vistaPadre, vistaActual): que se llama en cuento se crean la vista (view) y el modelo (view-model), recibe como primer parámetro la vista en la cual se declara el componente.
  3. bind (contexto, contextoSobrecargado): que recibe dos parámetros, siendo el primero el más importante, y es el contexto actual al cuál nos enlazaremos.
  4. attached(): que se invoca cuando el elemento se liga al DOM, para ponerlo en contexto para los que utilizarón Jquery alguna vez, es el momento en que se llama el método $().ready(). Este es el momento ideal para inicializar los plugins necesarios.
  5. dettached(): este método es lanzado cuando el elemento se quitará del DOM. Es el inverso de attached.
  6. unbind(): una vez que el elemento se ha quitado del DOM, también debe eliminarse del contexto actual.
Ahora, vamos a probar que esto funcione realmente y que no se las estoy charlando :D

Continuaremos con la base de proyecto anterior.

Solo para recordar, se puede descargar de github:

$ git clone https://github.com/feardarkness/HotelReservationAppFE.git
$ git checkout tags/creando-elemento

Para probar que esto funciona, modificaremos los archivo src/app.js, src/app.html y src/resources/elements/bootstrap-input.js, de la siguiente manera:

Al archivo app.js, donde agregaremos un logger para imprimir un mensaje cuando el callback se haya ejecutado. Llamaremos al logger app-js, esto no es muy distinto de poner un console.log, excepto que es posible mostrar los mensajes dinamicamente cuando lo necesitemos, lo que es muy útil para realizar un debug de la aplicación.

import {bindable, bindingMode, LogManager} from 'aurelia-framework';

export class BootstrapInput {
  @bindable({ defaultBindingMode: bindingMode.twoWay }) texto;

  constructor() {
    this.logger = LogManager.getLogger('app-js');
    this.message = 'Hello World!';
    this.logger.info('CONSTRUCTOR iniciado');
  }

  bind(contexto, contextoSobrecargado) {
    this.logger.info('BIND iniciado');
  }

  attached() {
    this.logger.info('ATTACHED iniciado');
  }

  unbind() {
    this.logger.info('UNBIND iniciado');
  }

  detached() {
    this.logger.info('DETACHED iniciado');
  }
}



El archivo app.html, donde haremos varias cosas, primero agregamos al primer input la opción de realizar el bind del value con un atributo del modelo con la propiedad value.bind="nombres" (o sea que cuando cambie el valor del input esto se reflejara en el modelo en el atributo nombres y viceversa). El input quedará de la siguiente manera:

<input type="text" class="form-control" id="nombres" 
                     placeholder="Nombres" value.bind="nombres">

Además, en el mismo archivo (app.html) agregamos la opción de mostrar u ocultar el elemento bootstrap-input con la propiedad if.bind="nombres ==='mostrar'? true: false". Esta sentencia simplemente indica que si el valor de el atributo nombres en el modelo (app.js) tiene el valor mostrar, el elemento debe mostrarse, de lo contrario el elemento no se muestra.

Nuestro elemento queda de la siguiente manera:

<bootstrap-input texto.bind="message"
         if.bind="nombres ==='mostrar'? true: false"></bootstrap-input>

En aurelia, existe dos maneras de ocultar o mostrar un elemento, una con if.bind que oculta el elemento evitando que se dibuje incluso como html en la página (lo quita por completo del DOM), otra manera es con show.bind, lo que simplemente agrega una clase para evitar que el elemento se vea, aunque el elemento sigue cargado en el html.

Veamos pues cómo funciona:

Prueba de llamada de callbacks del ciclo de vida de un elemento en Aurelia
Se puede observar que el órden en que se llaman los callbacks es constructor - bind - attached - detached - unbind,constructor, bind y attached al crearse el elemento y detached y unbind al momento de eliminarse. Además, si se ve detalladamente los mensajes, se puede ver que el constructor solamente se llama la primera vez, esto es porque Aurelia se encarga de que cada elemento creado sea instanciado una sola vez.

Y eso es todo por ahora, para bajar una versión del proyecto hasta este punto puedes hacer:

$ git clone https://github.com/feardarkness/HotelReservationAppFE.git
$ git checkout tags/lifecycle

Keep coding!!! >_<