Comunidad de diseño web y desarrollo en internet online

Cairngorm: Problemas para actualizar la vista

Intro disuasoria o disclaimer


Cairngorm es un framework creado específicamente para Flex inspirado en patrones de J2EE. Fue desarrollado por la agencia Iteration Two, luego adquirida por Adobe quien adoptó el framework como su "solución oficial", dándole soporte desde http://opensource.adobe.com y fomentando su useo entre la comunidad de Flex.

No es el único framework, y se puede nombrar a pureMVC o Fireclay como otras opciones, cada uno con sus bondades y defectos, pero todos orientados a facilitar y ordenar la tarea de construir RIAS siguiendo las mejores prácticas.

Xabi Beumala escribió hace un par de años una introducción en madeinflex donde resume las ideas básicas, aunque el mas actualizado (en inglés) artículo de los creadores del framework ilustra con el ejemplo de una tienda en línea los principales elementos en adobe.com

El problema que voy a abordar implica que conoces Cairngorm y que lo has aplicado alguna vez, sino seguramente conviene que aproveches mejor tu tiempo leyendo primero alguno de los artículos mencionados.

Problema


Al punto: cuando trabajamos con Cairngorm siguiendo la estructura propuesta, nos encontramos frecuentemente que nos falta algún tipo de feedback para poder actualizar la vista. Esto es debido básicamente a que los comandos no deben estar relacionados directamente con la vista, lo cual nos pone en aprietos a la hora de pequeñas respuestas visuales, ya que no todo se puede traducir via databindings. Pero para ilustrar mejor el problema, vamos a plantear un ejemplo

  • La primera pantalla de nuestra aplicación tiene la posibilidad de elegir entre varias operaciones
  • Cada operación necesita una pantalla de confirmación distinta
  • El workflow de la operación es mas o menos este: la vista hace un broadcast del evento que tipifica la operación,
    se dispara un comando que llama una operación en el servidor a través de un Delegator, que devuelve el resultado al comando que lo ha generado, modificando el modelo quye propaga la operación a través de los databindings ... pero la vista no se entera directamente, y no todo es fácilmente manejable via bindings.


Posibles soluciones a esto:


  • Hacer que el comando hable directamente a la vista ... esto rompe la encapsulación, porque el comando no debería saber nada de la vista
  • Usar algún flag en el modelo que relacionemos con la vista ... esto fuerza a guardar todo tipo de elementos en el modelo, por ejemplo el selectedIndex de un combobox o un string relacionado con el estaddo (currentState) de alguna pantalla
  • Una clase ViewHelper que trabaje un poco como el modelo pero para las vistas (una especie de event-proxy), indexando posibles vistas y lanzando eventos que los comandos puedan escuchar ... esto ya existía, pero fue quitado en la versión 2.1, se puede ver algo de esta discusión en http://nwebb.co.uk/blog/?p=84
  • El evento agrega una propiedad "callback" al evento, que luego puede ser invocada desde el result handler del comando ...
  • El evento agrega un responder adicional que le es pasado por la vista al generar el evento, esto permite que la vista misma sea agregada a la cadena de responders (por defecto el Delegate del framework postula al comando mismo como responder)


Esta última soluciónm, explicada en [url= http://www.thomasburleson.biz/2007/06/cairngorm_view_notifications.html]www.thomasburleson.biz[/url] suena bastante bien y es la que elegí aplicar en mi último proyecto.

Solución aplicada


Se trata de una aplicación del estilo ABM (Alta-Baja-Modificación), típico CMS de teléfonos móviles para una compañía de telefonía celular. En la pantalla donde se agrega un nuevo modelo, necesito mostrar un mensaje de confirmación y volver a la pantalla inicial en donde se pueden seleccionar todas las opciones. Aquí 3 screenshots que muestran lo que necesito:

Pantalla donde se agrega el modelo




El mensaje de confirmación




la pantalla de inicio




Como es usual en cualquier estructura de Cairngorm, tengo mis piezas en su sitio:

Event


- Un evento especializado que expone el modelo agregado a través de la operación ADD_MODEL en este caso (nótese que reuse el mismo evento para las cuatro operaciones:read-insert-modify-delete)

Código :

package com.hcd.events
{
import com.adobe.cairngorm.control.CairngormEvent;
import com.hcd.vo.MarcaVO;
import com.hcd.vo.ModeloVO;

import flash.events.Event;

import mx.rpc.IResponder;

public class ModeloEvent extends CairngormEvent
{
public static var ADD_MODELO : String = "addModelo";
public static var EDIT_MODELO : String = "editModelo";
public static var DELETE_MODELO : String = "deleteModelo";
public static var GET_MODELO : String = "getModelo";

public var modelo : ModeloVO;
public var marca: MarcaVO
public var responder:IResponder

/**
* Constructor.
*/
public function ModeloEvent(type:String, res:IResponder = null)
{
super(type);
responder = res
}

/**
* Override the inherited clone() method, but don't return any state.
*/
override public function clone() : Event
{
return new ModeloEvent(type, responder);
}
}

}


En el evento he agregado un responder opcional que injecto al hacer la llamada cuando necesito. Luego este evento es mapeado al comando adecuado mediante el controlador

Controlador


Código :

package com.hcd.controller
{
import com.adobe.cairngorm.control.FrontController;
import com.hcd.events.*
import com.hcd.commands.*

public class MovilController extends FrontController
{
public function MovilController()
{
initialiseCommands();
}
public function initialiseCommands() : void
{
addCommand( MarcaEvent.ADD_MARCA, AddMarca );
addCommand( MarcaEvent.EDIT_MARCA, EditMarca );
addCommand( MarcaEvent.DELETE_MARCA, DeleteMarca );
addCommand( MarcaEvent.GET_MARCA, GetMarcas );
addCommand( ModeloEvent.ADD_MODELO, AddModelo );
...(continue)...
}

}
}

Luego el comando que recibe el responder adicional y se lo pasa al Delegator

Delegator


Código :

package com.hcd.commands
{
import com.adobe.cairngorm.commands.ICommand;
import com.adobe.cairngorm.control.CairngormEvent;
import com.adobe.cairngorm.control.CairngormEventDispatcher;
import com.hcd.business.MovilDelegate;
import com.hcd.events.ModeloEvent;

import mx.controls.Alert;
import mx.rpc.IResponder;
import mx.rpc.events.FaultEvent;

public class AddModelo implements ICommand, IResponder
{
private var callback:IResponder

public function AddModelo()
{
}
public function execute( event : CairngormEvent ): void
{
var evt:ModeloEvent = event as ModeloEvent
callback = evt.responder
var delegate:MovilDelegate = new MovilDelegate(this)
delegate.addModelo(evt.modelo, callback);
}
public function result( event: Object ) : void
{
Alert.show(event.result)
//refresca modelos
CairngormEventDispatcher.getInstance().dispatchEvent( new CairngormEvent( ModeloEvent.GET_MODELO) );
}

public function fault( event : Object ) : void
{
var faultEvent : FaultEvent = FaultEvent( event );
Alert.show( "¡Falla al insertar el modelo!");
}
}
}

Nótese que los comandos generan directamente el mensaje de éxito (o error) a través de un alert. Dado que el Alert puede ser lanzado estáticamente independientemente de la vista en que se esté, parece una buena opción para generar algún feedback sin necesariamente meterse con detalles de la vista.

La última pieza, el Delegate, agrega el responder a la cadena

Delegate


Código :

package com.hcd.business
{
import com.adobe.cairngorm.business.ServiceLocator;
import com.hcd.vo.MarcaVO;
import com.hcd.vo.ModeloVO;

import mx.rpc.IResponder;



/**
* @version $Revision: $
*/
public class MovilDelegate
{
private var responder : IResponder;
private var service : Object;

public function MovilDelegate( responder : IResponder )
{
this.service = ServiceLocator.getInstance().getRemoteObject( "movilServices" );
this.responder = responder;
}
... (other methods not showed) ...
public function addModelo(mod:ModeloVO, callback:IResponder):void{
var call : Object = service.addModelo(mod);
call.addResponder( responder );
call.addResponder( callback );
}
}

}


Esta llamada agrega el comando como responder, pero también el responder adicional que fue enviado desde la vista. Así el onresult del comando es invocado, pero también el callback enviado desde la vista. He aquí la implementación en el MXML

Llamada desde el MXML


Código :

 private function addModelo(evt:Event):void{
var resp:IResponder = new mx.rpc.Responder(onResults,onFault);
var event:ModeloEvent= new ModeloEvent(ModeloEvent.ADD_MODELO, resp)
model.selectedModel.marca_id = model.selectedMarca.marca_id
model.selectedModel.visible = "S"
event.modelo = model.selectedModel
CairngormEventDispatcher.getInstance().dispatchEvent(event);
}
private function onResults(res:Object):void{
this.currentState = ""
ViewStack(parent).selectedIndex = 0
}
private function onFault(evt:FaultEvent):void{
//
}


Esto es parte del código del MXML donde se agrega el modelo, El callback onResults resetea el currentState de la vista y mueve el viewstack contenedor a la pantalla principal, cumpliendo con el feedback visual sin involucrar al comando directamente. El onFault está vacío porque en este caso solo muestro el Alert de error (lo hace el comando) y no muevo la pantalla para permitir las correcciones pertinentes.

Síntesis


Se ha explicado como lograr que la vista reciba el resultado de la ejecución de un comando que accede a recursos remotos sin comprometer la estructura del framework creando relaciones directas entre los comandos y la vista, manteniendo el desacople necesario que permita la implementación rápida de callbacks visuales. Habiéndose descartado los ViewHelpers en la versión 2.1, una solución similar a esta posiblemente sea incorporada al core del framework en un futuro próximo. Estaremos atento a ello

Jorge

¿Sabes SQL? ¿No-SQL? Aprende MySQL, PostgreSQL, MongoDB, Redis y más con el Curso Profesional de Bases de Datos que empieza el martes, en vivo.

Publica tu comentario

o puedes...

¿Estás registrado en Cristalab y quieres
publicar tu URL y avatar?

¿No estás registrado aún pero quieres hacerlo antes de publicar tu comentario?

Registrate