DataFilterLib – Le multi-filtrage par l'exemple sur droomhuiscostablanca.nl
Si vous suivez flex-tutorial depuis un moment, vous connaissez peut-être le projet DataFilterLib, une librairie que j'ai créé pour vous aider à faire du multi-filtrage sur une source de donnée comme un ArrayCollection. Un peu comme si vous pouviez avoir plusieurs filterFunction cumulatives.
Sinon, session de rattrapage:
DataFilterLib – Filtrez votre donnée de manière simple et dynamique
Le but est bien ici de vous simplifier la vie en écrivant vos filtres de manière déclarative (ou en AS si vous avez envie) avec possibilité de Binding et opérations de filtrage pré-configurées.
Il y a quelques jours, un certain Wout den Teuling est venu me demander un coup de main pour utiliser la DataFilterLib dans son application. Celle-ci reçoit des données par un script PHP, en l’occurrence une liste de résidence ayant des propriétés (région, nom, type, prix, …).
Le but était de faire un multi-filtrage sur ces propriétés et sur le résultat, de faire une pagination automatique.
Et voilà le résultat! :
http://www.droomhuiscostablanca.nl/
A gauche, vous avez le filtrage par propriété (LIKE sur le nom, emplacement, type de location, prix) et au dessus des résultats, la pagination:
Voilà, le site n'est certes pas "magnifique" (le header et le footer ne devraient pas faire partie de l'application selon moi) mais c'est un bon exemple de ce qu'il est possible de faire avec cette librairie. Elle est gratuite et open source (sur Google Code), n'hésitez pas à vous en servir !
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.
Flex 4 – (2) Création de composants Flex ré-utilisables, les meilleures techniques
La création de composants est un must quand on travaille avec Flex. Mais autant faut-il savoir les créer de la meilleures manière afin qu'ils puissent être ré-utilisés et faciliter la maintenance de votre code.
A ce propos, Tom Sugden a écrit un très bon article qui explique bien les meilleures manières pour faire un bon composant Flex:
Writing Genuinely Reusable Flex Components
Pour faire simple, je vais traduire cet article, et rajouter quelques commentaires et suggestions…
Pour de gros projets, en entreprise, on se retrouve souvent à extraire un ensemble de composants réutilisables dans un projet Flex Library. En théorie, les mêmes composants peuvent être utilisés dans plusieurs modules et/ou dans des applications Flex / Air. Cela permet d'avoir une base de code solide et facile à maintenir pour un développement plus rapide.
Cependant, dans la pratique, certaines erreurs de conception sont faites, qui limitent la ré-utilisabilité des composants. Cet article explique comment créer un composant totalement ré-utilisable et met en avant certaines techniques du SDK Flex qui peuvent être appliquées à vos propres composants.
Comment définir un composant "vraiment" réutilisable?
Il y a différents niveaux de ré-utilisabilité, mais un composant ré-utilisable devrait toujours pouvoir faire le rendu de n'importe quel type de donnée. Il devrait pouvoir fonctionner avec un tableau d'objets basiques (type Object) ou une collection d'objet de type Kangaroo par exemple.
Le composant Flex DataGrid permet de faire cela:
<mx:DataGrid dataProvider="{ kangaroos }">
<mx:columns>
<mx:DataGridColumn headerText="Name" dataField="name"/>
<mx:DataGridColumn headerText="Weight" labelFunction="calculateWeight"/>
</mx:columns>
</mx:DataGrid>
Notez que les propriétés "dataField" et "labelFunction" indiquent au composant comment récupérer sa donnée depuis les objets de type Kangaroo, sans imposer de dépendance. On a ici deux mécanismes possibles pour rendre un composant vraiment ré-utilisable. Même si le développeur n'as pas le contrôle sur la classe Kangaroo (si elle fait partie d'une librairie externe par exemple), il peuvent tout de même être rendu dans une DataGrid.
L'Anti-Pattern Data Interface
Une des erreurs courantes est d'obliger la donnée qui va être rendue par un composant à implémenter une interface spécifique. Par exemple, on va prendre le composant DistributionBar qui fait le rendu d'un graphique comme celui-ci:
On affiche un certain nombre de régions de tailles différentes, chacune contenant un label. Il est tentant de configurer ce composant pour qu'il utilise un tableau d'objet IRegion:
public interface IRegion
{
function get label() : String;
function get size() : int;
}
Le composant pourra ensuite extraire les informations de taille et de label pour chaque région par cette interface. L'argument en faveur de cette démarche est que l'interface découple le composant de l'objet concret qui est rendu. N'importe quel objet peut être rendu tant qu'il implémente IRegion, mais c'est dans le "tant que" que réside la faille de conception.
En imposant l'utilisation de l'interface IRegion, la ré-utilisabilité du composant est limitée. L'interface doit être implémentée par les classes du Model avant de pouvoir être rendue par le composant. Encore pire, si vos models sont produits par une autre librairie (dont vous pouvez ne pas avoir les sources) ou par une autre équipe, il se peut que vous ne puissiez pas changer les classes, elles devront donc être "wrappées" (encapsulées dans d'autres objets qui implémentent IRegion).
Pour ces raisons, le composant n'est pas vraiment ré-utilisable.
Notez que cette considération est importante pour les composants qui doivent être complètement ré-utilisables comme les DataGrid ou les List Flex. Pour des composants plus "métiers" ou le fait de pouvoir instancier son composant sans configurer trop de propriétés, on pourra utiliser le "pattern Data Interface". Parfois, il vaut mieux passer du temps à créer un composant qui soit ré-utilisable mais parfois il est plus aisé/rapide de créer plusieurs composants (par héritage) pour des besoins métiers spécifiques.
Les composants ré-utilisables du SDK Flex
Le SDK Flex contient de nombreux composants ré-utilisables, utilisant plusieurs approches:
- Data Fields
- Data Functions
- Data Descriptor
- Factory Objects
Ces approches et ces techniques peuvent être appliquées à vos propres composants pour les rendre ré-utilisables.
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









