Logo de islavisual
Isotipo de islavisual IslaVisual
imagen de sección

Ultima revisión 19/02/2010

Actualización de un DataGrid preservando los nodos abiertos y el scroll en Flex

NOTA: Este ejemplo se refresca cada 30 segundos.

Hace mas o menos una semana me solicitaron que realizase un Adavance Datagrid en Flex con la peculiaridad de que se actualizase solo. Me puse a realizar el trabajo cuando de repente me encontré con que al recargar los valores en el ADG se ponía el estado del Datagrid al estado inicial, es decir, si en el ADG pusimos como parámetro displayItemsExpanded = "true", al refrescar o recargar los valores, aunque hubieramos cerrado uno o varios elementos,se volvía a poner totalmente abierto y si el parámetro displayItemsExpanded = "false" se quedaba totalmente recogido. O sea, que ni preservaba los nodos abiertos ni el scroll, ni nada.

Estuve leyendo mucho y buscando por Internet en foros ingleses, americanos, españoles, buscando en las guías de documentación oficial de Adobe y nada. Así durante más de 5 días. Incluso he leído por ahí que el comportamiento de dicho componente al recargar los valores es un bug.

Pues bien, he conseguido realizar un código en Flex que permite no sólo preservar el estado de los nodos abiertos sino que además conserva el scroll ya que no toca para nada el refresh del datagrid.

Se basa en primero traernos un XML desde un HTTPService de un PHP, convertirlo en un ArrayCollection y después mediante un Array (ArrayCollection auxiliar) copiar celda a celda todos los elementos del Array proveniente del XML al Array que muestra el Advanced Datagrid.

Primero declaramos los controles necesarios y variables globales

import mx.collections.HierarchicalData;
import mx.controls.Alert;
import flash.utils.Timer;
import flash.events.TimerEvent;
import mx.rpc.events.ResultEvent;
import mx.collections.ArrayCollection;

// Variables usadas por el Timer
private var baseTimer:int;
private const TIMER_INTERVAL:int = 1000;
private var t:Timer;

// Arrays a manejar
[Bindable] public var arrCollectionAux:ArrayCollection; // Array intermediario
[Bindable] private var dpHierarchy:ArrayCollection = new ArrayCollection([]); // Array del Advanced Datagrid
// Directorio del PHP dónde está el PHP que devolverá el XML en formato
[Bindable] private var IPHostModules:String = "Modules";

Segundo necesitamos una función que contendrá y definirá el Timer

private function init():void {
         readStateAgrupaciones.send();
         t = new Timer(TIMER_INTERVAL);
         t.addEventListener(TimerEvent.TIMER, readStateAgrupaciones.send);
         baseTimer = getTimer();
         t.start();
}

Luego creamos una función que convertirá el XML en un ArrayCollection y si es la primera ejecución definimos el que contendrá el datagrid. Tanto si es la primera ejecución como si es posterior, el array resultante del XML se meterá en arrCollectionAux

public function convertXMLToArrayCollection(evt:ResultEvent):void{
         var agrupacionesData:XML;
         var arrAuxA:ArrayCollection = new ArrayCollection([]);
         var arrAuxS:ArrayCollection = new ArrayCollection([]);
         var arrAuxO:ArrayCollection = new ArrayCollection([]);

         agrupacionesData = new XML(evt.result);
         for each (var item:XML in agrupacionesData.*) {
                  // Si tiene hijos el 1º Nivel (Agrupaciones)
                  arrAuxS = new ArrayCollection([]);
                  if(item.children().length() > 0){
                           for each (var item2:XML in item.*) {
                           // Tiene hijos el 2º Nivel (SubAgrupaciones)
                           arrAuxO = new ArrayCollection([]);
                           if(item2.children().length() > 0){
                        for each (var item3:XML in item2.*) {
                                 arrAuxO.addItem({label:item3.@label, numOper:item3.@numOper, timeResp:item3.@timeResp, semaforo:item3.@semaforo, graph:item3.@graph});
                        }
                        arrAuxS.addItem({label:item2.@label, numOper:item2.@numOper, timeResp:item2.@timeResp, semaforo:item2.@semaforo, graph:item2.@graph, children: arrAuxO});
                        // Si no tiene hijos el 2º Nivel (SubAgrupaciones)
                           } else {
                        arrAuxS.addItem({label:item2.@label, numOper:item2.@numOper, timeResp:item2.@timeResp, semaforo:item2.@semaforo, graph:item2.@graph});
                           }
                  }
                  arrAuxA.addItem({label:item.@label, numOper:item.@numOper, timeResp:item.@timeResp, semaforo:item.@semaforo, graph:item.@graph, children: arrAuxS });
                  // Si no tiene hijos el 1º Nivel (Agrupaciones)
                  } else {
                           arrAuxA.addItem({label:item.@label, numOper:item.@numOper, timeResp:item.@timeResp, semaforo:item.@semaforo, graph:item.@graph});
                  }
         }
         if (dpHierarchy.toString() == "") dpHierarchy = arrAuxA;
         arrCollectionAux = arrAuxA;
}

Después creamos la función que copia celda a celda el array del XML nuevo al usado por el datagrid. Aquí los valores que se quieren recargar son las propiedades numOper, timeResp y semaforo. Si cambiaran mas valores sería repetir la operación con el nombre de la propiedad.

A modo de aclaración el XML, una vez ejecutado devolvería algo asi:

<data>
          <group label="Agrupación" numOper="22403,09" timeResp="1,62" semaforo="assets/img/minor.png" graph="assets/img/chart.png">
                   <subgroup label="Sub-Agrupación 1" numOper="3914,58" timeResp="4,55" semaforo="assets/img/critical.png" graph="assets/img/chart.png">
                             <node label="Nodo 1" numOper="500" timeResp="1,34" semaforo="assets/img/critical.png" graph="assets/img/chart.png" />
                             <node label="Nodo 2" numOper="664" timeResp="1.34" semaforo="assets/img/minor.png" graph="assets/img/chart.png" />
                             <node label="Nodo 3" numOper="1,205" timeResp="0,76" semaforo="assets/img/major.png" graph="assets/img/chart.png" />
                             <node label="Nodo 4" numOper="8988,49" timeResp="1,40" semaforo="assets/img/critical.png" graph="assets/img/chart.png" />
                             <node label="Nodo 5" numOper="135" timeResp="0,98" semaforo="assets/img/normal.png" graph="assets/img/chart.png" />
                             <node label="Nodo 6" numOper="4957,17" timeResp="4,26" semaforo="assets/img/unknown.png" graph="assets/img/chart.png" />
                             <node label="Nodo 7" numOper="6111,47" timeResp="1,72" semaforo="assets/img/unknown.png" graph="assets/img/chart.png" />
                             <node label="Nodo 8" numOper="4578,73" timeResp="1,86" semaforo="assets/img/minor.png" graph="assets/img/chart.png" />
                    </subgroup>
                    <subgroup label="Sub-Agrupación 2" numOper="935" timeResp="3,00" semaforo="assets/img/normal.png" graph="assets/img/chart.png" />
                    <subgroup label="Sub-Agrupación 3" numOper="935" timeResp="3,00" semaforo="assets/img/normal.png" graph="assets/img/chart.png" />
          </group>
</data> 

public function updateDataAgrupaciones(arrColectionNew:ArrayCollection):void{
         var x:uint; var y:uint; var z:uint;

         arrColectionNew.refresh();
         for (x = 0; x < arrColectionNew.length; x++){
                  dpHierarchy.getItemAt(x).numOper  = arrColectionNew.getItemAt(x).numOper;
                  dpHierarchy.getItemAt(x).timeResp = arrColectionNew.getItemAt(x).timeResp;
                  dpHierarchy.getItemAt(x).semaforo = arrColectionNew.getItemAt(x).semaforo;
                  dpHierarchy.refresh();
                  dgAgrupaciones.hierarchicalCollectionView.refresh();
                  if(arrColectionNew.getItemAt(x).hasOwnProperty("children") == true){
                           for(y = 0; y< arrColectionNew.getItemAt(x).children.length; y++){
                        dpHierarchy.getItemAt(x).children[y].numOper  = arrColectionNew.getItemAt(x).children[y].numOper;
                        dpHierarchy.getItemAt(x).children[y].timeResp = arrColectionNew.getItemAt(x).children[y].timeResp;
                        dpHierarchy.getItemAt(x).children[y].semaforo = arrColectionNew.getItemAt(x).children[y].semaforo;
                        dpHierarchy.refresh();
                        dgAgrupaciones.hierarchicalCollectionView.refresh();
                        if(arrColectionNew.getItemAt(x).children[y].hasOwnProperty("children") == true){
                                 for(z = 0; z< arrColectionNew.getItemAt(x).children[y].children.length; z++){
                                          dpHierarchy.getItemAt(x).children[y].children[z].numOper  = arrColectionNew.getItemAt(x).children[y].children[z].numOper;
                                          dpHierarchy.getItemAt(x).children[y].children[z].timeResp = arrColectionNew.getItemAt(x).children[y].children[z].timeResp;
                                          dpHierarchy.getItemAt(x).children[y].children[z].semaforo = arrColectionNew.getItemAt(x).children[y].children[z].semaforo;
                                          dpHierarchy.refresh();
                                          dgAgrupaciones.hierarchicalCollectionView.refresh();
                                 }
                        }
                           }
                  }
         }
         dpHierarchy.refresh();
}

Y finalmente ponemos el ADG y HTTPService y ya está...

<mx:AdvancedDataGrid id="dgAgrupaciones" displayItemsExpanded="true" folderClosedIcon="{null}" folderOpenIcon="{null}" defaultLeafIcon="{null}" width="100%" height="90%">
         <mx:dataProvider>
                  <mx:HierarchicalData source="{dpHierarchy}" childrenField="children"/>
         </mx:dataProvider>
         <mx:columns>
                  <mx:AdvancedDataGridColumn dataField="label" headerText="id"/>
                  <mx:AdvancedDataGridColumn dataField="semaforo" headerText="ST" >
                           <mx:itemRenderer><mx:Component><mx:Image brokenImageSkin="{null}" brokenImageBorderSkin="{null}" horizontalAlign="right" width="20" height="20"/></mx:Component></mx:itemRenderer>
                  </mx:AdvancedDataGridColumn>
                  <mx:AdvancedDataGridColumn dataField="numOper" headerText="Nº Operaciones"/>
                  <mx:AdvancedDataGridColumn dataField="timeResp" headerText="T. Respuesta"/>
                  <mx:AdvancedDataGridColumn dataField="graph" headerText="EYE" >
                           <mx:itemRenderer><mx:Component><mx:Image brokenImageSkin="{null}" brokenImageBorderSkin="{null}" horizontalAlign="right" width="20" height="20"/></mx:Component></mx:itemRenderer>
                  </mx:AdvancedDataGridColumn>
         </mx:columns>
</mx:AdvancedDataGrid>

<mx:HTTPService id="readStateAgrupaciones" result="convertXMLToArrayCollection(event); updateDataAgrupaciones(arrCollectionAux);" showBusyCursor="true" method="POST" url="{IPHostModules}/readStateAgrupaciones.php" useProxy="false" />

Nada mas...

Sobre el autor

Imagen de Pablo Enrique Fernández Casado
Pablo Enrique Fernández Casado

CEO de IslaVisual, Manager, Full Stack Analyst Developer y formador por cuenta ajena con más de 25 años de experiencia en el campo de la programación y más de 10 en el campo del diseño, UX, usabilidad web y accesibilidad web. También es escritor y compositor de música, además de presentar múltiples soft kills como la escucha activa, el trabajo en equipo, la creatividad, la resiliencia o la capacidad de aprendizaje, entre otras.

Especializado en proveer soluciones integrales de bajo coste y actividades de consultoría de Usabilidad, Accesibilidad y Experiencia de Usuario (UX), además de ofrecer asesoramiento en SEO, optimización de sistemas y páginas web, entre otras habilidades.