memeplastika

concretando la dispersión

Widget que muestra el tiempo con Actionscript 3 y MVC

con 4 comentarios

En este tutorial voy a crear una pequeña aplicación que nos muestra el clima actualizado de una ciudad de nuestra elección, realizado con Actionscript 3 y con el patrón de diseño MVC (Model-View-Controller).
widget weather.com as3
El aspecto final será algo así, un pequeño widget muy básico, pero que os puede servir para desarrollar y ampliar funcionalidades extra.

Un ejemplo funcional : http://www.plastikaweb.com/weather/weather.html

Los archivos de ejemplo los podéis descargar desde aquí: weather_mvc_plastikaweb.zip

Este widget se puede compilar como un proyector exe o como una aplicación Adobe AIR desde Adobe Flash y os funcionará correctamente como una aplicación de escritorio. Si por el contrario ejecutáis el compilado swf desde el escritorio producirá un error de seguridad Sandbox.
Igualmente si lo ejecutáis desde un servidor, la llamada al XML generado desde el servidor de weather.com os generará un error de seguridad. Esto que puede parecer un engorro, se debe a una directiva de seguridad típica en aplicaciones frontend como es Flash. Utilizando un un fichero de servidor intermedio que realize la petición al servidor de datos  se puede en cierta manera burlar.

De momento y para centrarnos en la estructura y el funcionamiento de la aplicación, obviaré estas consideraciones sobre seguridad en Flash Player, y realizaré la petición de datos directamente a weather.com.

1.Registro en weather.com.

Los datos los recogemos de www.weather.com mediante XML, para lo cual necesitamos crear una cuenta de usuario desde https://registration.weather.com/ursa/profile.  Una vez completado el proceso recibimos en nuestra cuenta de correo nuestro ID Partner, el License Key, la url para descargar el sdk con información ampliada  y ejemplos de como configurar una llamada al XML que nos ofrece el servicio.

La url que debemos configurar y que generará el XML con los datos del clima de la localización seleccionada será algo así:

http://xoap.weather.com/weather/local/30339?cc=*&dayf=5&link=xoap&prod=xoap&par=[PartnerID]&key=[LicenseKey]

donde PartnerID es tu Partner ID y License Key tu número único de licencia que te han enviado vía email. En este caso 30339 es el código local para Atlanta, puedes buscar el id para otras zonas desde weather.com extrayendo el id de la url al realizar una búsqueda.

En mi caso para buscar el tiempo de Barcelona, la url seria :

http://xoap.weather.com/weather/local/SPXX0015?cc=*&dayf=1&link=xoap&prod=xoap&par=1126782060&key=7d908860312f34ae&unit=m
el último parámetro “unit” nos permite elegir el sistema métrico para los datos y mostrar así por ejemplo la temperatura en ºCelsius.

Ejemplo de ID local para Barcelona

Ejemplo de ID local para Barcelona

2.Archivos y estructura de la aplicación.

estructura weatger widgetLa aplicación la compilaremos en la herramienta de autoría Flash de Adobe, correspondiendo un archivo .fla vacío con el nombre de Document Class de Weather.

Dentro del paquete com.plastikaweb.controller tenenos la clase WeatherController que será la parte de control del patrón.
Dentro de com.plastikaweb.model WeatherModel, correspondiente al modelo.
Dentro de com.plastikaweb.view tres clases correspondientes a los tres elementos que mostraremos, DataWeatherView (campo con una frase y varios datos en ella, ciudad, temperatura y humedad), IconWeatherView (pictograma correspondiente al clima) y TempWeatherView (campo con la temperatura en tamaño más grande).

La carpeta icons contiene todos los ficheros png que muestran el pictograma del clima, tienen una numeración predeterminada que va de 0.png a 47.png. Con el sdk de weather.com se entrega una colección de los mismos en dos formatos, grande y pequeño.
Para cambiar los iconos se pueden crear una nueva colección o descargar una desde por ejemplo http://liquidweather.net/icons.php#iconsets.

3.Model Class – WeatherModel.as.

Destacar la función load(), declarada como pública ya que será la que desde el controlador sea llamada cada vez que queramos actualizar los datos, el evento “update” que generamos en onComplete() cada vez recibimos los datos del servidor y que servirá para actualizar las vistas que estarán escuchando dicho evento, y los tres getters que simplemente retornan datos personalizados para actualizar las vistas.

package com.plastikaweb.model {

	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.net.URLLoader;
	import flash.net.URLRequest;

	public class WeatherModel extends EventDispatcher{

		public static const UPDATE:String = "update";//constante que utilizaremos como descripción de evento personalizado
		private var _weather_xml_url:String;
		private var _weather_loader:URLLoader;
		private var _data:XML;

		public function WeatherModel(url:String) {
			_weather_xml_url = url;
			init();
		}

		private function init():void {
			_weather_loader = new URLLoader();
			_weather_loader.addEventListener(Event.COMPLETE, onComplete);
		}

		public function load():void {
			try {
				_weather_loader.load(new URLRequest(_weather_xml_url));
			}catch (error:SecurityError) {
				trace("Error de seguridad al intentar abrir XML " + Error);
			}
		}

		private function onComplete(e:Event):void {
			_data = new XML(_weather_loader.data);
			dispatchEvent(new Event(WeatherModel.UPDATE));//generamos evento al completar con éxito la carga de datos
		}

		public function get temp():String {
			return _data.cc.tmp.toString() + "º";//datos Tª
		}

		public function get extendedData():String {
			var city:String = _data.loc.dnam;
			var temp:String = _data.cc.tmp;
			var hmid:String = _data.cc.hmid;

			var desc:String = "Tiempo para "+city+"\nLa temperatura es de "+temp+"ºC y la humedad relativa del aire es del "+hmid+"%";
			return desc;//datos detalle ciudad + Tª + Humedad relativa
		}

		public function get icon():String {
			var iconName:String = _data.cc.icon.toString();
			iconName += ".png";
			return iconName;//datos con nombre del pictograma a mostrar
		}
	}
}

4.Controller Class – WeatherController.as.

Sólo destacar el método público update() que llama a load() de la clase WeatherModel.

package com.plastikaweb.controller {

	import flash.display.Sprite;
	import flash.events.TimerEvent;
	import com.plastikaweb.model.WeatherModel;

	public class WeatherController extends Sprite {

		private var _model:WeatherModel;

		public function WeatherController(m:WeatherModel) {
			_model = m;
		}

		public function update(e:TimerEvent = null):void {
			//actualización de los datos
			_model.load();
		}
	}
}

5.View Class – DataWeatherView.as, IconWeatherView.as, TempWeatherView.as.

He creado tres clases diferentes con diferentes formatos para mostrar los datos. Se podría crear una, o una clase pseudo-abstracta y que todas heredasen de esta, o que extendiese desde una Interface. Por simplicidad en en ejemplo he elegido esta opción.
No necesitan mucha explicación, cada una de ellas genera elementos visuales, ya sea un TextField con formato o  un Loader para cargar los pictogramas y los ubica en el escenario. Se les asigna un listener para actualizar la vista cuando los datos cambien en el modelo.

5a-DataWeatherView.as

package com.plastikaweb.view {

	import flash.display.Sprite;
	import flash.events.Event;
	import com.plastikaweb.model.WeatherModel;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.text.TextFieldAutoSize;

	public class DataWeatherView extends Sprite{

		private var _model:WeatherModel;
		private var _data_txt:TextField;

		public function DataWeatherView(m:WeatherModel) {
			var formatTemp:TextFormat = new TextFormat();
			formatTemp.color = 0x999999;
			formatTemp.font = "_sans";
			formatTemp.size = 12;

			_data_txt = new TextField();
			_data_txt.defaultTextFormat = formatTemp;
			_data_txt.multiline = true;
			_data_txt.wordWrap = true;
			_data_txt.autoSize = TextFieldAutoSize.LEFT;
			_data_txt.width = 190;
			_data_txt.x = 10;
			_data_txt.y = 120;
			addChild(_data_txt);

			_model = m;
			_model.addEventListener(WeatherModel.UPDATE, onUpdateModel);
		}

		private function onUpdateModel(e:Event):void {
			if (_data_txt != null) {
				_data_txt.text = _model.extendedData;
			}
		}
	}
}

5b-IconWeatherView.as

package com.plastikaweb.view {

	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Loader;
	import flash.display.Sprite;
	import com.plastikaweb.model.WeatherModel;
	import flash.events.Event;
	import flash.net.URLRequest;
	import flash.events.IOErrorEvent;

	public class IconWeatherView extends Sprite {

		private var _model:WeatherModel;
		private var _loader:Loader;
		private var _iconName:String;

		public function IconWeatherView(m:WeatherModel) {
			_model = m;
			_model.addEventListener(WeatherModel.UPDATE, onUpdateModel);
			_loader = new Loader();
			_loader.x = 70;
			_loader.y = 10;
			addChild(_loader);
		}

		private function onUpdateModel(e:Event):void {
			//actualizamos el icono sólo si ha cambiado desde la última llamada del controlador
			if (_iconName != _model.icon) {
				_iconName = _model.icon;
				_loader.addEventListener(Event.COMPLETE, onLoaderComplete);
				_loader.addEventListener(IOErrorEvent.IO_ERROR, onLoaderError);
				_loader.unload();
				_loader.load(new URLRequest("icons/" + _iconName));
			}
		}

		private function onLoaderComplete(e:Event):void {
			_loader.removeEventListener(Event.COMPLETE, onLoaderComplete);
		}

		private function onLoaderError(e:IOErrorEvent):void {
			trace("error en la carga del icono");
		}
	}
}

5c-TempWeatherView.as

package com.plastikaweb.view {
	import flash.display.Sprite;
	import flash.events.Event;
	import com.plastikaweb.model.WeatherModel;
	import flash.text.TextField;
	import flash.text.TextFormat;

	public class TempWeatherView extends Sprite{

		private var _model:WeatherModel;
		private var _temp_txt:TextField;

		public function TempWeatherView(m:WeatherModel) {
			var formatTemp:TextFormat = new TextFormat();
			formatTemp.bold = true;
			formatTemp.color = 0xdddddd;
			formatTemp.font = "_sans";
			formatTemp.size = 50;
			_temp_txt = new TextField();
			_temp_txt.x = 10;
			_temp_txt.y = 40;
			_temp_txt.defaultTextFormat = formatTemp;
			addChild(_temp_txt);

			_model = m;
			_model.addEventListener(WeatherModel.UPDATE, onUpdateModel);

		}

		private function onUpdateModel(e:Event):void {
			if (_temp_txt != null) {
				_temp_txt.text = _model.temp;
			}
		}
	}
}

6.Document Class – Weather.as.

Por fin la clase vinculada a nuestro fichero .fla.

package {

	import flash.display.Sprite;
	import flash.utils.Timer;
	import flash.events.TimerEvent;
	import com.plastikaweb.model.WeatherModel;
	import com.plastikaweb.controller.WeatherController;
	import com.plastikaweb.view.TempWeatherView;
	import com.plastikaweb.view.DataWeatherView;
	import com.plastikaweb.view.IconWeatherView;

	public class Weather extends Sprite{

		public function Weather() {
			//modelo
			//parametro
			//url que genera el XML con la información sobre el tiempo de la localización elegida
			//SPXX0209 en este caso es el ID local para Barcelona
			//parametros importantes:
			//par - id individual de nuestra cuenta en weather.com
			//key - clave individual de nuestra cuenta en weather.com
			//unit - valor m para sistema métrico
			var model:WeatherModel = new WeatherModel("http://xoap.weather.com/weather/local/SPXX0015?cc=*&dayf=5&link=xoap&prod=xoap&par=1126782060&key=7d908860312f34ae&unit=m");
			//controlador
			var controller:WeatherController = new WeatherController(model);
			//vista de temperatura
			var tempview:TempWeatherView = new TempWeatherView(model);
			//vista del icono
			var iconview:IconWeatherView = new IconWeatherView(model);
			//vista de datos ampliados
			var dataview:DataWeatherView = new DataWeatherView(model);

			addChild(tempview);
			addChild(iconview);
			addChild(dataview);

			//primera llamada al servidor de datos
			controller.update(null);

			//intervalo de actualización de datos (1 minuto en este caso)
			var timer:Timer = new Timer(60000);
			timer.addEventListener(TimerEvent.TIMER, controller.update);
			timer.start();
		}
	}
}

Anexo

Lo dicho, si compiláis el fla y ejecutáis el swf en local veréis que no funciona. Si en las opciones de compilación de Flash activáis la opción de crear proyector para Windows o Mac, o creáis una aplicación AIR si que funcionará como aplicación de escritorio.

Un truco para ejecutarlo en servidor o en local desde un swf sin lidiar con los problemas de seguridad de Flash Player es crear un archivo de servidor (PHP, .NET, etc) que recoja el XML servido desde weather.com, por ejemplo en PHP:

proxy.php

<?php
	$dataURL = "http://xoap.weather.com/weather/local/SPXX0015?cc=*&dayf=5&link=xoap&prod=xoap&par=1126782060&key=7d908860312f34ae&unit=m";
	readfile($dataURL);
?>

Luego en nuestro caso cambiamos la url que pasamos por parámetro en Weather.as al instanciar el Model a la del fichero php alojado en nuestro servidor.

Tener en cuenta dos últimas cosas:
publicacion en flash

  1. En las opciones de configuración de publicación de Flash, pestaña “Flash”, en avanzado, aseguraos que  en la seguridad de reproducción local está seleccionada la opción “Acceder sólo a la red”, en caso contrario el Flash Player os pedirá permisos para acceder al dominio donde está alojado vuestro fichero de lenguaje de servidor.
  2. Al ejecutar en local el swf necesitáis colocar en la raíz de vuestro servidor un fichero crossdomain.xml que de permisos a cualquier dominio. Suena algo terrorífico, pero otra solución no he sabido encontrarle… Todo esto significa una cosa, que un formato como el swf no es un formato de servidor y en consecuencia está restringido su acceso con buen criterio.

Espero os haya sido de utlidad, cualquier mejora o comentario será bienvenido.

Escrito por Carlos

24/07/2009 a 09:42

4 comentarios

Suscríbete a los comentarios mediante RSS.

  1. no recibo el id.

    carlos

    05/10/2009 a 03:08

    • Supongo que te refieres al Id de weather.com, mira que tu cuenta de correo no haya tratado el mensaje de weater.com como spam.

      Carlos

      05/10/2009 a 08:23

  2. Hola Carlos, he tratado de buscar este tutorial, la verdad es que mis conocimientos en programacion son nulos, traté de bajar los archivos (.zip) pero ya no estan, ¿Podrias volver a subirlos?

    Saludos

    Roberto Avila

    18/11/2009 a 21:46

    • Perdona Roberto, lo había eliminado por error.
      Ya los vuelves a tener disponibles.
      Un saludo.

      Carlos

      18/11/2009 a 22:00


Deja un comentario