Al desarrollar RIAs siempre nos vemos en la necesidad de realizar alguna acción cuando un dato cambia al ser cargado desde el servidor. Flex nos ofrece el metatag [Bindable] para refrescar los cambios que sufren los datos, pero es limitado que sólo nos refresque la información en algunos casos.
Para dejar todo claro veamos un ejemplo de un Binding clásico.
Código :
package com.otakurzo.models { import flash.events.TimerEvent; import flash.utils.Timer; import mx.collections.ArrayCollection; [Bindable] public class UserModel { public var acList:ArrayCollection = new ArrayCollection(); //-- loadData(); sumilará el tiempo de respuesta de un servidor en 1.5 segundos private var _timer:Timer; public function loadData():void { _timer = new Timer(1500,1); _timer.addEventListener(TimerEvent.TIMER_COMPLETE,onTimerComplete,false,0,true); _timer.start(); } private function onTimerComplete(e:TimerEvent):void { // Removemos el evento, lo detenemos y destruimos el Timer _timer.removeEventListener(TimerEvent.TIMER_COMPLETE,onTimerComplete); _timer.stop(); _timer = null; // -- acList.source = new Array('Otaku RzO','Eldervaz','Elecash','Zguillez','Xklibur','Fernando','eParada','Freddier'); } //-- //-- Singleton private static var _instance:UserModel=null; public function UserModel(e:Enforcer){ trace('new instance of UserModel created'); } public static function getInstance():UserModel{ if(_instance==null){ _instance=new UserModel(new Enforcer()); } return _instance; } //-- } } class Enforcer{}
Esta clase UserModel es un Singleton que me permitirá consumir los datos desde cualquier parte de mi aplicación y tiene el Metatag [Bindable] para ver cambios que sufran sus propiedades públicas.
En acList cargaremos los datos y con la función loadData() usaremos un Timer para simular el retardo de la carga de los datos que se depositarán en acList. Como es de tipo Singleton para acceder hasta acList pondríamos:
Código :
UserModel.getInstance().acList
La interfaz:
Código :
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="199" height="324"> <mx:Script> <![CDATA[ import com.otakurzo.models.UserModel; private function loadUsers(e:Event):void { btnLoadUsers.enabled = false; btnLoadUsers.label = "Cargando..."; //Llamamos a la función que cargará los datos UserModel.getInstance().loadData(); } ]]> </mx:Script> <mx:Panel width="173" height="295" layout="absolute" styleName="opaquePanel" horizontalCenter="0" verticalCenter="0" title="Ejemplo 1"> <mx:List height="212" id="lstUsers" width="133" dataProvider="{UserModel.getInstance().acList}" x="10" y="10"></mx:List> <mx:Button id="btnLoadUsers" label="Cargar Usuarios" click="loadUsers(event);" width="133" x="10" y="228"/> </mx:Panel> </mx:Application>
Importamos nuestra anterior clase, preparamos el click del botón para que cargue la data y asociamos el dataProvider del componente List con el acList de nuestra clase así:
Código :
UserModel.getInstance().acListDe esta manera los datos se actualizarán cada vez que se produzca un cambio en ellos.
Es poco código y vemos que trabaja correctamente, pero nos falto algo. El botón para cargar los datos quedo deshabilitado y debió habilitarse cuando terminaron de cargarse los datos.
Para solucionarlo podríamos usar una clase de tipo Observer o usar el Tag <mx:Binding /> o incluso una función que pondríamos así: dataProvider="{ cambio(UserModel.getInstance().acList)}" , y esa función se ejecutaría cada que cambie la data pero se pierde el binding directo hacia el dataProvider o quizás nos de algún error.
Por suerte tenemos una forma más elegante de detectar cuando se produjo un cambio y hasta cuando debemos escuchar o no esos cambios. Para lograrlo usaremos la clase ChangeWatch.
Código :
ChangeWatcher.watch(obj:Object,property:String,handler:Function):ChangeWatcherTiene otros parámetros opcionales pero para el uso que le daremos con estos nos basta.
Usaremos la función watch para escuchar los cambios que nos retornará una instancia de ChangeWatcher.
Parámetros:
- obj: Colocaremos el objecto base donde se encuentra la variable/propiedad a detectar.
- property: Colocaremos la variable/propiedad a detectar.
- handler: la función a llamar después de haber sufrido un cambio la variable/propiedad a detectar.
Cuando obtengamos la instancia de ChangeWatcher podemos usar la función unwatch para dejar de escuchar cambios.
Ahora veamos el mismo código del ejemplo anterior aplicando ChangeWatcher:
Código :
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="199" height="324"> <mx:Script> <![CDATA[ import mx.binding.utils.ChangeWatcher; import com.otakurzo.models.UserModel; private var cwUsers:ChangeWatcher; private function loadUsers(e:Event):void { btnLoadUsers.enabled = false; btnLoadUsers.label = "Cargando..."; //Vaciamos la data para notar los cambios UserModel.getInstance().acList.source = new Array(); //Empezamos a escuchar cambios en la propiedad source del //objecto acList que esta dentro de la Clase UserModel cwUsers = ChangeWatcher.watch(UserModel.getInstance().acList,'source',onUserChange); //-- //llamamos a la función que cargará los datos UserModel.getInstance().loadData(); } private function onUserChange(e:Event):void { //Dejamos de escuchar cambios y liberamos la variable cwUsers.unwatch(); cwUsers = null; //-- btnLoadUsers.enabled = true; btnLoadUsers.label = "Cargar Usuarios"; } ]]> </mx:Script> <mx:Panel width="173" height="295" layout="absolute" styleName="opaquePanel" horizontalCenter="0" verticalCenter="0" title="Ejemplo 1"> <mx:List height="212" id="lstUsers" width="133" dataProvider="{UserModel.getInstance().acList}" x="10" y="10"></mx:List> <mx:Button id="btnLoadUsers" label="Cargar Usuarios" click="loadUsers(event);" width="133" x="10" y="228"/> </mx:Panel> </mx:Application>
El código esta bien comentado pero resumiendo se uso ChangeWatcher antes de la carga de los datos y se dejo de escuchar al inicio de la función que fue llamada cuando cambio el dato a detectar.
El resultado:
Nota importante:
Al usar ChangeWatch coloquen bien la propiedad que quieren detectar porque se da el caso en que quieren poner directamente:
Código :
ChangeWatcher.watch(UserModel.getInstance(),'acList',onUserChange);
en vez de:
Código :
ChangeWatcher.watch(UserModel.getInstance().acList,'source',onUserChange);Ya que acList es un objecto y source es su propiedad.
Otro sería el caso en que la clase UserModel tenga una propiedad más como: public var login_status:Boolean;
Aquí sí es correcto usar:
Código :
ChangeWatcher.watch(UserModel.getInstance(),'login_status',onUserChange);
porque login_status es una propiedad del UserModel.
Recuerden que getIntance() la estamos usando porque la clase UserModel es de tipo singleton, pero el ChangeWatcher es aplicable a cualquier tipo de objecto que tenga propiedades.
Archivos del ejemplo: descargar.
¿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.
Por Otaku RzO el 29 de Enero de 2009
*Ya Elder, ya te puse!
Por eldervaz el 29 de Enero de 2009
Otaku RzO :
*Ya Elder, ya te puse!
Buen tip felicidades
Por Zguillez el 29 de Enero de 2009
Muy util esa clase
Por fernando el 29 de Enero de 2009
nice!
Por eparada el 29 de Enero de 2009
Por Otaku RzO el 29 de Enero de 2009
fernando-blog :
Tú siempre fumando de esa
Uso esta clase con Cairngorm dentro de cada model y me he ahorrado muchas muchas líneas de código.
Pero usarlo como port con Flash sería fantástico!
FTW
Por solisarg el 29 de Enero de 2009
En fin, un buen complemento para enlazar elementos de la UI a cambios en el modelo, y sin duda tiene muchas aplicaciones más.
Buen tip
Jorge
Por fernando el 29 de Enero de 2009
El método a usar es abrir el ChangeWatcher.as y traducirlo sin usar los imports de mx...
<metiendo presión para que alguien me ahorre el trabajo a mi >
Salu2!
Por Deivit78 el 05 de Diciembre de 2010
El código del segundo componente es:
<?xml version="1.0" encoding="utf-8"?>
<!--
Este componente representa una tabla que muestra los datos de los alumnos, filtrados por grupo,
y que cuando seleccionamos a uno, nos devuelve un evento con el alumno que nosotros hemos seleccionado.
Como tabla de seleccion de alumnos, muestra información básica sobre los alumnos
ENTRADA: Requiere de una cadena de texto que identifique el grupo
SALIDA: Devolverá el alumno que hemos seleccionado de la tabla
-->
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" width="514" height="156"
creationComplete="init()">
<fx:Script>
<![CDATA[
import mx.binding.utils.*;
import mx.controls.Alert;
import mx.events.FlexEvent;
import mx.binding.utils.ChangeWatcher;
[Bindable]
public var p_grupo:String;
[Bindable]
public var watcherSetter:ChangeWatcher;
private function init():void
{
watcherSetter = ChangeWatcher.watch(id_label,"text",watcherListener);
}
public function watcherListener(val:String):void {
Alert.show("Paso");
id2_label.text =val;
}
]]>
</fx:Script>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<s:Label id="id_label" x="145" y="61" text="{p_grupo}"/>
<s:Label id="id2_label" x="145" y = "80" text="" />
</s:Group>
Por Otaku RzO el 05 de Diciembre de 2010
Puedes revisar:
Por Deivit78 el 06 de Diciembre de 2010
Falta código, se supone que tenemos un componente que es un selector y le pasa a la aplicación el item seleccionado y luego, la aplicación principal, le pasa ese item,COMO PARAMETRO al segundo componente, que es del que he puesto el código. El problema es que quiero que se lance un evento en el segundo componente cuando se cambia ese parámetro, parece igual a un evento con variable, pero parece ser que no es igual, la verdad es que ando perdido. El item seleccionado llega al segundo componente, eso funciona,pero no se dispara el evento cuando cambia el text del label.