Continuando con la Introducción a Flex y la Integración de Flex con Spring, en esta nota veremos cómo funciona el intercambio de objetos compuestos entre Flex y Java en el servidor, la utilización de formularios con validaciones y cómo mostrar la información en una grilla y en un gráfico (chart).
La aplicación
Realizaremos una aplicación Flex/Java que nos devolverá el pronóstico del clima para una ciudad y nos mostrará las temperaturas en un gráfico de líneas. Para poder seguir estos pasos se recomienda leer las notas anteriores: Introducción a Flex y Integrando Flex con Spring.
Los componentes de la aplicación serán los siguientes:
- DTOs (Data Transfer Objects) que se intercambiarán entre la aplicación Flex y el servicio Java, autogenerados con gas3.
- Modelo de negocio Java, configurado con Spring y accesible por medio de un servicio publicado con BlazeDS.
- Formulario Flex de Ingreso de datos.
- Validadores de los campos del formulario.
- Grilla que mostrará los resultados obtenidos del servicio.
- Chart de tipo gráfico de líneas para mostrar los resultados del servicio.
Servicios Java en el Servidor
Los DTO de intercambio Flex / Java
Crearemos los DTOs que recibirá el servicio Java y con los que se responderá al cliente Flex. Estos son POJOs comunes que contienen los datos del dominio.
- ConsultaPronosticoDto: información que enviará el cliente Flex para pedir un pronóstico.
- PronosticoDto: resultado del servicio, incluye una lista de TemperaturaDto con las temperaturas pedidas.
- TemperaturaDto: pronóstico de temperatura de un instante.
ConsultaPronosticoDto.java
package com.dosideas.flex.demo2.dto; import java.text.SimpleDateFormat; import java.util.Date; public class ConsultaPronosticoDto { private String ciudad; private Date fecha; private Integer horaDesde; private Integer horaHasta; //getters y setters... }
PronosticoDto.java
package com.dosideas.flex.demo2.dto; import java.util.Date; import java.util.List; public class PronosticoDto { private String ciudad; private Date fecha; private List<TemperaturaDto> temperaturas; //getters y setters... }
TemperaturaDto.java
package com.dosideas.flex.demo2.dto; public class TemperaturaDto { private Integer hora; private Integer temperatura; public Integer getHora() { return hora; } public TemperaturaDto(Integer hora, Integer temperatura) { super(); this.hora = hora; this.temperatura = temperatura; } //getters y setters... }
Del lado del cliente Flex, deben generarse los mismos DTOs pero en lenguaje Action Script (as3). Para no crearlos uno por uno, copiando los atributos y las relaciones, vamos a utilizar el generador automático Gas3, que genera código as3 a partir de clases Java. Se puede ver cómo se utiliza en la nota Generando clases as3 con Gas3.
Modelo de Negocio y Servicio BlazeDS
Las clases que resolverán el servicio son ClimaBoImpl.java (y su interfaz ClimaBo.java) y el servicio Flex ClimaFlexService.java.
ClimaBo.java
package com.dosideas.flex.demo2.business; import com.dosideas.flex.demo2.dto.ConsultaPronosticoDto; import com.dosideas.flex.demo2.dto.PronosticoDto; public interface ClimaBo { public PronosticoDto obtenerPronostico(ConsultaPronosticoDto consulta); }
ClimaBoImpl.java
package com.dosideas.flex.demo2.business.impl; import java.util.ArrayList; import java.util.List; import com.dosideas.flex.demo2.business.ClimaBo; import com.dosideas.flex.demo2.dto.ConsultaPronosticoDto; import com.dosideas.flex.demo2.dto.PronosticoDto; import com.dosideas.flex.demo2.dto.TemperaturaDto; public class ClimaBoImpl implements ClimaBo { public PronosticoDto obtenerPronostico( ConsultaPronosticoDto consultaPronostico) { System.out.println("Recibido: \n"); System.out.println(consultaPronostico); //creo el resultado del pronostico PronosticoDto resultado = new PronosticoDto(); resultado.setCiudad(consultaPronostico.getCiudad()); resultado.setFecha(consultaPronostico.getFecha()); resultado.setTemperaturas(getTemperaturas(consultaPronostico)); return resultado; } private List<TemperaturaDto> getTemperaturas(ConsultaPronosticoDto consulta) { //Temperatura base. List<TemperaturaDto> temps = new ArrayList<TemperaturaDto>(); //cada cuantas horas se mostrará la temperatura. int step = 1; //valor de la temperatura anterior/inicial. int valorAnterior = 11; for (int i = consulta.getHoraDesde(); i <= consulta.getHoraHasta() ; i+=step){ int valorActual = getTempRandom(valorAnterior); TemperaturaDto t = new TemperaturaDto(i,valorActual); temps.add(t); valorAnterior = valorActual; } return temps; } /** * La temperatura la obtiene de manera random. */ private Integer getTempRandom(int valorAnterior) { //maxima variacion por step int VAR = 2; // Valor entre M y N, ambos incluidos. int valorEntero = (int) Math.floor(Math.random()*(-VAR-VAR+1)+VAR); return valorEntero + valorAnterior; } }
ClimaFlexService.java
package com.dosideas.flex.demo2.service; import com.dosideas.flex.demo2.business.ClimaBo; import com.dosideas.flex.demo2.dto.ConsultaPronosticoDto; import com.dosideas.flex.demo2.dto.PronosticoDto; /** * Servicio que devuelve el clima en Java para llamar desde Flex. */ public class ClimaFlexService implements ClimaBo{ private ClimaBo climaBo; public ClimaBo getClimaBo() { return climaBo; } public void setClimaBo(ClimaBo climaBo) { this.climaBo = climaBo; } public PronosticoDto obtenerPronostico( ConsultaPronosticoDto consultaPronostico) { return climaBo.obtenerPronostico(consultaPronostico); } }
Y la configuración de Spring y BlazeDS quedaría así:
clima-business.xml: ubicado en la raíz de los fuentes, define el bean con la lógica de negocio.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <bean name='business.ClimaBo' class="com.dosideas.flex.demo2.business.impl.ClimaBoImpl"> </bean> </beans>
applicationContext.xml: configuración del servicio, ubicado en la carpeta WEB-INF.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <!-- bean de testing para la aplicacion flex --> <bean name="climaService" class="com.dosideas.flex.demo2.service.ClimaFlexService"> <property name="climaBo" ref="business.ClimaBo" /> </bean> </beans>
web.xml: acá agregamos la configuración de spring:
<listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <!-- Configuracion para el funcionamiento de spring --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml classpath:clima-business.xml </param-value> </context-param>
remoting-config.xml: la configuración de BlazeDS, donde agregamos el servicio. Notar que se utiliza el factory de Spring. Ver la nota <nota blazeDS Spring y Java> para configurarlo.
<destination id="clima-service"> <properties> <factory>spring</factory> <!-- nombre del bean al que se quiere acceder --> <source>climaService</source> </properties> </destination>
La aplicacion Flex
Iremos contruyendo la aplicacion por pasos, hasta llegar al resultado final.
Formulario y sus controles
Crear una aplicación flex, llamarla main.mxml, con este contenido:
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="horizontal" width="803" height="498" horizontalAlign="center" verticalAlign="middle" borderStyle="outset"> <mx:Script> <![CDATA[ import com.dosideas.flex.demo2.dto.PronosticoDto; import com.dosideas.flex.demo2.dto.ConsultaPronosticoDto; import mx.collections.ArrayCollection; import mx.rpc.events.ResultEvent; import mx.rpc.events.FaultEvent; import mx.utils.ObjectUtil; import mx.controls.Alert; import mx.utils.StringUtil; import mx.validators.StringValidator; import mx.validators.Validator; [Bindable] private var ciudades:ArrayCollection = new ArrayCollection([ "Buenos Aires", "Rosario", "Bariloche", "Tandil"]); /** * Valida y envia el formulario. */ public function submitForm():void{ } ]]> </mx:Script> <mx:Panel width="734" height="484" layout="vertical" title="Demo 2 - Flex 3. Intercambio de Dtos, Formularios, Grillas, Charts" borderColor="#A7FAFF" fontFamily="Arial" fontWeight="bold" fontSize="13" horizontalAlign="left" verticalGap="0"> <mx:Spacer height="10"/> <mx:Form width="536" height="161" defaultButton="{buttonConsultar}"> <mx:FormItem label="Ciudad"> <mx:ComboBox id="comboCiudad" dataProvider="{ciudades}" width="199"/> </mx:FormItem> <mx:FormItem label="Fecha"> <mx:DateField id ="dateFieldFecha" width="105"/> </mx:FormItem> <mx:FormItem label="Horario" width="312"> <mx:HBox width="100%"> <mx:FormItem label="Desde"> <mx:TextInput id="textHoraDesde" width="33" text="8"/> </mx:FormItem> <mx:Spacer width="37" height="1"/> <mx:FormItem label="Hasta"> <mx:TextInput id="textHoraHasta" width="33" text="21"/> </mx:FormItem> </mx:HBox> </mx:FormItem> <mx:Button id="buttonConsultar" label="Consultar" /> </mx:Form> </mx:Panel> </mx:Application>
Comentarios
- el codigo entre
<mx:Script> <![CDATA[ ]]> </mx:Script>
se escribe en lenguaje AS3 (Action Script 3). Queda fuera del alcance de esta nota, pero les dejo el link a la referencia del lenguaje as3.
- La variable ciudades está anotada como [Bindable] lo que le permite al control que hace referencia, en este caso el combo box, capturar cambios para refrescar su contenido, en este caso los datos son estáticos, pero bien podrían variar en tiempo de ejecución.
- Notar los id en los controles, no son obligatorios pero sirven para ser referenciados, como en el caso del button "buttonConsultar" que será el botón que se utilizará por defecto si el usuario presiona enter con el foco en algún control del form.
Validadores
Agregaremos estos 3 validadores, antes del elemento <mx:Panel>:
<mx:NumberValidator id="validadorHoraDesde" required="true" source="{textHoraDesde}" property="text" minValue="0" maxValue="{textHoraHasta.text}"/> <mx:NumberValidator id="validadorHoraHasta" required="true" source="{textHoraHasta}" property="text" minValue="{textHoraDesde.text}" maxValue="23"/> <mx:DateValidator id="validadorFecha" required="true" source="{dateFieldFecha}" property="text"/>
Comentarios
Hay muchos tipos de validadores en flex, cada uno con sus propiedades. Para destacar en este caso, ver cómo los validadores de las horas desde y hasta tienen restricciones haciendo referencias entre sí, ya que la "hora desde" no puede ser mayo que la "hora hasta" ni la "hora hasta" puede ser menor que la "hora desde". Asi mismo, el rango debe estar entre 0 y 23 para que sea coherente. El validador de la fecha sólo requiere que la fecha sea ingresada.
Agregamos en la parte del codigo as3, la funcion que se llamará cuando se presione al botón "Consultar" que realizará las validaciones.
/** * Valida y envia el formulario. */ public function submitForm():void{ if (!validarForm()){ Alert.show("Los valores del formulario no son correctos", "Error en los datos ingresados"); } //llamar al servicio aca... } /** * Valida el contenido del formulario */ private function validarForm():Boolean{ var validatorArr:Array = new Array(); validatorArr.push(validadorFecha); validatorArr.push(validadorHoraDesde); validatorArr.push(validadorHoraHasta); var validatorErrorArray:Array = Validator.validateAll(validatorArr);; var isFormValido:Boolean = validatorErrorArray.length == 0; return isFormValido; }
Agregamos también el atributo click="submitForm()" en el control del botón.
Llamada al servicio remoto
Agregamos ahora el RemoteObject para llamar al servicio Java:ObjetoRemoto, ingresar antes de los validadores:
<!-- Objeto remoto que se accede en el servidor.--> <mx:RemoteObject id="ro" destination="clima-service" result="climaResultHandler(event)" fault="climaFaultHandler(event)" endpoint="http://localhost:8080/demo2-DtosFormsGrillasChart/messagebroker/amf" />
Funciones para manejar los resultados:
/** * Manejador del resultado de la llamada al servicio */ private function climaResultHandler(event:ResultEvent):void{ //Alert.show( ObjectUtil.toString(event.result) ); clima = event.result as PronosticoDto; } /** * Manejador del fault que pueda causar la llamada al servicio */ private function climaFaultHandler(event:FaultEvent):void{ Alert.show( ObjectUtil.toString(event.fault) ); }
Función para invocar el servicio usando los parametros ingresados en el formulario:
/** * Llama al servicio para obtener el pronostico de la ciudad elegida */ private function actualizarPronostico():void{ var consultaDto:ConsultaPronosticoDto = new ConsultaPronosticoDto(); consultaDto.ciudad = comboCiudad.selectedLabel; consultaDto.fecha = new Date(dateFieldFecha.text); consultaDto.horaDesde = new Number(textHoraDesde.text); consultaDto.horaHasta = new Number(textHoraHasta.text); ro.obtenerPronostico(consultaDto); }
Modificamos la función submitForm para que luego de pasar la validación, llame a la funcion que invoca al servicio (se agrega el else):
/** * Valida y envia el formulario. */ public function submitForm():void{ if (!validarForm()){ Alert.show("Los valores del formulario no son correctos", "Error en los datos ingresados"); }else{ actualizarPronostico(); } }
Por último, agregamos la variable "clima" bajo la variable "ciudades". En ella guardaremos el resultado de la llamada al servicio.
[Bindable] private var clima: PronosticoDto = null;
Mostrando los resultados
Vamos a mostrar los resultados obtenidos en un label, una grilla y en un chart de tipo grafico de lineas:
Label con el título de los resultados obtenidos:
<mx:Label id="labelTituloResultados" text="{clima.ciudad} - {DateField.dateToString(clima.fecha, 'DD/MM/YYYY')}"/>
La grilla y el chart los acomodamos horizontalmente con un hbox:
<mx:HBox width="678" height="208" horizontalGap="17" paddingLeft="20" paddingTop="0"> <mx:AdvancedDataGrid id="valores" dataProvider="{clima.temperaturas}" designViewDataType="flat" width="317"> <mx:columns> <mx:AdvancedDataGridColumn headerText="Hora" dataField="hora"/> <mx:AdvancedDataGridColumn headerText="Temperatura °" dataField="temperatura"/> </mx:columns> </mx:AdvancedDataGrid> <mx:LineChart id="linechart1" width="201" height="181" dataProvider="{clima.temperaturas}" showDataTips="true"> <mx:horizontalAxis> <mx:CategoryAxis title="Hora" dataProvider="{clima.temperaturas}" categoryField="hora" displayName="Hora" /> </mx:horizontalAxis> <mx:series> <mx:LineSeries displayName="Temperatura" yField="temperatura" form="curve"/> </mx:series> </mx:LineChart> <mx:Legend dataProvider="{linechart1}" width="86" height="86.36363"/> </mx:HBox>
Notar que el atributo dataProvider de ambos es {clima.temperaturas}, haciendo referencia al ArrayList que contiene los valores de la temperatura pedida.
En la grilla se indican las columnas que se tendrán, con sus titulos, y el nombre del atributo del que se obtendrá el valor a mostrar para cada elemento.
En el gráfico se define el eje horizontal, indicando que el atributo "hora" de los elementos de la serie será el que se mide en ese eje.
Las series definen las líneas que se trazarán, en este caso una sola, correspondiente a las temperaturas, las que se ajustarán al eje Y y se mostrará como una curva uniendo todos los puntos del gráfico.
Notar cómo en cada consulta, los valores de los ejes del gráfico se acomodan automáticamente para mostrar los nuevos valores.
Ambos controles tienen muchas propiedades y variantes para configurar que se pueden ver en las páginas de <grillas: link> y <charts: link> de Adobe.
Resumen
En esta nota hemos visto cómo crear una aplicación Flex-Java con BlazeDS, Spring que intercambian DTOs compuestos y con listas para ser mostrados en grillas y en gráficos charts de lineas. Se pueden utilizar otros tipos de graficos y controles; con esta introducción se pretende mostrar las capacidades básicas que ofrecen estas tecnologías para poder comenzar a utilizar cualquiera de ellas.