Desarrollo de UI con componentes: Arena. Navegación. Estado de la vista.

Nos queda pendiente trabajar sobre dos temas que son comunes al diseñar UIs:
  1. Navegación (cómo es el flujo de una vista a otra y cómo se pasa información desde una vista a otra)
  2. Manejo del estado conversacional de los datos que se cargan en la vista

Pasaje de información entre paneles y navegación

Esto se da en dos oportunidades en nuestro ABM modelo:
  • cuando queremos crear un nuevo socio/celular/etc.
  • cuando queremos editar la información de un socio/celular/etc. existente
En el primero de los casos, la ventana de búsqueda no necesita pasar ninguna información a la pantalla de edición. Generamos un nuevo socio desde cero y luego se lo pasamos al repositorio o home que se encarga de agregarlo al repositorio. Entonces, ¿cómo me aseguro que el panel de búsqueda va a encontrar el socio/celular recientemente cargado?
  • si el repositorio trabaja con una base de objetos que es un archivo externo, este archivo sirve como sincronizador de las dos pantallas
  • si el repositorio o home trabaja con una colección en memoria, no podemos dejar que se creen múltiples homes. Esto quiere decir: si el panel de búsqueda trabaja con un home, y la pantalla de edición crea otro home diferente, esto va a traer problemas cuando volvamos a la búsqueda, porque no vamos a encontrar el socio/celular que acabamos de crear. Entonces el home lo creamos como Singleton, dado que al vivir ambas vistas (búsqueda y edición) en el mismo ambiente donde corre la aplicación, nos aseguramos un único punto de acceso. Claro, el Singleton tiene una desventaja: se parece mucho a una variable global. Por eso no es bueno abusar de singletons en nuestras aplicaciones. 
Para editar una entidad sí necesitamos pasar la información de esa entidad del panel de búsqueda al de edición.

Y además tenemos que ver cómo nos encargamos de navegar de una pantalla a otra. Entonces:
  • Hay que abrir una ventana de diálogo: a partir de aquí el control pasa al formulario de edición (porque es una ventana modal) 
  • Y setearle el modelo a la pantalla de edición
    • en el caso del alta, es una nueva entidad
    • en el caso de la edición, hay que pasarle el elemento seleccionado de la grilla (es la propiedad selected del application model del panel de búsqueda, está definido en Search<T>)
Esto lo vemos implementado en el ejemplo de los celulares:

// Xtend
def void crearCelular() {
  this.openDialog(new CrearCelularWindow(this))
}
def void modificarCelular() {
  this.openDialog(new EditarCelularWindow(this, modelObject.celularSeleccionado))
}
def openDialog(Dialog<?> dialog) {
  dialog.onAccept[ | modelObject.search ]
  dialog.open
}

// Java
public void crearCelular() {
  this.openDialog(new CrearCelularWindow(this));
}
public void modificarCelular() {
  this.openDialog(new EditarCelularWindow(this, this.getModelObject().getCelularSeleccionado()));
}
protected void openDialog(Dialog<?> dialog) {
    dialog.onAccept(getModelObject()::search);
    dialog.open();
}

Entonces, ¿cómo manejamos la navegación? Hacemos el new de la vista y le enviamos el mensaje open. Tan sencillo como eso.

¿Y cómo pasamos información de una pantalla a otra? Para inyectar dependencias hay dos formas sencillas de hacerlo
  • en el constructor (constructor injection)
  • a partir de un setter (setter injection)
Lo que le pasamos al constructor de la pantalla de edición es el modelo. Esto permite que se asigne como model y todos contentos.
Resumiendo:
  1. se abre la ventana de edición con un objeto nuevo o existente
  2. el usuario edita el objeto
  3. y presiona el botón aceptar (esto actualiza la información)
  4. entonces se dispara una nueva búsqueda (línea onAccept)
  5. que eventualmente envía una notificación a la grilla para que se actualice.
Esta es una forma declarativa de trabajar:
  • tenemos menos control sobre el algoritmo (no nos enteramos de lo que pasa por abajo, pasamos por alto muchos detalles de implementación)
  • necesitamos para eso que alguien resuelva ese algoritmo (en nuestro caso es el framework Arena el que actúa de motor, el que hace la magia)
  • nos concentramos más en el qué y no tanto en cómo lograrlo. 
Cuando enviamos el mensaje editor.open() estamos interrumpiendo el flujo de ejecución de la pantalla principal. Entonces toma el control la pantalla de edición que vimos en la primera clase.

Nos detenemos una vez más en la línea

// Xtend
dialog.onAccept [ | modelObject.search ]

// Java
dialog.onAccept(getModelObject()::search);

porque plantea algo muy interesante: antes de llamar a la pantalla de edición (mucho antes de que el usuario vea esa pantalla y presione Aceptar) estamos guardando un comportamiento que tiene que ocurrir cuando esa edición termine exitosamente. Esa idea (diferir la ejecución de código) está presente en el Command Pattern [1], y la estamos implementando de esta manera.

Cancelar la transacción

Recordemos que cuando abrimos una pantalla de edición, necesitamos como modelo un socio, un celular, una entidad. Entonces, si el usuario se arrepiente y cancela la acción, la transacción no debe completarse. Una de las maneras de lograr esto es no enviar ningún mensaje al repositorio para que lo actualice. Es importante entender que cuando hacemos un new del objeto de dominio eso no tenga efecto colateral sobre el home, de otra manera debemos deshacer ese new o la modificación propiamente dicha.

Estado de la vista

El usuario mantiene una conversación con el sistema, hasta ahora hemos visto ejemplos de pantallas más bien simples. En una pantalla de carga compleja, podríamos tener un wizard/asistente que nos vaya permitiendo cargar la información en varios pasos: varias ventanas diferentes o varios containers diferentes embebidos en la misma vista. Pensemos en la pantalla de alquiler de una película en el videoclub, que incluye una búsqueda avanzada de títulos, la cantidad de días que queremos tener en alquiler esa película, cómo vamos a pagar, etc.

Esto es lo que llamamos "estado conversacional", toda la información que forma parte de un requerimiento o "caso de uso".
¿Cómo manejamos ese estado?
  • Como la vista es una clase bien podríamos tener atributos private
  • Pero vimos que la vista no tiene n atributos sino que los agrupa por lo general en un model (que puede ser application model o domain model). Si la vista es simple, basta con un objeto de dominio. Si la vista es más compleja, usamos un modelo de vista o application model, que nos sirve para manejar ese estado: estamos representando un objeto que modela un caso de uso...
Como vista y modelo pertenecen a la misma tecnología (ambas las modelo con objetos) vemos que tener estado es algo simple y natural. Más adelante veremos que no siempre resulta así.