miércoles, 3 de noviembre de 2010

Flex/AIR: DataGrids dinámicos

Voy a plantear un problema que me ha surgido recientemente. En una aplicación se ha de mostrar la información recogida de una consulta lanzada por HTTPService, y debe ser plasmada en un DataGrid. En principio es simple, pero, ¿y si el resultado de la consulta genera resultados diferentes en el XML, tales como las columnas?. Imaginemos que en una consulta nos retorna el siguiente XML:

<?xml version="1.0" encoding="utf-8"?>
<vista2d>
 <fila>
  <Servicios>...</Servicios>
  <Costes>...</Costes>
  <Ventas>...</Ventas>
 </fila>
 ...
</vista2d>


En otra consulta nos retorna este otro XML:

<?xml version="1.0" encoding="utf-8"?>
<vista2d>
 <fila>
  <Objetivos>...</Objetivos>
  <Ventas>...</Ventas>
  <Gastos>...</Gastos>
  <ROI>...</ROI>
  <Desviacion>...</Desviacion>
 </fila>
 ...
</vista2d>


En un DataGrid hemos de especificar implícitamente cada una de sus columnas para que éstas se visualicen. Al menos, cuando utilizamos MXML. Para poder generar DataGrids dinámicos, trabajaremos con ActionScript.

En primer lugar, tendremos definido el DataGrid, el cual, al no conocer qué columnas va a tener, éstas no serán declaradas. Por tanto, el DataGrid no contendrá columnas:

<mx:DataGrid id="dgVista2D" width="100%" height="100%"/>

A continuación declararemos el servicio HTTPService, el cual será llamado mediante el método send(). El código sería similar al siguiente:

 <s:HTTPService id="srvObtenerVista2D"
   resultFormat="e4x"
   method="GET" useProxy="false"
   url="http://localhost:8080/ticube/SrvObtenerVista2D"
   fault="getHTTPServiceFault(event)"
   result="srvObtenerVista2DResult(event)"/>


Hemos de importar las siguientes clases para poder lanzar eventos en caso de que la llamada al HTTPService produzca algún error (fault), o en el caso de que se haya procesado con éxito (result):

import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;


En cada uno de estos casos, al producirse el evento, se ejecutará el método asociado. En el caso de que la llamada produjera un error (como en el caso de que no acceda a la URL, el servicio está caído, etc.), se lanzará un mensaje de error:

// Error en carga de datos HTTP
private function getHTTPServiceFault(event:FaultEvent):void {
  Alert.show("Error: " + event.toString());
}


En el caso de que la llamada se efectuara correctamente, y retornara el XML correspondiente, éste se pasará como parámetro (event.result). El código completo es el siguiente (a continuación se explicará en detalle):

private function srvObtenerVista2DResult(event:ResultEvent):void {
  var vxmll:XMLList = new XMLList(event.result.fila);

  // Extraccion de columnas
  if (vxmll.length()>0) {
    var cols:Array = new Array(); // Crear array de columnas
    for each(var element:XML in vxmll[0].elements()) {
      // element.name retorna el titulo del elemento
      // element.toString retorna el valor del elemento
      cols.push(new DataGridColumn(element.name())); // Añadir columna al array
    }
  }

  dgVista2D.columns = cols; // Asignar columnas al DataGrid
  dgVista2D.validateNow(); // Validar y refrescar el DataGrid

  // Convertir el XML retornado a ArrayCollection
  // y cargar los datos en el DataGrid
  var vxmllc:XMLListCollection = new XMLListCollection(vxmll);
  var aResult:Array = vxmllc.toArray();
  var acResult:ArrayCollection = acResult = new ArrayCollection(aResult);
  dgVista2D.dataProvider=acResult;
}


El parámetro event recoge toda la información del evento, incluyendo el resultado (result), el cual será el XML retornado por el HTTPService.

private function srvObtenerVista2DResult(event:ResultEvent):void {

Cuando se accede al miembro fila de este XML, éste retornará una lista (XMLList) de elementos fila, cada uno de los cuales es un XML que contiene los elementos de nivel inferior (columnas y valores por columna).

  var vxmll:XMLList = new XMLList(event.result.fila);

Si esta lista posee información (el XML no vino vacío), se creará un array (cols) que almacenará los objetos de columna (objetos DataGridColumn) que se añadirán al DataGrid). Se recorren los elementos de la primera fila (primer elemento de la lista), creando un objeto de columna, cuyo texto será el nombre del elemento substraído, y el cual será añadido al array.

  // Extraccion de columnas
  if (vxmll.length()>0) {
    var cols:Array = new Array(); // Crear array de columnas
    for each(var element:XML in vxmll[0].elements()) {
      // element.name retorna el titulo del elemento
      // element.toString retorna el valor del elemento
      cols.push(new DataGridColumn(element.name())); // Añadir columna al array
    }
  }

Finalizado el bucle, el array cols tendrá todas las columnnas (objetos DataGridColumn), con el texto de cada elemento leído de la primera fila del XML. Ahora sólo queda asignar dicho array al DataGrid, y a continuación validar dicho DataGrid para que se actualice y visualice correctamente.

  dgVista2D.columns = cols; // Asignar columnas al DataGrid
  dgVista2D.validateNow(); // Validar y refrescar el DataGrid


Por último, hay que asignar la información (datos) al DataGrid. Para ello, se transforman los datos de la lista (XMLList) a una colección (XMLListCollection), de ésta a un array, y de éste a un objeto ArrayCollection, el cual se puede asignar al DataGrid, mediante su propiedad dataProvider:

  // Convertir el XML retornado a ArrayCollection
  // y cargar los datos en el DataGrid
  var vxmllc:XMLListCollection = new XMLListCollection(vxmll);
  var aResult:Array = vxmllc.toArray();
  var acResult:ArrayCollection = acResult = new ArrayCollection(aResult);
  dgVista2D.dataProvider=acResult;