Archives du mot-clé ArrayCollection

AIR Mobile – Application Pokémon (6) – Filtrer la liste des pokémons avec un champ de recherche

Dans le tutorial précédent, on a vu comment récupérer la donnée (liste des pokémons) depuis une base SQLite et comment l’afficher dans un composant List:

AIR Mobile – Application Pokémon (5) – Liste des Pokémon depuis la BDD SQLite

Maintenant que l’on a les bases de notre application, on va ajouter une fonction de recherche. Vous l’avez remarqué, la liste des pokémons est longue, plus de 500 éléments. On a fait un « order » sur le nom dans notre requête pour qu’ils ne soient pas mélangés mais si vous cherchez « Salameche », vous allez scroller un moment.

Si vous vous souvenez de la maquette que l’on avait fait en début du projet, vous remarquerez que l’on a un champ texte en haut de la vue permettant de rechercher un Pokémon.

Ajout du composant TextInput

Un champ de saisie est un composant nommé « TextInput » en Flex. Celui-ci devra se trouver dans la partie du haut qui est nommée ActionBar. Une petite piqûre de rappel si vous n’avez pas suivi les tutoriaux précédents:

AIR Mobile – Comprendre les composants ViewNavigator et View

Regardez surtout à la fin, le schéma expliquant le découpage naturel de l’ActionBar en 3 parties:

ActionBarDiagram

Dans notre cas, on a pas affaire à des boutons de navigation ou d’action mais à un composant que l’on veut centrer. On va donc l’afficher dans la partie « titleContent« . Pour cela, ouvrez une balise XML (« < ») dans votre vue PokemonInfoHomeView.mxml et tapez « titlec » puis Entrée et fermez le tag XML pour que Flash Builder vous complète la balise fermante:

...
  </fx:Declarations>
  <s:titleContent>

  </s:titleContent>
  <s:List width="100%" height="100%" dataProvider="{_dp}" labelField="nom">
...

Insérez un tag TextInput dans cette balise. Au passage, donnez lui un id égal à « input » et une width de valeur « 100% ». Vous avez une nouvelle propriété sur les composants de saisie de texte en Flex 4.5 qui est la propriété « prompt ». Celle-ci sert à afficher une valeur lorsque le champ texte n’est pas sélectionné pour inviter l’utilisateur à taper. Donnez lui la valeur « Rechercher un Pokémon »:

  <s:titleContent>
    <s:TextInput id="input" width="100%" prompt="Rechercher un Pokémon" />
  </s:titleContent>

Lancez le programme et voilà le résultat:

tin

Ajout d’une méthode de filtrage « filterFunction »

Pour effectuer notre filtrage, nous avons plusieurs solutions. La plus évidente est de faire à chaque frappe, une nouvelle requête SQL avec une clause LIKE qui nous renvoie tous les pokémons dont le nom correspond. On ne va pas adopter cette solution car dans notre cas, on a déjà requête tous les éléments de la base, qui sont conservés dans une variable nommée « _dp » de type ArrayCollection. Notre filtrage étant plutôt simple, on va utiliser un mécanisme de filtrage présent sur la classe ArrayCollection.

ArrayCollection offre un mécanisme de filtrage paramétrable grâce à sa propriété « filterFunction« . Cette propriété va référencer comme son nom l’indique une « function » de votre code. Cette fonction a une signature bien particulière qui est la suivante (cf. docs):

function filterByName(item:Object):Boolean {

Elle prend un objet en entrée et renvoie un Boolean. En d’autre terme, on lui passe un élément de donnée et elle décide si cet élément doit être filtré ou non. Si la méthode renvoie « true » pour un élément, celui-ci fera partie de la liste, sinon celui-ci sera filtré.

On va tout d’abord récupérer la saisie de l’utilisateur. Celle-ci se trouve dans la propriété « text » de notre composant repéré par la propriété id= »input ». On va tout de suite faire un test d’échappement dans le cas où l’utilisateur n’a rien tapé:

  private function filterByName(item:Object):Boolean {
	var inputValue:String = input.text;
	if (!inputValue || inputValue == "") {
	  return true;
	}

  }

Si aucune saisie, on renvoie « true » pour laisser passer tous les éléments.

Lire la suite

AIR Mobile – Application Pokémon (5) – Liste des Pokémon depuis la BDD SQLite

Dans les tutoriaux Flex précédents, on a vu comment ouvrir la base de données SQLite et comment lancer notre projet dans l’émulateur:

AIR Mobile – Application Pokémon (3) – Ouverture de la BDD SQLite

AIR Mobile – Application Pokémon (4) – Lancer l’application dans l’émulateur + debugger

On va maintenant travailler sur notre première vue et afficher la liste des pokémons. Pour cela, on va effectuer une requête SQL (SELECT) et afficher le résultat dans un composant List.

Effectuer le traitement au bon moment

Avant d’effectuer notre requête SELECT, il faut savoir à quel moment on va la faire. En effet, si vous avez suivi l’article sur le cycle de vie des View en Flex, vous savez que celle-ci propage des évènements lors de sa création et de sa destruction.

Les évènements qui vont nous intéresser sont:

  • creationComplete : Propagé quand tous les composants de la vue (comme notre List) sont instanciés et accessible. La vue n’est pas encore affichée à l’écran mais vous pouvez la manipuler
  • viewActivate : Propagé après « creationComplete », une fois que la transition entre les vues est terminée.

Alors lequel utiliser? Et bien cela dépend. En effet, si vous effectuez trop de traitement lors du « creationComplete », la transition de la vue va peut-être saccader. Mais en même temps, si vous faîtes votre traitement sur « viewActivate », il sera presque « trop tard » et si votre traitement est d’afficher / masquer des composants, votre vue va légèrement clignoter. A vous donc de voir et de choisir la meilleure solution, c’est à dire la plus adaptée à votre utilisation.

Pour cette première vue, ce n’est pas très important car en fait, la première vue n’arrive pas avec une transition. Que vous soyez sur « creationComplete » ou sur « viewActivate » ne fera donc que très peu de différence. Mais si on était sur une autre vue, sur laquelle on arrive par une transition, on aurait sûrement mis notre SELECT sur l’évènement « viewActivate » car nous effectuons nos requêtes en mode synchrone. Si nous l’avions ouverte en mode asynchrone, on aurait utilisé « creationComplete » plutôt. Voilà le genre de question qu’il faut se poser, souvent résolues par de rapides tests.

Récupérer la connexion SQL (SQLConnection)

Ouvrez donc le fichier « PokemonInfosHomeView.mxml » qui est notre première vue. Comme vous l’avez fait dans la 3eme partie du tutorial, ajouter un écouteur d’évènement sur « creationComplete » que vous allez appeler « onCComplete ». De la même manière, laissez-vous guider par Flash Builder:

<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark" title="HomeView"
        creationComplete="onCComplete(event)">
  <fx:Script>
    <![CDATA[
      import mx.events.FlexEvent;

      protected function onCComplete(event:FlexEvent):void
      {

      }

    ]]>
  </fx:Script>
  <fx:Declarations>
    <!-- Place non-visual elements (e.g., services, value objects) here -->
  </fx:Declarations>
</s:View>

Première chose que l’on va faire dans onCComplete, on va récupérer la connexion SQL que l’on avait ouvert dans la partie 3 du tutorial. Notre connexion est conservée au niveau de l’application. Celle ci peut être accédée depuis n’importe quel endroit grâce à la variable static:

FlexGlobals.topLevelApplication

Pour éviter de récupérer à chaque fois la référence vers notre SQLConnection depuis notre application, on va conserver une référence directement dans notre vue:

import mx.core.FlexGlobals;
  import mx.events.FlexEvent;

  private var _sqlConnection:SQLConnection = null;

  protected function onCComplete(event:FlexEvent):void{
	_sqlConnection = FlexGlobals.topLevelApplication.sqlConnection;
  }

Ajouter le composant List

Pour avoir un affichage visuel de notre liste de pokémons, on va ajouter un composant List. Pour une liste des composants (non exhaustive) des composants du SDK 4.5, je vous conseille de lire cet article:

AIR Mobile – Les composants et le thème Mobile Flex 4.5

Rendez-vous donc entre la fin du tag fx:Declaration et la fin du tag View puis tapez le caractère « < ». Appuyez sur Ctrl+Espace et la liste des composants disponibles va s’afficher. Tapez « List » puis appuyez sur Entrée:

list

Appuyez sur Espace et la liste des propriétés du composant va s’afficher. Vous pouvez commencer à taper le nom des propriétés comme « width » ou « height » et valider par Entrée. Comme exercice, on va fixer les propriétés « width » et « height » à la valeur « 100% ». Cela signifie que notre liste va prendre toute la place offerte par son conteneur, la vue.

Une fois que vous avez terminé, fermez le tag avec un chevron fermant « > ». Le tag XML </s:List> fermant la balise sera automatiquement ajouté:

<s:List width="100%" height="100%">

  </s:List>

Le composant List et la propriété « dataProvider »

Notre composant List est un composant graphique permettant d’afficher de la donnée. Pour définir quelle donnée doit être affichée dans ce composant, on renseigne la propriété « dataProvider ». Ce « dataProvider » est de type IList. Comme vous pouvez le voir dans la documentation, dans la partie « Implementors », les classes qui implémentent IList sont ArrayList, AsyncListView, ListCollectionView et les classes qui en héritent.

Lire la suite

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.

Lire la suite

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

Flex Modules – Communication Module vers Application

Dans l’article précédent, on a vu comment communiquer entre une application Flex et un module. Voyons maintenant comment créer une communication entre un module et l’application qui le charge.

Les modules peuvent accéder aux propriétés et aux méthodes de l’application parente en utilisant une référence vers la propriété « parentApplication« .

L’exemple suivant accède à la propriété « expenses » de l’application parente au chargement du module. Le module utilise ensuite cette propriété, un ArrayCollection, comme source pour la donnée du graphique. Quand l’utilisateur clique sur le bouton, le module appelle la méthode getNewData() de l’application parente qui renvoie un nouvel ArrayCollection pour le Chart:

Le code du module

<?xml version="1.0"?>
<!-- modules/ChartChildModule.mxml -->
<mx:Module xmlns:mx="http://www.adobe.com/2006/mxml"
           width="100%"
           height="100%"
           creationComplete="getDataFromParent()">
  <mx:Script>
    <![CDATA[
      import mx.collections.ArrayCollection;

      [Bindable]
      private var expenses:ArrayCollection;

// Access properties of the parent application.
      private function getDataFromParent():void {
        expenses = parentApplication.expenses;
      }
    ]]>
  </mx:Script>
  <mx:ColumnChart id="myChart"
                  dataProvider="{expenses}">
    <mx:horizontalAxis>
      <mx:CategoryAxis dataProvider="{expenses}"
                       categoryField="Month"/>
    </mx:horizontalAxis>
    <mx:series>
      <mx:ColumnSeries xField="Month"
                       yField="Profit"
                       displayName="Profit"/>
      <mx:ColumnSeries xField="Month"
                       yField="Expenses"
                       displayName="Expenses"/>
    </mx:series>
  </mx:ColumnChart>
  <mx:Legend dataProvider="{myChart}"/>
  <mx:Button id="b1"
             click="expenses = parentApplication.getNewData();"
             label="Get New Data"/>
</mx:Module>

L’application parente qui va être appelée

Lire la suite