Apache Adobe Flex TutorialTutoriaux Adobe Flex & AIR en Français

24juil/1012

DataFilterLib – Pagination de données filtrées (ArrayCollection avec filterFunction)

On m'a posé une question très intéressante sur la DataFilterLib:

Cedric
Bonjour moi j'aurais une petite question :
Comment intégrer les fonctionnalités de filtrage sur une dataGrid dont les données sont paginées (à l'aide du paginateur disponible sur ce site)?

En effet, la DataFilterLib permet de filtrer des données côté client selon plusieurs critères, de manière déclarative (MXML) ou dynamique (AS). Elle permet, pour simplifier, de donner plusieurs filterFunction à un ArrayCollection.

Et pour la pagination (côté client), le plus simple et le plus effectif est d'utiliser aussi une filterFunction sur une ICollectionView, un ArrayCollection par exemple. La question est alors, comment combiner les deux?

Application de test

Voilà le code utilisé pour tester l'application. Le dataProvider se trouve dans un fichier à part pour faciliter la lecture:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%"
                paddingRight="20" xmlns:filter="com.fnicollet.datafilter.filter.*"
                xmlns:pagination="pagination.*">
  <mx:Script>
    <![CDATA[
      import pagination.event.PaginateEvent;
      import com.fnicollet.datafilter.filter.DataFilterType;
      import com.fnicollet.datafilter.filter.DataFilterSingleValueOperator;

      [Bindable]
      private var _startIdx:int = 0;
      [Bindable]
      private var _endIdx:int = 999999999;

      private function pageChangeHandler(e:PaginateEvent):void {
        var page:int = e.index;
        _startIdx = (page * e.itemsPerPage) + 1;
        _endIdx = Math.min((_startIdx + e.itemsPerPage - 1), e.itemsTotal);
        //txt.text = "Results: " + String(_startIdx) + " - " + String(_endIdx) + " of " + e.itemsTotal;
      }
    ]]>
  </mx:Script>

  <mx:Script source="/data/StateData.as"/>
  <filter:DataFilterSet id="filterSet" data="{statesData}">
    <filter:dataFilterParameters>
      <filter:DataFilterParameters id="simpleParam" filterType="{DataFilterType.SINGLE_VALUE}"
                                   filterKeys="state"
                                   filterOperator="{DataFilterSingleValueOperator.LIKE}"
                                   filterValues="{stateInput.text}"/>
    </filter:dataFilterParameters>
  </filter:DataFilterSet>

  <mx:Label text="Filter By State Name (Contains)" fontSize="14" fontWeight="bold"/>
  <mx:TextInput id="stateInput"/>
  <mx:Label text="Pagination over the filtered elements" fontSize="14" fontWeight="bold"/>
  <pagination:PageSelector id="paginator" itemsPerPage="6" rangeCount="6"
                           itemsTotal="{statesData.length}" selectedIndex="0"
                           pageChange="pageChangeHandler(event)"/>
  <mx:Label text="Filtered Data" fontSize="14" fontWeight="bold"/>
  <mx:DataGrid rowCount="7" dataProvider="{statesData}" width="100%">
    <mx:columns>
      <mx:DataGridColumn dataField="state" headerText="State Name"/>
      <mx:DataGridColumn dataField="sales" headerText="Sales"/>
      <mx:DataGridColumn dataField="employees" headerText="Number of Employees"/>
      <mx:DataGridColumn dataField="population" headerText="Population"/>
    </mx:columns>
  </mx:DataGrid>

  <mx:Label text="Unfiltered Data" fontSize="14" fontWeight="bold"/>
  <mx:DataGrid rowCount="8" dataProvider="{statesData.source}" width="100%">
    <mx:columns>
      <mx:DataGridColumn dataField="state" headerText="State Name"/>
      <mx:DataGridColumn dataField="sales" headerText="Sales"/>
      <mx:DataGridColumn dataField="employees" headerText="Number of Employees"/>
      <mx:DataGridColumn dataField="population" headerText="Population"/>
    </mx:columns>
  </mx:DataGrid>

</mx:Application>

Un composant générique de pagination est intégré et met seulement à jour 2 variables Bindable. Il n'effectue encore aucun filtrage:

Composant Flex – Interface de Pagination générique

Premier test: Ajouter un nouveau filtre pour la pagination

Dans un premier temps, la première idée que j'ai eu fut de simplement ajouter un nouveau filtre, se comportant comme un filtre de type Interval, dont l'intervalle irait entre l'index de mon premier élément de page et le dernier élément.

Pour créer ce nouveau filtre, qui n'existe pas par défaut, il suffit de créer une classe qui hérite de "DataFilterInterval": DataFilterIntervalPagination. Pour l'ajouter en tant que filtre, il suffit d'utiliser la technique que j'expose dans cet article:

DataFilterLib – Utilisation de filtres personnalisés

Pour une utilisation dans une application, il suffit ensuite de donner:

<filter:DataFilterParameters filterClass="{DataFilterIntervalPagination}"
 id="intervalPagination" filterValues="{[_startIdx, _endIdx]}"/>

Et déjà, là, premier obstacle, la méthode de filtrage de base. Pour simplifier, la DataFilterLib boucle sur la méthode "apply" de chaque filtre dont la signature est la suiavnte:

public function apply(item:Object):Boolean {

On prend ensuite chaque résultat et si au moins un d'entre eux est false, l'objet est filtré. Pour les filtres sur valeur et sur intervalle, on cherche à comparer une certaine propriété de l'item (donnée par le paramètre "filterKeys"). En pseudo-code, on a :

public function apply(item:Object):Boolean {
 var value:String = item[filterKey];
 if (value == filterValue){
  return true;
 }
 return false;
}

On fait ici la distinction par rapport à une propriété de l'objet. Seulement dans notre cas, on ne veut pas comparer une des propriétés de l'objet mais bien la position de l'objet dans le dataProvider. Si son index est dans la page courante on garde l'objet, sinon on le filtre.

19mar/102

Flex UIComponent – Déclencher l'évènement change lors de la modification du selectedItem en AS3 (ComboBox, List et DataGrid)

Sur les composants Flex de type List (ComboBox, List et DataGrid par exemple), on dispose d'un évènement ListEvent.CHANGE ("change") qui est dispatché quand l'utilisateur fait une sélection dans la liste. Il est légèrement différent de l'évènement "itemClick" qui n'est déclenché qu'au click sur un élément de la liste.

De manière générale, il vaut mieux utiliser "change" que "itemClick" car change va dispatcher un évènement lorsque l'utilisateur fera une sélection au clavier (avec les touches directionnelles).

Le problème

Lorsque l'utilisateur fait une sélection, on a bien un évènement CHANGE qui est dispatché. Cependant, si on fixe le "selectedItem" en ActionScript (code), vous pourrez voir que l'évènement ne sera pas dispatché et pourtant la sélection sera bien faite. L'affichage sera aussi mis à jour mais aucun évènement.

Dans votre code métier, vous voulez sûrement déclencher une action quand un élément est sélectionné, que ce soit par action utilisateur ou par code. L'évènement CHANGE est donc critique pour que votre code reste homogène.

La solution

Puisque le composant Flex ne le fait pas, vous pouvez créer votre propre ComboBox (par héritage) qui va rajouter ce comportement ou tout simplement dispatcher vous-même l'évènement CHANGE pour que votre programme se joue correctement. Voici un extrait de code qui réalise cette action:

...
secondComboBox.selectedItem = item;
secondComboBox.dispatchEvent(new ListEvent(ListEvent.CHANGE));
...

Exemple complet en ligne

Ci-dessous, la démonstration des deux comportements (avec et sans dispatch d'évènement):

12mar/103

AdvancedDataGrid, Charting et OLAPDataGrid (datavisualization.swc) enfin GRATUIT!

En voilà une excellente nouvelle de la part d'Adobe (non, pas la sortie de Flash Player 10.1 :P ), la mise à disposition du SWC datavisualization.swc dans le SDK Open Source et sans restriction de licence.

Il y a 9 mois, je vous annonçais l'open-sourcing du Flex Data Visualization mais ici on parle bien de l'aspect gratuit de cette librairie. En effet, Open source ne veut pas forcement dire gratuit.

La nouvelle responsable produit sur Flex (Deepa Subramaniam) en a fait l'annonce lors du 360|Flex:

Flex advanced data visualization components now part of the FREE open source Flex SDK!

Pour résumer, vous n'aurez plus besoin d'une licence Flex Builder Professionnal pour utiliser les composants de la librairie datavisualization.swc. Parmi eux, on peut trouver:

  • AdvancedDataGrid: Composant qui va bien au delà de la DataGrid, avec de nouvelle possibilités d'itemRenderer, de bilan et de groupement (et bien d'autres)
  • Charting: Tous les graphiques possibles avec Flex (histogramme, bulles, etc.)
  • OLAPDataGrid: Peut servir pour des besoins spécifiques d'agrégation de données.

Je viens de vérifier sur le dernier "nightly build" de Flex 4, le swc est bien là dans le SDK Open Source:

dv

Télécharger le dernier build du Flex 4 SDK

Voila donc une excellente nouvelle pour la communauté Flex, puisque l'on peut maintenant réellement créer l'ensemble des applications entreprise que l'on souhaite sans débourser un sou (pour rappel, le SDK est gratuit, seul Flex Builder est payant). Le prix d'entrée pour un développeur se trouve du coup largement réduit. Pour rappel, voici la grille des prix Adobe:

  • Flex Builder 3 standard: 249$
  • Flex Builder 3 professional: 699$

Bon maintenant, on peut se demander à quoi va servir une licence Flex Builder Pro par rapport à une licence Flex standard. Pour l'instant je ne vois que le profiler et peut-être les wizards (même pas sûr et puis qui utilise les wizards ?^^). En regardant les derniers build Flex 3 SDK, on dirait que ce n'est que pour le SDK Flex 4. Mais bon, on peut compiler des applications Flex 3 avec le Flex 4 SDK donc peu importe.

Bref, vivement que Flex 4 et Flex Builder 4 sortent, il se font vraiment attendre maintenant :P

21fév/105

Flex Tips – Adapter automatiquement la taille d'une List / DataGrid / ComboBox suivant la donnée

Voilà un petit "tip" que j'utilise très fréquemment pour créer des interfaces plus agréables à utiliser. Ce n'est pas grand chose mais c'est toujours un petit plus. Ce petit truc sert simplement à adapter la taille d'une composant de type "liste" (List / DataGrid / ComboBox) suivant le nombre de données qu'il doit afficher.

En effet, par défaut, ces composants affichent un certain nombre d'éléments de votre dataProvider:

  • 5 pour la ComboBox (le DropDown)
  • 6 pour la DataGrid
  • 7 pour la List

Prenons maintenant l'exemple dans lequel vous avez un dataProvider à 6 éléments pour votre ComboBox. Quand vous allez l'ouvrir, une scrollbar (barre de défilement) va apparaitre pour vous permettre d'accéder au 6ème élément. Pour accéder au 6eme (ou 7e, ...) élément, on doit donc effectuer un click de plus alors que ce ne serait pas vraiment nécessaire, on pourrait afficher un DropDown affichant les 6 éléments par exemple.

Pour définir le nombre d'éléments à afficher dans un composant Flex de type List, il existe une propriété nommée "rowCount". Il suffirait donc de mettre votre rowCount à 6 et le tour est joué. Mais on va être plus malin car on ne connait pas forcement le nombre d'élément à afficher à l'avance (peut-être même qu'il peut varier).

On va ici utiliser le Data Binding pour automatiser la tâche et ne pas se soucier du pourquoi et du comment. On va simplement faire un Binding (liaison dynamique) entre la propriété "rowCount" de notre composant de type "liste" et la propriété "length" de notre dataProvider (en admettant que ce soit un Collection comme une ArrayCollection).

Voici un petit exemple:

<mx:ComboBox dataProvider="{_listOperatorsDP}" rowCount="{_listOperatorsDP.length}"/>

Et magie du Binding, notre affichage sera exactement à la taille souhaitée, pas de scrollbar. Alors vous avez sûrement déjà pensé à un cas qui pose problème, c'est le cas où l'on a beaucoup d'élément. Dans ce cas, notre Binding fera que notre liste sera immense et en plus, vous n'aurez potentiellement pas de scrollbar.

La solution est donc de simuler un "rowCountMax" (qui n'existe pas) à l'aide d'une mini-expression de test (test ternaire en ligne):

<mx:ComboBox dataProvider="{_listOperatorsDP}" rowCount="{_listOperatorsDP.length > 15 ? 15 : _listOperatorsDP.length}"/>

Ici, on va s'arrêter à 15 éléments affichés au maximum. A vous d'appliquer ce "tip" dans vos applications pour les rendre plus agréables à utiliser.

Un petit exemple exposant la différence:

Flex Source Code Download: Télécharger le code source complet de l'application

This movie requires Flash Player 11

4nov/0915

Composant Flex – Interface de Pagination générique (exemple)

Il y a quelques semaines, je présentais sur Adobe Flex Tutorial un composant Flex nommé Paginator qui était une interface de pagination générique pour vos applications. Pour rappel, la pagination sert à présenter la donnée par bouts (pages) afin de ne pas noyer l'utilisateur. Dans un commentaire, CapoeiraDance demandait à voir un exemple d'application de ce composant. Voici donc un exemple que j'ai concocté moi-même.

Dans cet exemple, on a une donnée sur les états américains avec quelques valeurs. J'ai choisi de n'afficher que 8 éléments pas page pour montrer le fonctionnement de l'interface de pagination. La vraie pagination (au niveau de la data) se fait par une "filterFunction" sur le dataProvider (ArrayCollection). Ainsi, on va regarder l'index de l'item dans la donnée pour savoir s'il est dans la page courante. Le code n'est sûrement pas optimal mais cela donne une idée.

Attention, pour indiquer le nombre total d'éléments au paginator, on utilise bien la propriété "length" de la "source" de l'ArrayCollection et pas la "length" de l'ArrayCollection. En effet, si on utilise directement statesData.length, les résultats seront erronés car la fonction de filtre va modifier cette longueur.

J'aurai pu être chauvin et utiliser la DataFilterLib pour créer cette pagination mais gardons les choses simples pour l'instant :) .

Un peu de code pour commencer

 [Bindable]
  private var statesData:ArrayCollection = new ArrayCollection([...]);

  ...

  // variables utilisées par la fonction de filtre
  private var _startIdx:int = 0;
  private var _endIdx:int = 1000;

  private function init():void{
	// on donne une fonction de filtre à la donnée pour n'afficher que les résultats courants
	statesData.filterFunction = paginateFilterFunction;
	statesData.refresh();
  }

  private function pageChangeHandler(e:PaginateEvent):void{
	// affichage du nombre de résultats courant
	var page:int = e.index;

	var startIndex:int = (page * e.itemsPerPage) + 1;
	var endIndex:int = Math.min((startIndex + e.itemsPerPage -1), e.itemsTotal);
	_startIdx = startIndex - 1;
	_endIdx = endIndex;

	txt.text = "Results: " + String(startIndex) + " - " + String(endIndex) + " of " + e.itemsTotal;
	// filtrage de la donnée par page
	statesData.refresh();
  }

  private function paginateFilterFunction(item:Object):Boolean{
	var itemIdx:int = statesData.getItemIndex(item);
	return (itemIdx >= _startIdx && itemIdx < _endIdx) || itemIdx == statesData.source.length;
  }

  ...

  <local:PageSelector id="paginator"
					  itemsPerPage="{8}"
					  rangeCount="6"
					  itemsTotal="{statesData.source.length}"
					  selectedIndex="0"
					  pageChange="pageChangeHandler(event)" />

L'application en ligne

Flex Source Code Download: Télécharger le code source complet de l'application

This movie requires Flash Player 11